michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: set ts=2 sw=2 et tw=99 ft=cpp: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/dom/DOMJSProxyHandler.h" michael@0: #include "xpcpublic.h" michael@0: #include "xpcprivate.h" michael@0: #include "XPCQuickStubs.h" michael@0: #include "XPCWrapper.h" michael@0: #include "WrapperFactory.h" michael@0: #include "nsDOMClassInfo.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: michael@0: #include "jsapi.h" michael@0: michael@0: using namespace JS; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: jsid s_length_id = JSID_VOID; michael@0: michael@0: bool michael@0: DefineStaticJSVals(JSContext* cx) michael@0: { michael@0: return InternJSString(cx, s_length_id, "length"); michael@0: } michael@0: michael@0: michael@0: const char HandlerFamily = 0; michael@0: michael@0: js::DOMProxyShadowsResult michael@0: DOMProxyShadows(JSContext* cx, JS::Handle proxy, JS::Handle id) michael@0: { michael@0: JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO); michael@0: if (v.isObject()) { michael@0: bool hasOwn; michael@0: Rooted object(cx, &v.toObject()); michael@0: if (!JS_AlreadyHasOwnPropertyById(cx, object, id, &hasOwn)) michael@0: return js::ShadowCheckFailed; michael@0: michael@0: return hasOwn ? js::Shadows : js::DoesntShadow; michael@0: } michael@0: michael@0: if (v.isUndefined()) { michael@0: return js::DoesntShadow; michael@0: } michael@0: michael@0: bool hasOwn; michael@0: if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn)) michael@0: return js::ShadowCheckFailed; michael@0: michael@0: return hasOwn ? js::Shadows : js::DoesntShadowUnique; michael@0: } michael@0: michael@0: // Store the information for the specialized ICs. michael@0: struct SetDOMProxyInformation michael@0: { michael@0: SetDOMProxyInformation() { michael@0: js::SetDOMProxyInformation((const void*) &HandlerFamily, michael@0: js::PROXY_EXTRA_SLOT + JSPROXYSLOT_EXPANDO, DOMProxyShadows); michael@0: } michael@0: }; michael@0: michael@0: SetDOMProxyInformation gSetDOMProxyInformation; michael@0: michael@0: // static michael@0: JSObject* michael@0: DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) michael@0: { michael@0: MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object"); michael@0: JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); michael@0: if (v.isUndefined()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (v.isObject()) { michael@0: js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, UndefinedValue()); michael@0: XPCWrappedNativeScope* scope = xpc::MaybeGetObjectScope(obj); michael@0: if (scope) { michael@0: scope->RemoveDOMExpandoObject(obj); michael@0: } michael@0: } else { michael@0: js::ExpandoAndGeneration* expandoAndGeneration = michael@0: static_cast(v.toPrivate()); michael@0: v = expandoAndGeneration->expando; michael@0: if (v.isUndefined()) { michael@0: return nullptr; michael@0: } michael@0: expandoAndGeneration->expando = UndefinedValue(); michael@0: } michael@0: michael@0: michael@0: return &v.toObject(); michael@0: } michael@0: michael@0: // static michael@0: JSObject* michael@0: DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle obj) michael@0: { michael@0: NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object"); michael@0: JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); michael@0: if (v.isObject()) { michael@0: return &v.toObject(); michael@0: } michael@0: michael@0: js::ExpandoAndGeneration* expandoAndGeneration; michael@0: if (!v.isUndefined()) { michael@0: expandoAndGeneration = static_cast(v.toPrivate()); michael@0: if (expandoAndGeneration->expando.isObject()) { michael@0: return &expandoAndGeneration->expando.toObject(); michael@0: } michael@0: } else { michael@0: expandoAndGeneration = nullptr; michael@0: } michael@0: michael@0: JS::Rooted parent(cx, js::GetObjectParent(obj)); michael@0: JS::Rooted expando(cx, michael@0: JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), parent)); michael@0: if (!expando) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsISupports* native = UnwrapDOMObject(obj); michael@0: nsWrapperCache* cache; michael@0: CallQueryInterface(native, &cache); michael@0: if (expandoAndGeneration) { michael@0: cache->PreserveWrapper(native); michael@0: expandoAndGeneration->expando.setObject(*expando); michael@0: michael@0: return expando; michael@0: } michael@0: michael@0: XPCWrappedNativeScope* scope = xpc::GetObjectScope(obj); michael@0: if (!scope->RegisterDOMExpandoObject(obj)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: cache->SetPreservingWrapper(true); michael@0: js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando)); michael@0: michael@0: return expando; michael@0: } michael@0: michael@0: bool michael@0: DOMProxyHandler::isExtensible(JSContext *cx, JS::Handle proxy, bool *extensible) michael@0: { michael@0: // always extensible per WebIDL michael@0: *extensible = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DOMProxyHandler::preventExtensions(JSContext *cx, JS::Handle proxy) michael@0: { michael@0: // Throw a TypeError, per WebIDL. michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_CANT_CHANGE_EXTENSIBILITY); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: BaseDOMProxyHandler::getPropertyDescriptor(JSContext* cx, michael@0: JS::Handle proxy, michael@0: JS::Handle id, michael@0: MutableHandle desc) michael@0: { michael@0: if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) { michael@0: return false; michael@0: } michael@0: if (desc.object()) { michael@0: return true; michael@0: } michael@0: michael@0: JS::Rooted proto(cx); michael@0: if (!js::GetObjectProto(cx, proxy, &proto)) { michael@0: return false; michael@0: } michael@0: if (!proto) { michael@0: desc.object().set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: return JS_GetPropertyDescriptorById(cx, proto, id, desc); michael@0: } michael@0: michael@0: bool michael@0: BaseDOMProxyHandler::getOwnPropertyDescriptor(JSContext* cx, michael@0: JS::Handle proxy, michael@0: JS::Handle id, michael@0: MutableHandle desc) michael@0: { michael@0: return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false, michael@0: desc); michael@0: } michael@0: michael@0: bool michael@0: DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle proxy, JS::Handle id, michael@0: MutableHandle desc, bool* defined) michael@0: { michael@0: if (desc.hasGetterObject() && desc.setter() == JS_StrictPropertyStub) { michael@0: return JS_ReportErrorFlagsAndNumber(cx, michael@0: JSREPORT_WARNING | JSREPORT_STRICT | michael@0: JSREPORT_STRICT_MODE_ERROR, michael@0: js_GetErrorMessage, nullptr, michael@0: JSMSG_GETTER_ONLY); michael@0: } michael@0: michael@0: if (xpc::WrapperFactory::IsXrayWrapper(proxy)) { michael@0: return true; michael@0: } michael@0: michael@0: JSObject* expando = EnsureExpandoObject(cx, proxy); michael@0: if (!expando) { michael@0: return false; michael@0: } michael@0: michael@0: bool dummy; michael@0: return js_DefineOwnProperty(cx, expando, id, desc, &dummy); michael@0: } michael@0: michael@0: bool michael@0: DOMProxyHandler::set(JSContext *cx, Handle proxy, Handle receiver, michael@0: Handle id, bool strict, MutableHandle vp) michael@0: { michael@0: MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), michael@0: "Should not have a XrayWrapper here"); michael@0: bool done; michael@0: if (!setCustom(cx, proxy, id, vp, &done)) { michael@0: return false; michael@0: } michael@0: if (done) { michael@0: return true; michael@0: } michael@0: michael@0: // Make sure to ignore our named properties when checking for own michael@0: // property descriptors for a set. michael@0: JS::Rooted desc(cx); michael@0: if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true, michael@0: &desc)) { michael@0: return false; michael@0: } michael@0: bool descIsOwn = desc.object() != nullptr; michael@0: if (!desc.object()) { michael@0: // Don't just use getPropertyDescriptor, unlike BaseProxyHandler::set, michael@0: // because that would call getOwnPropertyDescriptor on ourselves. Instead, michael@0: // directly delegate to the proto, if any. michael@0: JS::Rooted proto(cx); michael@0: if (!js::GetObjectProto(cx, proxy, &proto)) { michael@0: return false; michael@0: } michael@0: if (proto && !JS_GetPropertyDescriptorById(cx, proto, id, &desc)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return js::SetPropertyIgnoringNamedGetter(cx, this, proxy, receiver, id, michael@0: &desc, descIsOwn, strict, vp); michael@0: } michael@0: michael@0: bool michael@0: DOMProxyHandler::delete_(JSContext* cx, JS::Handle proxy, michael@0: JS::Handle id, bool* bp) michael@0: { michael@0: JS::Rooted expando(cx); michael@0: if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { michael@0: return JS_DeletePropertyById2(cx, expando, id, bp); michael@0: } michael@0: michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BaseDOMProxyHandler::enumerate(JSContext* cx, JS::Handle proxy, michael@0: AutoIdVector& props) michael@0: { michael@0: JS::Rooted proto(cx); michael@0: if (!JS_GetPrototype(cx, proxy, &proto)) { michael@0: return false; michael@0: } michael@0: return keys(cx, proxy, props) && michael@0: (!proto || js::GetPropertyNames(cx, proto, 0, &props)); michael@0: } michael@0: michael@0: bool michael@0: BaseDOMProxyHandler::watch(JSContext* cx, JS::Handle proxy, JS::Handle id, michael@0: JS::Handle callable) michael@0: { michael@0: return js::WatchGuts(cx, proxy, id, callable); michael@0: } michael@0: michael@0: bool michael@0: BaseDOMProxyHandler::unwatch(JSContext* cx, JS::Handle proxy, JS::Handle id) michael@0: { michael@0: return js::UnwatchGuts(cx, proxy, id); michael@0: } michael@0: michael@0: bool michael@0: BaseDOMProxyHandler::getOwnPropertyNames(JSContext* cx, michael@0: JS::Handle proxy, michael@0: JS::AutoIdVector& props) michael@0: { michael@0: return ownPropNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN, props); michael@0: } michael@0: michael@0: bool michael@0: BaseDOMProxyHandler::keys(JSContext* cx, michael@0: JS::Handle proxy, michael@0: JS::AutoIdVector& props) michael@0: { michael@0: return ownPropNames(cx, proxy, JSITER_OWNONLY, props); michael@0: } michael@0: michael@0: bool michael@0: DOMProxyHandler::has(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) michael@0: { michael@0: if (!hasOwn(cx, proxy, id, bp)) { michael@0: return false; michael@0: } michael@0: michael@0: if (*bp) { michael@0: // We have the property ourselves; no need to worry about our prototype michael@0: // chain. michael@0: return true; michael@0: } michael@0: michael@0: // OK, now we have to look at the proto michael@0: JS::Rooted proto(cx); michael@0: if (!js::GetObjectProto(cx, proxy, &proto)) { michael@0: return false; michael@0: } michael@0: if (!proto) { michael@0: return true; michael@0: } michael@0: bool protoHasProp; michael@0: bool ok = JS_HasPropertyById(cx, proto, id, &protoHasProp); michael@0: if (ok) { michael@0: *bp = protoHasProp; michael@0: } michael@0: return ok; michael@0: } michael@0: michael@0: int32_t michael@0: IdToInt32(JSContext* cx, JS::Handle id) michael@0: { michael@0: JS::Rooted idval(cx); michael@0: double array_index; michael@0: int32_t i; michael@0: if (!::JS_IdToValue(cx, id, &idval) || michael@0: !JS::ToNumber(cx, idval, &array_index) || michael@0: !::JS_DoubleIsInt32(array_index, &i)) { michael@0: return -1; michael@0: } michael@0: michael@0: return i; michael@0: } michael@0: michael@0: bool michael@0: DOMProxyHandler::setCustom(JSContext* cx, JS::Handle proxy, JS::Handle id, michael@0: JS::MutableHandle vp, bool *done) michael@0: { michael@0: *done = false; michael@0: return true; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla