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 /* aDefineOnGlobal = */ false);
37 if (!interfaceObject
) {
41 // This is a call over Xrays, so we will actually use the return value
42 // (instead of just having it defined on the global now). Check for named
43 // constructors with this id, in case that's what the caller is asking for.
44 for (unsigned slot
= DOM_INTERFACE_SLOTS_BASE
;
45 slot
< JSCLASS_RESERVED_SLOTS(JS::GetClass(interfaceObject
)); ++slot
) {
46 JSObject
* constructor
=
47 &JS::GetReservedSlot(interfaceObject
, slot
).toObject();
48 if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor
)) ==
54 // None of the legacy factory functions match, so the caller must want the
55 // interface object itself.
56 return interfaceObject
;
60 bool WebIDLGlobalNameHash::DefineIfEnabled(
61 JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
, JS::Handle
<jsid
> aId
,
62 JS::MutableHandle
<mozilla::Maybe
<JS::PropertyDescriptor
>> aDesc
,
64 MOZ_ASSERT(aId
.isString(), "Check for string id before calling this!");
66 const WebIDLNameTableEntry
* entry
= GetEntry(aId
.toLinearString());
74 ConstructorEnabled checkEnabledForScope
= entry
->mEnabled
;
75 // We do the enabled check on the current Realm of aCx, but for the
76 // actual object we pass in the underlying object in the Xray case. That
77 // way the callee can decide whether to allow access based on the caller
78 // or the window being touched.
80 // Using aCx to represent the current Realm for CheckedUnwrapDynamic
81 // purposes is OK here, because that's the Realm where we plan to do
82 // our property-defining.
83 JS::Rooted
<JSObject
*> global(
85 js::CheckedUnwrapDynamic(aObj
, aCx
, /* stopAtWindowProxy = */ false));
87 return Throw(aCx
, NS_ERROR_DOM_SECURITY_ERR
);
91 // It's safe to pass "&global" here, because we've already unwrapped it, but
92 // for general sanity better to not have debug code even having the
93 // appearance of mutating things that opt code uses.
95 JS::Rooted
<JSObject
*> temp(aCx
, global
);
96 DebugOnly
<nsGlobalWindowInner
*> win
;
97 MOZ_ASSERT(NS_SUCCEEDED(
98 UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window
, &temp
, win
, aCx
)));
102 if (checkEnabledForScope
&& !checkEnabledForScope(aCx
, global
)) {
106 // The DOM constructor resolve machinery interacts with Xrays in tricky
107 // ways, and there are some asymmetries that are important to understand.
109 // In the regular (non-Xray) case, we only want to resolve constructors
110 // once (so that if they're deleted, they don't reappear). We do this by
111 // stashing the constructor in a slot on the global, such that we can see
112 // during resolve whether we've created it already. This is rather
113 // memory-intensive, so we don't try to maintain these semantics when
114 // manipulating a global over Xray (so the properties just re-resolve if
115 // they've been deleted).
117 // Unfortunately, there's a bit of an impedance-mismatch between the Xray
118 // and non-Xray machinery. The Xray machinery wants an API that returns a
119 // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
120 // snared up with trying to define a property on the Xray holder. At the
121 // same time, the DefineInterface callbacks are set up to define things
122 // directly on the global. And re-jiggering them to return property
123 // descriptors is tricky, because some DefineInterface callbacks define
124 // multiple things (like the Image() alias for HTMLImageElement).
126 // So the setup is as-follows:
128 // * The resolve function takes a JS::PropertyDescriptor, but in the
129 // non-Xray case, callees may define things directly on the global, and
130 // set the value on the property descriptor to |undefined| to indicate
131 // that there's nothing more for the caller to do. We assert against
132 // this behavior in the Xray case.
134 // * We make sure that we do a non-Xray resolve first, so that all the
135 // slots are set up. In the Xray case, this means unwrapping and doing
136 // a non-Xray resolve before doing the Xray resolve.
138 // This all could use some grand refactoring, but for now we just limp
140 if (xpc::WrapperFactory::IsXrayWrapper(aObj
)) {
141 JS::Rooted
<JSObject
*> constructor(aCx
);
143 JSAutoRealm
ar(aCx
, global
);
144 constructor
= FindNamedConstructorForXray(aCx
, aId
, entry
);
146 if (NS_WARN_IF(!constructor
)) {
147 return Throw(aCx
, NS_ERROR_FAILURE
);
149 if (!JS_WrapObject(aCx
, &constructor
)) {
150 return Throw(aCx
, NS_ERROR_FAILURE
);
153 aDesc
.set(mozilla::Some(JS::PropertyDescriptor::Data(
154 JS::ObjectValue(*constructor
), {JS::PropertyAttribute::Configurable
,
155 JS::PropertyAttribute::Writable
})));
159 JS::Rooted
<JSObject
*> interfaceObject(
161 GetPerInterfaceObjectHandle(aCx
, entry
->mConstructorId
, entry
->mCreate
,
162 /* aDefineOnGlobal = */ true));
163 if (NS_WARN_IF(!interfaceObject
)) {
164 return Throw(aCx
, NS_ERROR_FAILURE
);
167 // We've already defined the property. We indicate this to the caller
168 // by filling a property descriptor with JS::UndefinedValue() as the
169 // value. We still have to fill in a property descriptor, though, so
170 // that the caller knows the property is in fact on this object.
172 mozilla::Some(JS::PropertyDescriptor::Data(JS::UndefinedValue(), {})));
177 bool WebIDLGlobalNameHash::MayResolve(jsid aId
) {
178 return GetEntry(aId
.toLinearString()) != nullptr;
182 bool WebIDLGlobalNameHash::GetNames(JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
,
184 JS::MutableHandleVector
<jsid
> aNames
) {
185 // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe.
186 ProtoAndIfaceCache
* cache
= GetProtoAndIfaceCache(aObj
);
187 for (size_t i
= 0; i
< sCount
; ++i
) {
188 const WebIDLNameTableEntry
& entry
= sEntries
[i
];
189 // If aNameType is not AllNames, only include things whose entry slot in the
190 // ProtoAndIfaceCache is null.
191 if ((aNameType
== AllNames
||
192 !cache
->HasEntryInSlot(entry
.mConstructorId
)) &&
193 (!entry
.mEnabled
|| entry
.mEnabled(aCx
, aObj
))) {
194 JSString
* str
= JS_AtomizeStringN(aCx
, BindingName(entry
.mNameOffset
),
196 if (!str
|| !aNames
.append(JS::PropertyKey::NonIntAtom(str
))) {
206 bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext
* aCx
,
207 JS::Handle
<JSObject
*> aObj
,
208 JS::Handle
<jsid
> aId
,
210 MOZ_ASSERT(JS_IsGlobalObject(aObj
));
212 // First we try to resolve standard classes.
213 if (!JS_ResolveStandardClass(aCx
, aObj
, aId
, aResolvedp
)) {
220 // We don't resolve any non-string entries.
221 if (!aId
.isString()) {
225 // XXX(nika): In the Window case, we unwrap our global object here to handle
226 // XRays. I don't think we ever create xrays to system globals, so I believe
227 // we can skip this step.
228 MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(aObj
), "Xrays not supported!");
230 // Look up the corresponding entry in the name table, and resolve if enabled.
231 const WebIDLNameTableEntry
* entry
= GetEntry(aId
.toLinearString());
232 if (entry
&& (!entry
->mEnabled
|| entry
->mEnabled(aCx
, aObj
))) {
233 if (NS_WARN_IF(!GetPerInterfaceObjectHandle(
234 aCx
, entry
->mConstructorId
, entry
->mCreate
,
235 /* aDefineOnGlobal = */ true))) {
236 return Throw(aCx
, NS_ERROR_FAILURE
);
245 bool WebIDLGlobalNameHash::NewEnumerateSystemGlobal(
246 JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
,
247 JS::MutableHandleVector
<jsid
> aProperties
, bool aEnumerableOnly
) {
248 MOZ_ASSERT(JS_IsGlobalObject(aObj
));
250 if (!JS_NewEnumerateStandardClasses(aCx
, aObj
, aProperties
,
255 // All properties defined on our global are non-enumerable, so we can skip
256 // remaining properties.
257 if (aEnumerableOnly
) {
261 // Enumerate all entries & add enabled ones.
262 for (size_t i
= 0; i
< sCount
; ++i
) {
263 const WebIDLNameTableEntry
& entry
= sEntries
[i
];
264 if (!entry
.mEnabled
|| entry
.mEnabled(aCx
, aObj
)) {
265 JSString
* str
= JS_AtomizeStringN(aCx
, BindingName(entry
.mNameOffset
),
267 if (!str
|| !aProperties
.append(JS::PropertyKey::NonIntAtom(str
))) {
275 } // namespace mozilla::dom