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 /*
8  * A class for handing out nodeinfos and ensuring sharing of them as needed.
9  */
10 
11 #include "nsNodeInfoManager.h"
12 
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/DebugOnly.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/NodeInfo.h"
17 #include "mozilla/dom/NodeInfoInlines.h"
18 #include "mozilla/dom/DocGroup.h"
19 #include "mozilla/NullPrincipal.h"
20 #include "nsCOMPtr.h"
21 #include "nsString.h"
22 #include "nsAtom.h"
23 #include "nsIPrincipal.h"
24 #include "nsContentUtils.h"
25 #include "nsReadableUtils.h"
26 #include "nsGkAtoms.h"
27 #include "nsComponentManagerUtils.h"
28 #include "nsLayoutStatics.h"
29 #include "nsHashKeys.h"
30 #include "nsCCUncollectableMarker.h"
31 #include "nsNameSpaceManager.h"
32 #include "nsWindowSizes.h"
33 
34 using namespace mozilla;
35 using mozilla::dom::NodeInfo;
36 
37 #include "mozilla/Logging.h"
38 
39 static LazyLogModule gNodeInfoManagerLeakPRLog("NodeInfoManagerLeak");
40 static const uint32_t kInitialNodeInfoHashSize = 32;
41 
nsNodeInfoManager()42 nsNodeInfoManager::nsNodeInfoManager()
43     : mNodeInfoHash(kInitialNodeInfoHashSize),
44       mDocument(nullptr),
45       mNonDocumentNodeInfos(0),
46       mTextNodeInfo(nullptr),
47       mCommentNodeInfo(nullptr),
48       mDocumentNodeInfo(nullptr),
49       mRecentlyUsedNodeInfos(),
50       mArena(nullptr) {
51   nsLayoutStatics::AddRef();
52 
53   if (gNodeInfoManagerLeakPRLog)
54     MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug,
55             ("NODEINFOMANAGER %p created", this));
56 }
57 
~nsNodeInfoManager()58 nsNodeInfoManager::~nsNodeInfoManager() {
59   // Note: mPrincipal may be null here if we never got inited correctly
60   mPrincipal = nullptr;
61 
62   mArena = nullptr;
63 
64   if (gNodeInfoManagerLeakPRLog)
65     MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug,
66             ("NODEINFOMANAGER %p destroyed", this));
67 
68   nsLayoutStatics::Release();
69 }
70 
71 NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager)
72 
73 NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager)
74 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager)
75   if (tmp->mNonDocumentNodeInfos) {
76     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDocument)
77   }
78 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
79 
80 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsNodeInfoManager, AddRef)
81 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsNodeInfoManager, Release)
82 
83 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsNodeInfoManager)
84   if (tmp->mDocument) {
85     return NS_CYCLE_COLLECTION_PARTICIPANT(mozilla::dom::Document)
86         ->CanSkip(tmp->mDocument, aRemovingAllowed);
87   }
88 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
89 
90 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsNodeInfoManager)
91   if (tmp->mDocument) {
92     return NS_CYCLE_COLLECTION_PARTICIPANT(mozilla::dom::Document)
93         ->CanSkipInCC(tmp->mDocument);
94   }
95 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
96 
97 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsNodeInfoManager)
98   if (tmp->mDocument) {
99     return NS_CYCLE_COLLECTION_PARTICIPANT(mozilla::dom::Document)
100         ->CanSkipThis(tmp->mDocument);
101   }
102 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
103 
Init(mozilla::dom::Document * aDocument)104 nsresult nsNodeInfoManager::Init(mozilla::dom::Document* aDocument) {
105   MOZ_ASSERT(!mPrincipal, "Being inited when we already have a principal?");
106 
107   mPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
108 
109   mDefaultPrincipal = mPrincipal;
110 
111   mDocument = aDocument;
112 
113   if (gNodeInfoManagerLeakPRLog)
114     MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug,
115             ("NODEINFOMANAGER %p Init document=%p", this, aDocument));
116 
117   return NS_OK;
118 }
119 
DropDocumentReference()120 void nsNodeInfoManager::DropDocumentReference() {
121   // This is probably not needed anymore.
122   for (auto iter = mNodeInfoHash.Iter(); !iter.Done(); iter.Next()) {
123     iter.Data()->mDocument = nullptr;
124   }
125 
126   NS_ASSERTION(!mNonDocumentNodeInfos,
127                "Shouldn't have non-document nodeinfos!");
128   mDocument = nullptr;
129 }
130 
GetNodeInfo(nsAtom * aName,nsAtom * aPrefix,int32_t aNamespaceID,uint16_t aNodeType,nsAtom * aExtraName)131 already_AddRefed<mozilla::dom::NodeInfo> nsNodeInfoManager::GetNodeInfo(
132     nsAtom* aName, nsAtom* aPrefix, int32_t aNamespaceID, uint16_t aNodeType,
133     nsAtom* aExtraName /* = nullptr */) {
134   CheckValidNodeInfo(aNodeType, aName, aNamespaceID, aExtraName);
135 
136   NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType,
137                                  aExtraName);
138 
139   auto p = mRecentlyUsedNodeInfos.Lookup(tmpKey);
140   if (p) {
141     RefPtr<NodeInfo> nodeInfo = p.Data();
142     return nodeInfo.forget();
143   }
144 
145   // We don't use LookupForAdd here as that would end up storing the temporary
146   // key instead of using `mInner`.
147   RefPtr<NodeInfo> nodeInfo = mNodeInfoHash.Get(&tmpKey);
148   if (!nodeInfo) {
149     ++mNonDocumentNodeInfos;
150     if (mNonDocumentNodeInfos == 1) {
151       NS_IF_ADDREF(mDocument);
152     }
153 
154     nodeInfo =
155         new NodeInfo(aName, aPrefix, aNamespaceID, aNodeType, aExtraName, this);
156     mNodeInfoHash.Put(&nodeInfo->mInner, nodeInfo);
157   }
158 
159   // Have to do the swap thing, because already_AddRefed<nsNodeInfo>
160   // doesn't cast to already_AddRefed<mozilla::dom::NodeInfo>
161   p.Set(nodeInfo);
162   return nodeInfo.forget();
163 }
164 
GetNodeInfo(const nsAString & aName,nsAtom * aPrefix,int32_t aNamespaceID,uint16_t aNodeType,NodeInfo ** aNodeInfo)165 nsresult nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsAtom* aPrefix,
166                                         int32_t aNamespaceID,
167                                         uint16_t aNodeType,
168                                         NodeInfo** aNodeInfo) {
169   // TODO(erahm): Combine this with the atom version.
170 #ifdef DEBUG
171   {
172     RefPtr<nsAtom> nameAtom = NS_Atomize(aName);
173     CheckValidNodeInfo(aNodeType, nameAtom, aNamespaceID, nullptr);
174   }
175 #endif
176 
177   NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType);
178 
179   auto p = mRecentlyUsedNodeInfos.Lookup(tmpKey);
180   if (p) {
181     RefPtr<NodeInfo> nodeInfo = p.Data();
182     nodeInfo.forget(aNodeInfo);
183     return NS_OK;
184   }
185 
186   RefPtr<NodeInfo> nodeInfo = mNodeInfoHash.Get(&tmpKey);
187   if (!nodeInfo) {
188     ++mNonDocumentNodeInfos;
189     if (mNonDocumentNodeInfos == 1) {
190       NS_IF_ADDREF(mDocument);
191     }
192 
193     RefPtr<nsAtom> nameAtom = NS_Atomize(aName);
194     nodeInfo =
195         new NodeInfo(nameAtom, aPrefix, aNamespaceID, aNodeType, nullptr, this);
196     mNodeInfoHash.Put(&nodeInfo->mInner, nodeInfo);
197   }
198 
199   p.Set(nodeInfo);
200   nodeInfo.forget(aNodeInfo);
201 
202   return NS_OK;
203 }
204 
GetNodeInfo(const nsAString & aName,nsAtom * aPrefix,const nsAString & aNamespaceURI,uint16_t aNodeType,NodeInfo ** aNodeInfo)205 nsresult nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsAtom* aPrefix,
206                                         const nsAString& aNamespaceURI,
207                                         uint16_t aNodeType,
208                                         NodeInfo** aNodeInfo) {
209   int32_t nsid = kNameSpaceID_None;
210 
211   if (!aNamespaceURI.IsEmpty()) {
212     nsresult rv = nsContentUtils::NameSpaceManager()->RegisterNameSpace(
213         aNamespaceURI, nsid);
214     NS_ENSURE_SUCCESS(rv, rv);
215   }
216 
217   return GetNodeInfo(aName, aPrefix, nsid, aNodeType, aNodeInfo);
218 }
219 
GetTextNodeInfo()220 already_AddRefed<NodeInfo> nsNodeInfoManager::GetTextNodeInfo() {
221   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
222 
223   if (!mTextNodeInfo) {
224     nodeInfo = GetNodeInfo(nsGkAtoms::textTagName, nullptr, kNameSpaceID_None,
225                            nsINode::TEXT_NODE, nullptr);
226     // Hold a weak ref; the nodeinfo will let us know when it goes away
227     mTextNodeInfo = nodeInfo;
228   } else {
229     nodeInfo = mTextNodeInfo;
230   }
231 
232   return nodeInfo.forget();
233 }
234 
GetCommentNodeInfo()235 already_AddRefed<NodeInfo> nsNodeInfoManager::GetCommentNodeInfo() {
236   RefPtr<NodeInfo> nodeInfo;
237 
238   if (!mCommentNodeInfo) {
239     nodeInfo = GetNodeInfo(nsGkAtoms::commentTagName, nullptr,
240                            kNameSpaceID_None, nsINode::COMMENT_NODE, nullptr);
241     // Hold a weak ref; the nodeinfo will let us know when it goes away
242     mCommentNodeInfo = nodeInfo;
243   } else {
244     nodeInfo = mCommentNodeInfo;
245   }
246 
247   return nodeInfo.forget();
248 }
249 
GetDocumentNodeInfo()250 already_AddRefed<NodeInfo> nsNodeInfoManager::GetDocumentNodeInfo() {
251   RefPtr<NodeInfo> nodeInfo;
252 
253   if (!mDocumentNodeInfo) {
254     NS_ASSERTION(mDocument, "Should have mDocument!");
255     nodeInfo = GetNodeInfo(nsGkAtoms::documentNodeName, nullptr,
256                            kNameSpaceID_None, nsINode::DOCUMENT_NODE, nullptr);
257     // Hold a weak ref; the nodeinfo will let us know when it goes away
258     mDocumentNodeInfo = nodeInfo;
259 
260     --mNonDocumentNodeInfos;
261     if (!mNonDocumentNodeInfos) {
262       mDocument->Release();  // Don't set mDocument to null!
263     }
264   } else {
265     nodeInfo = mDocumentNodeInfo;
266   }
267 
268   return nodeInfo.forget();
269 }
270 
Allocate(size_t aSize)271 void* nsNodeInfoManager::Allocate(size_t aSize) {
272   if (!mHasAllocated) {
273     if (mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
274       if (!mArena) {
275         mozilla::dom::DocGroup* docGroup = GetDocument()->GetDocGroupOrCreate();
276         if (docGroup) {
277           MOZ_ASSERT(!GetDocument()->HasChildren());
278           mArena = docGroup->ArenaAllocator();
279         }
280       }
281 #ifdef DEBUG
282       else {
283         mozilla::dom::DocGroup* docGroup = GetDocument()->GetDocGroup();
284         MOZ_ASSERT(docGroup);
285         MOZ_ASSERT(mArena == docGroup->ArenaAllocator());
286       }
287 #endif
288     }
289     mHasAllocated = true;
290   }
291 
292 #ifdef DEBUG
293   if (!mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
294     MOZ_ASSERT(!mArena, "mArena should not set if the pref is not on");
295   };
296 #endif
297 
298   if (mArena) {
299     return mArena->Allocate(aSize);
300   }
301   return malloc(aSize);
302 }
303 
SetArenaAllocator(mozilla::dom::DOMArena * aArena)304 void nsNodeInfoManager::SetArenaAllocator(mozilla::dom::DOMArena* aArena) {
305   MOZ_DIAGNOSTIC_ASSERT_IF(mArena, mArena == aArena);
306   MOZ_DIAGNOSTIC_ASSERT(!mHasAllocated);
307   MOZ_DIAGNOSTIC_ASSERT(
308       mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup());
309 
310   if (!mArena) {
311     mArena = aArena;
312   }
313 }
314 
SetDocumentPrincipal(nsIPrincipal * aPrincipal)315 void nsNodeInfoManager::SetDocumentPrincipal(nsIPrincipal* aPrincipal) {
316   mPrincipal = nullptr;
317   if (!aPrincipal) {
318     aPrincipal = mDefaultPrincipal;
319   }
320 
321   NS_ASSERTION(aPrincipal, "Must have principal by this point!");
322   MOZ_DIAGNOSTIC_ASSERT(!nsContentUtils::IsExpandedPrincipal(aPrincipal),
323                         "Documents shouldn't have an expanded principal");
324 
325   mPrincipal = aPrincipal;
326 }
327 
RemoveNodeInfo(NodeInfo * aNodeInfo)328 void nsNodeInfoManager::RemoveNodeInfo(NodeInfo* aNodeInfo) {
329   MOZ_ASSERT(aNodeInfo, "Trying to remove null nodeinfo from manager!");
330 
331   if (aNodeInfo == mDocumentNodeInfo) {
332     mDocumentNodeInfo = nullptr;
333     mDocument = nullptr;
334   } else {
335     if (--mNonDocumentNodeInfos == 0) {
336       if (mDocument) {
337         // Note, whoever calls this method should keep NodeInfoManager alive,
338         // even if mDocument gets deleted.
339         mDocument->Release();
340       }
341     }
342     // Drop weak reference if needed
343     if (aNodeInfo == mTextNodeInfo) {
344       mTextNodeInfo = nullptr;
345     } else if (aNodeInfo == mCommentNodeInfo) {
346       mCommentNodeInfo = nullptr;
347     }
348   }
349 
350   mRecentlyUsedNodeInfos.Remove(aNodeInfo->mInner);
351   DebugOnly<bool> ret = mNodeInfoHash.Remove(&aNodeInfo->mInner);
352   MOZ_ASSERT(ret, "Can't find mozilla::dom::NodeInfo to remove!!!");
353 }
354 
IsSystemOrAddonPrincipal(nsIPrincipal * aPrincipal)355 static bool IsSystemOrAddonPrincipal(nsIPrincipal* aPrincipal) {
356   return aPrincipal->IsSystemPrincipal() ||
357          BasePrincipal::Cast(aPrincipal)->AddonPolicy();
358 }
359 
InternalSVGEnabled()360 bool nsNodeInfoManager::InternalSVGEnabled() {
361   // If the svg.disabled pref. is true, convert all SVG nodes into
362   // disabled SVG nodes by swapping the namespace.
363   nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
364   nsCOMPtr<nsILoadInfo> loadInfo;
365   bool SVGEnabled = false;
366 
367   if (nsmgr && !nsmgr->mSVGDisabled) {
368     SVGEnabled = true;
369   } else {
370     nsCOMPtr<nsIChannel> channel = mDocument->GetChannel();
371     // We don't have a channel for SVGs constructed inside a SVG script
372     if (channel) {
373       loadInfo = channel->LoadInfo();
374     }
375   }
376 
377   // We allow SVG (regardless of the pref) if this is a system or add-on
378   // principal, or if this load was requested for a system or add-on principal
379   // (e.g. a remote image being served as part of system or add-on UI)
380   bool conclusion =
381       (SVGEnabled || IsSystemOrAddonPrincipal(mPrincipal) ||
382        (loadInfo &&
383         (loadInfo->GetExternalContentPolicyType() ==
384              nsIContentPolicy::TYPE_IMAGE ||
385          loadInfo->GetExternalContentPolicyType() ==
386              nsIContentPolicy::TYPE_OTHER) &&
387         (IsSystemOrAddonPrincipal(loadInfo->GetLoadingPrincipal()) ||
388          IsSystemOrAddonPrincipal(loadInfo->TriggeringPrincipal()))));
389   mSVGEnabled = Some(conclusion);
390   return conclusion;
391 }
392 
InternalMathMLEnabled()393 bool nsNodeInfoManager::InternalMathMLEnabled() {
394   // If the mathml.disabled pref. is true, convert all MathML nodes into
395   // disabled MathML nodes by swapping the namespace.
396   nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
397   bool conclusion =
398       ((nsmgr && !nsmgr->mMathMLDisabled) || mPrincipal->IsSystemPrincipal());
399   mMathMLEnabled = Some(conclusion);
400   return conclusion;
401 }
402 
AddSizeOfIncludingThis(nsWindowSizes & aSizes) const403 void nsNodeInfoManager::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
404   aSizes.mDOMOtherSize += aSizes.mState.mMallocSizeOf(this);
405 
406   // Measurement of the following members may be added later if DMD finds it
407   // is worthwhile:
408   // - mNodeInfoHash
409 }
410