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