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/. */
7 #include "WebIDLGlobalNameHash.h"
11 #include "js/Object.h" // JS::GetClass, JS::GetReservedSlot
12 #include "js/Wrapper.h"
14 #include "jsfriendapi.h"
15 #include "mozilla/ArrayUtils.h"
16 #include "mozilla/ErrorResult.h"
17 #include "mozilla/HashFunctions.h"
18 #include "mozilla/Maybe.h"
19 #include "mozilla/dom/BindingNames.h"
20 #include "mozilla/dom/DOMJSClass.h"
21 #include "mozilla/dom/Exceptions.h"
22 #include "mozilla/dom/JSSlots.h"
23 #include "mozilla/dom/PrototypeList.h"
24 #include "mozilla/dom/ProxyHandlerUtils.h"
25 #include "mozilla/dom/RegisterBindings.h"
26 #include "nsGlobalWindowInner.h"
27 #include "nsTHashtable.h"
28 #include "WrapperFactory.h"
30 namespace mozilla::dom
{
32 static JSObject
* FindNamedConstructorForXray(
33 JSContext
* aCx
, JS::Handle
<jsid
> aId
, const WebIDLNameTableEntry
* aEntry
) {
34 JSObject
* interfaceObject
=
35 GetPerInterfaceObjectHandle(aCx
, aEntry
->mConstructorId
, aEntry
->mCreate
,
36 DefineInterfaceProperty::No
);
37 if (!interfaceObject
) {
41 if (IsInterfaceObject(interfaceObject
)) {
42 // This is a call over Xrays, so we will actually use the return value
43 // (instead of just having it defined on the global now). Check for named
44 // constructors with this id, in case that's what the caller is asking for.
45 for (unsigned slot
= INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION
;
46 slot
< INTERFACE_OBJECT_MAX_SLOTS
; ++slot
) {
47 const JS::Value
& v
= js::GetFunctionNativeReserved(interfaceObject
, slot
);
51 JSObject
* constructor
= &v
.toObject();
52 if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor
)) ==
59 // None of the legacy factory functions match, so the caller must want the
60 // interface object itself.
61 return interfaceObject
;
65 bool WebIDLGlobalNameHash::DefineIfEnabled(
66 JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
, JS::Handle
<jsid
> aId
,
67 JS::MutableHandle
<mozilla::Maybe
<JS::PropertyDescriptor
>> aDesc
,
69 MOZ_ASSERT(aId
.isString(), "Check for string id before calling this!");
71 const WebIDLNameTableEntry
* entry
= GetEntry(aId
.toLinearString());
79 ConstructorEnabled checkEnabledForScope
= entry
->mEnabled
;
80 // We do the enabled check on the current Realm of aCx, but for the
81 // actual object we pass in the underlying object in the Xray case. That
82 // way the callee can decide whether to allow access based on the caller
83 // or the window being touched.
85 // Using aCx to represent the current Realm for CheckedUnwrapDynamic
86 // purposes is OK here, because that's the Realm where we plan to do
87 // our property-defining.
88 JS::Rooted
<JSObject
*> global(
90 js::CheckedUnwrapDynamic(aObj
, aCx
, /* stopAtWindowProxy = */ false));
92 return Throw(aCx
, NS_ERROR_DOM_SECURITY_ERR
);
96 // It's safe to pass "&global" here, because we've already unwrapped it, but
97 // for general sanity better to not have debug code even having the
98 // appearance of mutating things that opt code uses.
100 JS::Rooted
<JSObject
*> temp(aCx
, global
);
101 DebugOnly
<nsGlobalWindowInner
*> win
;
102 MOZ_ASSERT(NS_SUCCEEDED(
103 UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window
, &temp
, win
, aCx
)));
107 if (checkEnabledForScope
&& !checkEnabledForScope(aCx
, global
)) {
111 // The DOM constructor resolve machinery interacts with Xrays in tricky
112 // ways, and there are some asymmetries that are important to understand.
114 // In the regular (non-Xray) case, we only want to resolve constructors
115 // once (so that if they're deleted, they don't reappear). We do this by
116 // stashing the constructor in a slot on the global, such that we can see
117 // during resolve whether we've created it already. This is rather
118 // memory-intensive, so we don't try to maintain these semantics when
119 // manipulating a global over Xray (so the properties just re-resolve if
120 // they've been deleted).
122 // Unfortunately, there's a bit of an impedance-mismatch between the Xray
123 // and non-Xray machinery. The Xray machinery wants an API that returns a
124 // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
125 // snared up with trying to define a property on the Xray holder. At the
126 // same time, the DefineInterface callbacks are set up to define things
127 // directly on the global. And re-jiggering them to return property
128 // descriptors is tricky, because some DefineInterface callbacks define
129 // multiple things (like the Image() alias for HTMLImageElement).
131 // So the setup is as-follows:
133 // * The resolve function takes a JS::PropertyDescriptor, but in the
134 // non-Xray case, callees may define things directly on the global, and
135 // set the value on the property descriptor to |undefined| to indicate
136 // that there's nothing more for the caller to do. We assert against
137 // this behavior in the Xray case.
139 // * We make sure that we do a non-Xray resolve first, so that all the
140 // slots are set up. In the Xray case, this means unwrapping and doing
141 // a non-Xray resolve before doing the Xray resolve.
143 // This all could use some grand refactoring, but for now we just limp
145 if (xpc::WrapperFactory::IsXrayWrapper(aObj
)) {
146 JS::Rooted
<JSObject
*> constructor(aCx
);
148 JSAutoRealm
ar(aCx
, global
);
149 constructor
= FindNamedConstructorForXray(aCx
, aId
, entry
);
151 if (NS_WARN_IF(!constructor
)) {
152 return Throw(aCx
, NS_ERROR_FAILURE
);
154 if (!JS_WrapObject(aCx
, &constructor
)) {
155 return Throw(aCx
, NS_ERROR_FAILURE
);
158 aDesc
.set(mozilla::Some(JS::PropertyDescriptor::Data(
159 JS::ObjectValue(*constructor
), {JS::PropertyAttribute::Configurable
,
160 JS::PropertyAttribute::Writable
})));
164 // We've already checked whether the interface is enabled (see
165 // checkEnabledForScope above), so it's fine to pass
166 // DefineInterfaceProperty::Always here.
167 JS::Rooted
<JSObject
*> interfaceObject(
169 GetPerInterfaceObjectHandle(aCx
, entry
->mConstructorId
, entry
->mCreate
,
170 DefineInterfaceProperty::Always
));
171 if (NS_WARN_IF(!interfaceObject
)) {
172 return Throw(aCx
, NS_ERROR_FAILURE
);
175 // We've already defined the property. We indicate this to the caller
176 // by filling a property descriptor with JS::UndefinedValue() as the
177 // value. We still have to fill in a property descriptor, though, so
178 // that the caller knows the property is in fact on this object.
180 mozilla::Some(JS::PropertyDescriptor::Data(JS::UndefinedValue(), {})));
185 bool WebIDLGlobalNameHash::MayResolve(jsid aId
) {
186 return GetEntry(aId
.toLinearString()) != nullptr;
190 bool WebIDLGlobalNameHash::GetNames(JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
,
192 JS::MutableHandleVector
<jsid
> aNames
) {
193 // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe.
194 ProtoAndIfaceCache
* cache
= GetProtoAndIfaceCache(aObj
);
195 for (size_t i
= 0; i
< sCount
; ++i
) {
196 const WebIDLNameTableEntry
& entry
= sEntries
[i
];
197 // If aNameType is not AllNames, only include things whose entry slot in the
198 // ProtoAndIfaceCache is null.
199 if ((aNameType
== AllNames
||
200 !cache
->HasEntryInSlot(entry
.mConstructorId
)) &&
201 (!entry
.mEnabled
|| entry
.mEnabled(aCx
, aObj
))) {
202 JSString
* str
= JS_AtomizeStringN(aCx
, BindingName(entry
.mNameOffset
),
204 if (!str
|| !aNames
.append(JS::PropertyKey::NonIntAtom(str
))) {
214 bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext
* aCx
,
215 JS::Handle
<JSObject
*> aObj
,
216 JS::Handle
<jsid
> aId
,
218 MOZ_ASSERT(JS_IsGlobalObject(aObj
));
220 // First we try to resolve standard classes.
221 if (!JS_ResolveStandardClass(aCx
, aObj
, aId
, aResolvedp
)) {
228 // We don't resolve any non-string entries.
229 if (!aId
.isString()) {
233 // XXX(nika): In the Window case, we unwrap our global object here to handle
234 // XRays. I don't think we ever create xrays to system globals, so I believe
235 // we can skip this step.
236 MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(aObj
), "Xrays not supported!");
238 // Look up the corresponding entry in the name table, and resolve if enabled.
239 const WebIDLNameTableEntry
* entry
= GetEntry(aId
.toLinearString());
240 if (entry
&& (!entry
->mEnabled
|| entry
->mEnabled(aCx
, aObj
))) {
241 // We've already checked whether the interface is enabled (see
242 // entry->mEnabled above), so it's fine to pass
243 // DefineInterfaceProperty::Always here.
244 if (NS_WARN_IF(!GetPerInterfaceObjectHandle(
245 aCx
, entry
->mConstructorId
, entry
->mCreate
,
246 DefineInterfaceProperty::Always
))) {
247 return Throw(aCx
, NS_ERROR_FAILURE
);
256 bool WebIDLGlobalNameHash::NewEnumerateSystemGlobal(
257 JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
,
258 JS::MutableHandleVector
<jsid
> aProperties
, bool aEnumerableOnly
) {
259 MOZ_ASSERT(JS_IsGlobalObject(aObj
));
261 if (!JS_NewEnumerateStandardClasses(aCx
, aObj
, aProperties
,
266 // All properties defined on our global are non-enumerable, so we can skip
267 // remaining properties.
268 if (aEnumerableOnly
) {
272 // Enumerate all entries & add enabled ones.
273 for (size_t i
= 0; i
< sCount
; ++i
) {
274 const WebIDLNameTableEntry
& entry
= sEntries
[i
];
275 if (!entry
.mEnabled
|| entry
.mEnabled(aCx
, aObj
)) {
276 JSString
* str
= JS_AtomizeStringN(aCx
, BindingName(entry
.mNameOffset
),
278 if (!str
|| !aProperties
.append(JS::PropertyKey::NonIntAtom(str
))) {
286 } // namespace mozilla::dom