michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ 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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ChromeObjectWrapper.h" michael@0: #include "jsapi.h" michael@0: michael@0: using namespace JS; michael@0: michael@0: namespace xpc { michael@0: michael@0: // When creating wrappers for chrome objects in content, we detect if the michael@0: // prototype of the wrapped chrome object is a prototype for a standard class michael@0: // (like Array.prototype). If it is, we use the corresponding standard prototype michael@0: // from the wrapper's scope, rather than the wrapped standard prototype michael@0: // from the wrappee's scope. michael@0: // michael@0: // One of the reasons for doing this is to allow standard operations like michael@0: // chromeArray.forEach(..) to Just Work without explicitly listing them in michael@0: // __exposedProps__. Since proxies don't automatically inherit behavior from michael@0: // their prototype, we have to instrument the traps to do this manually. michael@0: ChromeObjectWrapper ChromeObjectWrapper::singleton; michael@0: michael@0: using js::assertEnteredPolicy; michael@0: michael@0: static bool michael@0: AllowedByBase(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: js::Wrapper::Action act) michael@0: { michael@0: MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) == michael@0: &ChromeObjectWrapper::singleton); michael@0: bool bp; michael@0: ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton; michael@0: return handler->ChromeObjectWrapperBase::enter(cx, wrapper, id, act, &bp); michael@0: } michael@0: michael@0: static bool michael@0: PropIsFromStandardPrototype(JSContext *cx, JS::MutableHandle desc) michael@0: { michael@0: MOZ_ASSERT(desc.object()); michael@0: RootedObject unwrapped(cx, js::UncheckedUnwrap(desc.object())); michael@0: JSAutoCompartment ac(cx, unwrapped); michael@0: return IdentifyStandardPrototype(unwrapped) != JSProto_Null; michael@0: } michael@0: michael@0: // Note that we're past the policy enforcement stage, here, so we can query michael@0: // ChromeObjectWrapperBase and get an unfiltered view of the underlying object. michael@0: // This lets us determine whether the property we would have found (given a michael@0: // transparent wrapper) would have come off a standard prototype. michael@0: static bool michael@0: PropIsFromStandardPrototype(JSContext *cx, HandleObject wrapper, michael@0: HandleId id) michael@0: { michael@0: MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) == michael@0: &ChromeObjectWrapper::singleton); michael@0: Rooted desc(cx); michael@0: ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton; michael@0: if (!handler->ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id, michael@0: &desc) || michael@0: !desc.object()) michael@0: { michael@0: return false; michael@0: } michael@0: return PropIsFromStandardPrototype(cx, &desc); michael@0: } michael@0: michael@0: bool michael@0: ChromeObjectWrapper::getPropertyDescriptor(JSContext *cx, michael@0: HandleObject wrapper, michael@0: HandleId id, michael@0: JS::MutableHandle desc) michael@0: { michael@0: assertEnteredPolicy(cx, wrapper, id, GET | SET); michael@0: // First, try a lookup on the base wrapper if permitted. michael@0: desc.object().set(nullptr); michael@0: if (AllowedByBase(cx, wrapper, id, Wrapper::GET) && michael@0: !ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id, michael@0: desc)) { michael@0: return false; michael@0: } michael@0: michael@0: // If the property is something that can be found on a standard prototype, michael@0: // prefer the one we'll get via the prototype chain in the content michael@0: // compartment. michael@0: if (desc.object() && PropIsFromStandardPrototype(cx, desc)) michael@0: desc.object().set(nullptr); michael@0: michael@0: // If we found something or have no proto, we're done. michael@0: RootedObject wrapperProto(cx); michael@0: if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) michael@0: return false; michael@0: if (desc.object() || !wrapperProto) michael@0: return true; michael@0: michael@0: // If not, try doing the lookup on the prototype. michael@0: MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); michael@0: return JS_GetPropertyDescriptorById(cx, wrapperProto, id, desc); michael@0: } michael@0: michael@0: bool michael@0: ChromeObjectWrapper::has(JSContext *cx, HandleObject wrapper, michael@0: HandleId id, bool *bp) michael@0: { michael@0: assertEnteredPolicy(cx, wrapper, id, GET); michael@0: // Try the lookup on the base wrapper if permitted. michael@0: if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) && michael@0: !ChromeObjectWrapperBase::has(cx, wrapper, id, bp)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: // If we found something or have no prototype, we're done. michael@0: RootedObject wrapperProto(cx); michael@0: if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) michael@0: return false; michael@0: if (*bp || !wrapperProto) michael@0: return true; michael@0: michael@0: // Try the prototype if that failed. michael@0: MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); michael@0: Rooted desc(cx); michael@0: if (!JS_GetPropertyDescriptorById(cx, wrapperProto, id, &desc)) michael@0: return false; michael@0: *bp = !!desc.object(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ChromeObjectWrapper::get(JSContext *cx, HandleObject wrapper, michael@0: HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) michael@0: { michael@0: assertEnteredPolicy(cx, wrapper, id, GET); michael@0: vp.setUndefined(); michael@0: // Only call through to the get trap on the underlying object if we're michael@0: // allowed to see the property, and if what we'll find is not on a standard michael@0: // prototype. michael@0: if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) && michael@0: !PropIsFromStandardPrototype(cx, wrapper, id)) michael@0: { michael@0: // Call the get trap. michael@0: if (!ChromeObjectWrapperBase::get(cx, wrapper, receiver, id, vp)) michael@0: return false; michael@0: // If we found something, we're done. michael@0: if (!vp.isUndefined()) michael@0: return true; michael@0: } michael@0: michael@0: // If we have no proto, we're done. michael@0: RootedObject wrapperProto(cx); michael@0: if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) michael@0: return false; michael@0: if (!wrapperProto) michael@0: return true; michael@0: michael@0: // Try the prototype. michael@0: MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); michael@0: return js::GetGeneric(cx, wrapperProto, receiver, id, vp.address()); michael@0: } michael@0: michael@0: // SecurityWrapper categorically returns false for objectClassIs, but the michael@0: // contacts API depends on Array.isArray returning true for COW-implemented michael@0: // contacts. This isn't really ideal, but make it work for now. michael@0: bool michael@0: ChromeObjectWrapper::objectClassIs(HandleObject obj, js::ESClassValue classValue, michael@0: JSContext *cx) michael@0: { michael@0: return CrossCompartmentWrapper::objectClassIs(obj, classValue, cx); michael@0: } michael@0: michael@0: // This mechanism isn't ideal because we end up calling enter() on the base class michael@0: // twice (once during enter() here and once during the trap itself), and policy michael@0: // enforcement or COWs isn't cheap. But it results in the cleanest code, and this michael@0: // whole proto remapping thing for COWs is going to be phased out anyway. michael@0: bool michael@0: ChromeObjectWrapper::enter(JSContext *cx, HandleObject wrapper, michael@0: HandleId id, js::Wrapper::Action act, bool *bp) michael@0: { michael@0: if (AllowedByBase(cx, wrapper, id, act)) michael@0: return true; michael@0: // COWs fail silently for GETs, and that also happens to be the only case michael@0: // where we might want to redirect the lookup to the home prototype chain. michael@0: *bp = act == Wrapper::GET || act == Wrapper::ENUMERATE; michael@0: if (!*bp || id == JSID_VOID) michael@0: return false; michael@0: michael@0: // Note that PropIsFromStandardPrototype needs to invoke getPropertyDescriptor michael@0: // before we've fully entered the policy. Waive our policy. michael@0: js::AutoWaivePolicy policy(cx, wrapper, id, act); michael@0: return PropIsFromStandardPrototype(cx, wrapper, id); michael@0: } michael@0: michael@0: }