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