blob: 722ffbe8c0e9f0a5a3c3a71e37f90f970142d9a3 [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/script/mozjs-45/proxy_handler.h"
#include "cobalt/script/mozjs-45/conversion_helpers.h"
#include "cobalt/script/mozjs-45/mozjs_exception_state.h"
#include "third_party/mozjs-45/js/src/jsiter.h"
#include "third_party/mozjs-45/js/src/vm/ProxyObject.h"
namespace cobalt {
namespace script {
namespace mozjs {
ProxyHandler::ProxyHandler(const IndexedPropertyHooks& indexed_hooks,
const NamedPropertyHooks& named_hooks)
: js::DirectProxyHandler(NULL),
indexed_property_hooks_(indexed_hooks),
named_property_hooks_(named_hooks),
has_custom_property_(false) {
// If an interface supports named/indexed properties, they must have a hook to
// check if the name/index is supported and to enumerate the properties.
if (supports_named_properties()) {
DCHECK(named_property_hooks_.is_supported);
DCHECK(named_property_hooks_.enumerate_supported);
}
if (supports_indexed_properties()) {
DCHECK(indexed_property_hooks_.is_supported);
DCHECK(indexed_property_hooks_.enumerate_supported);
}
}
/* static */
JSObject* ProxyHandler::NewProxy(JSContext* context, ProxyHandler* handler,
JSObject* object, JSObject* prototype) {
JS::RootedValue object_as_value(context, JS::ObjectOrNullValue(object));
return js::NewProxyObject(context, handler, object_as_value, prototype);
}
bool ProxyHandler::getOwnPropertyDescriptor(
JSContext* context, JS::HandleObject proxy, JS::HandleId id,
JS::MutableHandle<JSPropertyDescriptor> descriptor) const {
if (!LegacyPlatformObjectGetOwnPropertyDescriptor(context, proxy, id,
descriptor)) {
return false;
}
if (descriptor.object() == NULL) {
return js::DirectProxyHandler::getOwnPropertyDescriptor(context, proxy, id,
descriptor);
}
return true;
}
bool ProxyHandler::defineProperty(JSContext* cx, JS::HandleObject proxy,
JS::HandleId id,
js::Handle<JSPropertyDescriptor> desc,
JS::ObjectOpResult& result) const {
// This member function needs to be marked const in order to properly
// override the js::DirectProxyHandler, however will modify its own,
// ProxyHandler specific components, hence the need for the (ab)use of
// const_cast.
const_cast<ProxyHandler*>(this)->has_custom_property_ = true;
return js::DirectProxyHandler::defineProperty(cx, proxy, id, desc, result);
}
bool ProxyHandler::ownPropertyKeys(JSContext* context, JS::HandleObject proxy,
JS::AutoIdVector& properties) const {
// https://www.w3.org/TR/WebIDL/#property-enumeration
// Indexed properties go first, then named properties, then everything
// else.
JS::RootedObject object(context, js::GetProxyTargetObject(proxy));
if (supports_indexed_properties()) {
indexed_property_hooks_.enumerate_supported(context, object, &properties);
}
if (supports_named_properties()) {
named_property_hooks_.enumerate_supported(context, object, &properties);
}
return js::DirectProxyHandler::ownPropertyKeys(context, proxy, properties);
}
bool ProxyHandler::delete_(JSContext* context, JS::HandleObject proxy,
JS::HandleId id, JS::ObjectOpResult& result) const {
// https://www.w3.org/TR/WebIDL/#delete
if (!JSID_IS_SYMBOL(id) &&
(supports_named_properties() || supports_indexed_properties())) {
// Convert the id to a JSValue, so we can easily convert it to Uint32 and
// JSString.
JS::RootedValue id_value(context);
if (!JS_IdToValue(context, id, &id_value)) {
NOTREACHED();
return result.failCantDelete();
}
DCHECK(js::IsProxy(proxy));
JS::RootedObject object(context, js::GetProxyTargetObject(proxy));
if (supports_indexed_properties()) {
// If the interface supports indexed properties and this is an array index
// property name, and it is a supported property index.
uint32_t index;
// 1. If O supports indexed properties and P is an array index property
// name,
// then:
if (IsArrayIndexPropertyName(context, id_value, &index)) {
if (!IsSupportedIndex(context, object, index)) {
// 1.2. If index is not a supported property index, then return true.
result.succeed();
} else if (!indexed_property_hooks_.deleter) {
// 1.3. If O does not implement an interface with an indexed property
// deleter, then Reject.
result.failCantDelete();
} else {
bool succeeded =
indexed_property_hooks_.deleter(context, object, index) &&
result.succeed();
if (succeeded) {
result.succeed();
} else {
result.failCantDelete();
}
}
return true;
}
}
if (supports_named_properties()) {
std::string property_name;
MozjsExceptionState exception_state(context);
FromJSValue(context, id_value, kNoConversionFlags, &exception_state,
&property_name);
if (exception_state.is_exception_set()) {
// The ID should be an integer or a string, so we shouldn't have any
// exceptions converting to string.
NOTREACHED();
return result.failCantDelete();
}
if (IsNamedPropertyVisible(context, object, property_name)) {
if (!named_property_hooks_.deleter) {
result.failCantDelete();
} else {
bool succeeded =
named_property_hooks_.deleter(context, object, property_name);
if (succeeded) {
result.succeed();
} else {
result.failCantDelete();
}
}
return true;
}
}
}
return js::DirectProxyHandler::delete_(context, proxy, id, result);
}
bool ProxyHandler::LegacyPlatformObjectGetOwnPropertyDescriptor(
JSContext* context, JS::HandleObject proxy, JS::HandleId id,
JS::MutableHandle<JSPropertyDescriptor> descriptor) const {
if (!JSID_IS_SYMBOL(id) &&
(supports_named_properties() || supports_indexed_properties())) {
// Convert the id to a JSValue, so we can easily convert it to Uint32 and
// JSString.
JS::RootedValue id_value(context);
if (!JS_IdToValue(context, id, &id_value)) {
NOTREACHED();
return false;
}
JS::RootedObject object(context, js::GetProxyTargetObject(proxy));
if (supports_indexed_properties()) {
// If the interface supports indexed properties and this is an array index
// property name, and it is a supported property index.
uint32_t index;
if (IsArrayIndexPropertyName(context, id_value, &index) &&
IsSupportedIndex(context, object, index)) {
descriptor.object().set(object);
descriptor.setGetter(indexed_property_hooks_.getter);
descriptor.setAttributes(JSPROP_SHARED | JSPROP_ENUMERATE);
if (indexed_property_hooks_.setter) {
descriptor.setSetter(indexed_property_hooks_.setter);
} else {
descriptor.get().attrs |= JSPROP_READONLY;
}
return true;
}
}
if (supports_named_properties()) {
std::string property_name;
MozjsExceptionState exception_state(context);
FromJSValue(context, id_value, kNoConversionFlags, &exception_state,
&property_name);
if (exception_state.is_exception_set()) {
// The ID should be an integer or a string, so we shouldn't have any
// exceptions converting to string.
NOTREACHED();
return false;
}
if (IsNamedPropertyVisible(context, object, property_name)) {
descriptor.object().set(object);
descriptor.setAttributes(JSPROP_SHARED | JSPROP_ENUMERATE);
descriptor.setGetter(named_property_hooks_.getter);
if (named_property_hooks_.setter) {
descriptor.setSetter(named_property_hooks_.setter);
} else {
descriptor.get().attrs |= JSPROP_READONLY;
}
return true;
}
}
}
return true;
}
bool ProxyHandler::IsArrayIndexPropertyName(JSContext* context,
JS::HandleValue property_value,
uint32_t* out_index) const {
// https://www.w3.org/TR/WebIDL/#dfn-array-index-property-name
// 1. Let i be ToUint32(P).
if (property_value.isSymbol()) {
return false;
}
uint32_t index;
if (!JS::ToUint32(context, property_value, &index)) {
return false;
}
// 3. If i = 2^32 - 1, then return false.
if (index == 0xFFFFFFFF) {
return false;
}
// 2. Let s be ToString(i).
// 3. If s != P then return false.
JS::RootedValue index_as_number_value(context, JS::NumberValue(index));
JS::RootedString index_as_string(
context, JS::ToString(context, index_as_number_value));
JS::RootedValue index_as_string_value(context);
index_as_string_value.setString(index_as_string);
bool loosely_equal_output;
const bool loosely_equal_success = JS_LooselyEqual(
context, index_as_string_value, property_value, &loosely_equal_output);
if (!loosely_equal_success || !loosely_equal_output) {
return false;
}
// 4. Return true.
*out_index = index;
return true;
}
bool ProxyHandler::IsNamedPropertyVisible(
JSContext* context, JS::HandleObject object,
const std::string& property_name) const {
// Named property visibility algorithm.
// https://www.w3.org/TR/WebIDL/#dfn-named-property-visibility
// 1. If P is an unforgeable property name on O, then return false.
// 2. If O implements an interface with an [Unforgeable]-annotated attribute
// whose identifier is P, then return false.
// TODO: Implement Unforgeable extended attribute.
// 3. If P is not a supported property name of O, then return false.
if (!IsSupportedName(context, object, property_name)) {
return false;
}
// 4. If O implements an interface that has the [OverrideBuiltins] extended
// attribute, then return true.
// TODO: Implement OverrideBuiltins extended attribute
// 5. If O has an own property named P, then return false.
// 6~7. ( Walk the prototype chain and if the protoype has P, return false)
bool found_property;
if (!JS_HasProperty(context, object, property_name.c_str(),
&found_property)) {
// An error occurred searching for the property.
NOTREACHED();
return true;
}
return !found_property;
}
} // namespace mozjs
} // namespace script
} // namespace cobalt