Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsGlobalWindowInner.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;
}
if (IsInterfaceObject(interfaceObject)) {
// 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 = INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION;
slot < INTERFACE_OBJECT_MAX_SLOTS; ++slot) {
const JS::Value& v = js::GetFunctionNativeReserved(interfaceObject, slot);
if (!v.isObject()) {
break;
}
JSObject* constructor = &v.toObject();
if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor)) ==
aId.toString()) {
return constructor;
}
}
}
// None of the legacy factory functions 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