blob: 2c45dbbab71256a2c112eea7602e85b84596c8c1 [file] [log] [blame]
// Copyright 2016 Google Inc. 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/proxy_handler.h"
#include "cobalt/script/mozjs/conversion_helpers.h"
#include "cobalt/script/mozjs/mozjs_exception_state.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);
}
}
JSObject* ProxyHandler::NewProxy(JSContext* context, JSObject* object,
JSObject* prototype, JSObject* parent,
ProxyHandler* handler) {
JS::RootedValue as_value(context, OBJECT_TO_JSVAL(object));
return js::NewProxyObject(context, handler, as_value, prototype, parent);
}
bool ProxyHandler::getPropertyDescriptor(JSContext* context,
JS::HandleObject proxy,
JS::HandleId id,
JSPropertyDescriptor* descriptor,
unsigned flags) {
if (!getOwnPropertyDescriptor(context, proxy, id, descriptor, flags)) {
return false;
}
if (descriptor->obj == NULL) {
JS::RootedObject prototype(context);
if (getPrototypeOf(context, proxy, &prototype)) {
return JS_GetPropertyDescriptorById(context, prototype, id, flags,
descriptor);
}
}
return true;
}
bool ProxyHandler::getOwnPropertyDescriptor(JSContext* context,
JS::HandleObject proxy,
JS::HandleId id,
JSPropertyDescriptor* descriptor,
unsigned flags) {
if (!LegacyPlatformObjectGetOwnPropertyDescriptor(context, proxy, id,
descriptor)) {
return false;
}
if (descriptor->obj == NULL) {
return js::DirectProxyHandler::getOwnPropertyDescriptor(context, proxy, id,
descriptor, flags);
}
return true;
}
bool ProxyHandler::delete_(JSContext* context, JS::HandleObject proxy,
JS::HandleId id, bool* succeeded) {
// https://www.w3.org/TR/WebIDL/#delete
if (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.address())) {
NOTREACHED();
return false;
}
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.
*succeeded = true;
} else if (!indexed_property_hooks_.deleter) {
// 1.3. If O does not implement an interface with an indexed property
// deleter, then Reject.
*succeeded = false;
} else {
*succeeded = indexed_property_hooks_.deleter(context, object, index);
}
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)) {
if (!named_property_hooks_.deleter) {
*succeeded = false;
} else {
*succeeded =
named_property_hooks_.deleter(context, object, property_name);
}
return true;
}
}
}
return js::DirectProxyHandler::delete_(context, proxy, id, succeeded);
}
bool ProxyHandler::enumerate(JSContext* context, JS::HandleObject proxy,
JS::AutoIdVector& properties) {
// 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::enumerate(context, proxy, properties);
}
bool ProxyHandler::defineProperty(JSContext* context, JS::HandleObject proxy,
JS::HandleId id,
JSPropertyDescriptor* descriptor) {
has_custom_property_ = true;
return js::DirectProxyHandler::defineProperty(context, proxy, id, descriptor);
}
bool ProxyHandler::LegacyPlatformObjectGetOwnPropertyDescriptor(
JSContext* context, JS::HandleObject proxy, JS::HandleId id,
JSPropertyDescriptor* descriptor) {
// https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty
JS::RootedObject object(context, js::GetProxyTargetObject(proxy));
if (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.address())) {
NOTREACHED();
return false;
}
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->obj = object;
descriptor->attrs = JSPROP_SHARED | JSPROP_INDEX | JSPROP_ENUMERATE;
descriptor->getter = indexed_property_hooks_.getter;
if (indexed_property_hooks_.setter) {
descriptor->setter = indexed_property_hooks_.setter;
} else {
descriptor->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->obj = object;
descriptor->attrs = JSPROP_SHARED | JSPROP_ENUMERATE;
descriptor->getter = named_property_hooks_.getter;
if (named_property_hooks_.setter) {
descriptor->setter = named_property_hooks_.setter;
} else {
descriptor->attrs |= JSPROP_READONLY;
}
return true;
}
}
}
return true;
}
bool ProxyHandler::IsSupportedIndex(JSContext* context, JS::HandleObject object,
uint32_t index) {
DCHECK(indexed_property_hooks_.is_supported);
return indexed_property_hooks_.is_supported(context, object, index);
}
bool ProxyHandler::IsSupportedName(JSContext* context, JS::HandleObject object,
const std::string& name) {
DCHECK(named_property_hooks_.is_supported);
return named_property_hooks_.is_supported(context, object, name);
}
bool ProxyHandler::IsArrayIndexPropertyName(JSContext* context,
JS::HandleValue property_value,
uint32_t* out_index) {
// https://www.w3.org/TR/WebIDL/#dfn-array-index-property-name
// 1. Let i be ToUint32(P).
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.
JSBool are_equal;
JS::RootedString index_as_string(
context, JS_ValueToString(context, UINT_TO_JSVAL(index)));
if (!JS_LooselyEqual(context, JS::StringValue(index_as_string),
property_value, &are_equal) ||
!are_equal) {
return false;
}
// 4. Return true.
*out_index = index;
return true;
}
bool ProxyHandler::IsNamedPropertyVisible(JSContext* context,
JS::HandleObject object,
const std::string& property_name) {
// Named property visiblity 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 prootype has P, return false)
JSBool 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