| // 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 |