-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Implement ChakraRuntime::getPropertyNames() properly in native. #3568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b6d0cb4
2f2b2d9
509873d
7c96aa9
d0f10f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "type": "none", | ||
| "comment": "Implement ChakraRuntime::getPropertyNames() properly in native.", | ||
| "packageName": "react-native-windows", | ||
| "email": "yicyao@microsoft.com", | ||
| "commit": "7c96aa9beae65248b1b4c4c668b08ea56e4b4392", | ||
| "date": "2019-10-30T23:44:34.909Z", | ||
| "file": "D:\\Git\\hansenyy-react-native-windows-3\\change\\react-native-windows-2019-10-30-16-44-34-FixGetPropertyNames.json" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,8 +118,7 @@ facebook::jsi::Value ChakraRuntime::evaluateJavaScript( | |
| if (!runtimeArgs().scriptStore) { | ||
| if (!buffer) | ||
| throw facebook::jsi::JSINativeException("Script buffer is empty!"); | ||
| evaluateJavaScriptSimple(*buffer, sourceURL); | ||
| return facebook::jsi::Value::undefined(); | ||
| return evaluateJavaScriptSimple(*buffer, sourceURL); | ||
| } | ||
|
|
||
| uint64_t scriptVersion = 0; | ||
|
|
@@ -140,8 +139,7 @@ facebook::jsi::Value ChakraRuntime::evaluateJavaScript( | |
|
|
||
| // Simple evaluate if script version can't be computed. | ||
| if (scriptVersion == 0) { | ||
| evaluateJavaScriptSimple(*scriptBuffer, sourceURL); | ||
| return facebook::jsi::Value::undefined(); | ||
| return evaluateJavaScriptSimple(*scriptBuffer, sourceURL); | ||
| } | ||
|
|
||
| auto sharedScriptBuffer = std::shared_ptr<const facebook::jsi::Buffer>(std::move(scriptBuffer)); | ||
|
|
@@ -305,9 +303,7 @@ facebook::jsi::HostFunctionType &ChakraRuntime::getHostFunction(const facebook:: | |
| facebook::jsi::Value ChakraRuntime::getProperty( | ||
| const facebook::jsi::Object &obj, | ||
| const facebook::jsi::PropNameID &name) { | ||
| JsValueRef result = JS_INVALID_REFERENCE; | ||
| VerifyJsErrorElseThrow(JsGetProperty(GetChakraObjectRef(obj), GetChakraObjectRef(name), &result)); | ||
| return ToJsiValue(ChakraObjectRef(result)); | ||
| return ToJsiValue(GetProperty(GetChakraObjectRef(obj), GetChakraObjectRef(name))); | ||
| } | ||
|
|
||
| facebook::jsi::Value ChakraRuntime::getProperty(const facebook::jsi::Object &obj, const facebook::jsi::String &name) { | ||
|
|
@@ -376,25 +372,60 @@ bool ChakraRuntime::isHostFunction(const facebook::jsi::Function &obj) const { | |
| } | ||
|
|
||
| facebook::jsi::Array ChakraRuntime::getPropertyNames(const facebook::jsi::Object &object) { | ||
| JsValueRef propertyNamesArrayRef; | ||
| VerifyJsErrorElseThrow(JsGetOwnPropertyNames(GetChakraObjectRef(object), &propertyNamesArrayRef)); | ||
| // Handle to the null JS value. | ||
| ChakraObjectRef jsNull = ToChakraObjectRef(facebook::jsi::Value::null()); | ||
|
|
||
| // Handle to the Object constructor. | ||
| ChakraObjectRef objectConstructor = GetProperty(GetChakraObjectRef(global()), "Object"); | ||
|
|
||
| // Handle to the Object.prototype Object. | ||
| ChakraObjectRef objectPrototype = GetProperty(objectConstructor, "prototype"); | ||
|
|
||
| // Handle to the Object.prototype.propertyIsEnumerable() Function. | ||
| ChakraObjectRef objectPrototypePropertyIsEnumerable = GetProperty(objectPrototype, "propertyIsEnumerable"); | ||
|
|
||
| std::vector<ChakraObjectRef> enumerablePropNames{}; | ||
| ChakraObjectRef currentObject = GetChakraObjectRef(object); | ||
|
|
||
| JsPropertyIdRef propertyId; | ||
| VerifyJsErrorElseThrow(JsGetPropertyIdFromName(L"length", &propertyId)); | ||
| JsValueRef countRef; | ||
| VerifyJsErrorElseThrow(JsGetProperty(propertyNamesArrayRef, propertyId, &countRef)); | ||
| int count; | ||
| // We have a small optimization here where we stop traversing the prototype | ||
| // chain as soon as we hit Object.prototype. However, we still need to check | ||
| // for null here, as one can create an Object with no prototype through | ||
| // Object.create(null). | ||
| while (!CompareJsValues(currentObject, objectPrototype) && !CompareJsValues(currentObject, jsNull)) { | ||
| JsValueRef propNamesRef = JS_INVALID_REFERENCE; | ||
| VerifyJsErrorElseThrow(JsGetOwnPropertyNames(currentObject, &propNamesRef)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) please add /out/ #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will need to add this to all sites where Chakra APIs are called. Punting this work for now. In reply to: 341265034 [](ancestors = 341265034) |
||
| ChakraObjectRef propNames(propNamesRef); | ||
|
|
||
| VerifyJsErrorElseThrow(JsNumberToInt(countRef, &count)); | ||
| int numPropNames = ToInteger(GetProperty(propNames, "length")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) propNamesLength? or propNamesSize? #Resolved |
||
|
|
||
| auto result = createArray(count); | ||
| for (int i = 0; i < count; i++) { | ||
| JsValueRef index; | ||
| VerifyJsErrorElseThrow(JsIntToNumber(i, &index)); | ||
| JsValueRef propertyName; | ||
| VerifyJsErrorElseThrow(JsGetIndexedProperty(propertyNamesArrayRef, index, &propertyName)); | ||
| for (int i = 0; i < numPropNames; ++i) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) can you please add a small comment describing what this loop does? We are getting propNames on "currentObject", but then we are switching "currentObject" inside of loop. #Resolved |
||
| JsValueRef propNameRef = JS_INVALID_REFERENCE; | ||
| VerifyJsErrorElseThrow(JsGetIndexedProperty(propNames, ToJsNumber(i), &propNameRef)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) /out/ #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ChakraObjectRef propName(propNameRef); | ||
|
|
||
| result.setValueAtIndex(*this, i, MakePointer<facebook::jsi::String>(propertyName)); | ||
| std::vector<JsValueRef> args = {currentObject, propName}; | ||
|
|
||
| JsValueRef result; | ||
| VerifyJsErrorElseThrow(JsCallFunction( | ||
| objectPrototypePropertyIsEnumerable, args.data(), static_cast<unsigned short>(args.size()), &result)); | ||
|
|
||
| bool propIsEnumerable = ToJsiValue(ChakraObjectRef(result)).getBool(); | ||
|
|
||
| if (propIsEnumerable) { | ||
| enumerablePropNames.emplace_back(propName); | ||
| } | ||
| } | ||
|
|
||
| JsValueRef prototype = JS_INVALID_REFERENCE; | ||
| VerifyJsErrorElseThrow(JsGetPrototype(currentObject, &prototype)); | ||
| currentObject = ChakraObjectRef(prototype); | ||
| } | ||
|
|
||
| size_t numEnumerablePropNames = enumerablePropNames.size(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) enumerablePropNamesSize or enumerablePropNamesLength #Resolved |
||
| facebook::jsi::Array result = createArray(numEnumerablePropNames); | ||
|
|
||
| for (size_t i = 0; i < numEnumerablePropNames; ++i) { | ||
| result.setValueAtIndex(*this, i, MakePointer<facebook::jsi::String>(enumerablePropNames[i])); | ||
| } | ||
|
|
||
| return result; | ||
|
|
@@ -425,12 +456,26 @@ facebook::jsi::Array ChakraRuntime::createArray(size_t length) { | |
|
|
||
| size_t ChakraRuntime::size(const facebook::jsi::Array &arr) { | ||
| assert(isArray(arr)); | ||
| return static_cast<size_t>(GetProperty(arr, "length").asNumber()); | ||
|
|
||
| int result = ToInteger(GetProperty(GetChakraObjectRef(arr), "length")); | ||
|
|
||
| if (result < 0 || result > SIZE_MAX) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) use std::numeric_limits::max instead #Resolved |
||
| throw facebook::jsi::JSINativeException("Invalid JS array length detected."); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (nit) can we replace this with something like VerifyElseThrow(result < 0 || result > SIZE_MAX, "Invalid JS array length detected.") #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are other places that might benefit from a similar pattern. I think we should put this on hold till when we work on ensuring correct exception propagation. In reply to: 341273515 [](ancestors = 341273515) |
||
|
|
||
| return static_cast<size_t>(result); | ||
| } | ||
|
|
||
| size_t ChakraRuntime::size(const facebook::jsi::ArrayBuffer &arrBuf) { | ||
| assert(isArrayBuffer(arrBuf)); | ||
| return static_cast<size_t>(GetProperty(arrBuf, "bytelength").asNumber()); | ||
|
|
||
| int result = ToInteger(GetProperty(GetChakraObjectRef(arrBuf), "bytelength")); | ||
|
|
||
| if (result < 0 || result > SIZE_MAX) { | ||
| throw facebook::jsi::JSINativeException("Invalid JS array buffer bytelength detected."); | ||
| } | ||
|
|
||
| return static_cast<size_t>(result); | ||
| } | ||
|
|
||
| uint8_t *ChakraRuntime::data(const facebook::jsi::ArrayBuffer &arrBuf) { | ||
|
|
@@ -566,13 +611,6 @@ bool ChakraRuntime::instanceOf(const facebook::jsi::Object &obj, const facebook: | |
|
|
||
| #pragma endregion Functions_inherited_from_Runtime | ||
|
|
||
| facebook::jsi::Value ChakraRuntime::GetProperty(const facebook::jsi::Object &obj, const char *const name) const { | ||
| // We have to use const_casts here because createPropNameIDFromAscii and | ||
| // getProperty are not marked as const. | ||
| facebook::jsi::PropNameID propId = const_cast<ChakraRuntime *>(this)->createPropNameIDFromAscii(name, strlen(name)); | ||
| return const_cast<ChakraRuntime *>(this)->getProperty(obj, propId); | ||
| } | ||
|
|
||
| void ChakraRuntime::VerifyJsErrorElseThrow(JsErrorCode error) { | ||
| switch (error) { | ||
| case JsNoError: { | ||
|
|
@@ -697,6 +735,12 @@ std::vector<ChakraObjectRef> ChakraRuntime::ToChakraObjectRefs(const facebook::j | |
| return result; | ||
| } | ||
|
|
||
| ChakraObjectRef ChakraRuntime::GetProperty(const ChakraObjectRef &obj, const ChakraObjectRef &id) { | ||
| JsValueRef result = JS_INVALID_REFERENCE; | ||
| VerifyJsErrorElseThrow(JsGetProperty(obj, id, &result)); | ||
| return ChakraObjectRef(result); | ||
| } | ||
|
|
||
| JsValueRef CALLBACK ChakraRuntime::HostFunctionCall( | ||
| JsValueRef callee, | ||
| bool isConstructCall, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,15 +154,6 @@ class ChakraRuntime : public facebook::jsi::Runtime { | |
| } | ||
|
|
||
| private: | ||
| // Since the function | ||
| // Object::getProperty(Runtime& runtime, const char* name) | ||
| // causes mulitple copies of name, we do not want to use it when implementing | ||
| // ChakraRuntime methods. This function does the same thing as | ||
| // Object::getProperty, but without the extra overhead. This function is | ||
| // declared as const so that it can be used when implementing | ||
| // isHostFunction and isHostObject. | ||
| facebook::jsi::Value GetProperty(const facebook::jsi::Object &obj, const char *const name) const; | ||
|
|
||
| void VerifyJsErrorElseThrow(JsErrorCode error); | ||
|
|
||
| // ChakraPointerValue is needed for working with Facebook's jsi::Pointer class | ||
|
|
@@ -248,15 +239,35 @@ class ChakraRuntime : public facebook::jsi::Runtime { | |
| ChakraObjectRef ToChakraObjectRef(const facebook::jsi::Value &value); | ||
| std::vector<ChakraObjectRef> ToChakraObjectRefs(const facebook::jsi::Value *value, size_t count); | ||
|
|
||
| // Host function and host object helpers | ||
| // Convenience functions for property access. | ||
| ChakraObjectRef GetProperty(const ChakraObjectRef &obj, const ChakraObjectRef &id); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) just curious, why in one function this is "id" and in another it's "name" shouldn't they be consistent? #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference is intentional as a JS property ID can either be backed by a Symbol or a name (a.k.a. string) In reply to: 341261195 [](ancestors = 341261195) |
||
| inline ChakraObjectRef GetProperty(const ChakraObjectRef &obj, const char *const name) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) can you add empty line here for readability? #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) is this needed? compilers do inlining anyways #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea the compiler should automatically do inlining here, but I like the explicitness. In reply to: 341261679 [](ancestors = 341261679) |
||
| return GetProperty(obj, GetChakraObjectRef(createPropNameIDFromAscii(name, strlen(name)))); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(nit) similarly, use "id" or "name" consistently. #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| // Since the function | ||
| // Object::getProperty(Runtime& runtime, const char* name) | ||
| // causes mulitple copies of name, we do not want to use it when implementing | ||
| // ChakraRuntime methods. This function does the same thing as | ||
| // Object::getProperty, but without the extra overhead. This function is | ||
| // declared as const so that it can be used when implementing | ||
| // isHostFunction and isHostObject. | ||
| inline facebook::jsi::Value GetProperty(const facebook::jsi::Object &obj, const char *const name) const { | ||
| // We have to use const_casts here because ToJsiValue and GetProperty cannnot | ||
| // be marked as const. | ||
| return const_cast<ChakraRuntime *>(this)->ToJsiValue( | ||
| const_cast<ChakraRuntime *>(this)->GetProperty(GetChakraObjectRef(obj), name)); | ||
| } | ||
|
|
||
| // Host function helper | ||
| static JsValueRef CALLBACK HostFunctionCall( | ||
| JsValueRef callee, | ||
| bool isConstructCall, | ||
| JsValueRef *argumentsIncThis, | ||
| unsigned short argumentCountIncThis, | ||
| void *callbackState); | ||
|
|
||
| // For the following functions, runtime must be referring to a ChakraRuntime. | ||
| // Host object helpers; runtime must be referring to a ChakraRuntime. | ||
| static facebook::jsi::Value HostObjectGetTrap( | ||
| Runtime &runtime, | ||
| const facebook::jsi::Value & /*thisVal*/, | ||
|
|
@@ -272,7 +283,6 @@ class ChakraRuntime : public facebook::jsi::Runtime { | |
| const facebook::jsi::Value & /*thisVal*/, | ||
| const facebook::jsi::Value *args, | ||
| size_t count); | ||
|
|
||
| facebook::jsi::Object createHostObjectProxyHandler() noexcept; | ||
|
|
||
| // Promise Helpers | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) naming. would it make more sense to call
facebook::jsi::object as "jsiObject"
?
#Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll rename currentObjec to currentObjectOnPrototypeChain.
In reply to: 341264449 [](ancestors = 341264449)