no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / bindings / WebIDLGlobalNameHash.cpp
blob3201f71ffd2071652bb0538268c24f11ba172938
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"
8 #include "js/Class.h"
9 #include "js/GCAPI.h"
10 #include "js/Id.h"
11 #include "js/Object.h" // JS::GetClass, JS::GetReservedSlot
12 #include "js/Wrapper.h"
13 #include "jsapi.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) {
38 return nullptr;
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)) ==
49 aId.toString()) {
50 return constructor;
54 // None of the legacy factory functions match, so the caller must want the
55 // interface object itself.
56 return interfaceObject;
59 /* static */
60 bool WebIDLGlobalNameHash::DefineIfEnabled(
61 JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
62 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc,
63 bool* aFound) {
64 MOZ_ASSERT(aId.isString(), "Check for string id before calling this!");
66 const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString());
67 if (!entry) {
68 *aFound = false;
69 return true;
72 *aFound = true;
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(
84 aCx,
85 js::CheckedUnwrapDynamic(aObj, aCx, /* stopAtWindowProxy = */ false));
86 if (!global) {
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.
94 #ifdef DEBUG
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)));
99 #endif
102 if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
103 return true;
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
139 // along.
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})));
156 return true;
159 JS::Rooted<JSObject*> interfaceObject(
160 aCx,
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.
171 aDesc.set(
172 mozilla::Some(JS::PropertyDescriptor::Data(JS::UndefinedValue(), {})));
173 return true;
176 /* static */
177 bool WebIDLGlobalNameHash::MayResolve(jsid aId) {
178 return GetEntry(aId.toLinearString()) != nullptr;
181 /* static */
182 bool WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
183 NameType aNameType,
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),
195 entry.mNameLength);
196 if (!str || !aNames.append(JS::PropertyKey::NonIntAtom(str))) {
197 return false;
202 return true;
205 /* static */
206 bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext* aCx,
207 JS::Handle<JSObject*> aObj,
208 JS::Handle<jsid> aId,
209 bool* aResolvedp) {
210 MOZ_ASSERT(JS_IsGlobalObject(aObj));
212 // First we try to resolve standard classes.
213 if (!JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp)) {
214 return false;
216 if (*aResolvedp) {
217 return true;
220 // We don't resolve any non-string entries.
221 if (!aId.isString()) {
222 return true;
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);
239 *aResolvedp = true;
241 return true;
244 /* static */
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,
251 aEnumerableOnly)) {
252 return false;
255 // All properties defined on our global are non-enumerable, so we can skip
256 // remaining properties.
257 if (aEnumerableOnly) {
258 return true;
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),
266 entry.mNameLength);
267 if (!str || !aProperties.append(JS::PropertyKey::NonIntAtom(str))) {
268 return false;
272 return true;
275 } // namespace mozilla::dom