Line data Source code
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 file,
5 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "WebIDLGlobalNameHash.h"
8 : #include "js/GCAPI.h"
9 : #include "mozilla/HashFunctions.h"
10 : #include "mozilla/Maybe.h"
11 : #include "mozilla/dom/DOMJSClass.h"
12 : #include "mozilla/dom/DOMJSProxyHandler.h"
13 : #include "mozilla/dom/PrototypeList.h"
14 : #include "mozilla/dom/RegisterBindings.h"
15 : #include "nsIMemoryReporter.h"
16 : #include "nsTHashtable.h"
17 :
18 : namespace mozilla {
19 : namespace dom {
20 :
21 5291 : struct MOZ_STACK_CLASS WebIDLNameTableKey
22 : {
23 3158 : explicit WebIDLNameTableKey(JSFlatString* aJSString)
24 3158 : : mLength(js::GetFlatStringLength(aJSString))
25 : {
26 3158 : mNogc.emplace();
27 3158 : JSLinearString* jsString = js::FlatStringToLinearString(aJSString);
28 3158 : if (js::LinearStringHasLatin1Chars(jsString)) {
29 3158 : mLatin1String = reinterpret_cast<const char*>(
30 3158 : js::GetLatin1LinearStringChars(*mNogc, jsString));
31 3158 : mTwoBytesString = nullptr;
32 3158 : mHash = mLatin1String ? HashString(mLatin1String, mLength) : 0;
33 : } else {
34 0 : mLatin1String = nullptr;
35 0 : mTwoBytesString = js::GetTwoByteLinearStringChars(*mNogc, jsString);
36 0 : mHash = mTwoBytesString ? HashString(mTwoBytesString, mLength) : 0;
37 : }
38 3158 : }
39 2133 : explicit WebIDLNameTableKey(const char* aString, size_t aLength)
40 2133 : : mLatin1String(aString),
41 : mTwoBytesString(nullptr),
42 : mLength(aLength),
43 2133 : mHash(HashString(aString, aLength))
44 : {
45 2133 : MOZ_ASSERT(aString[aLength] == '\0');
46 2133 : }
47 :
48 : Maybe<JS::AutoCheckCannotGC> mNogc;
49 : const char* mLatin1String;
50 : const char16_t* mTwoBytesString;
51 : size_t mLength;
52 : PLDHashNumber mHash;
53 : };
54 :
55 : struct WebIDLNameTableEntry : public PLDHashEntryHdr
56 : {
57 : typedef const WebIDLNameTableKey& KeyType;
58 : typedef const WebIDLNameTableKey* KeyTypePointer;
59 :
60 2070 : explicit WebIDLNameTableEntry(KeyTypePointer aKey)
61 2070 : : mNameOffset(0),
62 : mNameLength(0),
63 : mConstructorId(constructors::id::_ID_Count),
64 : mDefine(nullptr),
65 2070 : mEnabled(nullptr)
66 2070 : {}
67 : WebIDLNameTableEntry(WebIDLNameTableEntry&& aEntry)
68 : : mNameOffset(aEntry.mNameOffset),
69 : mNameLength(aEntry.mNameLength),
70 : mConstructorId(aEntry.mConstructorId),
71 : mDefine(aEntry.mDefine),
72 : mEnabled(aEntry.mEnabled)
73 : {}
74 0 : ~WebIDLNameTableEntry()
75 0 : {}
76 :
77 28 : bool KeyEquals(KeyTypePointer aKey) const
78 : {
79 28 : if (mNameLength != aKey->mLength) {
80 0 : return false;
81 : }
82 :
83 28 : const char* name = WebIDLGlobalNameHash::sNames + mNameOffset;
84 :
85 28 : if (aKey->mLatin1String) {
86 28 : return PodEqual(aKey->mLatin1String, name, aKey->mLength);
87 : }
88 :
89 0 : return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name,
90 0 : aKey->mLength) == 0;
91 : }
92 :
93 5291 : static KeyTypePointer KeyToPointer(KeyType aKey)
94 : {
95 5291 : return &aKey;
96 : }
97 :
98 5291 : static PLDHashNumber HashKey(KeyTypePointer aKey)
99 : {
100 5291 : return aKey->mHash;
101 : }
102 :
103 : enum { ALLOW_MEMMOVE = true };
104 :
105 : uint16_t mNameOffset;
106 : uint16_t mNameLength;
107 : constructors::id::ID mConstructorId;
108 : WebIDLGlobalNameHash::DefineGlobalName mDefine;
109 : // May be null if enabled unconditionally
110 : WebIDLGlobalNameHash::ConstructorEnabled* mEnabled;
111 : };
112 :
113 : static nsTHashtable<WebIDLNameTableEntry>* sWebIDLGlobalNames;
114 :
115 3 : class WebIDLGlobalNamesHashReporter final : public nsIMemoryReporter
116 : {
117 0 : MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
118 :
119 0 : ~WebIDLGlobalNamesHashReporter() {}
120 :
121 : public:
122 : NS_DECL_ISUPPORTS
123 :
124 0 : NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
125 : nsISupports* aData, bool aAnonymize) override
126 : {
127 : int64_t amount =
128 0 : sWebIDLGlobalNames ?
129 0 : sWebIDLGlobalNames->ShallowSizeOfIncludingThis(MallocSizeOf) : 0;
130 :
131 0 : MOZ_COLLECT_REPORT(
132 : "explicit/dom/webidl-globalnames", KIND_HEAP, UNITS_BYTES, amount,
133 0 : "Memory used by the hash table for WebIDL's global names.");
134 :
135 0 : return NS_OK;
136 : }
137 : };
138 :
139 39 : NS_IMPL_ISUPPORTS(WebIDLGlobalNamesHashReporter, nsIMemoryReporter)
140 :
141 : /* static */
142 : void
143 3 : WebIDLGlobalNameHash::Init()
144 : {
145 3 : sWebIDLGlobalNames = new nsTHashtable<WebIDLNameTableEntry>(sCount);
146 3 : RegisterWebIDLGlobalNames();
147 :
148 3 : RegisterStrongMemoryReporter(new WebIDLGlobalNamesHashReporter());
149 3 : }
150 :
151 : /* static */
152 : void
153 0 : WebIDLGlobalNameHash::Shutdown()
154 : {
155 0 : delete sWebIDLGlobalNames;
156 0 : }
157 :
158 : /* static */
159 : void
160 2070 : WebIDLGlobalNameHash::Register(uint16_t aNameOffset, uint16_t aNameLength,
161 : DefineGlobalName aDefine,
162 : ConstructorEnabled* aEnabled,
163 : constructors::id::ID aConstructorId)
164 : {
165 2070 : const char* name = sNames + aNameOffset;
166 4140 : WebIDLNameTableKey key(name, aNameLength);
167 2070 : WebIDLNameTableEntry* entry = sWebIDLGlobalNames->PutEntry(key);
168 2070 : entry->mNameOffset = aNameOffset;
169 2070 : entry->mNameLength = aNameLength;
170 2070 : entry->mDefine = aDefine;
171 2070 : entry->mEnabled = aEnabled;
172 2070 : entry->mConstructorId = aConstructorId;
173 2070 : }
174 :
175 : /* static */
176 : void
177 63 : WebIDLGlobalNameHash::Remove(const char* aName, uint32_t aLength)
178 : {
179 126 : WebIDLNameTableKey key(aName, aLength);
180 63 : sWebIDLGlobalNames->RemoveEntry(key);
181 63 : }
182 :
183 : /* static */
184 : bool
185 3150 : WebIDLGlobalNameHash::DefineIfEnabled(JSContext* aCx,
186 : JS::Handle<JSObject*> aObj,
187 : JS::Handle<jsid> aId,
188 : JS::MutableHandle<JS::PropertyDescriptor> aDesc,
189 : bool* aFound)
190 : {
191 3150 : MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!");
192 :
193 : const WebIDLNameTableEntry* entry;
194 : {
195 6300 : WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
196 : // Rooting analysis thinks nsTHashtable<...>::GetEntry may GC because it
197 : // ends up calling through PLDHashTableOps' matchEntry function pointer, but
198 : // we know WebIDLNameTableEntry::KeyEquals can't cause a GC.
199 6300 : JS::AutoSuppressGCAnalysis suppress;
200 3150 : entry = sWebIDLGlobalNames->GetEntry(key);
201 : }
202 :
203 3150 : if (!entry) {
204 3130 : *aFound = false;
205 3130 : return true;
206 : }
207 :
208 20 : *aFound = true;
209 :
210 20 : ConstructorEnabled* checkEnabledForScope = entry->mEnabled;
211 : // We do the enabled check on the current compartment of aCx, but for the
212 : // actual object we pass in the underlying object in the Xray case. That
213 : // way the callee can decide whether to allow access based on the caller
214 : // or the window being touched.
215 : JS::Rooted<JSObject*> global(aCx,
216 40 : js::CheckedUnwrap(aObj, /* stopAtWindowProxy = */ false));
217 20 : if (!global) {
218 0 : return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
219 : }
220 :
221 : {
222 : // It's safe to pass "&global" here, because we've already unwrapped it, but
223 : // for general sanity better to not have debug code even having the
224 : // appearance of mutating things that opt code uses.
225 : #ifdef DEBUG
226 40 : JS::Rooted<JSObject*> temp(aCx, global);
227 40 : DebugOnly<nsGlobalWindow*> win;
228 20 : MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, &temp, win)));
229 : #endif
230 : }
231 :
232 20 : if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
233 0 : return true;
234 : }
235 :
236 : // The DOM constructor resolve machinery interacts with Xrays in tricky
237 : // ways, and there are some asymmetries that are important to understand.
238 : //
239 : // In the regular (non-Xray) case, we only want to resolve constructors
240 : // once (so that if they're deleted, they don't reappear). We do this by
241 : // stashing the constructor in a slot on the global, such that we can see
242 : // during resolve whether we've created it already. This is rather
243 : // memory-intensive, so we don't try to maintain these semantics when
244 : // manipulating a global over Xray (so the properties just re-resolve if
245 : // they've been deleted).
246 : //
247 : // Unfortunately, there's a bit of an impedance-mismatch between the Xray
248 : // and non-Xray machinery. The Xray machinery wants an API that returns a
249 : // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
250 : // snared up with trying to define a property on the Xray holder. At the
251 : // same time, the DefineInterface callbacks are set up to define things
252 : // directly on the global. And re-jiggering them to return property
253 : // descriptors is tricky, because some DefineInterface callbacks define
254 : // multiple things (like the Image() alias for HTMLImageElement).
255 : //
256 : // So the setup is as-follows:
257 : //
258 : // * The resolve function takes a JS::PropertyDescriptor, but in the
259 : // non-Xray case, callees may define things directly on the global, and
260 : // set the value on the property descriptor to |undefined| to indicate
261 : // that there's nothing more for the caller to do. We assert against
262 : // this behavior in the Xray case.
263 : //
264 : // * We make sure that we do a non-Xray resolve first, so that all the
265 : // slots are set up. In the Xray case, this means unwrapping and doing
266 : // a non-Xray resolve before doing the Xray resolve.
267 : //
268 : // This all could use some grand refactoring, but for now we just limp
269 : // along.
270 20 : if (xpc::WrapperFactory::IsXrayWrapper(aObj)) {
271 12 : JS::Rooted<JSObject*> interfaceObject(aCx);
272 : {
273 12 : JSAutoCompartment ac(aCx, global);
274 6 : interfaceObject = entry->mDefine(aCx, global, aId, false);
275 : }
276 6 : if (NS_WARN_IF(!interfaceObject)) {
277 0 : return Throw(aCx, NS_ERROR_FAILURE);
278 : }
279 6 : if (!JS_WrapObject(aCx, &interfaceObject)) {
280 0 : return Throw(aCx, NS_ERROR_FAILURE);
281 : }
282 :
283 6 : FillPropertyDescriptor(aDesc, aObj, 0, JS::ObjectValue(*interfaceObject));
284 6 : return true;
285 : }
286 :
287 : JS::Rooted<JSObject*> interfaceObject(aCx,
288 28 : entry->mDefine(aCx, aObj, aId, true));
289 14 : if (NS_WARN_IF(!interfaceObject)) {
290 0 : return Throw(aCx, NS_ERROR_FAILURE);
291 : }
292 :
293 : // We've already defined the property. We indicate this to the caller
294 : // by filling a property descriptor with JS::UndefinedValue() as the
295 : // value. We still have to fill in a property descriptor, though, so
296 : // that the caller knows the property is in fact on this object. It
297 : // doesn't matter what we pass for the "readonly" argument here.
298 14 : FillPropertyDescriptor(aDesc, aObj, JS::UndefinedValue(), false);
299 :
300 14 : return true;
301 : }
302 :
303 : /* static */
304 : bool
305 8 : WebIDLGlobalNameHash::MayResolve(jsid aId)
306 : {
307 16 : WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
308 : // Rooting analysis thinks nsTHashtable<...>::Contains may GC because it ends
309 : // up calling through PLDHashTableOps' matchEntry function pointer, but we
310 : // know WebIDLNameTableEntry::KeyEquals can't cause a GC.
311 16 : JS::AutoSuppressGCAnalysis suppress;
312 16 : return sWebIDLGlobalNames->Contains(key);
313 : }
314 :
315 : /* static */
316 : bool
317 0 : WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
318 : NameType aNameType, JS::AutoIdVector& aNames)
319 : {
320 : // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe.
321 0 : ProtoAndIfaceCache* cache = GetProtoAndIfaceCache(aObj);
322 0 : for (auto iter = sWebIDLGlobalNames->Iter(); !iter.Done(); iter.Next()) {
323 0 : const WebIDLNameTableEntry* entry = iter.Get();
324 : // If aNameType is not AllNames, only include things whose entry slot in the
325 : // ProtoAndIfaceCache is null.
326 0 : if ((aNameType == AllNames ||
327 0 : !cache->HasEntryInSlot(entry->mConstructorId)) &&
328 0 : (!entry->mEnabled || entry->mEnabled(aCx, aObj))) {
329 0 : JSString* str = JS_AtomizeStringN(aCx, sNames + entry->mNameOffset,
330 0 : entry->mNameLength);
331 0 : if (!str || !aNames.append(NON_INTEGER_ATOM_TO_JSID(str))) {
332 0 : return false;
333 : }
334 : }
335 : }
336 :
337 0 : return true;
338 : }
339 :
340 : } // namespace dom
341 : } // namespace mozilla
|