js/xpconnect/wrappers/ChromeObjectWrapper.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/xpconnect/wrappers/ChromeObjectWrapper.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,195 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     1.5 +/* vim: set ts=8 sts=4 et sw=4 tw=99: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "ChromeObjectWrapper.h"
    1.11 +#include "jsapi.h"
    1.12 +
    1.13 +using namespace JS;
    1.14 +
    1.15 +namespace xpc {
    1.16 +
    1.17 +// When creating wrappers for chrome objects in content, we detect if the
    1.18 +// prototype of the wrapped chrome object is a prototype for a standard class
    1.19 +// (like Array.prototype). If it is, we use the corresponding standard prototype
    1.20 +// from the wrapper's scope, rather than the wrapped standard prototype
    1.21 +// from the wrappee's scope.
    1.22 +//
    1.23 +// One of the reasons for doing this is to allow standard operations like
    1.24 +// chromeArray.forEach(..) to Just Work without explicitly listing them in
    1.25 +// __exposedProps__. Since proxies don't automatically inherit behavior from
    1.26 +// their prototype, we have to instrument the traps to do this manually.
    1.27 +ChromeObjectWrapper ChromeObjectWrapper::singleton;
    1.28 +
    1.29 +using js::assertEnteredPolicy;
    1.30 +
    1.31 +static bool
    1.32 +AllowedByBase(JSContext *cx, HandleObject wrapper, HandleId id,
    1.33 +              js::Wrapper::Action act)
    1.34 +{
    1.35 +    MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) ==
    1.36 +               &ChromeObjectWrapper::singleton);
    1.37 +    bool bp;
    1.38 +    ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton;
    1.39 +    return handler->ChromeObjectWrapperBase::enter(cx, wrapper, id, act, &bp);
    1.40 +}
    1.41 +
    1.42 +static bool
    1.43 +PropIsFromStandardPrototype(JSContext *cx, JS::MutableHandle<JSPropertyDescriptor> desc)
    1.44 +{
    1.45 +    MOZ_ASSERT(desc.object());
    1.46 +    RootedObject unwrapped(cx, js::UncheckedUnwrap(desc.object()));
    1.47 +    JSAutoCompartment ac(cx, unwrapped);
    1.48 +    return IdentifyStandardPrototype(unwrapped) != JSProto_Null;
    1.49 +}
    1.50 +
    1.51 +// Note that we're past the policy enforcement stage, here, so we can query
    1.52 +// ChromeObjectWrapperBase and get an unfiltered view of the underlying object.
    1.53 +// This lets us determine whether the property we would have found (given a
    1.54 +// transparent wrapper) would have come off a standard prototype.
    1.55 +static bool
    1.56 +PropIsFromStandardPrototype(JSContext *cx, HandleObject wrapper,
    1.57 +                            HandleId id)
    1.58 +{
    1.59 +    MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) ==
    1.60 +               &ChromeObjectWrapper::singleton);
    1.61 +    Rooted<JSPropertyDescriptor> desc(cx);
    1.62 +    ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton;
    1.63 +    if (!handler->ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id,
    1.64 +                                                                 &desc) ||
    1.65 +        !desc.object())
    1.66 +    {
    1.67 +        return false;
    1.68 +    }
    1.69 +    return PropIsFromStandardPrototype(cx, &desc);
    1.70 +}
    1.71 +
    1.72 +bool
    1.73 +ChromeObjectWrapper::getPropertyDescriptor(JSContext *cx,
    1.74 +                                           HandleObject wrapper,
    1.75 +                                           HandleId id,
    1.76 +                                           JS::MutableHandle<JSPropertyDescriptor> desc)
    1.77 +{
    1.78 +    assertEnteredPolicy(cx, wrapper, id, GET | SET);
    1.79 +    // First, try a lookup on the base wrapper if permitted.
    1.80 +    desc.object().set(nullptr);
    1.81 +    if (AllowedByBase(cx, wrapper, id, Wrapper::GET) &&
    1.82 +        !ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id,
    1.83 +                                                        desc)) {
    1.84 +        return false;
    1.85 +    }
    1.86 +
    1.87 +    // If the property is something that can be found on a standard prototype,
    1.88 +    // prefer the one we'll get via the prototype chain in the content
    1.89 +    // compartment.
    1.90 +    if (desc.object() && PropIsFromStandardPrototype(cx, desc))
    1.91 +        desc.object().set(nullptr);
    1.92 +
    1.93 +    // If we found something or have no proto, we're done.
    1.94 +    RootedObject wrapperProto(cx);
    1.95 +    if (!JS_GetPrototype(cx, wrapper, &wrapperProto))
    1.96 +      return false;
    1.97 +    if (desc.object() || !wrapperProto)
    1.98 +        return true;
    1.99 +
   1.100 +    // If not, try doing the lookup on the prototype.
   1.101 +    MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
   1.102 +    return JS_GetPropertyDescriptorById(cx, wrapperProto, id, desc);
   1.103 +}
   1.104 +
   1.105 +bool
   1.106 +ChromeObjectWrapper::has(JSContext *cx, HandleObject wrapper,
   1.107 +                         HandleId id, bool *bp)
   1.108 +{
   1.109 +    assertEnteredPolicy(cx, wrapper, id, GET);
   1.110 +    // Try the lookup on the base wrapper if permitted.
   1.111 +    if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) &&
   1.112 +        !ChromeObjectWrapperBase::has(cx, wrapper, id, bp))
   1.113 +    {
   1.114 +        return false;
   1.115 +    }
   1.116 +
   1.117 +    // If we found something or have no prototype, we're done.
   1.118 +    RootedObject wrapperProto(cx);
   1.119 +    if (!JS_GetPrototype(cx, wrapper, &wrapperProto))
   1.120 +        return false;
   1.121 +    if (*bp || !wrapperProto)
   1.122 +        return true;
   1.123 +
   1.124 +    // Try the prototype if that failed.
   1.125 +    MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
   1.126 +    Rooted<JSPropertyDescriptor> desc(cx);
   1.127 +    if (!JS_GetPropertyDescriptorById(cx, wrapperProto, id, &desc))
   1.128 +        return false;
   1.129 +    *bp = !!desc.object();
   1.130 +    return true;
   1.131 +}
   1.132 +
   1.133 +bool
   1.134 +ChromeObjectWrapper::get(JSContext *cx, HandleObject wrapper,
   1.135 +                         HandleObject receiver, HandleId id,
   1.136 +                         MutableHandleValue vp)
   1.137 +{
   1.138 +    assertEnteredPolicy(cx, wrapper, id, GET);
   1.139 +    vp.setUndefined();
   1.140 +    // Only call through to the get trap on the underlying object if we're
   1.141 +    // allowed to see the property, and if what we'll find is not on a standard
   1.142 +    // prototype.
   1.143 +    if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) &&
   1.144 +        !PropIsFromStandardPrototype(cx, wrapper, id))
   1.145 +    {
   1.146 +        // Call the get trap.
   1.147 +        if (!ChromeObjectWrapperBase::get(cx, wrapper, receiver, id, vp))
   1.148 +            return false;
   1.149 +        // If we found something, we're done.
   1.150 +        if (!vp.isUndefined())
   1.151 +            return true;
   1.152 +    }
   1.153 +
   1.154 +    // If we have no proto, we're done.
   1.155 +    RootedObject wrapperProto(cx);
   1.156 +    if (!JS_GetPrototype(cx, wrapper, &wrapperProto))
   1.157 +        return false;
   1.158 +    if (!wrapperProto)
   1.159 +        return true;
   1.160 +
   1.161 +    // Try the prototype.
   1.162 +    MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
   1.163 +    return js::GetGeneric(cx, wrapperProto, receiver, id, vp.address());
   1.164 +}
   1.165 +
   1.166 +// SecurityWrapper categorically returns false for objectClassIs, but the
   1.167 +// contacts API depends on Array.isArray returning true for COW-implemented
   1.168 +// contacts. This isn't really ideal, but make it work for now.
   1.169 +bool
   1.170 +ChromeObjectWrapper::objectClassIs(HandleObject obj, js::ESClassValue classValue,
   1.171 +                                   JSContext *cx)
   1.172 +{
   1.173 +  return CrossCompartmentWrapper::objectClassIs(obj, classValue, cx);
   1.174 +}
   1.175 +
   1.176 +// This mechanism isn't ideal because we end up calling enter() on the base class
   1.177 +// twice (once during enter() here and once during the trap itself), and policy
   1.178 +// enforcement or COWs isn't cheap. But it results in the cleanest code, and this
   1.179 +// whole proto remapping thing for COWs is going to be phased out anyway.
   1.180 +bool
   1.181 +ChromeObjectWrapper::enter(JSContext *cx, HandleObject wrapper,
   1.182 +                           HandleId id, js::Wrapper::Action act, bool *bp)
   1.183 +{
   1.184 +    if (AllowedByBase(cx, wrapper, id, act))
   1.185 +        return true;
   1.186 +    // COWs fail silently for GETs, and that also happens to be the only case
   1.187 +    // where we might want to redirect the lookup to the home prototype chain.
   1.188 +    *bp = act == Wrapper::GET || act == Wrapper::ENUMERATE;
   1.189 +    if (!*bp || id == JSID_VOID)
   1.190 +        return false;
   1.191 +
   1.192 +    // Note that PropIsFromStandardPrototype needs to invoke getPropertyDescriptor
   1.193 +    // before we've fully entered the policy. Waive our policy.
   1.194 +    js::AutoWaivePolicy policy(cx, wrapper, id, act);
   1.195 +    return PropIsFromStandardPrototype(cx, wrapper, id);
   1.196 +}
   1.197 +
   1.198 +}

mercurial