Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / bindings / WebIDLGlobalNameHash.cpp
blob9e87c89536733b8006704cdc0277a13ffcf7c4b0
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 DefineInterfaceProperty::No);
37 if (!interfaceObject) {
38 return nullptr;
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);
48 if (!v.isObject()) {
49 break;
51 JSObject* constructor = &v.toObject();
52 if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor)) ==
53 aId.toString()) {
54 return constructor;
59 // None of the legacy factory functions match, so the caller must want the
60 // interface object itself.
61 return interfaceObject;
64 /* static */
65 bool WebIDLGlobalNameHash::DefineIfEnabled(
66 JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
67 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc,
68 bool* aFound) {
69 MOZ_ASSERT(aId.isString(), "Check for string id before calling this!");
71 const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString());
72 if (!entry) {
73 *aFound = false;
74 return true;
77 *aFound = true;
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(
89 aCx,
90 js::CheckedUnwrapDynamic(aObj, aCx, /* stopAtWindowProxy = */ false));
91 if (!global) {
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.
99 #ifdef DEBUG
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)));
104 #endif
107 if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
108 return true;
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
144 // along.
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})));
161 return true;
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(
168 aCx,
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.
179 aDesc.set(
180 mozilla::Some(JS::PropertyDescriptor::Data(JS::UndefinedValue(), {})));
181 return true;
184 /* static */
185 bool WebIDLGlobalNameHash::MayResolve(jsid aId) {
186 return GetEntry(aId.toLinearString()) != nullptr;
189 /* static */
190 bool WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
191 NameType aNameType,
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),
203 entry.mNameLength);
204 if (!str || !aNames.append(JS::PropertyKey::NonIntAtom(str))) {
205 return false;
210 return true;
213 /* static */
214 bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext* aCx,
215 JS::Handle<JSObject*> aObj,
216 JS::Handle<jsid> aId,
217 bool* aResolvedp) {
218 MOZ_ASSERT(JS_IsGlobalObject(aObj));
220 // First we try to resolve standard classes.
221 if (!JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp)) {
222 return false;
224 if (*aResolvedp) {
225 return true;
228 // We don't resolve any non-string entries.
229 if (!aId.isString()) {
230 return true;
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);
250 *aResolvedp = true;
252 return true;
255 /* static */
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,
262 aEnumerableOnly)) {
263 return false;
266 // All properties defined on our global are non-enumerable, so we can skip
267 // remaining properties.
268 if (aEnumerableOnly) {
269 return true;
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),
277 entry.mNameLength);
278 if (!str || !aProperties.append(JS::PropertyKey::NonIntAtom(str))) {
279 return false;
283 return true;
286 } // namespace mozilla::dom