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