Bug 1363208 part 2. Add a helper class for implementing the HTML requirements for cross-origin-accessible objects. r=jandem,peterv

Differential Revision: https://phabricator.services.mozilla.com/D15425

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Boris Zbarsky 2019-01-21 03:28:06 +00:00
Родитель 67be15b8fc
Коммит 79d353e3bc
6 изменённых файлов: 798 добавлений и 124 удалений

Просмотреть файл

@ -0,0 +1,433 @@
/* -*- 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 "mozilla/dom/MaybeCrossOriginObject.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/RemoteObjectProxy.h"
#include "js/Proxy.h"
#include "js/RootingAPI.h"
#include "js/Wrapper.h"
#include "jsfriendapi.h"
#include "AccessCheck.h"
#include "nsContentUtils.h"
#ifdef DEBUG
static bool IsLocation(JSObject* obj) {
return strcmp(js::GetObjectClass(obj)->name, "Location") == 0;
}
#endif // DEBUG
namespace mozilla {
namespace dom {
/* static */
bool MaybeCrossOriginObjectMixins::IsPlatformObjectSameOrigin(
JSContext* cx, JS::Handle<JSObject*> obj) {
MOZ_ASSERT(!js::IsCrossCompartmentWrapper(obj));
// WindowProxy and Window must always be same-Realm, so we can do
// our IsPlatformObjectSameOrigin check against either one. But verify that
// in case we have a WindowProxy the right things happen.
MOZ_ASSERT(js::GetNonCCWObjectRealm(obj) ==
// "true" for second arg means to unwrap WindowProxy to
// get at the Window.
js::GetNonCCWObjectRealm(js::UncheckedUnwrap(obj, true)),
"WindowProxy not same-Realm as Window?");
BasePrincipal* subjectPrincipal =
BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
nsIPrincipal* objectPrincipal = nsContentUtils::ObjectPrincipal(obj);
// The spec effectively has an EqualsConsideringDomain check here,
// because the spec has no concept of asymmetric security
// relationships. But we shouldn't ever end up here in the
// asymmetric case anyway: That case should end up with Xrays, which
// don't call into this code.
//
// Let's assert that EqualsConsideringDomain and
// SubsumesConsideringDomain give the same results and use
// EqualsConsideringDomain for the check we actually do, since it's
// stricter and more closely matches the spec.
MOZ_ASSERT(
subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal) ==
subjectPrincipal->FastSubsumesConsideringDomain(objectPrincipal),
"Why are we in an asymmetric case here?");
return subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal);
}
bool MaybeCrossOriginObjectMixins::CrossOriginGetOwnPropertyHelper(
JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const {
MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
"Why did we get called?");
// First check for an IDL-defined cross-origin property with the given name.
// This corresponds to
// https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
// step 2.
JS::Rooted<JSObject*> holder(cx);
if (!EnsureHolder(cx, obj, &holder)) {
return false;
}
if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) {
return false;
}
if (desc.object()) {
desc.object().set(obj);
}
return true;
}
/* static */
bool MaybeCrossOriginObjectMixins::CrossOriginPropertyFallback(
JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) {
MOZ_ASSERT(!desc.object(), "Why are we being called?");
// Step 1.
if (xpc::IsCrossOriginWhitelistedProp(cx, id)) {
// Spec says to return PropertyDescriptor {
// [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true
// }.
desc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
desc.object().set(obj);
return true;
}
// Step 2.
return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("access"));
}
/* static */
bool MaybeCrossOriginObjectMixins::CrossOriginGet(
JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> receiver,
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) {
// This is fairly similar to BaseProxyHandler::get, but there are some
// differences. Most importantly, we want to throw if we have a descriptor
// with no getter, while BaseProxyHandler::get returns undefined. The other
// big difference is that we don't have to worry about prototypes (ours is
// always null).
// We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
// compartment, because for the proxies we have here [[GetOwnProperty]] will
// do security checks based on the current Realm. Unfortunately,
// JS_GetPropertyDescriptorById asserts that compartments match. Luckily, we
// know that "obj" is a proxy here, so we can directly call its
// getOwnPropertyDescriptor() hook.
//
// It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
// the handler and call its getOwnPropertyDescriptor hook directly.
MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
MOZ_ASSERT(
js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
"Unexpected proxy");
MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
"Why did we get called?");
js::AssertSameCompartment(cx, receiver);
// Step 1.
JS::Rooted<JS::PropertyDescriptor> desc(cx);
if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
return false;
}
desc.assertCompleteIfFound();
// Step 2.
MOZ_ASSERT(desc.object(),
"Callees should throw in all cases when they are not finding a "
"property decriptor");
// Step 3.
if (desc.isDataDescriptor()) {
vp.set(desc.value());
return true;
}
// Step 4.
MOZ_ASSERT(desc.isAccessorDescriptor());
// Step 5.
JS::Rooted<JSObject*> getter(cx);
if (!desc.hasGetterObject() || !(getter = desc.getterObject())) {
// Step 6.
return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("get"));
}
// Step 7.
return JS::Call(cx, receiver, getter, JS::HandleValueArray::empty(), vp);
}
/* static */
bool MaybeCrossOriginObjectMixins::CrossOriginSet(
JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
JS::ObjectOpResult& result) {
// We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
// compartment, because for the proxies we have here [[GetOwnProperty]] will
// do security checks based on the current Realm. Unfortunately,
// JS_GetPropertyDescriptorById asserts that compartments match. Luckily, we
// know that "obj" is a proxy here, so we can directly call its
// getOwnPropertyDescriptor() hook.
//
// It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
// the handler and call its getOwnPropertyDescriptor hook directly.
MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
MOZ_ASSERT(
js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
"Unexpected proxy");
MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
"Why did we get called?");
js::AssertSameCompartment(cx, receiver);
js::AssertSameCompartment(cx, v);
// Step 1.
JS::Rooted<JS::PropertyDescriptor> desc(cx);
if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
return false;
}
desc.assertCompleteIfFound();
// Step 2.
MOZ_ASSERT(desc.object(),
"Callees should throw in all cases when they are not finding a "
"property decriptor");
// Step 3.
JS::Rooted<JSObject*> setter(cx);
if (desc.hasSetterObject() && (setter = desc.setterObject())) {
JS::Rooted<JS::Value> ignored(cx);
// Step 3.1.
if (!JS::Call(cx, receiver, setter, JS::HandleValueArray(v), &ignored)) {
return false;
}
// Step 3.2.
return result.succeed();
}
// Step 4.
return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("set"));
}
/* static */
bool MaybeCrossOriginObjectMixins::EnsureHolder(
JSContext* cx, JS::Handle<JSObject*> obj, size_t slot,
JSPropertySpec* attributes, JSFunctionSpec* methods,
JS::MutableHandle<JSObject*> holder) {
MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
"Why are we calling this at all in same-origin cases?");
// We store the holders in a weakmap stored in obj's slot. Our object is
// always a proxy, so we can just go ahead and use GetProxyReservedSlot here.
JS::Rooted<JS::Value> weakMapVal(cx, js::GetProxyReservedSlot(obj, slot));
if (weakMapVal.isUndefined()) {
// Enter the Realm of "obj" when we allocate the WeakMap, since we are going
// to store it in a slot on "obj" and in general we may not be
// same-compartment with "obj" here.
JSAutoRealm ar(cx, obj);
JSObject* newMap = JS::NewWeakMapObject(cx);
if (!newMap) {
return false;
}
weakMapVal.setObject(*newMap);
js::SetProxyReservedSlot(obj, slot, weakMapVal);
}
MOZ_ASSERT(weakMapVal.isObject(),
"How did a non-object else end up in this slot?");
JS::Rooted<JSObject*> map(cx, &weakMapVal.toObject());
MOZ_ASSERT(JS::IsWeakMapObject(map),
"How did something else end up in this slot?");
// We need to be in "map"'s compartment to work with it. Per spec, the key
// for this map is supposed to be the pair (current settings, relevant
// settings). The current settings corresponds to the current Realm of cx.
// The relevant settings corresponds to the Realm of "obj", but since all of
// our objects are per-Realm singletons, we are basically using "obj" itself
// as part of the key.
//
// To represent the current settings, we use the current-Realm
// Object.prototype. We can't use the current global, because we can't get a
// useful cross-compartment wrapper for it; such wrappers would always go
// through a WindowProxy and would not be guarantee to keep pointing to a
// single Realm when unwrapped. We want to grab this key before we start
// changing Realms.
JS::Rooted<JSObject*> key(cx, JS::GetRealmObjectPrototype(cx));
if (!key) {
return false;
}
JS::Rooted<JS::Value> holderVal(cx);
{ // Scope for working with the map
JSAutoRealm ar(cx, map);
if (!MaybeWrapObject(cx, &key)) {
return false;
}
if (!JS::GetWeakMapEntry(cx, map, key, &holderVal)) {
return false;
}
}
if (holderVal.isObject()) {
// We want to do an unchecked unwrap, because the holder (and the current
// caller) may actually be more privileged than our map.
holder.set(js::UncheckedUnwrap(&holderVal.toObject()));
// holder might be a dead object proxy if things got nuked.
if (!JS_IsDeadWrapper(holder)) {
MOZ_ASSERT(js::GetContextRealm(cx) == js::GetNonCCWObjectRealm(holder),
"How did we end up with a key/value mismatch?");
return true;
}
}
// We didn't find a usable holder. Go ahead and allocate one. At this point
// we have two options: we could allocate the holder in the current Realm and
// store a cross-compartment wrapper for it in the map as needed, or we could
// allocate the holder in the Realm of the map and have it hold
// cross-compartment references to all the methods it holds, since those
// methods need to be in our current Realm. It seems better to allocate the
// holder in our current Realm.
holder.set(JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
if (!holder || !JS_DefineProperties(cx, holder, attributes) ||
!JS_DefineFunctions(cx, holder, methods)) {
return false;
}
holderVal.setObject(*holder);
{ // Scope for working with the map
JSAutoRealm ar(cx, map);
// Key is already in the right Realm, but we need to wrap the value.
if (!MaybeWrapValue(cx, &holderVal)) {
return false;
}
if (!JS::SetWeakMapEntry(cx, map, key, holderVal)) {
return false;
}
}
return true;
}
/* static */
bool MaybeCrossOriginObjectMixins::ReportCrossOriginDenial(
JSContext* aCx, JS::Handle<jsid> aId, const nsACString& aAccessType) {
xpc::AccessCheck::reportCrossOriginDenial(aCx, aId, aAccessType);
return false;
}
template <typename Base>
bool MaybeCrossOriginObject<Base>::getPrototype(
JSContext* cx, JS::Handle<JSObject*> proxy,
JS::MutableHandle<JSObject*> protop) const {
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
protop.set(nullptr);
return true;
}
{ // Scope for JSAutoRealm
JSAutoRealm ar(cx, proxy);
protop.set(getSameOriginPrototype(cx));
if (!protop) {
return false;
}
}
return MaybeWrapObject(cx, protop);
}
template <typename Base>
bool MaybeCrossOriginObject<Base>::setPrototype(
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<JSObject*> proto,
JS::ObjectOpResult& result) const {
// Inlined version of
// https://tc39.github.io/ecma262/#sec-set-immutable-prototype
js::AssertSameCompartment(cx, proto);
// We have to be careful how we get the prototype. In particular, we do _NOT_
// want to enter the Realm of "proxy" to do that, in case we're not
// same-origin with it here.
JS::Rooted<JSObject*> wrappedProxy(cx, proxy);
if (!MaybeWrapObject(cx, &wrappedProxy)) {
return false;
}
JS::Rooted<JSObject*> currentProto(cx);
if (!js::GetObjectProto(cx, wrappedProxy, &currentProto)) {
return false;
}
if (currentProto != proto) {
return result.failCantSetProto();
}
return result.succeed();
}
template <typename Base>
bool MaybeCrossOriginObject<Base>::getPrototypeIfOrdinary(
JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
JS::MutableHandle<JSObject*> protop) const {
// We have a custom [[GetPrototypeOf]]
*isOrdinary = false;
return true;
}
template <typename Base>
bool MaybeCrossOriginObject<Base>::isExtensible(JSContext* cx,
JS::Handle<JSObject*> proxy,
bool* extensible) const {
// We never allow [[PreventExtensions]] to succeed.
*extensible = true;
return true;
}
template <typename Base>
bool MaybeCrossOriginObject<Base>::preventExtensions(
JSContext* cx, JS::Handle<JSObject*> proxy,
JS::ObjectOpResult& result) const {
return result.failCantPreventExtensions();
}
template <typename Base>
bool MaybeCrossOriginObject<Base>::defineProperty(
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("define"));
}
// Enter the Realm of proxy and do the remaining work in there.
JSAutoRealm ar(cx, proxy);
JS::Rooted<JS::PropertyDescriptor> descCopy(cx, desc);
if (!JS_WrapPropertyDescriptor(cx, &descCopy)) {
return false;
}
JS_MarkCrossZoneId(cx, id);
return definePropertySameOrigin(cx, proxy, id, descCopy, result);
}
template <typename Base>
JSObject* MaybeCrossOriginObject<Base>::enumerate(
JSContext* cx, JS::Handle<JSObject*> proxy) const {
// We want to avoid any possible magic here and just do the BaseProxyHandler
// thing of using our property keys to enumerate.
//
// Note that we do not need to enter the Realm of "proxy" here, nor do we want
// to: if this is a cross-origin access we want to handle it appropriately.
return js::BaseProxyHandler::enumerate(cx, proxy);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,325 @@
/* -*- 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/. */
#ifndef mozilla_dom_MaybeCrossOriginObject_h
#define mozilla_dom_MaybeCrossOriginObject_h
/**
* Shared infrastructure for WindowProxy and Location objects. These
* are the objects that can be accessed cross-origin in the HTML
* specification.
*
* This class can be inherited from by the relevant proxy handlers to
* help implement spec algorithms.
*
* The algorithms this class implements come from
* <https://html.spec.whatwg.org/multipage/browsers.html#shared-abstract-operations>,
* <https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object>,
* and
* <https://html.spec.whatwg.org/multipage/history.html#the-location-interface>.
*
* The class is templated on its base so we can directly implement the things
* that should have identical implementations for WindowProxy and Location. The
* templating is needed because WindowProxy needs to be a wrapper and Location
* shouldn't be one.
*/
#include "js/Class.h"
#include "js/TypeDecls.h"
#include "nsStringFwd.h"
namespace mozilla {
namespace dom {
// Methods that MaybeCrossOriginObject wants that do not depend on the "Base"
// template parameter. We can avoid having multiple instantiations of them by
// pulling them out into this helper class.
class MaybeCrossOriginObjectMixins {
protected:
/**
* Implementation of
* <https://html.spec.whatwg.org/multipage/browsers.html#isplatformobjectsameorigin-(-o-)>.
* "cx" and "obj" may or may not be same-compartment and even when
* same-compartment may not be same-Realm. "obj" can be a WindowProxy, a
* Window, or a Location.
*/
static bool IsPlatformObjectSameOrigin(JSContext* cx,
JS::Handle<JSObject*> obj);
/**
* Implementation of
* <https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)>.
*
* "cx" and "obj" are expected to be different-Realm here, and may be
* different-compartment. "obj" can be a "WindowProxy" or a "Location" or a
* cross-process proxy for one of those.
*/
bool CrossOriginGetOwnPropertyHelper(
JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const;
/**
* Implementation of
* <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginpropertyfallback-(-p-)>.
*
* This should be called at the end of getOwnPropertyDescriptor
* methods in the cross-origin case.
*
* "cx" and "obj" are expected to be different-Realm here, and may
* be different-compartment. "obj" can be a "WindowProxy" or a
* "Location" or a cross-process proxy for one of those.
*/
static bool CrossOriginPropertyFallback(
JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc);
/**
* Implementation of
* <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginget-(-o,-p,-receiver-)>.
*
* "cx" and "obj" are expected to be different-Realm here and may be
* different-compartment. "obj" can be a "WindowProxy" or a
* "Location" or a cross-process proxy for one of those.
*
* "receiver" will be in the compartment of "cx". The return value will
* be in the compartment of "cx".
*/
static bool CrossOriginGet(JSContext* cx, JS::Handle<JSObject*> obj,
JS::Handle<JS::Value> receiver,
JS::Handle<jsid> id,
JS::MutableHandle<JS::Value> vp);
/**
* Implementation of
* <https://html.spec.whatwg.org/multipage/browsers.html#crossoriginset-(-o,-p,-v,-receiver-)>.
*
* "cx" and "obj" are expected to be different-Realm here and may be
* different-compartment. "obj" can be a "WindowProxy" or a
* "Location" or a cross-process proxy for one of those.
*
* "receiver" and "v" will be in the compartment of "cx".
*/
static bool CrossOriginSet(JSContext* cx, JS::Handle<JSObject*> obj,
JS::Handle<jsid> id, JS::Handle<JS::Value> v,
JS::Handle<JS::Value> receiver,
JS::ObjectOpResult& result);
/**
* Utility method to ensure a holder for cross-origin properties for the
* current global of the JSContext.
*
* When this is called, "cx" and "obj" are _always_ different-Realm, because
* this is only used in cross-origin situations. The "holder" return value is
* always in the Realm of "cx".
*
* "obj" is the object which has space to store the collection of holders in
* the given slot.
*
* "attributes" and "methods" are the cross-origin attributes and methods we
* care about, which should get defined on holders.
*/
static bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> obj,
size_t slot, JSPropertySpec* attributes,
JSFunctionSpec* methods,
JS::MutableHandle<JSObject*> holder);
/**
* Ensures we have a holder object for the current Realm. When this is
* called, "obj" is guaranteed to not be same-Realm with "cx", because this
* is only used for cross-origin cases.
*
* Subclasses are expected to implement this by calling our static
* EnsureHolder with the appropriate arguments.
*/
virtual bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::MutableHandle<JSObject*> holder) const = 0;
/**
* Report a cross-origin denial for a property named by aId. Always
* returns false, so it can be used as "return
* ReportCrossOriginDenial(...);".
*/
static bool ReportCrossOriginDenial(JSContext* aCx, JS::Handle<jsid> aId,
const nsACString& aAccessType);
};
// A proxy handler for objects that may be cross-origin objects. Whether they
// actually _are_ cross-origin objects can change dynamically if document.domain
// is set.
template <typename Base>
class MaybeCrossOriginObject : public Base,
public MaybeCrossOriginObjectMixins {
protected:
template <typename... Args>
constexpr MaybeCrossOriginObject(Args&&... aArgs)
: Base(std::forward<Args>(aArgs)...) {}
/**
* Implementation of [[GetPrototypeOf]] as defined in
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getprototypeof>
* and
* <https://html.spec.whatwg.org/multipage/history.html#location-getprototypeof>.
*
* Our prototype-storage model looks quite different from the spec's, so we
* need to implement some hooks that don't directly map to the spec.
*
* "proxy" is the WindowProxy or Location involved. It may or may not be
* same-compartment with cx.
*
* "protop" is the prototype value (possibly null). It is guaranteed to be
* same-compartment with cx after this function returns successfully.
*/
bool getPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::MutableHandle<JSObject*> protop) const final;
/**
* Hook for doing the OrdinaryGetPrototypeOf bits that [[GetPrototypeOf]] does
* in the spec. Location and WindowProxy store that information somewhat
* differently.
*
* The prototype should come from the Realm of "cx".
*/
virtual JSObject* getSameOriginPrototype(JSContext* cx) const = 0;
/**
* Implementation of [[SetPrototypeOf]] as defined in
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-setprototypeof>
* and
* <https://html.spec.whatwg.org/multipage/history.html#location-setprototypeof>.
*
* "proxy" is the WindowProxy or Location object involved. It may or may not
* be same-compartment with "cx".
*
* "proto" is the new prototype object (possibly null). It must be
* same-compartment with "cx".
*/
bool setPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<JSObject*> proto,
JS::ObjectOpResult& result) const final;
/**
* Our non-standard getPrototypeIfOrdinary hook. We don't need to implement
* setImmutablePrototype, because the default behavior of not allowing it is
* fine for us.
*/
bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy,
bool* isOrdinary,
JS::MutableHandle<JSObject*> protop) const final;
/**
* Implementation of [[IsExtensible]] as defined in
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-isextensible>
* and
* <https://html.spec.whatwg.org/multipage/history.html#location-isextensible>.
*/
bool isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
bool* extensible) const final;
/**
* Implementation of [[PreventExtensions]] as defined in
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-preventextensions>
* and
* <https://html.spec.whatwg.org/multipage/history.html#location-preventextensions>.
*/
bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::ObjectOpResult& result) const final;
/**
* Implementation of [[GetOwnProperty]] is completely delegated to subclasses.
*
* "proxy" is the WindowProxy or Location object involved. It may or may not
* be same-compartment with cx.
*/
bool getOwnPropertyDescriptor(
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override = 0;
/**
* Implementation of [[DefineOwnProperty]] as defined in
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-defineownproperty>
* and
* <https://html.spec.whatwg.org/multipage/history.html#location-defineownproperty>.
* "proxy" is the WindowProxy or Location object involved. It may or may not
* be same-compartment with cx.
*
*/
bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::ObjectOpResult& result) const final;
/**
* Hook for handling the same-origin case in defineProperty.
*
* "proxy" is the WindowProxy or Location object involved. It will be
* same-compartment with cx.
*
* "desc" is a the descriptor being defined. It will be same-compartment with
* cx.
*/
virtual bool definePropertySameOrigin(JSContext* cx,
JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::ObjectOpResult& result) const = 0;
/**
* Implementation of [[Get]] is completely delegated to subclasses.
*
* "proxy" is the WindowProxy or Location object involved. It may or may not
* be same-compartment with "cx".
*
* "receiver" is the receiver ("this") for the get. It will be
* same-compartment with "cx"
*
* "vp" is the return value. It will be same-compartment with "cx".
*/
bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
JS::MutableHandle<JS::Value> vp) const override = 0;
/**
* Implementation of [[Set]] is completely delegated to subclasses.
*
* "proxy" is the WindowProxy or Location object involved. It may or may not
* be same-compartment with "cx".
*
* "v" is the value being set. It will be same-compartment with "cx".
*
* "receiver" is the receiver ("this") for the set. It will be
* same-compartment with "cx".
*/
bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
JS::ObjectOpResult& result) const override = 0;
/**
* Implementation of [[Delete]] is completely delegated to subclasses.
*
* "proxy" is the WindowProxy or Location object involved. It may or may not
* be same-compartment with "cx".
*/
bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
JS::ObjectOpResult& result) const override = 0;
/**
* Spidermonkey-internal hook for enumerating objects.
*/
JSObject* enumerate(JSContext* cx, JS::Handle<JSObject*> proxy) const final;
/**
* Spidermonkey-internal hook used by Object.prototype.toString. Subclasses
* need to implement this, because we don't know what className they want.
* Except in the cross-origin case, when we could maybe handle it...
*/
const char* className(JSContext* cx,
JS::Handle<JSObject*> proxy) const override = 0;
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_MaybeCrossOriginObject_h */

Просмотреть файл

@ -121,8 +121,7 @@ bool RemoteOuterWindowProxy::getOwnPropertyDescriptor(
return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("access"));
}
bool ok = RemoteObjectProxy::getOwnPropertyDescriptorInternal(aCx, aProxy,
aId, aDesc);
bool ok = CrossOriginGetOwnPropertyHelper(aCx, aProxy, aId, aDesc);
if (!ok || aDesc.object()) {
return ok;
}
@ -140,7 +139,7 @@ bool RemoteOuterWindowProxy::getOwnPropertyDescriptor(
}
}
return getOwnPropertyDescriptorTail(aCx, aProxy, aId, aDesc);
return CrossOriginPropertyFallback(aCx, aProxy, aId, aDesc);
}
bool AppendIndexedPropertyNames(JSContext* aCx, BrowsingContext* aContext,

Просмотреть файл

@ -193,6 +193,7 @@ EXPORTS.mozilla.dom += [
'IntlUtils.h',
'Link.h',
'Location.h',
'MaybeCrossOriginObject.h',
'MessageBroadcaster.h',
'MessageListenerManager.h',
'MessageManagerGlobal.h',
@ -298,6 +299,7 @@ UNIFIED_SOURCES += [
'IntlUtils.cpp',
'Link.cpp',
'Location.cpp',
'MaybeCrossOriginObject.cpp',
'MessageBroadcaster.cpp',
'MessageListenerManager.cpp',
'MessageManagerGlobal.cpp',

Просмотреть файл

@ -20,12 +20,12 @@ const js::Class RemoteObjectProxyClass =
bool RemoteObjectProxyBase::getOwnPropertyDescriptor(
JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc) const {
bool ok = getOwnPropertyDescriptorInternal(aCx, aProxy, aId, aDesc);
bool ok = CrossOriginGetOwnPropertyHelper(aCx, aProxy, aId, aDesc);
if (!ok || aDesc.object()) {
return ok;
}
return getOwnPropertyDescriptorTail(aCx, aProxy, aId, aDesc);
return CrossOriginPropertyFallback(aCx, aProxy, aId, aDesc);
}
bool RemoteObjectProxyBase::defineProperty(
@ -137,24 +137,7 @@ bool RemoteObjectProxyBase::get(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::Value> aReceiver,
JS::Handle<jsid> aId,
JS::MutableHandle<JS::Value> aVp) const {
Rooted<PropertyDescriptor> desc(aCx);
if (!getOwnPropertyDescriptor(aCx, aProxy, aId, &desc)) {
return false;
}
MOZ_ASSERT(desc.object());
if (desc.isDataDescriptor()) {
aVp.set(desc.value());
return true;
}
JS::Rooted<JSObject*> getter(aCx);
if (!desc.hasGetterObject() || !(getter = desc.getterObject())) {
return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("get"));
}
return JS::Call(aCx, aReceiver, getter, JS::HandleValueArray::empty(), aVp);
return CrossOriginGet(aCx, aProxy, aReceiver, aId, aVp);
}
bool RemoteObjectProxyBase::set(JSContext* aCx, JS::Handle<JSObject*> aProxy,
@ -162,21 +145,7 @@ bool RemoteObjectProxyBase::set(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::Value> aValue,
JS::Handle<JS::Value> aReceiver,
JS::ObjectOpResult& aResult) const {
Rooted<PropertyDescriptor> desc(aCx);
if (!getOwnPropertyDescriptor(aCx, aProxy, aId, &desc)) {
return false;
}
MOZ_ASSERT(desc.object());
JS::Rooted<JSObject*> setter(aCx);
if (!desc.hasSetterObject() || !(setter = desc.setterObject())) {
return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("set"));
}
JS::Rooted<JS::Value> rv(aCx);
return JS::Call(aCx, aReceiver, setter, JS::HandleValueArray(aValue), &rv) &&
aResult.succeed();
return CrossOriginSet(aCx, aProxy, aId, aValue, aReceiver, aResult);
}
bool RemoteObjectProxyBase::hasOwn(JSContext* aCx, JS::Handle<JSObject*> aProxy,
@ -208,32 +177,6 @@ JSObject* RemoteObjectProxyBase::CreateProxyObject(
return js::NewProxyObject(aCx, this, native, nullptr, options);
}
/* static */
bool RemoteObjectProxyBase::getOwnPropertyDescriptorTail(
JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc) {
if (xpc::IsCrossOriginWhitelistedProp(aCx, aId)) {
// https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
// step 3 says to return PropertyDescriptor {
// [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true
// }.
//
aDesc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
aDesc.object().set(aProxy);
return true;
}
return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("access"));
}
/* static */
bool RemoteObjectProxyBase::ReportCrossOriginDenial(
JSContext* aCx, JS::Handle<jsid> aId, const nsACString& aAccessType) {
xpc::AccessCheck::reportCrossOriginDenial(aCx, aId, aAccessType);
return false;
}
const char RemoteObjectProxyBase::sCrossOriginProxyFamily = 0;
} // namespace dom

Просмотреть файл

@ -8,6 +8,7 @@
#define mozilla_dom_RemoteObjectProxy_h
#include "js/Proxy.h"
#include "mozilla/dom/MaybeCrossOriginObject.h"
#include "mozilla/dom/PrototypeList.h"
#include "xpcpublic.h"
@ -19,7 +20,8 @@ namespace dom {
* don't depend on properties/methods of the specific WebIDL interface that this
* proxy implements.
*/
class RemoteObjectProxyBase : public js::BaseProxyHandler {
class RemoteObjectProxyBase : public js::BaseProxyHandler,
public MaybeCrossOriginObjectMixins {
protected:
explicit constexpr RemoteObjectProxyBase(prototypes::ID aPrototypeID)
: BaseProxyHandler(&sCrossOriginProxyFamily, false),
@ -77,8 +79,9 @@ class RemoteObjectProxyBase : public js::BaseProxyHandler {
}
/**
* Returns true if aProxy represents an object implementing the WebIDL
* interface for aProtoID. aProxy should be a proxy object.
* Returns true if aProxy is a cross-process proxy that represents
* an object implementing the WebIDL interface for aProtoID. aProxy
* should be a proxy object.
*/
static inline bool IsRemoteObjectProxy(JSObject* aProxy,
prototypes::ID aProtoID) {
@ -88,62 +91,19 @@ class RemoteObjectProxyBase : public js::BaseProxyHandler {
aProtoID;
}
protected:
bool getOwnPropertyDescriptorInternal(
JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc) const {
JS::Rooted<JSObject*> holder(aCx);
if (!EnsureHolder(aCx, aProxy, &holder) ||
!JS_GetOwnPropertyDescriptorById(aCx, holder, aId, aDesc)) {
return false;
}
if (aDesc.object()) {
aDesc.object().set(aProxy);
}
return true;
/**
* Returns true if aProxy is a cross-process proxy, no matter which
* interface it represents. aProxy should be a proxy object.
*/
static inline bool IsRemoteObjectProxy(JSObject* aProxy) {
const js::BaseProxyHandler* handler = js::GetProxyHandler(aProxy);
return handler->family() == &sCrossOriginProxyFamily;
}
protected:
JSObject* CreateProxyObject(JSContext* aCx, void* aNative,
const js::Class* aClasp) const;
/**
* Implements the tail of getOwnPropertyDescriptor, dealing in particular with
* properties that are whitelisted by xpc::IsCrossOriginWhitelistedProp.
*/
static bool getOwnPropertyDescriptorTail(
JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc);
static bool ReportCrossOriginDenial(JSContext* aCx, JS::Handle<jsid> aId,
const nsACString& aAccessType);
/**
* This gets a cached, or creates and caches, a holder object that contains
* the WebIDL properties for this proxy.
*/
bool EnsureHolder(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::MutableHandle<JSObject*> aHolder) const {
// FIXME Need to have a holder per realm, should store a weakmap in the
// reserved slot.
JS::Value v = js::GetProxyReservedSlot(aProxy, 0);
if (v.isObject()) {
aHolder.set(&v.toObject());
return true;
}
aHolder.set(JS_NewObjectWithGivenProto(aCx, nullptr, nullptr));
if (!aHolder || !DefinePropertiesAndFunctions(aCx, aHolder)) {
return false;
}
js::SetProxyReservedSlot(aProxy, 0, JS::ObjectValue(*aHolder));
return true;
}
virtual bool DefinePropertiesAndFunctions(
JSContext* aCx, JS::Handle<JSObject*> aHolder) const = 0;
const prototypes::ID mPrototypeID;
static const char sCrossOriginProxyFamily;
@ -175,16 +135,17 @@ class RemoteObjectProxy : public RemoteObjectProxyBase {
using RemoteObjectProxyBase::RemoteObjectProxyBase;
private:
bool DefinePropertiesAndFunctions(JSContext* aCx,
JS::Handle<JSObject*> aHolder) const final {
return JS_DefineProperties(aCx, aHolder, P) &&
JS_DefineFunctions(aCx, aHolder, F);
bool EnsureHolder(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::MutableHandle<JSObject*> aHolder) const final {
return MaybeCrossOriginObjectMixins::EnsureHolder(
aCx, aProxy, /* slot = */ 0, P, F, aHolder);
}
};
/**
* Returns true if aObj is a proxy object that represents an object implementing
* the WebIDL interface for aProtoID.
* Returns true if aObj is a cross-process proxy object that
* represents an object implementing the WebIDL interface for
* aProtoID.
*/
static inline bool IsRemoteObjectProxy(JSObject* aObj,
prototypes::ID aProtoID) {
@ -194,6 +155,17 @@ static inline bool IsRemoteObjectProxy(JSObject* aObj,
return RemoteObjectProxyBase::IsRemoteObjectProxy(aObj, aProtoID);
}
/**
* Returns true if aObj is a cross-process proxy object, no matter
* which WebIDL interface it corresponds to.
*/
static inline bool IsRemoteObjectProxy(JSObject* aObj) {
if (!js::IsProxy(aObj)) {
return false;
}
return RemoteObjectProxyBase::IsRemoteObjectProxy(aObj);
}
} // namespace dom
} // namespace mozilla