summaryrefslogtreecommitdiffstats
path: root/dom/bindings/WebIDLGlobalNameHash.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/bindings/WebIDLGlobalNameHash.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/bindings/WebIDLGlobalNameHash.cpp')
-rw-r--r--dom/bindings/WebIDLGlobalNameHash.cpp274
1 files changed, 274 insertions, 0 deletions
diff --git a/dom/bindings/WebIDLGlobalNameHash.cpp b/dom/bindings/WebIDLGlobalNameHash.cpp
new file mode 100644
index 0000000000..4ed6b96706
--- /dev/null
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebIDLGlobalNameHash.h"
+#include "js/Class.h"
+#include "js/GCAPI.h"
+#include "js/Id.h"
+#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingNames.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/JSSlots.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "mozilla/dom/ProxyHandlerUtils.h"
+#include "mozilla/dom/RegisterBindings.h"
+#include "nsGlobalWindow.h"
+#include "nsTHashtable.h"
+#include "WrapperFactory.h"
+
+namespace mozilla::dom {
+
+static JSObject* FindNamedConstructorForXray(
+ JSContext* aCx, JS::Handle<jsid> aId, const WebIDLNameTableEntry* aEntry) {
+ JSObject* interfaceObject =
+ GetPerInterfaceObjectHandle(aCx, aEntry->mConstructorId, aEntry->mCreate,
+ /* aDefineOnGlobal = */ false);
+ if (!interfaceObject) {
+ return nullptr;
+ }
+
+ // This is a call over Xrays, so we will actually use the return value
+ // (instead of just having it defined on the global now). Check for named
+ // constructors with this id, in case that's what the caller is asking for.
+ for (unsigned slot = DOM_INTERFACE_SLOTS_BASE;
+ slot < JSCLASS_RESERVED_SLOTS(JS::GetClass(interfaceObject)); ++slot) {
+ JSObject* constructor =
+ &JS::GetReservedSlot(interfaceObject, slot).toObject();
+ if (JS_GetFunctionId(JS_GetObjectFunction(constructor)) == aId.toString()) {
+ return constructor;
+ }
+ }
+
+ // None of the named constructors match, so the caller must want the
+ // interface object itself.
+ return interfaceObject;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::DefineIfEnabled(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc,
+ bool* aFound) {
+ MOZ_ASSERT(aId.isString(), "Check for string id before calling this!");
+
+ const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString());
+ if (!entry) {
+ *aFound = false;
+ return true;
+ }
+
+ *aFound = true;
+
+ ConstructorEnabled checkEnabledForScope = entry->mEnabled;
+ // We do the enabled check on the current Realm of aCx, but for the
+ // actual object we pass in the underlying object in the Xray case. That
+ // way the callee can decide whether to allow access based on the caller
+ // or the window being touched.
+ //
+ // Using aCx to represent the current Realm for CheckedUnwrapDynamic
+ // purposes is OK here, because that's the Realm where we plan to do
+ // our property-defining.
+ JS::Rooted<JSObject*> global(
+ aCx,
+ js::CheckedUnwrapDynamic(aObj, aCx, /* stopAtWindowProxy = */ false));
+ if (!global) {
+ return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ {
+ // It's safe to pass "&global" here, because we've already unwrapped it, but
+ // for general sanity better to not have debug code even having the
+ // appearance of mutating things that opt code uses.
+#ifdef DEBUG
+ JS::Rooted<JSObject*> temp(aCx, global);
+ DebugOnly<nsGlobalWindowInner*> win;
+ MOZ_ASSERT(NS_SUCCEEDED(
+ UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, &temp, win, aCx)));
+#endif
+ }
+
+ if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
+ return true;
+ }
+
+ // The DOM constructor resolve machinery interacts with Xrays in tricky
+ // ways, and there are some asymmetries that are important to understand.
+ //
+ // In the regular (non-Xray) case, we only want to resolve constructors
+ // once (so that if they're deleted, they don't reappear). We do this by
+ // stashing the constructor in a slot on the global, such that we can see
+ // during resolve whether we've created it already. This is rather
+ // memory-intensive, so we don't try to maintain these semantics when
+ // manipulating a global over Xray (so the properties just re-resolve if
+ // they've been deleted).
+ //
+ // Unfortunately, there's a bit of an impedance-mismatch between the Xray
+ // and non-Xray machinery. The Xray machinery wants an API that returns a
+ // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
+ // snared up with trying to define a property on the Xray holder. At the
+ // same time, the DefineInterface callbacks are set up to define things
+ // directly on the global. And re-jiggering them to return property
+ // descriptors is tricky, because some DefineInterface callbacks define
+ // multiple things (like the Image() alias for HTMLImageElement).
+ //
+ // So the setup is as-follows:
+ //
+ // * The resolve function takes a JS::PropertyDescriptor, but in the
+ // non-Xray case, callees may define things directly on the global, and
+ // set the value on the property descriptor to |undefined| to indicate
+ // that there's nothing more for the caller to do. We assert against
+ // this behavior in the Xray case.
+ //
+ // * We make sure that we do a non-Xray resolve first, so that all the
+ // slots are set up. In the Xray case, this means unwrapping and doing
+ // a non-Xray resolve before doing the Xray resolve.
+ //
+ // This all could use some grand refactoring, but for now we just limp
+ // along.
+ if (xpc::WrapperFactory::IsXrayWrapper(aObj)) {
+ JS::Rooted<JSObject*> constructor(aCx);
+ {
+ JSAutoRealm ar(aCx, global);
+ constructor = FindNamedConstructorForXray(aCx, aId, entry);
+ }
+ if (NS_WARN_IF(!constructor)) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+ if (!JS_WrapObject(aCx, &constructor)) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+
+ aDesc.set(mozilla::Some(JS::PropertyDescriptor::Data(
+ JS::ObjectValue(*constructor), {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+ }
+
+ JS::Rooted<JSObject*> interfaceObject(
+ aCx,
+ GetPerInterfaceObjectHandle(aCx, entry->mConstructorId, entry->mCreate,
+ /* aDefineOnGlobal = */ true));
+ if (NS_WARN_IF(!interfaceObject)) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+
+ // We've already defined the property. We indicate this to the caller
+ // by filling a property descriptor with JS::UndefinedValue() as the
+ // value. We still have to fill in a property descriptor, though, so
+ // that the caller knows the property is in fact on this object.
+ aDesc.set(
+ mozilla::Some(JS::PropertyDescriptor::Data(JS::UndefinedValue(), {})));
+ return true;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::MayResolve(jsid aId) {
+ return GetEntry(aId.toLinearString()) != nullptr;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ NameType aNameType,
+ JS::MutableHandleVector<jsid> aNames) {
+ // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe.
+ ProtoAndIfaceCache* cache = GetProtoAndIfaceCache(aObj);
+ for (size_t i = 0; i < sCount; ++i) {
+ const WebIDLNameTableEntry& entry = sEntries[i];
+ // If aNameType is not AllNames, only include things whose entry slot in the
+ // ProtoAndIfaceCache is null.
+ if ((aNameType == AllNames ||
+ !cache->HasEntryInSlot(entry.mConstructorId)) &&
+ (!entry.mEnabled || entry.mEnabled(aCx, aObj))) {
+ JSString* str = JS_AtomizeStringN(aCx, BindingName(entry.mNameOffset),
+ entry.mNameLength);
+ if (!str || !aNames.append(JS::PropertyKey::NonIntAtom(str))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ JS::Handle<jsid> aId,
+ bool* aResolvedp) {
+ MOZ_ASSERT(JS_IsGlobalObject(aObj));
+
+ // First we try to resolve standard classes.
+ if (!JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp)) {
+ return false;
+ }
+ if (*aResolvedp) {
+ return true;
+ }
+
+ // We don't resolve any non-string entries.
+ if (!aId.isString()) {
+ return true;
+ }
+
+ // XXX(nika): In the Window case, we unwrap our global object here to handle
+ // XRays. I don't think we ever create xrays to system globals, so I believe
+ // we can skip this step.
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(aObj), "Xrays not supported!");
+
+ // Look up the corresponding entry in the name table, and resolve if enabled.
+ const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString());
+ if (entry && (!entry->mEnabled || entry->mEnabled(aCx, aObj))) {
+ if (NS_WARN_IF(!GetPerInterfaceObjectHandle(
+ aCx, entry->mConstructorId, entry->mCreate,
+ /* aDefineOnGlobal = */ true))) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+
+ *aResolvedp = true;
+ }
+ return true;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::NewEnumerateSystemGlobal(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandleVector<jsid> aProperties, bool aEnumerableOnly) {
+ MOZ_ASSERT(JS_IsGlobalObject(aObj));
+
+ if (!JS_NewEnumerateStandardClasses(aCx, aObj, aProperties,
+ aEnumerableOnly)) {
+ return false;
+ }
+
+ // All properties defined on our global are non-enumerable, so we can skip
+ // remaining properties.
+ if (aEnumerableOnly) {
+ return true;
+ }
+
+ // Enumerate all entries & add enabled ones.
+ for (size_t i = 0; i < sCount; ++i) {
+ const WebIDLNameTableEntry& entry = sEntries[i];
+ if (!entry.mEnabled || entry.mEnabled(aCx, aObj)) {
+ JSString* str = JS_AtomizeStringN(aCx, BindingName(entry.mNameOffset),
+ entry.mNameLength);
+ if (!str || !aProperties.append(JS::PropertyKey::NonIntAtom(str))) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace mozilla::dom