Compare commits

...
46 Commits
Author SHA1 Message Date
cpojer b867cf8974 [0.60.2] Bump version numbers 2019-07-11 17:17:13 +01:00
cpojer 0738fe5738 Fix path to Hermes package. 2019-07-11 17:17:08 +01:00
cpojer 8dd8ec7cb0 [0.60.1] Bump version numbers 2019-07-11 16:51:34 +01:00
cpojer e857d7066b Add Hermes support to React Native 0.60 2019-07-11 16:51:00 +01:00
Lorenzo Sciandra 769e35ba5f [0.60.0] Bump version numbers 2019-07-03 13:54:51 +01:00
Lorenzo Sciandra 35aeb8c027 [LOCAL] bump CLI 2019-07-03 13:00:27 +01:00
Michał PierzchałaandLorenzo Sciandra 8fdecf3010 - Publish react-native.config.js (#25436)
Summary:
Looks we forgot to add this file to publish list. By adding it, we can remove some code in CLI that special-cases `react-native` package, since it now announces itself as a proper platform.

cc kelset, we'd like to have this cherry-picked for 0.60.

## Changelog

[Internal] [Fixed] - Publish `react-native.config.js`
Pull Request resolved: https://github.com/facebook/react-native/pull/25436

Test Plan: `npm pack` shows `react-native.config.js`

Differential Revision: D16071113

Pulled By: cpojer

fbshipit-source-id: df18affbb9a0ad7f2cfdd1a4cc93191aa5ffadeb
2019-07-03 11:56:25 +01:00
Jerry.LuoandLorenzo Sciandra ff9855cc3b Check if mCurrentActivity is set according to LifecycleState (#23336)
Summary:
Issues: Related to  #13439
react-native-website:Related to PR [#792](https://github.com/facebook/react-native-website/pull/792)
solution: https://github.com/facebook/react-native/issues/13439#issuecomment-400256114

When we integration with Existing  Android Apps.and set LifecycleState  is `LifecycleState.RESUMED`.
It's lead to `mCurrentActivity`  is null .

At this time , the behave of set `mCurrentActivity ` which  is unexpectedly.

## Changelog
[Android] [Fixed] - Check if mCurrentActivity is set according to LifecycleState
Pull Request resolved: https://github.com/facebook/react-native/pull/23336

Differential Revision: D14298654

Pulled By: cpojer

fbshipit-source-id: 5cc17539a51154faeb838349b068d92511946f79
2019-07-03 11:55:29 +01:00
Lorenzo Sciandra 8a43321271 [0.60.0-rc.3] Bump version numbers 2019-06-28 10:49:34 +01:00
Lorenzo Sciandra db1d60fa95 bump jsc dep 2019-06-28 08:39:52 +01:00
Lorenzo Sciandra 93c83181fb bump CLI rc 2019-06-28 08:39:43 +01:00
soh335andLorenzo Sciandra 9837d2480c Fix some languages wrapped texts are cut off on android (#25306)
Summary:
Fix wrapped some languages (like Japanese, Chinese) texts are cut off on android. This p-r is based on linjson [patch](https://github.com/facebook/react-native/issues/25275#issuecomment-502548807).

- related (maybe)
    - https://github.com/facebook/react-native/issues/25297
    - https://github.com/facebook/react-native/issues/25275
    - https://github.com/facebook/react-native/issues/24837
    - https://github.com/facebook/react-native/issues/25155

`setUseLineSpacingFromFallbacks` is recommended to set true on [document](https://developer.android.com/reference/android/text/StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean))

>For backward compatibility reasons, the default is false, but setting this to true is strongly recommended. It is required to be true if text could be in languages like Burmese or Tibetan where text is typically much taller or deeper than Latin text.

## Changelog

[Android] [Fixed] - Fix some languages wrapped texts are cut off.
Pull Request resolved: https://github.com/facebook/react-native/pull/25306

Test Plan:
Set the target SDK to 28 in ``fbsource/fbandroid/java/com/facebook/catalyst/shell/AndroidManifest.xml``:
```
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28"/>
```

Insert the following code into Playground.js: P67720709

Start the Catalyst Android app and navigate to the playground:

`buck install -r catalyst`

|Before|After|
|{F163482789}|{F163481060}|

Reviewed By: cpojer

Differential Revision: D15985809

Pulled By: makovkastar

fbshipit-source-id: 0f98760b7a7fe4689fa3fe90ca747e9bf9fc4780
2019-06-28 08:38:26 +01:00
Janic DuplessisandLorenzo Sciandra b68966ec7b Use CALayers to draw text (#24387)
Summary:
The current technique we use to draw text uses linear memory, which means that when text is too long the UIView layer is unable to draw it. This causes the issue described [here](https://github.com/facebook/react-native/issues/19453). On an iOS simulator the bug happens at around 500 lines which is quite annoying. It can also happen on a real device but requires a lot more text.

To be more specific the amount of text doesn't actually matter, it is the size of the UIView that we use to draw the text. When we use `[drawRect:]` the view creates a bitmap to send to the gpu to render, if that bitmap is too big it cannot render.

To fix this we can use `CATiledLayer` which will split drawing into smaller parts, that gets executed when the content is about to be visible. This drawing is also async which means the text can seem to appear during scroll. See https://developer.apple.com/documentation/quartzcore/calayer?language=objc.

`CATiledLayer` also adds some overhead that we don't want when rendering small amount of text. To fix this we can use either a regular `CALayer` or a `CATiledLayer` depending on the size of the view containing the text. I picked 1024 as the threshold which is about 1 screen and a half, and is still smaller than the height needed for the bug to occur when using a regular `CALayer` on a iOS simulator.

Also found this which addresses the problem in a similar manner and took some inspiration from the code linked there https://github.com/GitHawkApp/StyledTextKit/issues/14#issuecomment-395234885

Fixes https://github.com/facebook/react-native/issues/19453

## Changelog

[iOS] [Fixed] - Use CALayers to draw text, fixes rendering for long text
Pull Request resolved: https://github.com/facebook/react-native/pull/24387

Test Plan:
- Added the example I was using to verify the fix to RNTester.
- Made sure all other examples are still rendering properly.
- Tested text selection

Reviewed By: shergin

Differential Revision: D15918277

Pulled By: sammy-SC

fbshipit-source-id: c45409a8413e6e3ad272be39ba527a4e8d349e28
2019-06-28 08:38:17 +01:00
Robert YingandLorenzo Sciandra 99bc31cfa6 Fix regression of improper assets copy (revert #24518 #24778) (#25363)
Summary:
Pull requests https://github.com/facebook/react-native/issues/24518 #24778 make Gradle copy all **generated** assets and resources into `android/app/src/res`, which is a bad behavior, because `src/res` goes into version control and should hold only those **original** resource files.

These changes in https://github.com/facebook/react-native/issues/24518 #24778 were merged into 0.60.0-rc release and cause regression.

This pull request will:

- Revert pull requests https://github.com/facebook/react-native/issues/24518 #24778
- Close https://github.com/facebook/react-native/issues/25325

## Changelog

[Android] [Fixed] - Fix regression of improper assets copy (revert https://github.com/facebook/react-native/issues/24518 #24778)
Pull Request resolved: https://github.com/facebook/react-native/pull/25363

Test Plan: It is a revert pull request and the reverted script should work the same as it has in 0.59.x.

Differential Revision: D15963329

Pulled By: cpojer

fbshipit-source-id: 5619a318dbdb40e816e37b6e37d4fe32caa46e9e
2019-06-28 08:38:10 +01:00
DulmandakhandLorenzo Sciandra c36c481016 bump fresco to 2.0.0, supports AndroidX (#25358)
Summary:
Bump Fresco to 2.0.0, which supports AndroidX. We should cherry-pick to 0.60 release, to support brown field apps but also native components.

## Changelog

[Android] [Changed] - Bump Fresco to 2.0.0, supports AndroidX
Pull Request resolved: https://github.com/facebook/react-native/pull/25358

Test Plan: CI is green, and RNTester builds and runs as expected.

Differential Revision: D15959443

Pulled By: mdvacca

fbshipit-source-id: 58ba2c3e4d1342014d6ea632cd865b4f413548d9
2019-06-28 08:38:01 +01:00
DulmandakhandLorenzo Sciandra 13f4fa0245 custom fontWeight numeric values for Text on Android (#25341)
Summary:
I found that on Android we only support 2 fontWeight options, either **normal** or **bold**, even developer can set any numeric value. But iOS supports all possible numeric values. This PR tries to add support for all possible numeric values on Android, even if it's supported only on Android P(28) and above.

This change might break texts where fontWeight use improperly, because this PR removes conversion of values above 500 to BOLD and below 500 to normal.

FYI, also moved **mCustomTypefaceCache** usage up because it was working after unnecessary mFontCache usage.

## Changelog

[Android] [Changed] - add custom font weight support to Text component on Android, only on P(API 28) and above versions.
Pull Request resolved: https://github.com/facebook/react-native/pull/25341

Test Plan: RNTester app's Text examples will show Rubik Regular, Rubik Light, Rubik Bold, Rubik Medium and Rubik Medium Italic texts in corresponding font family, style and weights.

Differential Revision: D15956350

Pulled By: mdvacca

fbshipit-source-id: 61079d953c65fb34ab4497d44c22317912a5a616
2019-06-28 08:37:51 +01:00
Lorenzo Sciandra 9792f2c9d7 [0.60.0-rc.2] Bump version numbers 2019-06-20 11:40:30 +01:00
Lorenzo Sciandra 53cec2dc1f [LOCAL] bump version in template to match repo 2019-06-20 10:07:32 +01:00
Rick HanlonandLorenzo Sciandra b4f3d4b92e Move scheduler to dependencies
Summary: Fixes https://github.com/react-native-community/releases/issues/116#issuecomment-503449687

Reviewed By: gaearon

Differential Revision: D15901557

fbshipit-source-id: 653119c181585cf9a4a561b350c66bd10cb674bd
2019-06-20 10:06:01 +01:00
SalakarandLorenzo Sciandra e741488659 Implement changes to enable native modules auto linking (#24506)
Summary:
Replaces #24099 (original PR became detached for some reason)

Implements the template changes required to enable native modules auto-linking for both Android & iOS.

Requires the following to be merged first and an updated CLI to be published:

- [x] https://github.com/react-native-community/react-native-cli/pull/254
- [x] https://github.com/react-native-community/react-native-cli/pull/256
- [x] https://github.com/react-native-community/react-native-cli/pull/258

cc grabbou thymikee orta for review

- [ ] https://github.com/facebook/react-native/pull/24517 update CLI version)

[TEMPLATE] [FEATURE] - Enable auto-initialization/linking of react native modules for new projects
Pull Request resolved: https://github.com/facebook/react-native/pull/24506

Differential Revision: D15062701

Pulled By: cpojer

fbshipit-source-id: 65296cbec2925405fe8033de71910325e0c719bc

# Conflicts:
#	template/ios/Podfile
2019-06-19 14:10:57 +01:00
Michał PierzchałaandLorenzo Sciandra bf4ee6f5c1 Bump CLI to 2.0.0-rc.2 (#25241)
Summary:
Fixing Metro validation issue. Hope that helps with fixing the CI �.

## Changelog

[General] [Changed] - Bump CLI to 2.0.0-rc.2
Pull Request resolved: https://github.com/facebook/react-native/pull/25241

Differential Revision: D15803610

Pulled By: cpojer

fbshipit-source-id: 22136db1583f8cf5a40afd5d8a561d98cb6de982
2019-06-19 12:54:50 +01:00
zhongwuzwandLorenzo Sciandra cecba01b71 Removed autoresizing mask for modal host container view (#25150)
Summary:
Fixes #18177 . Related #24497. Autoresizing mask would conflict with `AutoLayout`. For example , it would impact `SafeAreaView`. And actually we don't need to use autoresizing mask,  we observe the bounds change notification and [update the frame manually](https://github.com/facebook/react-native/blob/1151c096dab17e5d9a6ac05b61aacecd4305f3db/React/Views/RCTModalHostView.m#L59).

## Changelog

[iOS] [Fixed] - Removed autoresizing mask for modal host container view
Pull Request resolved: https://github.com/facebook/react-native/pull/25150

Differential Revision: D15645148

Pulled By: cpojer

fbshipit-source-id: 95d5f40feaa980b959a3de6e273dccac8158c57b
2019-06-19 12:54:39 +01:00
Lorenzo Sciandra 06fffc2042 [0.60.0-rc.1] Bump version numbers 2019-06-10 12:30:15 +01:00
Lorenzo Sciandra 5ecc87bf3e bump versions to match the requirements 2019-06-07 17:54:20 +01:00
Lorenzo Sciandra 7082c3e449 re-add the hasteImpl 2019-06-07 15:52:42 +01:00
Michał PierzchałaandLorenzo Sciandra 39ce412b25 Bump CLI to 2.0.0-rc.0 (#25175)
Summary:
Upgrading the CLI to the latest with a bunch of fixes and features included.

## Changelog

[General] [Changed] - Bump CLI to 2.0.0-rc.0
Pull Request resolved: https://github.com/facebook/react-native/pull/25175

Differential Revision: D15694764

Pulled By: cpojer

fbshipit-source-id: 25fbf1c275ed5379e1cdb372512b6bb6327dea92

# Conflicts:
#	jest/hasteImpl.js
#	package.json
#	yarn.lock
2019-06-07 15:14:32 +01:00
Пётр ПотаповandLorenzo Sciandra 00c7cf3d68 Fix: RefreshControl in FlatList makes borderWidth not working (#24411)
Summary:
Fixes #22752

On line 1021 you are passing base style to props:
`style: [baseStyle, this.props.style],`

Explicitly passing base style to ScrollView just overrides this line and doesn't let developers to customise style of any inheritors of ScrollView (not only FlatList) with custom RefreshControl.

So this line (1113) seems to be removed.

## Changelog

[GENERAL] [Fixed] - fix of Android's bug that doesn't let override ScrollView's Style with custom RefreshControl.
Pull Request resolved: https://github.com/facebook/react-native/pull/24411

Differential Revision: D15713061

Pulled By: cpojer

fbshipit-source-id: 461259800f867af15e53e0743a5057ea4528ae69
2019-06-07 15:13:00 +01:00
NateandLorenzo Sciandra a916dd6632 Android Fix for 9145: No longer hard code build port (#23616)
Summary:
### Problem

According to https://github.com/facebook/react-native/issues/9145, the `--port` setting is not respected when executing `react-native run-android`. The templates that report things like what port the dev server runs on are hard coded as well.

### Solution

This commit replaces the hardcoded instances of port 8081 on Android with a build configuration property. This allows setting of the port React Native Android connects to for the local build server.

For this change to work, there must also be an update to the react native CLI to pass along this setting:

https://github.com/react-native-community/react-native-cli/compare/master...nhunzaker:9145-android-no-port-hardcode-cli

To avoid some noise on their end, I figured I wouldn't submit a PR until it's this approach is deemed workable.

## Changelog

[Android][fixed] - `react-native run-android --port <x>` correctly connects to dev server and related error messages display the correct port
Pull Request resolved: https://github.com/facebook/react-native/pull/23616

Differential Revision: D15645200

Pulled By: cpojer

fbshipit-source-id: 3bdfd458b8ac3ec78290736c9ed0db2e5776ed46
2019-06-07 15:12:11 +01:00
Eric LewisandLorenzo Sciandra eb73dbe24e Fix Xcode 11 build (#25146)
Summary:
Fixes build in Xcode 11 beta, the signature for `__unused` was changed. This adds a new check for the new style.

## Changelog

[iOS] [Fixed] - Xcode 11 beta build
Pull Request resolved: https://github.com/facebook/react-native/pull/25146

Differential Revision: D15628404

Pulled By: cpojer

fbshipit-source-id: 781a188a0e1562a3316fbe62920b12b03a44e4a7
2019-06-07 15:12:03 +01:00
Sharon GongandLorenzo Sciandra bcc9fcf1c7 Fix accessibilityActions accessors (#25134)
Summary:
The accessibilityActions accessors  in UIView+React.m are not aligned with the property declaration in the header file.

## Changelog

[General] [Fixed] - Fix accessibilityActions accessors
Pull Request resolved: https://github.com/facebook/react-native/pull/25134

Differential Revision: D15621848

Pulled By: cpojer

fbshipit-source-id: f344689292ae7988e46d0d4263980306d364366b
2019-06-07 15:11:56 +01:00
DratwasandLorenzo Sciandra 3e937eac2b fix indexed RAM bundle (#24967)
Summary:
Co-Authored: zamotany
With React Native 0.59.8 the app keeps crashing with indexed RAM bundle on Android with the following error:

```
2019-05-09 11:58:06.684 2793-2856/? E/AndroidRuntime: FATAL EXCEPTION: mqt_js
    Process: com.ramtestapp, PID: 2793
    com.facebook.jni.CppException: getPropertyAsObject: property '__fbRequireBatchedBridge' is not an Object

    no stack
        at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:29)
        at android.os.Looper.loop(Looper.java:193)
        at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:232)
        at java.lang.Thread.run(Thread.java:764)
```

After investigation we found that when using any bundle, let it be non-ram, FIle RAM bundle or Index RAM bundle, the `CatalystInstanceImpl.java` is always using `loadScriptsFromAsset`, which is calling `CatalystInstanceImpl::jniLoadScriptFromAssets` in C++. This method when checking if bundle is a RAM bundle, uses `JniJSModulesUnbundle::isUnbundle` which only check for js-modules/UNBUNDLE - file generated when building File RAM bundle. There is no other logic to handle Indexed RAM bundle, so it figures that the bundle is not RAM, cause there is no js-modules/UNBUNDLE file and tries to load as regular bundle and fails.

In this PR we added check if it is indexed RAM bundle in `jniLoadScriptFromAssets` and handle it if it is.
## Changelog
[Android] [Fixed] fix indexed RAM bundle

Solves https://github.com/facebook/react-native/issues/21282
Pull Request resolved: https://github.com/facebook/react-native/pull/24967

Differential Revision: D15575924

Pulled By: cpojer

fbshipit-source-id: 5ea428e0b793edd8242243f39f933d1092b35260
2019-06-07 15:11:50 +01:00
Michael MasonandLorenzo Sciandra 0d05051f3c - Fix missing whitespace in debug instructions (#25122)
Summary:
Fixes minor whitespace issue with the new new-app template.

## Changelog

[Android] [Fixed] - Fix missing whitespace in debug instructions
Pull Request resolved: https://github.com/facebook/react-native/pull/25122

Differential Revision: D15602100

Pulled By: cpojer

fbshipit-source-id: 07c51c6359e37826941de659bcedea692ff3315a
2019-06-07 15:11:43 +01:00
Janic DuplessisandLorenzo Sciandra 54471963e0 Remove vendored fetch polyfill, update to whatwg-fetch@3.0 (#24418)
Summary:
The original reason for vendoring the fetch polyfill was to remove the default blob response type but this was reverted.

Here's a little history around the fetch polyfill and the blob issue:

- Original commit introducing the vendored polyfill: #19333, the goal was to fix a memory leak because our blob implementation doesn't release resources automatically. Not an ideal fix but since the issue was pretty severe and the infra for a proper fix was not in place.
- This introduced an issue when downloading images using `fetch` which was fixed by #22063 which re-added the default blob content type. However that re-introduced the original fetch memory leak.
- We have better infra now with jsi and I was able to get blob deallocation working, see #24405

Currently the vendored fetch polyfill is useless since it was changed back to the original version. We can just use the npm version again. I also updated to 3.0 which brings better spec compliance and support for cancellation via `AbortController`, https://github.com/github/fetch/releases/tag/v3.0.0.

## Changelog

[General] [Changed] - Remove vendored fetch polyfill, update to whatwg-fetch@3.0
Pull Request resolved: https://github.com/facebook/react-native/pull/24418

Differential Revision: D14932683

Pulled By: cpojer

fbshipit-source-id: 915e3d25978e8b9d7507ed807e7fba45aa88385a
2019-06-07 15:11:36 +01:00
Petter HesselbergandLorenzo Sciandra 1b8f7e7a36 Don't reference null android.ndkDirectory in build.gradle (#25088)
Summary:
If you (try to) build React Native for Android without having the NDK properly installed and referenced, you get the following error:

>A problem occurred evaluating project ':ReactAndroid'.
\> Cannot get property 'absolutePath' on null object

This is not an overly helpful diagnostic. This PR results in this message instead:

>ndk-build binary cannot be found, check if you've set $ANDROID_NDK environment variable correctly or if ndk.dir is setup in local.properties

Fixes #25087

## Changelog

[Android] [Fixed] - Show proper error message instead of throwing a NullReferenceException if Gradle cannot find the NDK
Pull Request resolved: https://github.com/facebook/react-native/pull/25088

Differential Revision: D15559271

Pulled By: cpojer

fbshipit-source-id: 35c9a9321af4e4a34bf519144ada48884b48352d
2019-06-07 15:11:29 +01:00
Andrea CimitanandLorenzo Sciandra 46500b3e36 Linking.getInitialURL() to work with NFC tags on Android (#25055)
Summary:
This PR solves bug https://github.com/facebook/react-native/issues/24393 for Android. Allows an app to be opened with an NFC tag and getting the url trough Linking.getInitialURL()

## Changelog
[Android] [Fixed] - This branch checks also for `ACTION_NDEF_DISCOVERED` intent matches to set the initialURL
Pull Request resolved: https://github.com/facebook/react-native/pull/25055

Differential Revision: D15516873

Pulled By: cpojer

fbshipit-source-id: e8803738d857a69e1063e926fc3858a416a0b25e
2019-06-07 15:11:22 +01:00
Oleksandr MelnykovandLorenzo Sciandra ed40f382e8 Fix backgroundColor top level prop of TextInput
Summary:
Changelog: [Android] [FIXED] - Fix backgroundColor top level prop of TextInput

This diff fixes two issues with the `backgroundColor` top level property of TextInput on Android:
 * Now it is possible to set a **string** value for the top-level `backgroundColor` property of TextInput (crashed the app previously):
```
<TextInput backgroundColor="#ffccbb">Hello, React Native</TextInput>
```
* Now it's possible to set an **integer** value for the top-level `backgroundColor` property of TextInput (had no effect previously):
```
<TextInput backgroundColor={0xffccbbff}>Hello, React Native</TextInput>
```

A `customType = "Color"` annotation parameter must be provided for `ReactBaseTextShadowNode.setBackgroundColor(...)` since the color value must be previously processed in JS before sending it over the bridge to the native code. The JS code will parse the color value and return the proper ARGB color integer to the native platforms (https://fburl.com/uqup52tn).

Without providing the custom type for the background color, if a string value is set for the top-level `backgroundColor` property in the JS code, the Android code will crash since it expects an integer value for the color in `ReactBaseTextShadowNode.setBackgroundColor(...)`, but a string will be passed from JS without any conversion and there will be a `ClassCastException` thrown. If an integer value without the alpha component (like `0xffccbb`) is set, the Android native view would get an integer color value with its alpha component set to `0x00`, which means a transparent color.

On a side note: the alpha component of a color must always be set when using an integer value for `backgroundColor` since the JS code, while processing the color type, shifts the rightmost 8 bytes (alpha component) to the leftmost position. If those 8 bytes are not the alpha component, you will get the wrong color in the end. It doesn't seem to be a problem for string values of `backgroundColor` though.

Reviewed By: mdvacca

Differential Revision: D15453980

fbshipit-source-id: f3f5d9c9877cdbce79a67f2ed93ad4589576d166
2019-06-07 15:11:14 +01:00
DulmandakhandLorenzo Sciandra 8d61a4e5f9 bump android gradle plugin to 3.4.1 (#24883)
Summary:
bump android gradle plugin to 3.4.1, includes many fixes and improvements.

## Changelog

[Android] [Changed] - bump android gradle plugin to 3.4.1
Pull Request resolved: https://github.com/facebook/react-native/pull/24883

Differential Revision: D15474556

Pulled By: hramos

fbshipit-source-id: 8d1eb91855b9f416ed3380c61f34672deded26c1
2019-06-07 15:11:05 +01:00
Héctor Ramos 55332afb29 [0.60.0-rc.0] Bump version numbers 2019-05-30 08:29:45 -07:00
Héctor Ramos d014fc7153 Use newer Docker container, with ssl support 2019-05-30 08:28:56 -07:00
Héctor Ramos 29496ede07 Revert "[0.60.0-rc.0] Bump version numbers"
This reverts commit f4508a6765.
2019-05-30 08:23:24 -07:00
Mike Grabowski f4508a6765 [0.60.0-rc.0] Bump version numbers 2019-05-30 13:34:40 +02:00
Héctor Ramos 53e32a47e4 Remove duplicate Android SDK steps already present in container 2019-05-28 18:15:43 -07:00
Héctor Ramos 41742b3fe3 Fix config for 0.60 release 2019-05-28 07:56:31 -07:00
Héctor Ramos 5be47faff7 Make publish job depend on checkout_code as git is not available in RNAndroid docker container 2019-05-28 07:55:22 -07:00
Héctor Ramos ea460d6d3e Revert "[0.60.0-rc.0] Bump version numbers"
This reverts commit edb749f283.
2019-05-28 07:39:11 -07:00
Mike Grabowski edb749f283 [0.60.0-rc.0] Bump version numbers 2019-05-22 22:29:43 +02:00
162 changed files with 18002 additions and 1133 deletions
+6 -6
View File
@@ -157,7 +157,7 @@ js_defaults: &js_defaults
android_defaults: &android_defaults
<<: *defaults
docker:
- image: reactnativecommunity/react-native-android:2019-5-7
- image: reactnativecommunity/react-native-android:2019-5-29
resource_class: "large"
environment:
- TERM: "dumb"
@@ -544,10 +544,9 @@ jobs:
# Keep configuring Android dependencies while AVD boots up
# Install Buck
- restore-cache: *restore-buck-downloads-cache
- run:
name: Install BUCK
name: Install Buck
command: |
buck --version
# Install related tooling
@@ -684,9 +683,6 @@ jobs:
- restore-cache: *restore-gradle-downloads-cache
- run: *download-dependencies-gradle
- restore-cache: *restore-yarn-cache
- run: *yarn
- run:
name: Authenticate with npm
command: echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc
@@ -727,8 +723,12 @@ workflows:
releases:
jobs:
- checkout_code:
filters: *filter-only-version-tags
- publish_npm_package:
filters: *filter-only-version-tags
requires:
- checkout_code
analysis:
jobs:
@@ -25,6 +25,7 @@ const invariant = require('invariant');
const processDecelerationRate = require('./processDecelerationRate');
const requireNativeComponent = require('../../ReactNative/requireNativeComponent');
const resolveAssetSource = require('../../Image/resolveAssetSource');
const splitLayoutProps = require('../../StyleSheet/splitLayoutProps');
import type {
PressEvent,
@@ -1125,15 +1126,15 @@ class ScrollView extends React.Component<Props, State> {
// On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.
// Since the ScrollView is wrapped add the style props to the
// AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.
// Note: we should only apply props.style on the wrapper
// Note: we should split props.style on the inner and outer props
// however, the ScrollView still needs the baseStyle to be scrollable
const {outer, inner} = splitLayoutProps(flattenStyle(props.style));
return React.cloneElement(
refreshControl,
{style: props.style},
{style: [baseStyle, outer]},
<ScrollViewClass
{...props}
style={baseStyle}
style={[baseStyle, inner]}
// $FlowFixMe
ref={this._setScrollViewRef}>
{contentContainer}
+4 -4
View File
@@ -1,17 +1,17 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @generated by scripts/bump-oss-version.js
* @flow
*/
exports.version = {
major: 0,
minor: 0,
patch: 0,
minor: 60,
patch: 2,
prerelease: null,
};
+4
View File
@@ -21,6 +21,10 @@ if (global.window === undefined) {
global.window = global;
}
if (global.self === undefined) {
global.self = global;
}
// Set up process
global.process = global.process || {};
global.process.env = global.process.env || {};
+1 -1
View File
@@ -11,7 +11,7 @@
'use strict';
const whatwg = require('../vendor/core/whatwg-fetch');
const whatwg = require('whatwg-fetch');
if (whatwg && whatwg.fetch) {
module.exports = whatwg;
@@ -27,7 +27,7 @@ const DebugInstructions = Platform.select({
),
default: () => (
<Text>
Press <Text style={styles.highlight}>menu button</Text> or
Press <Text style={styles.highlight}>menu button</Text> or{' '}
<Text style={styles.highlight}>Shake</Text> your device to open the React
Native debug menu.
</Text>
@@ -0,0 +1,44 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @emails oncall+react_native
*/
'use strict';
const splitLayoutProps = require('../splitLayoutProps');
test('splits style objects', () => {
const style = {width: 10, margin: 20, padding: 30};
const {outer, inner} = splitLayoutProps(style);
expect(outer).toMatchInlineSnapshot(`
Object {
"margin": 20,
"width": 10,
}
`);
expect(inner).toMatchInlineSnapshot(`
Object {
"padding": 30,
}
`);
});
test('does not copy values to both returned objects', () => {
const style = {marginVertical: 5, paddingHorizontal: 10};
const {outer, inner} = splitLayoutProps(style);
expect(outer).toMatchInlineSnapshot(`
Object {
"marginVertical": 5,
}
`);
expect(inner).toMatchInlineSnapshot(`
Object {
"paddingHorizontal": 10,
}
`);
});
+62
View File
@@ -0,0 +1,62 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
import type {DangerouslyImpreciseStyle} from './StyleSheet';
const OUTER_PROPS = Object.assign(Object.create(null), {
margin: true,
marginHorizontal: true,
marginVertical: true,
marginBottom: true,
marginTop: true,
marginLeft: true,
marginRight: true,
flex: true,
flexGrow: true,
flexShrink: true,
flexBasis: true,
alignSelf: true,
height: true,
minHeight: true,
maxHeight: true,
width: true,
minWidth: true,
maxWidth: true,
position: true,
left: true,
right: true,
bottom: true,
top: true,
});
function splitLayoutProps(
props: ?DangerouslyImpreciseStyle,
): {
outer: DangerouslyImpreciseStyle,
inner: DangerouslyImpreciseStyle,
} {
const inner = {};
const outer = {};
if (props) {
Object.keys(props).forEach(k => {
const value: $ElementType<DangerouslyImpreciseStyle, typeof k> = props[k];
if (OUTER_PROPS[k]) {
outer[k] = value;
} else {
inner[k] = value;
}
});
}
return {outer, inner};
}
module.exports = splitLayoutProps;
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
19461666225DC3B300E4E008 /* RCTTextRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 19461664225DC3B300E4E008 /* RCTTextRenderer.m */; };
5956B130200FEBAA008D9D16 /* RCTRawTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B0FD200FEBA9008D9D16 /* RCTRawTextShadowView.m */; };
5956B131200FEBAA008D9D16 /* RCTRawTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B0FE200FEBA9008D9D16 /* RCTRawTextViewManager.m */; };
5956B132200FEBAA008D9D16 /* RCTSinglelineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B101200FEBA9008D9D16 /* RCTSinglelineTextInputView.m */; };
@@ -187,6 +188,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
19461664225DC3B300E4E008 /* RCTTextRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextRenderer.m; sourceTree = "<group>"; };
19461665225DC3B300E4E008 /* RCTTextRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextRenderer.h; sourceTree = "<group>"; };
2D2A287B1D9B048500D4039D /* libRCTText-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTText-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
58B5119B1A9E6C1200147676 /* libRCTText.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTText.a; sourceTree = BUILT_PRODUCTS_DIR; };
5956B0F9200FEBA9008D9D16 /* RCTConvert+Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Text.h"; sourceTree = "<group>"; };
@@ -360,6 +363,8 @@
children = (
5956B129200FEBAA008D9D16 /* NSTextStorage+FontScaling.h */,
5956B125200FEBAA008D9D16 /* NSTextStorage+FontScaling.m */,
19461665225DC3B300E4E008 /* RCTTextRenderer.h */,
19461664225DC3B300E4E008 /* RCTTextRenderer.m */,
5956B126200FEBAA008D9D16 /* RCTTextShadowView.h */,
5956B122200FEBAA008D9D16 /* RCTTextShadowView.m */,
5956B123200FEBAA008D9D16 /* RCTTextView.h */,
@@ -439,6 +444,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511921A9E6C1200147676;
@@ -504,6 +510,7 @@
5956B142200FEBAA008D9D16 /* RCTTextViewManager.m in Sources */,
5956B135200FEBAA008D9D16 /* RCTBaseTextInputView.m in Sources */,
5956B144200FEBAA008D9D16 /* RCTVirtualTextViewManager.m in Sources */,
19461666225DC3B300E4E008 /* RCTTextRenderer.m in Sources */,
5C245F39205E216A00D936E9 /* RCTInputAccessoryShadowView.m in Sources */,
5956B13B200FEBAA008D9D16 /* RCTMultilineTextInputViewManager.m in Sources */,
5956B134200FEBAA008D9D16 /* RCTSinglelineTextInputViewManager.m in Sources */,
+23
View File
@@ -0,0 +1,23 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Used by text layers to render text. Note that UIKit crashes if this delegate is implemented
* directly on a UIView subclass since it already implements it for the view's root
* layer. This is why this is implemented in a separate class.
*/
@interface RCTTextRenderer : NSObject <CALayerDelegate>
- (void)setTextStorage:(NSTextStorage *)textStorage contentFrame:(CGRect)contentFrame;
@end
NS_ASSUME_NONNULL_END
+56
View File
@@ -0,0 +1,56 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTextRenderer.h"
#import "RCTTextAttributes.h"
@implementation RCTTextRenderer
{
NSTextStorage *_Nullable _textStorage;
CGRect _contentFrame;
}
- (void)setTextStorage:(NSTextStorage *)textStorage
contentFrame:(CGRect)contentFrame
{
_textStorage = textStorage;
_contentFrame = contentFrame;
}
- (void)drawLayer:(CALayer *)layer
inContext:(CGContextRef)ctx;
{
if (!_textStorage) {
return;
}
CGRect boundingBox = CGContextGetClipBoundingBox(ctx);
CGContextSaveGState(ctx);
UIGraphicsPushContext(ctx);
NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
NSRange glyphRange =
[layoutManager glyphRangeForBoundingRect:boundingBox
inTextContainer:textContainer];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin];
UIGraphicsPopContext();
CGContextRestoreGState(ctx);
}
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
// Disable all implicit animations.
return (id)[NSNull null];
}
@end
+112 -30
View File
@@ -13,15 +13,36 @@
#import <React/UIView+React.h>
#import "RCTTextShadowView.h"
#import "RCTTextRenderer.h"
@interface RCTTextTiledLayer : CATiledLayer
@end
@implementation RCTTextTiledLayer
+ (CFTimeInterval)fadeDuration
{
return 0.05;
}
@end
@implementation RCTTextView
{
CAShapeLayer *_highlightLayer;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
NSArray<UIView *> *_Nullable _descendantViews;
NSTextStorage *_Nullable _textStorage;
CGRect _contentFrame;
RCTTextRenderer *_renderer;
// For small amount of text avoid the overhead of CATiledLayer and
// make render text synchronously. For large amount of text, use
// CATiledLayer to chunk text rendering and avoid linear memory
// usage.
CALayer *_Nullable _syncLayer;
RCTTextTiledLayer *_Nullable _asyncTiledLayer;
CAShapeLayer *_highlightLayer;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -31,6 +52,7 @@
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
_renderer = [RCTTextRenderer new];
}
return self;
}
@@ -65,6 +87,7 @@
// This disables the frame animation, without affecting opacity, etc.
[UIView performWithoutAnimation:^{
[super reactSetFrame:frame];
[self configureLayer];
}];
}
@@ -91,55 +114,101 @@
[self addSubview:view];
}
[self setNeedsDisplay];
[_renderer setTextStorage:textStorage contentFrame:contentFrame];
[self configureLayer];
[self setCurrentLayerNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
- (void)configureLayer
{
if (!_textStorage) {
return;
}
CALayer *currentLayer;
CGSize screenSize = RCTScreenSize();
CGFloat textViewTileSize = MAX(screenSize.width, screenSize.height) * 1.5;
if (self.frame.size.width > textViewTileSize || self.frame.size.height > textViewTileSize) {
// Cleanup sync layer
if (_syncLayer != nil) {
_syncLayer.delegate = nil;
[_syncLayer removeFromSuperlayer];
_syncLayer = nil;
}
if (_asyncTiledLayer == nil) {
RCTTextTiledLayer *layer = [RCTTextTiledLayer layer];
layer.delegate = _renderer;
layer.contentsScale = RCTScreenScale();
layer.tileSize = CGSizeMake(textViewTileSize, textViewTileSize);
_asyncTiledLayer = layer;
[self.layer addSublayer:layer];
[layer setNeedsDisplay];
}
_asyncTiledLayer.frame = self.bounds;
currentLayer = _asyncTiledLayer;
} else {
// Cleanup async tiled layer
if (_asyncTiledLayer != nil) {
_asyncTiledLayer.delegate = nil;
[_asyncTiledLayer removeFromSuperlayer];
_asyncTiledLayer = nil;
}
if (_syncLayer == nil) {
CALayer *layer = [CALayer layer];
layer.delegate = _renderer;
layer.contentsScale = RCTScreenScale();
_syncLayer = layer;
[self.layer addSublayer:layer];
[layer setNeedsDisplay];
}
_syncLayer.frame = self.bounds;
currentLayer = _syncLayer;
}
NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin];
NSRange glyphRange =
[layoutManager glyphRangeForTextContainer:textContainer];
__block UIBezierPath *highlightPath = nil;
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange
actualGlyphRange:NULL];
[_textStorage enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName
inRange:characterRange
options:0
usingBlock:
^(NSNumber *value, NSRange range, __unused BOOL *stop) {
if (!value.boolValue) {
return;
}
^(NSNumber *value, NSRange range, __unused BOOL *stop) {
if (!value.boolValue) {
return;
}
[layoutManager enumerateEnclosingRectsForGlyphRange:range
withinSelectedGlyphRange:range
inTextContainer:textContainer
usingBlock:
^(CGRect enclosingRect, __unused BOOL *anotherStop) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) cornerRadius:2];
if (highlightPath) {
[highlightPath appendPath:path];
} else {
highlightPath = path;
}
[layoutManager enumerateEnclosingRectsForGlyphRange:range
withinSelectedGlyphRange:range
inTextContainer:textContainer
usingBlock:
^(CGRect enclosingRect, __unused BOOL *anotherStop) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) cornerRadius:2];
if (highlightPath) {
[highlightPath appendPath:path];
} else {
highlightPath = path;
}
}
];
}];
}];
if (highlightPath) {
if (!_highlightLayer) {
_highlightLayer = [CAShapeLayer layer];
_highlightLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.25].CGColor;
[self.layer addSublayer:_highlightLayer];
}
if (![currentLayer.sublayers containsObject:_highlightLayer]) {
[currentLayer addSublayer:_highlightLayer];
}
_highlightLayer.position = _contentFrame.origin;
_highlightLayer.path = highlightPath.CGPath;
@@ -149,6 +218,15 @@
}
}
- (void)setCurrentLayerNeedsDisplay
{
if (_asyncTiledLayer != nil) {
[_asyncTiledLayer setNeedsDisplay];
} else if (_syncLayer != nil) {
[_syncLayer setNeedsDisplay];
}
[_highlightLayer setNeedsDisplay];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
@@ -174,14 +252,18 @@
{
[super didMoveToWindow];
// When an `RCTText` instance moves offscreen (possibly due to parent clipping),
// we unset the layer's contents until it comes onscreen again.
if (!self.window) {
self.layer.contents = nil;
if (_highlightLayer) {
[_highlightLayer removeFromSuperlayer];
_highlightLayer = nil;
}
[_syncLayer removeFromSuperlayer];
_syncLayer = nil;
[_asyncTiledLayer removeFromSuperlayer];
_asyncTiledLayer = nil;
[_highlightLayer removeFromSuperlayer];
_highlightLayer = nil;
} else if (_textStorage) {
[self setNeedsDisplay];
[self configureLayer];
[self setCurrentLayerNeedsDisplay];
}
}
-533
View File
@@ -1,533 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
// Fork of https://github.com/github/fetch/blob/master/fetch.js that does not
// use reponseType: 'blob' by default. RN already has specific native implementations
// for different response types so there is no need to add the extra blob overhead.
// Copyright (c) 2014-2016 GitHub, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(function(self) {
'use strict';
if (self.fetch) {
return;
}
var support = {
searchParams: 'URLSearchParams' in self,
iterable: 'Symbol' in self && 'iterator' in Symbol,
blob:
'FileReader' in self &&
'Blob' in self &&
(function() {
try {
new Blob();
return true;
} catch (e) {
return false;
}
})(),
formData: 'FormData' in self,
arrayBuffer: 'ArrayBuffer' in self,
};
if (support.arrayBuffer) {
var viewClasses = [
'[object Int8Array]',
'[object Uint8Array]',
'[object Uint8ClampedArray]',
'[object Int16Array]',
'[object Uint16Array]',
'[object Int32Array]',
'[object Uint32Array]',
'[object Float32Array]',
'[object Float64Array]',
];
var isDataView = function(obj) {
return obj && DataView.prototype.isPrototypeOf(obj);
};
var isArrayBufferView =
ArrayBuffer.isView ||
function(obj) {
return (
obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
);
};
}
function normalizeName(name) {
if (typeof name !== 'string') {
name = String(name);
}
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
throw new TypeError('Invalid character in header field name');
}
return name.toLowerCase();
}
function normalizeValue(value) {
if (typeof value !== 'string') {
value = String(value);
}
return value;
}
// Build a destructive iterator for the value list
function iteratorFor(items) {
var iterator = {
next: function() {
var value = items.shift();
return {done: value === undefined, value: value};
},
};
if (support.iterable) {
iterator[Symbol.iterator] = function() {
return iterator;
};
}
return iterator;
}
function Headers(headers) {
this.map = {};
if (headers instanceof Headers) {
headers.forEach(function(value, name) {
this.append(name, value);
}, this);
} else if (Array.isArray(headers)) {
headers.forEach(function(header) {
this.append(header[0], header[1]);
}, this);
} else if (headers) {
Object.getOwnPropertyNames(headers).forEach(function(name) {
this.append(name, headers[name]);
}, this);
}
}
Headers.prototype.append = function(name, value) {
name = normalizeName(name);
value = normalizeValue(value);
var oldValue = this.map[name];
this.map[name] = oldValue ? oldValue + ',' + value : value;
};
Headers.prototype['delete'] = function(name) {
delete this.map[normalizeName(name)];
};
Headers.prototype.get = function(name) {
name = normalizeName(name);
return this.has(name) ? this.map[name] : null;
};
Headers.prototype.has = function(name) {
return this.map.hasOwnProperty(normalizeName(name));
};
Headers.prototype.set = function(name, value) {
this.map[normalizeName(name)] = normalizeValue(value);
};
Headers.prototype.forEach = function(callback, thisArg) {
for (var name in this.map) {
if (this.map.hasOwnProperty(name)) {
callback.call(thisArg, this.map[name], name, this);
}
}
};
Headers.prototype.keys = function() {
var items = [];
this.forEach(function(value, name) {
items.push(name);
});
return iteratorFor(items);
};
Headers.prototype.values = function() {
var items = [];
this.forEach(function(value) {
items.push(value);
});
return iteratorFor(items);
};
Headers.prototype.entries = function() {
var items = [];
this.forEach(function(value, name) {
items.push([name, value]);
});
return iteratorFor(items);
};
if (support.iterable) {
Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
}
function consumed(body) {
if (body.bodyUsed) {
return Promise.reject(new TypeError('Already read'));
}
body.bodyUsed = true;
}
function fileReaderReady(reader) {
return new Promise(function(resolve, reject) {
reader.onload = function() {
resolve(reader.result);
};
reader.onerror = function() {
reject(reader.error);
};
});
}
function readBlobAsArrayBuffer(blob) {
var reader = new FileReader();
var promise = fileReaderReady(reader);
reader.readAsArrayBuffer(blob);
return promise;
}
function readBlobAsText(blob) {
var reader = new FileReader();
var promise = fileReaderReady(reader);
reader.readAsText(blob);
return promise;
}
function readArrayBufferAsText(buf) {
var view = new Uint8Array(buf);
var chars = new Array(view.length);
for (var i = 0; i < view.length; i++) {
chars[i] = String.fromCharCode(view[i]);
}
return chars.join('');
}
function bufferClone(buf) {
if (buf.slice) {
return buf.slice(0);
} else {
var view = new Uint8Array(buf.byteLength);
view.set(new Uint8Array(buf));
return view.buffer;
}
}
function Body() {
this.bodyUsed = false;
this._initBody = function(body) {
this._bodyInit = body;
if (!body) {
this._bodyText = '';
} else if (typeof body === 'string') {
this._bodyText = body;
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
this._bodyBlob = body;
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
this._bodyFormData = body;
} else if (
support.searchParams &&
URLSearchParams.prototype.isPrototypeOf(body)
) {
this._bodyText = body.toString();
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
this._bodyArrayBuffer = bufferClone(body.buffer);
// IE 10-11 can't handle a DataView body.
this._bodyInit = new Blob([this._bodyArrayBuffer]);
} else if (
support.arrayBuffer &&
(ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))
) {
this._bodyArrayBuffer = bufferClone(body);
} else {
throw new Error('unsupported BodyInit type');
}
if (!this.headers.get('content-type')) {
if (typeof body === 'string') {
this.headers.set('content-type', 'text/plain;charset=UTF-8');
} else if (this._bodyBlob && this._bodyBlob.type) {
this.headers.set('content-type', this._bodyBlob.type);
} else if (
support.searchParams &&
URLSearchParams.prototype.isPrototypeOf(body)
) {
this.headers.set(
'content-type',
'application/x-www-form-urlencoded;charset=UTF-8',
);
}
}
};
if (support.blob) {
this.blob = function() {
var rejected = consumed(this);
if (rejected) {
return rejected;
}
if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([this._bodyArrayBuffer]));
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as blob');
} else {
return Promise.resolve(new Blob([this._bodyText]));
}
};
this.arrayBuffer = function() {
if (this._bodyArrayBuffer) {
return consumed(this) || Promise.resolve(this._bodyArrayBuffer);
} else {
return this.blob().then(readBlobAsArrayBuffer);
}
};
}
this.text = function() {
var rejected = consumed(this);
if (rejected) {
return rejected;
}
if (this._bodyBlob) {
return readBlobAsText(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as text');
} else {
return Promise.resolve(this._bodyText);
}
};
if (support.formData) {
this.formData = function() {
return this.text().then(decode);
};
}
this.json = function() {
return this.text().then(JSON.parse);
};
return this;
}
// HTTP methods whose capitalization should be normalized
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
function normalizeMethod(method) {
var upcased = method.toUpperCase();
return methods.indexOf(upcased) > -1 ? upcased : method;
}
function Request(input, options) {
options = options || {};
var body = options.body;
if (input instanceof Request) {
if (input.bodyUsed) {
throw new TypeError('Already read');
}
this.url = input.url;
this.credentials = input.credentials;
if (!options.headers) {
this.headers = new Headers(input.headers);
}
this.method = input.method;
this.mode = input.mode;
if (!body && input._bodyInit != null) {
body = input._bodyInit;
input.bodyUsed = true;
}
} else {
this.url = String(input);
}
this.credentials = options.credentials || this.credentials || 'omit';
if (options.headers || !this.headers) {
this.headers = new Headers(options.headers);
}
this.method = normalizeMethod(options.method || this.method || 'GET');
this.mode = options.mode || this.mode || null;
this.referrer = null;
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
throw new TypeError('Body not allowed for GET or HEAD requests');
}
this._initBody(body);
}
Request.prototype.clone = function() {
return new Request(this, {body: this._bodyInit});
};
function decode(body) {
var form = new FormData();
body
.trim()
.split('&')
.forEach(function(bytes) {
if (bytes) {
var split = bytes.split('=');
var name = split.shift().replace(/\+/g, ' ');
var value = split.join('=').replace(/\+/g, ' ');
form.append(decodeURIComponent(name), decodeURIComponent(value));
}
});
return form;
}
function parseHeaders(rawHeaders) {
var headers = new Headers();
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
var parts = line.split(':');
var key = parts.shift().trim();
if (key) {
var value = parts.join(':').trim();
headers.append(key, value);
}
});
return headers;
}
Body.call(Request.prototype);
function Response(bodyInit, options) {
if (!options) {
options = {};
}
this.type = 'default';
this.status = options.status === undefined ? 200 : options.status;
this.ok = this.status >= 200 && this.status < 300;
this.statusText = 'statusText' in options ? options.statusText : 'OK';
this.headers = new Headers(options.headers);
this.url = options.url || '';
this._initBody(bodyInit);
}
Body.call(Response.prototype);
Response.prototype.clone = function() {
return new Response(this._bodyInit, {
status: this.status,
statusText: this.statusText,
headers: new Headers(this.headers),
url: this.url,
});
};
Response.error = function() {
var response = new Response(null, {status: 0, statusText: ''});
response.type = 'error';
return response;
};
var redirectStatuses = [301, 302, 303, 307, 308];
Response.redirect = function(url, status) {
if (redirectStatuses.indexOf(status) === -1) {
throw new RangeError('Invalid status code');
}
return new Response(null, {status: status, headers: {location: url}});
};
self.Headers = Headers;
self.Request = Request;
self.Response = Response;
self.fetch = function(input, init) {
return new Promise(function(resolve, reject) {
var request = new Request(input, init);
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || ''),
};
options.url =
'responseURL' in xhr
? xhr.responseURL
: options.headers.get('X-Request-URL');
var body = 'response' in xhr ? xhr.response : xhr.responseText;
resolve(new Response(body, options));
};
xhr.onerror = function() {
reject(new TypeError('Network request failed'));
};
xhr.ontimeout = function() {
reject(new TypeError('Network request failed'));
};
xhr.open(request.method, request.url, true);
if (request.credentials === 'include') {
xhr.withCredentials = true;
} else if (request.credentials === 'omit') {
xhr.withCredentials = false;
}
if ('responseType' in xhr && support.blob) {
xhr.responseType = 'blob';
}
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value);
});
xhr.send(
typeof request._bodyInit === 'undefined' ? null : request._bodyInit,
);
});
};
self.fetch.polyfill = true;
})(typeof self !== 'undefined' ? self : this);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

After

Width:  |  Height:  |  Size: 315 KiB

+24 -1
View File
@@ -68,7 +68,10 @@ project.ext.react = [
bundleAssetName: "RNTesterApp.android.bundle",
entryFile: file("../../js/RNTesterApp.android.js"),
root: "$rootDir",
inputExcludes: ["android/**", "./**", ".gradle/**"]
inputExcludes: ["android/**", "./**", ".gradle/**"],
composeSourceMapsPath: "$rootDir/scripts/compose-source-maps.js",
hermesCommand: "../../../node_modules/hermesvm/%OS-BIN%/hermes",
enableHermesForVariant: { def v -> v.name.contains("hermes") }
]
apply from: "../../../react.gradle"
@@ -105,6 +108,16 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
flavorDimensions "vm"
productFlavors {
hermes {
dimension "vm"
}
jsc {
dimension "vm"
}
}
defaultConfig {
applicationId "com.facebook.react.uiapp"
minSdkVersion 16
@@ -138,6 +151,12 @@ android {
signingConfig signingConfigs.release
}
}
packagingOptions {
pickFirst '**/armeabi-v7a/libc++_shared.so'
pickFirst '**/x86/libc++_shared.so'
pickFirst '**/arm64-v8a/libc++_shared.so'
pickFirst '**/x86_64/libc++_shared.so'
}
}
dependencies {
@@ -146,6 +165,10 @@ dependencies {
// Build React Native from source
implementation project(':ReactAndroid')
def hermesPath = '$projectDir/../../../../node_modules/hermesvm/android/'
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
if (useIntlJsc) {
implementation 'org.webkit:android-jsc-intl:+'
} else {
@@ -14,8 +14,11 @@ import com.facebook.react.BuildConfig;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.views.text.ReactFontManager;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
@@ -47,8 +50,9 @@ public class RNTesterApplication extends Application implements ReactApplication
@Override
public void onCreate() {
ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi);
ReactFontManager.getInstance().addCustomFont(this, "Rubik", R.font.rubik);
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
@Override
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
<font app:fontStyle="normal" app:fontWeight="300" app:font="@font/rubik_light"/>
<font app:fontStyle="normal" app:fontWeight="400" app:font="@font/rubik_regular"/>
<font app:fontStyle="normal" app:fontWeight="500" app:font="@font/rubik_medium" />
<font app:fontStyle="normal" app:fontWeight="700" app:font="@font/rubik_bold" />
<font app:fontStyle="italic" app:fontWeight="500" app:font="@font/rubik_medium_italic" />
</font-family>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
<font app:fontStyle="normal" app:fontWeight="400" app:font="@font/srisakdi_regular"/>
<font app:fontStyle="normal" app:fontWeight="700" app:font="@font/srisakdi_bold" />
</font-family>
+36 -8
View File
@@ -183,13 +183,41 @@ class TextExample extends React.Component<{}> {
<Text style={{fontFamily: 'notoserif', fontStyle: 'italic'}}>
NotoSerif Italic (Missing Font file)
</Text>
<Text style={{fontFamily: 'Srisakdi'}}>Srisakdi Regular</Text>
<Text
style={{
fontFamily: 'Srisakdi',
fontWeight: 'bold',
fontFamily: 'Rubik',
fontWeight: 'normal',
}}>
Srisakdi Bold
Rubik Regular
</Text>
<Text
style={{
fontFamily: 'Rubik',
fontWeight: '300',
}}>
Rubik Light
</Text>
<Text
style={{
fontFamily: 'Rubik',
fontWeight: '700',
}}>
Rubik Bold
</Text>
<Text
style={{
fontFamily: 'Rubik',
fontWeight: '500',
}}>
Rubik Medium
</Text>
<Text
style={{
fontFamily: 'Rubik',
fontStyle: 'italic',
fontWeight: '500',
}}>
Rubik Medium Italic
</Text>
</View>
</View>
@@ -205,15 +233,15 @@ class TextExample extends React.Component<{}> {
</RNTesterBlock>
<RNTesterBlock title="Font Weight">
<Text style={{fontWeight: 'bold'}}>Move fast and be bold</Text>
<Text style={{fontWeight: 'normal'}}>Move fast and be bold</Text>
<Text style={{fontWeight: 'normal'}}>Move fast and be normal</Text>
</RNTesterBlock>
<RNTesterBlock title="Font Style">
<Text style={{fontStyle: 'italic'}}>Move fast and be bold</Text>
<Text style={{fontStyle: 'normal'}}>Move fast and be bold</Text>
<Text style={{fontStyle: 'italic'}}>Move fast and be italic</Text>
<Text style={{fontStyle: 'normal'}}>Move fast and be normal</Text>
</RNTesterBlock>
<RNTesterBlock title="Font Style and Weight">
<Text style={{fontStyle: 'italic', fontWeight: 'bold'}}>
Move fast and be bold
Move fast and be both bold and italic
</Text>
</RNTesterBlock>
<RNTesterBlock title="Text Decoration">
+23
View File
@@ -453,6 +453,23 @@ class TextWithCapBaseBox extends React.Component<*, *> {
}
}
function LongTextExample() {
const [collapsed, setCollapsed] = React.useState(true);
return (
<View>
<Button
onPress={() => setCollapsed(state => !state)}
title="Toggle long text"
/>
<Text>
{Array.from({length: collapsed ? 5 : 5000})
.map((_, i) => i)
.join('\n')}
</Text>
</View>
);
}
exports.title = '<Text>';
exports.description = 'Base component for rendering styled text.';
exports.displayName = 'TextExample';
@@ -1125,4 +1142,10 @@ exports.examples = [
);
},
},
{
title: 'Async rendering for long text',
render: function() {
return <LongTextExample />;
},
},
];
+1
View File
@@ -91,6 +91,7 @@ static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector)
static BOOL RCTParseUnused(const char **input)
{
return RCTReadString(input, "__attribute__((unused))") ||
RCTReadString(input, "__attribute__((__unused__))") ||
RCTReadString(input, "__unused");
}
+2 -2
View File
@@ -21,8 +21,8 @@ static void __makeVersion()
{
__rnVersion = @{
RCTVersionMajor: @(0),
RCTVersionMinor: @(0),
RCTVersionPatch: @(0),
RCTVersionMinor: @(60),
RCTVersionPatch: @(2),
RCTVersionPrerelease: [NSNull null],
};
}
-2
View File
@@ -129,8 +129,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
[subview addGestureRecognizer:_menuButtonGestureRecognizer];
}
#endif
subview.autoresizingMask = UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleWidth;
[_modalViewController.view insertSubview:subview atIndex:0];
_reactSubview = subview;
+2 -2
View File
@@ -297,12 +297,12 @@
return self;
}
- (NSArray<NSString *> *)accessibilityActions
- (NSArray<NSDictionary *> *)accessibilityActions
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setAccessibilityActions:(NSArray<NSString *> *)accessibilityActions
- (void)setAccessibilityActions:(NSArray<NSDictionary *> *)accessibilityActions
{
objc_setAssociatedObject(self, @selector(accessibilityActions), accessibilityActions, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+28 -2
View File
@@ -88,6 +88,17 @@ task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy
into("$thirdPartyNdkDir/folly")
}
task prepareHermes() {
def hermesAAR = new File("$projectDir/../node_modules/hermesvm/android/hermes-debug.aar")
def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
copy {
from soFiles
from "src/main/jni/first-party/hermes/Android.mk"
into "$thirdPartyNdkDir/hermes"
}
}
task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
onlyIfNewer(true)
@@ -193,13 +204,23 @@ def findNdkBuildFullPath() {
def ndkDir = android.hasProperty("plugin") ? android.plugin.ndkFolder :
plugins.getPlugin("com.android.library").hasProperty("sdkHandler") ?
plugins.getPlugin("com.android.library").sdkHandler.getNdkFolder() :
android.ndkDirectory.absolutePath
android.ndkDirectory ? android.ndkDirectory.absolutePath : null
if (ndkDir) {
return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
}
return null
}
def reactNativeDevServerPort() {
def value = project.getProperties().get("reactNativeDevServerPort")
return value != null ? value : "8081"
}
def reactNativeInspectorProxyPort() {
def value = project.getProperties().get("reactNativeInspectorProxyPort")
return value != null ? value : reactNativeDevServerPort()
}
def getNdkBuildFullPath() {
def ndkBuildFullPath = findNdkBuildFullPath()
if (ndkBuildFullPath == null) {
@@ -219,7 +240,7 @@ def getNdkBuildFullPath() {
return ndkBuildFullPath
}
task buildReactNdkLib(dependsOn: [prepareJSC, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog], type: Exec) {
task buildReactNdkLib(dependsOn: [prepareJSC, prepareHermes, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog], type: Exec) {
inputs.dir("$projectDir/../ReactCommon")
inputs.dir("src/main/jni")
outputs.dir("$buildDir/react-ndk/all")
@@ -255,6 +276,7 @@ task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) {
from("$buildDir/react-ndk/all")
into("$buildDir/react-ndk/exported")
exclude("**/libjsc.so")
exclude("**/libhermes.so")
}
task packageReactNdkLibsForBuck(dependsOn: packageReactNdkLibs, type: Copy) {
@@ -284,6 +306,10 @@ android {
buildConfigField("boolean", "IS_INTERNAL_BUILD", "false")
buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort()
resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()
testApplicationId("com.facebook.react.tests.gradle")
testInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner")
}
+2 -2
View File
@@ -1,4 +1,4 @@
VERSION_NAME=1000.0.0-master
VERSION_NAME=0.60.2
GROUP=com.facebook.react
POM_NAME=ReactNative
@@ -12,7 +12,7 @@ JUNIT_VERSION=4.12
FEST_ASSERT_CORE_VERSION=2.0M10
ANDROID_SUPPORT_TEST_VERSION=1.0.2
FRESCO_VERSION=1.13.0
FRESCO_VERSION=2.0.0
OKHTTP_VERSION=3.12.1
SO_LOADER_VERSION=0.6.0
@@ -213,6 +213,14 @@ public class ReactAppTestActivity extends FragmentActivity
} else {
builder.addPackage(new MainReactPackage());
}
/**
* The {@link ReactContext#mCurrentActivity} never to be set if initial lifecycle state is resumed.
* So we should call {@link ReactInstanceManagerBuilder#setCurrentActivity}.
*
* Finally,{@link ReactInstanceManagerBuilder#build()} will create instance of {@link ReactInstanceManager}.
* And also will set {@link ReactContext#mCurrentActivity}.
*/
builder.setCurrentActivity(this);
builder
.addPackage(new InstanceSpecForTestPackage(spec))
// By not setting a JS module name, we force the bundle to be always loaded from
@@ -0,0 +1,48 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#include <fb/fbjni.h>
#include <string>
namespace facebook {
namespace jsi {
namespace jni {
namespace jni = ::facebook::jni;
class HermesMemoryDumper : public jni::JavaClass<HermesMemoryDumper> {
public:
constexpr static auto kJavaDescriptor =
"Lcom/facebook/hermes/instrumentation/HermesMemoryDumper;";
bool shouldSaveSnapshot() {
static auto shouldSaveSnapshotMethod =
javaClassStatic()->getMethod<jboolean()>("shouldSaveSnapshot");
return shouldSaveSnapshotMethod(self());
}
std::string getInternalStorage() {
static auto getInternalStorageMethod =
javaClassStatic()->getMethod<jstring()>("getInternalStorage");
return getInternalStorageMethod(self())->toStdString();
}
std::string getId() {
static auto getInternalStorageMethod =
javaClassStatic()->getMethod<jstring()>("getId");
return getInternalStorageMethod(self())->toStdString();
}
void setMetaData(std::string crashId) {
static auto getIdMethod =
javaClassStatic()->getMethod<void(std::string)>("setMetaData");
getIdMethod(self(), crashId);
}
};
} // namespace jni
} // namespace jsi
} // namespace facebook
@@ -0,0 +1,14 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.hermes.instrumentation;
public interface HermesMemoryDumper {
boolean shouldSaveSnapshot();
String getInternalStorage();
String getId();
void setMetaData(String crashId);
}
@@ -0,0 +1,40 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
LOCAL_MODULE := hermes-executor-release
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(REACT_NATIVE)/node_modules/hermesvm/android/include
LOCAL_CPP_FEATURES := exceptions
LOCAL_STATIC_LIBRARIES := libjsireact libjsi
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni libhermes
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
LOCAL_MODULE := hermes-executor-debug
LOCAL_CFLAGS := -DHERMES_ENABLE_DEBUGGER=1
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(REACT_NATIVE)/node_modules/hermesvm/android/include
LOCAL_CPP_FEATURES := exceptions
LOCAL_STATIC_LIBRARIES := libjsireact libjsi libhermes-inspector
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni libhermes
include $(BUILD_SHARED_LIBRARY)
@@ -0,0 +1,68 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.hermes.reactexecutor;
import com.facebook.hermes.instrumentation.HermesMemoryDumper;
import com.facebook.jni.HybridData;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.soloader.SoLoader;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
public class HermesExecutor extends JavaScriptExecutor {
private static String mode_;
static {
// libhermes must be loaded explicitly to invoke its JNI_OnLoad.
SoLoader.loadLibrary("hermes");
try {
SoLoader.loadLibrary("hermes-executor-release");
mode_ = "Release";
} catch(UnsatisfiedLinkError e) {
SoLoader.loadLibrary("hermes-executor-debug");
mode_ = "Debug";
}
}
HermesExecutor(@Nullable RuntimeConfig config) {
super(
config == null
? initHybridDefaultConfig()
: initHybrid(
config.heapSizeMB,
config.es6Symbol,
config.bytecodeWarmupPercent,
config.tripWireEnabled,
config.heapDumper,
config.tripWireCooldownMS,
config.tripWireLimitBytes));
}
@Override
public String getName() {
return "HermesExecutor" + mode_;
}
/**
* Return whether this class can load a file at the given path, based on a binary compatibility
* check between the contents of the file and the Hermes VM.
*
* @param path the path containing the file to inspect.
* @return whether the given file is compatible with the Hermes VM.
*/
public static native boolean canLoadFile(String path);
private static native HybridData initHybridDefaultConfig();
private static native HybridData initHybrid(
long heapSizeMB,
boolean es6Symbol,
int bytecodeWarmupPercent,
boolean tripWireEnabled,
@Nullable HermesMemoryDumper heapDumper,
long tripWireCooldownMS,
long tripWireLimitBytes);
}
@@ -0,0 +1,230 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "HermesExecutorFactory.h"
#include <thread>
#include <cxxreact/MessageQueueThread.h>
#include <cxxreact/SystraceSection.h>
#include <hermes/hermes_tracing.h>
#include <jsi/decorator.h>
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/Registration.h>
#endif
#include "JSITracing.h"
using namespace facebook::hermes;
using namespace facebook::jsi;
namespace facebook {
namespace react {
namespace {
std::unique_ptr<HermesRuntime> makeHermesRuntimeSystraced(
const ::hermes::vm::RuntimeConfig &runtimeConfig) {
SystraceSection s("HermesExecutorFactory::makeHermesRuntimeSystraced");
return hermes::makeHermesRuntime(runtimeConfig);
}
#ifdef HERMES_ENABLE_DEBUGGER
class HermesExecutorRuntimeAdapter
: public facebook::hermes::inspector::RuntimeAdapter {
public:
HermesExecutorRuntimeAdapter(
std::shared_ptr<Runtime> runtime,
HermesRuntime &hermesRuntime,
std::shared_ptr<MessageQueueThread> thread)
: runtime_(runtime),
hermesRuntime_(hermesRuntime),
thread_(std::move(thread)) {}
virtual ~HermesExecutorRuntimeAdapter() = default;
HermesRuntime &getRuntime() override {
return hermesRuntime_;
}
void tickleJs() override {
// The queue will ensure that runtime_ is still valid when this
// gets invoked.
// clang-format off
thread_->runOnQueue([&runtime = hermesRuntime_]() {
// clang-format on
auto func = runtime.global().getPropertyAsFunction(runtime, "__tickleJs");
func.call(runtime);
});
}
private:
std::shared_ptr<Runtime> runtime_;
HermesRuntime &hermesRuntime_;
std::shared_ptr<MessageQueueThread> thread_;
};
#endif
struct ReentrancyCheck {
// This is effectively a very subtle and complex assert, so only
// include it in builds which would include asserts.
#ifndef NDEBUG
ReentrancyCheck() : tid(std::thread::id()), depth(0) {}
void before() {
std::thread::id this_id = std::this_thread::get_id();
std::thread::id expected = std::thread::id();
// A note on memory ordering: the main purpose of these checks is
// to observe a before/before race, without an intervening after.
// This will be detected by the compare_exchange_strong atomicity
// properties, regardless of memory order.
//
// For everything else, it is easiest to think of 'depth' as a
// proxy for any access made inside the VM. If access to depth
// are reordered incorrectly, the same could be true of any other
// operation made by the VM. In fact, using acquire/release
// memory ordering could create barriers which mask a programmer
// error. So, we use relaxed memory order, to avoid masking
// actual ordering errors. Although, in practice, ordering errors
// of this sort would be surprising, because the decorator would
// need to call after() without before().
if (tid.compare_exchange_strong(
expected, this_id, std::memory_order_relaxed)) {
// Returns true if tid and expected were the same. If they
// were, then the stored tid referred to no thread, and we
// atomically saved this thread's tid. Now increment depth.
assert(depth == 0 && "No thread id, but depth != 0");
++depth;
} else if (expected == this_id) {
// If the stored tid referred to a thread, expected was set to
// that value. If that value is this thread's tid, that's ok,
// just increment depth again.
assert(depth != 0 && "Thread id was set, but depth == 0");
++depth;
} else {
// The stored tid was some other thread. This indicates a bad
// programmer error, where VM methods were called on two
// different threads unsafely. Fail fast (and hard) so the
// crash can be analyzed.
__builtin_trap();
}
}
void after() {
assert(
tid.load(std::memory_order_relaxed) == std::this_thread::get_id() &&
"No thread id in after()");
if (--depth == 0) {
// If we decremented depth to zero, store no-thread into tid.
std::thread::id expected = std::this_thread::get_id();
bool didWrite = tid.compare_exchange_strong(
expected, std::thread::id(), std::memory_order_relaxed);
assert(didWrite && "Decremented to zero, but no tid write");
}
}
std::atomic<std::thread::id> tid;
// This is not atomic, as it is only written or read from the owning
// thread.
unsigned int depth;
#endif
};
// This adds ReentrancyCheck and debugger enable/teardown to the given
// Runtime.
class DecoratedRuntime : public jsi::WithRuntimeDecorator<ReentrancyCheck> {
public:
// The first argument may be a tracing runtime which itself
// decorates the real HermesRuntime, depending on the build config.
// The second argument is the the real HermesRuntime as well to
// manage the debugger registration.
DecoratedRuntime(
std::unique_ptr<Runtime> runtime,
HermesRuntime &hermesRuntime,
std::shared_ptr<MessageQueueThread> jsQueue)
: jsi::WithRuntimeDecorator<ReentrancyCheck>(*runtime, reentrancyCheck_),
runtime_(std::move(runtime)),
hermesRuntime_(hermesRuntime) {
#ifdef HERMES_ENABLE_DEBUGGER
auto adapter = std::make_unique<HermesExecutorRuntimeAdapter>(
runtime_, hermesRuntime_, jsQueue);
facebook::hermes::inspector::chrome::enableDebugging(
std::move(adapter), "Hermes React Native");
#else
(void)hermesRuntime_;
#endif
}
~DecoratedRuntime() {
#ifdef HERMES_ENABLE_DEBUGGER
facebook::hermes::inspector::chrome::disableDebugging(hermesRuntime_);
#endif
}
private:
// runtime_ is a TracingRuntime, but we don't need to worry about
// the details. hermesRuntime is a reference to the HermesRuntime
// managed by the TracingRuntime.
//
// HermesExecutorRuntimeAdapter requirements are kept, because the
// dtor will disable debugging on the HermesRuntime before the
// member managing it is destroyed.
std::shared_ptr<Runtime> runtime_;
ReentrancyCheck reentrancyCheck_;
HermesRuntime &hermesRuntime_;
};
} // namespace
std::unique_ptr<JSExecutor> HermesExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) {
std::unique_ptr<HermesRuntime> hermesRuntime =
makeHermesRuntimeSystraced(runtimeConfig_);
HermesRuntime& hermesRuntimeRef = *hermesRuntime;
auto decoratedRuntime = std::make_shared<DecoratedRuntime>(
makeTracingHermesRuntime(std::move(hermesRuntime), runtimeConfig_),
hermesRuntimeRef,
jsQueue);
// So what do we have now?
// DecoratedRuntime -> TracingRuntime -> HermesRuntime
//
// DecoratedRuntime is held by JSIExecutor. When it gets used, it
// will check that it's on the right thread, do any necessary trace
// logging, then call the real HermesRuntime. When it is destroyed,
// it will shut down the debugger before the HermesRuntime is. In
// the normal case where tracing and debugging are not compiled in,
// all that's left is the thread checking.
// Add js engine information to Error.prototype so in error reporting we
// can send this information.
auto errorPrototype =
decoratedRuntime->global()
.getPropertyAsObject(*decoratedRuntime, "Error")
.getPropertyAsObject(*decoratedRuntime, "prototype");
errorPrototype.setProperty(*decoratedRuntime, "jsEngine", "hermes");
return std::make_unique<HermesExecutor>(
decoratedRuntime, delegate, jsQueue, timeoutInvoker_, runtimeInstaller_);
}
HermesExecutor::HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker &timeoutInvoker,
RuntimeInstaller runtimeInstaller)
: JSIExecutor(runtime, delegate, timeoutInvoker, runtimeInstaller) {
jsi::addNativeTracingHooks(*runtime);
}
} // namespace react
} // namespace facebook
@@ -0,0 +1,50 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <hermes/hermes.h>
#include <jsireact/JSIExecutor.h>
#include <functional>
#include <utility>
namespace facebook {
namespace react {
class HermesExecutorFactory : public JSExecutorFactory {
public:
explicit HermesExecutorFactory(
JSIExecutor::RuntimeInstaller runtimeInstaller,
const JSIScopedTimeoutInvoker& timeoutInvoker =
JSIExecutor::defaultTimeoutInvoker,
::hermes::vm::RuntimeConfig runtimeConfig = ::hermes::vm::RuntimeConfig())
: runtimeInstaller_(runtimeInstaller),
timeoutInvoker_(timeoutInvoker),
runtimeConfig_(std::move(runtimeConfig)) {
assert(timeoutInvoker_ && "Should not have empty timeoutInvoker");
}
std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override;
private:
JSIExecutor::RuntimeInstaller runtimeInstaller_;
JSIScopedTimeoutInvoker timeoutInvoker_;
::hermes::vm::RuntimeConfig runtimeConfig_;
};
class HermesExecutor : public JSIExecutor {
public:
HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker& timeoutInvoker,
RuntimeInstaller runtimeInstaller);
private:
JSIScopedTimeoutInvoker timeoutInvoker_;
};
} // namespace react
} // namespace facebook
@@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.hermes.reactexecutor;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
public class HermesExecutorFactory implements JavaScriptExecutorFactory {
private static final String TAG = "Hermes";
private final RuntimeConfig mConfig;
public HermesExecutorFactory() {
this(null);
}
public HermesExecutorFactory(RuntimeConfig config) {
mConfig = config;
}
@Override
public JavaScriptExecutor create() {
return new HermesExecutor(mConfig);
}
@Override
public String toString() {
return "JSIExecutor+HermesRuntime";
}
}
@@ -0,0 +1,15 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#include "JSITracing.h"
namespace facebook {
namespace jsi {
void addNativeTracingHooks(Runtime &rt) {
assert(false && "unimplemented");
}
} // namespace jsi
} // namespace facebook
@@ -0,0 +1,15 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
void addNativeTracingHooks(Runtime &rt);
} // namespace jsi
} // namespace facebook
@@ -0,0 +1,154 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#include <../instrumentation/HermesMemoryDumper.h>
#include <HermesExecutorFactory.h>
#include <fb/fbjni.h>
#include <folly/Memory.h>
#include <hermes/Public/GCConfig.h>
#include <hermes/Public/RuntimeConfig.h>
#include <jni.h>
#include <react/jni/JReactMarker.h>
#include <react/jni/JSLogging.h>
#include <react/jni/JavaScriptExecutorHolder.h>
namespace facebook {
namespace react {
/// Converts a duration given as a long from Java, into a std::chrono duration.
static constexpr std::chrono::hours msToHours(jlong ms) {
using namespace std::chrono;
return duration_cast<hours>(milliseconds(ms));
}
static ::hermes::vm::RuntimeConfig makeRuntimeConfig(
jlong heapSizeMB,
bool es6Symbol,
jint bytecodeWarmupPercent,
bool tripWireEnabled,
jni::alias_ref<jsi::jni::HermesMemoryDumper> heapDumper,
jlong tripWireCooldownMS,
jlong tripWireLimitBytes) {
namespace vm = ::hermes::vm;
auto gcConfigBuilder =
vm::GCConfig::Builder()
.withMaxHeapSize(heapSizeMB << 20)
.withName("RN")
// For the next two arguments: avoid GC before TTI by initializing the
// runtime to allocate directly in the old generation, but revert to
// normal operation when we reach the (first) TTI point.
.withAllocInYoung(false)
.withRevertToYGAtTTI(true);
if (tripWireEnabled) {
assert(
heapDumper &&
"Must provide a heap dumper instance if tripwire is enabled");
gcConfigBuilder.withTripwireConfig(
vm::GCTripwireConfig::Builder()
.withLimit(tripWireLimitBytes)
.withCooldown(msToHours(tripWireCooldownMS))
.withCallback([globalHeapDumper = jni::make_global(heapDumper)](
vm::GCTripwireContext &ctx) mutable {
if (!globalHeapDumper->shouldSaveSnapshot()) {
return;
}
std::string crashId = globalHeapDumper->getId();
std::string path = globalHeapDumper->getInternalStorage();
path += "/dump_";
path += crashId;
path += ".hermes";
bool successful = ctx.createSnapshotToFile(path, true);
if (!successful) {
LOG(ERROR) << "Failed to write Hermes Memory Dump to " << path
<< "\n";
return;
}
LOG(INFO) << "Hermes Memory Dump saved on: " << path << "\n";
globalHeapDumper->setMetaData(crashId);
})
.build());
}
return vm::RuntimeConfig::Builder()
.withGCConfig(gcConfigBuilder.build())
.withES6Symbol(es6Symbol)
.withBytecodeWarmupPercent(bytecodeWarmupPercent)
.build();
}
static void installBindings(jsi::Runtime &runtime) {
react::Logger androidLogger =
static_cast<void (*)(const std::string &, unsigned int)>(
&reactAndroidLoggingHook);
react::bindNativeLogger(runtime, androidLogger);
}
class HermesExecutorHolder
: public jni::HybridClass<HermesExecutorHolder, JavaScriptExecutorHolder> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/hermes/reactexecutor/HermesExecutor;";
static jni::local_ref<jhybriddata> initHybridDefaultConfig(
jni::alias_ref<jclass>) {
JReactMarker::setLogPerfMarkerIfNeeded();
return makeCxxInstance(
folly::make_unique<HermesExecutorFactory>(installBindings));
}
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jclass>,
jlong heapSizeMB,
bool es6Symbol,
jint bytecodeWarmupPercent,
bool tripWireEnabled,
jni::alias_ref<jsi::jni::HermesMemoryDumper> heapDumper,
jlong tripWireCooldownMS,
jlong tripWireLimitBytes) {
JReactMarker::setLogPerfMarkerIfNeeded();
auto runtimeConfig = makeRuntimeConfig(
heapSizeMB,
es6Symbol,
bytecodeWarmupPercent,
tripWireEnabled,
heapDumper,
tripWireCooldownMS,
tripWireLimitBytes);
return makeCxxInstance(folly::make_unique<HermesExecutorFactory>(
installBindings, JSIExecutor::defaultTimeoutInvoker, runtimeConfig));
}
static bool canLoadFile(jni::alias_ref<jclass>, const std::string &path) {
return true;
}
static void registerNatives() {
registerHybrid(
{makeNativeMethod("initHybrid", HermesExecutorHolder::initHybrid),
makeNativeMethod(
"initHybridDefaultConfig",
HermesExecutorHolder::initHybridDefaultConfig),
makeNativeMethod("canLoadFile", HermesExecutorHolder::canLoadFile)});
}
private:
friend HybridBase;
using HybridBase::HybridBase;
};
} // namespace react
} // namespace facebook
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return facebook::jni::initialize(
vm, [] { facebook::react::HermesExecutorHolder::registerNatives(); });
}
@@ -0,0 +1,22 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.hermes.reactexecutor;
import com.facebook.hermes.instrumentation.HermesMemoryDumper;
import javax.annotation.Nullable;
/** Holds runtime configuration for a Hermes VM instance (master or snapshot). */
public final class RuntimeConfig {
public long heapSizeMB;
public boolean enableSampledStats;
public boolean es6Symbol;
public int bytecodeWarmupPercent;
public boolean tripWireEnabled;
@Nullable public HermesMemoryDumper heapDumper;
public long tripWireCooldownMS;
public long tripWireLimitBytes;
}
@@ -0,0 +1,75 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.hermes.unicode;
import java.text.Collator;
import java.text.DateFormat;
import java.text.Normalizer;
import java.util.Locale;
// TODO: use com.facebook.common.locale.Locales.getApplicationLocale() as the current locale,
// rather than the device locale. This is challenging because getApplicationLocale() is only
// available via DI.
public class AndroidUnicodeUtils {
public static int localeCompare(String left, String right) {
Collator collator = Collator.getInstance();
return collator.compare(left, right);
}
public static String dateFormat(double unixtimeMs, boolean formatDate, boolean formatTime) {
DateFormat format;
if (formatDate && formatTime) {
format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
} else if (formatDate) {
format = DateFormat.getDateInstance(DateFormat.MEDIUM);
} else if (formatTime) {
format = DateFormat.getTimeInstance(DateFormat.MEDIUM);
} else {
throw new RuntimeException("Bad dateFormat configuration");
}
return format.format((long) unixtimeMs).toString();
}
public static String convertToCase(String input, int targetCase, boolean useCurrentLocale) {
// These values must match CaseConversion in PlatformUnicode.h
final int targetUppercase = 0;
final int targetLowercase = 1;
// Note Java's case conversions use the user's locale. For example "I".toLowerCase()
// will produce a dotless i. From Java's docs: "To obtain correct results for locale
// insensitive strings, use toLowerCase(Locale.ENGLISH)."
Locale locale = useCurrentLocale ? Locale.getDefault() : Locale.ENGLISH;
switch (targetCase) {
case targetLowercase:
return input.toLowerCase(locale);
case targetUppercase:
return input.toUpperCase(locale);
default:
throw new RuntimeException("Invalid target case");
}
}
public static String normalize(String input, int form) {
// Values must match NormalizationForm in PlatformUnicode.h.
final int formC = 0;
final int formD = 1;
final int formKC = 2;
final int formKD = 3;
switch (form) {
case formC:
return Normalizer.normalize(input, Normalizer.Form.NFC);
case formD:
return Normalizer.normalize(input, Normalizer.Form.NFD);
case formKC:
return Normalizer.normalize(input, Normalizer.Form.NFKC);
case formKD:
return Normalizer.normalize(input, Normalizer.Form.NFKD);
default:
throw new RuntimeException("Invalid form");
}
}
}
@@ -16,8 +16,10 @@ import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class NativeRunnable implements Runnable {
@DoNotStrip
private final HybridData mHybridData;
@DoNotStrip
private NativeRunnable(HybridData hybridData) {
mHybridData = hybridData;
}
@@ -10,6 +10,7 @@ import static com.facebook.react.modules.systeminfo.AndroidInfoHelpers.getFriend
import android.app.Activity;
import android.app.Application;
import com.facebook.infer.annotation.Assertions;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
@@ -23,6 +24,7 @@ import com.facebook.react.jscexecutor.JSCExecutorFactory;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.packagerconnection.RequestHandler;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.soloader.SoLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -246,6 +248,12 @@ public class ReactInstanceManagerBuilder {
mApplication,
"Application property has not been set with this builder");
if (mInitialLifecycleState == LifecycleState.RESUMED) {
Assertions.assertNotNull(
mCurrentActivity,
"Activity needs to be set if initial lifecycle state is resumed");
}
Assertions.assertCondition(
mUseDeveloperSupport || mJSBundleAssetUrl != null || mJSBundleLoader != null,
"JS Bundle File or Asset URL has to be provided when dev support is disabled");
@@ -268,7 +276,7 @@ public class ReactInstanceManagerBuilder {
mCurrentActivity,
mDefaultHardwareBackBtnHandler,
mJavaScriptExecutorFactory == null
? new JSCExecutorFactory(appName, deviceName)
? getDefaultJSExecutorFactory(appName, deviceName)
: mJavaScriptExecutorFactory,
(mJSBundleLoader == null && mJSBundleAssetUrl != null)
? JSBundleLoader.createAssetLoader(
@@ -289,4 +297,15 @@ public class ReactInstanceManagerBuilder {
mJSIModulesPackage,
mCustomPackagerCommandHandlers);
}
private JavaScriptExecutorFactory getDefaultJSExecutorFactory(String appName, String deviceName) {
try {
// If JSC is included, use it as normal
SoLoader.loadLibrary("jscexecutor");
return new JSCExecutorFactory(appName, deviceName);
} catch(UnsatisfiedLinkError jscE) {
// Otherwise use Hermes
return new HermesExecutorFactory();
}
}
}
@@ -72,7 +72,7 @@ public abstract class JSBundleLoader {
delegate.loadScriptFromFile(cachedFileLocation, sourceURL, false);
return sourceURL;
} catch (Exception e) {
throw DebugServerException.makeGeneric(e.getMessage(), e);
throw DebugServerException.makeGeneric(sourceURL, e.getMessage(), e);
}
}
};
@@ -94,7 +94,7 @@ public abstract class JSBundleLoader {
delegate.loadScriptFromDeltaBundle(sourceURL, nativeDeltaClient, false);
return sourceURL;
} catch (Exception e) {
throw DebugServerException.makeGeneric(e.getMessage(), e);
throw DebugServerException.makeGeneric(sourceURL, e.getMessage(), e);
}
}
};
@@ -11,6 +11,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import android.net.Uri;
import android.text.TextUtils;
import com.facebook.common.logging.FLog;
@@ -28,15 +29,19 @@ public class DebugServerException extends RuntimeException {
"\u2022 Ensure that the packager server is running\n" +
"\u2022 Ensure that your device/emulator is connected to your machine and has USB debugging enabled - run 'adb devices' to see a list of connected devices\n" +
"\u2022 Ensure Airplane Mode is disabled\n" +
"\u2022 If you're on a physical device connected to the same machine, run 'adb reverse tcp:8081 tcp:8081' to forward requests from your device\n" +
"\u2022 If your device is on the same Wi-Fi network, set 'Debug server host & port for device' in 'Dev settings' to your machine's IP address and the port of the local dev server - e.g. 10.0.1.1:8081\n\n";
"\u2022 If you're on a physical device connected to the same machine, run 'adb reverse tcp:<PORT> tcp:<PORT>' to forward requests from your device\n" +
"\u2022 If your device is on the same Wi-Fi network, set 'Debug server host & port for device' in 'Dev settings' to your machine's IP address and the port of the local dev server - e.g. 10.0.1.1:<PORT>\n\n";
public static DebugServerException makeGeneric(String reason, Throwable t) {
return makeGeneric(reason, "", t);
public static DebugServerException makeGeneric(String url, String reason, Throwable t) {
return makeGeneric(url, reason, "", t);
}
public static DebugServerException makeGeneric(String reason, String extra, Throwable t) {
return new DebugServerException(reason + GENERIC_ERROR_MESSAGE + extra, t);
public static DebugServerException makeGeneric(String url, String reason, String extra, Throwable t) {
Uri uri = Uri.parse(url);
String message = GENERIC_ERROR_MESSAGE.replace("<PORT>", String.valueOf(uri.getPort()));
return new DebugServerException(reason + message + extra, t);
}
private DebugServerException(String description, String fileName, int lineNumber, int column) {
@@ -56,7 +61,7 @@ public class DebugServerException extends RuntimeException {
* @param str json string returned by the debug server
* @return A DebugServerException or null if the string is not of proper form.
*/
@Nullable public static DebugServerException parse(String str) {
@Nullable public static DebugServerException parse(String url, String str) {
if (TextUtils.isEmpty(str)) {
return null;
}
@@ -142,10 +142,12 @@ public class BundleDownloader {
}
mDownloadBundleFromURLCall = null;
String url = call.request().url().toString();
callback.onFailure(
DebugServerException.makeGeneric(
DebugServerException.makeGeneric(url,
"Could not connect to development server.",
"URL: " + call.request().url().toString(),
"URL: " + url,
e));
}
@@ -284,7 +286,7 @@ public class BundleDownloader {
// Check for server errors. If the server error has the expected form, fail with more info.
if (statusCode != 200) {
String bodyString = body.readUtf8();
DebugServerException debugServerException = DebugServerException.parse(bodyString);
DebugServerException debugServerException = DebugServerException.parse(url, bodyString);
if (debugServerException != null) {
callback.onFailure(debugServerException);
} else {
@@ -8,6 +8,7 @@
package com.facebook.react.devsupport;
import android.content.Context;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
@@ -261,7 +262,7 @@ public class DevServerHelper {
public boolean doSync() {
try {
String attachToNuclideUrl = getInspectorAttachUrl(title);
String attachToNuclideUrl = getInspectorAttachUrl(context, title);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(attachToNuclideUrl).build();
client.newCall(request).execute();
@@ -367,11 +368,11 @@ public class DevServerHelper {
mPackageName);
}
private String getInspectorAttachUrl(String title) {
private String getInspectorAttachUrl(Context context, String title) {
return String.format(
Locale.US,
"http://%s/nuclide/attach-debugger-nuclide?title=%s&app=%s&device=%s",
AndroidInfoHelpers.getServerHost(),
AndroidInfoHelpers.getServerHost(context),
title,
mPackageName,
AndroidInfoHelpers.getFriendlyDeviceName());
@@ -12,6 +12,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.provider.Settings;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
@@ -59,7 +60,7 @@ public class IntentModule extends ReactContextBaseJavaModule {
String action = intent.getAction();
Uri uri = intent.getData();
if (Intent.ACTION_VIEW.equals(action) && uri != null) {
if (uri != null && (Intent.ACTION_VIEW.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action))) {
initialURL = uri.toString();
}
}
@@ -8,12 +8,14 @@ package com.facebook.react.modules.systeminfo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import com.facebook.common.logging.FLog;
import com.facebook.react.R;
public class AndroidInfoHelpers {
@@ -23,9 +25,6 @@ public class AndroidInfoHelpers {
public static final String METRO_HOST_PROP_NAME = "metro.host";
private static final int DEBUG_SERVER_HOST_PORT = 8081;
private static final int INSPECTOR_PROXY_PORT = 8081;
private static final String TAG = AndroidInfoHelpers.class.getSimpleName();
private static boolean isRunningOnGenymotion() {
@@ -36,12 +35,24 @@ public class AndroidInfoHelpers {
return Build.FINGERPRINT.contains("generic");
}
public static String getServerHost() {
return getServerIpAddress(DEBUG_SERVER_HOST_PORT);
public static String getServerHost(Integer port) {
return getServerIpAddress(port);
}
public static String getInspectorProxyHost() {
return getServerIpAddress(INSPECTOR_PROXY_PORT);
public static String getServerHost(Context context) {
return getServerIpAddress(getDevServerPort(context));
}
public static String getAdbReverseTcpCommand(Integer port) {
return "adb reverse tcp:" + port + " tcp:" + port;
}
public static String getAdbReverseTcpCommand(Context context) {
return getAdbReverseTcpCommand(getDevServerPort(context));
}
public static String getInspectorProxyHost(Context context) {
return getServerIpAddress(getInspectorProxyPort(context));
}
// WARNING(festevezga): This RN helper method has been copied to another FB-only target. Any changes should be applied to both.
@@ -54,6 +65,16 @@ public class AndroidInfoHelpers {
}
}
private static Integer getDevServerPort(Context context) {
Resources resources = context.getResources();
return resources.getInteger(R.integer.react_native_dev_server_port);
}
private static Integer getInspectorProxyPort(Context context) {
Resources resources = context.getResources();
return resources.getInteger(R.integer.react_native_dev_server_port);
}
private static String getServerIpAddress(int port) {
// Since genymotion runs in vbox it use different hostname to refer to adb host.
// We detect whether app runs on genymotion and replace js bundle server hostname accordingly
@@ -9,10 +9,13 @@ package com.facebook.react.modules.systeminfo;
import android.annotation.SuppressLint;
import android.app.UiModeManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.provider.Settings.Secure;
import com.facebook.react.R;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.common.build.ReactBuildConfig;
@@ -35,9 +38,7 @@ public class AndroidInfoModule extends ReactContextBaseJavaModule {
public static final String NAME = "PlatformConstants";
private static final String IS_TESTING = "IS_TESTING";
public AndroidInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
}
public AndroidInfoModule(ReactApplicationContext reactContext) { super(reactContext); }
/**
* See: https://developer.android.com/reference/android/app/UiModeManager.html#getCurrentModeType()
@@ -74,7 +75,7 @@ public class AndroidInfoModule extends ReactContextBaseJavaModule {
constants.put("Fingerprint", Build.FINGERPRINT);
constants.put("Model", Build.MODEL);
if (ReactBuildConfig.DEBUG) {
constants.put("ServerHost", AndroidInfoHelpers.getServerHost());
constants.put("ServerHost", getServerHost());
}
constants.put("isTesting", "true".equals(System.getProperty(IS_TESTING))
|| isRunningScreenshotTest());
@@ -96,4 +97,12 @@ public class AndroidInfoModule extends ReactContextBaseJavaModule {
return false;
}
}
private String getServerHost() {
Resources resources = getReactApplicationContext().getApplicationContext().getResources();
Integer devServerPort = resources.getInteger(R.integer.react_native_dev_server_port);
return AndroidInfoHelpers.getServerHost(devServerPort);
}
}
@@ -14,6 +14,7 @@ rn_android_library(
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("res:systeminfo"),
],
exported_deps = [
":systeminfo-moduleless",
@@ -29,8 +30,10 @@ rn_android_library(
"PUBLIC",
],
deps = [
react_native_target("java/com/facebook/react/common:common"),
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("res:systeminfo"),
],
)
@@ -16,7 +16,7 @@ import java.util.Map;
public class ReactNativeVersion {
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
"major", 0,
"minor", 0,
"patch", 0,
"minor", 60,
"patch", 2,
"prerelease", null);
}
@@ -7,13 +7,13 @@
package com.facebook.react.packagerconnection;
import javax.annotation.Nullable;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import javax.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.modules.systeminfo.AndroidInfoHelpers;
@@ -24,10 +24,12 @@ public class PackagerConnectionSettings {
private final SharedPreferences mPreferences;
private final String mPackageName;
private final Context mAppContext;
public PackagerConnectionSettings(Context applicationContext) {
mPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext);
mPackageName = applicationContext.getPackageName();
mAppContext = applicationContext;
}
public String getDebugServerHost() {
@@ -39,12 +41,12 @@ public class PackagerConnectionSettings {
return Assertions.assertNotNull(hostFromSettings);
}
String host = AndroidInfoHelpers.getServerHost();
String host = AndroidInfoHelpers.getServerHost(mAppContext);
if (host.equals(AndroidInfoHelpers.DEVICE_LOCALHOST)) {
FLog.w(
TAG,
"You seem to be running on device. Run 'adb reverse tcp:8081 tcp:8081' " +
"You seem to be running on device. Run '" + AndroidInfoHelpers.getAdbReverseTcpCommand(mAppContext) + "' " +
"to forward the debug server's port to the device.");
}
@@ -52,7 +54,7 @@ public class PackagerConnectionSettings {
}
public String getInspectorServerHost() {
return AndroidInfoHelpers.getInspectorProxyHost();
return AndroidInfoHelpers.getInspectorProxyHost(mAppContext);
}
public @Nullable String getPackageName() {
@@ -104,7 +104,7 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
}
if (family != null) {
typeface = ReactFontManager.getInstance().getTypeface(family, want, assetManager);
typeface = ReactFontManager.getInstance().getTypeface(family, want, weight, assetManager);
} else if (typeface != null) {
// TODO(t9055065): Fix custom fonts getting applied to text children with different style
typeface = Typeface.create(typeface, want);
@@ -309,7 +309,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
&& fontWeightString.charAt(0) <= '9'
&& fontWeightString.charAt(0) >= '1'
? 100 * (fontWeightString.charAt(0) - '0')
: -1;
: UNSET;
}
protected TextAttributes mTextAttributes;
@@ -459,8 +459,8 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
markUpdated();
}
@ReactProp(name = ViewProps.BACKGROUND_COLOR)
public void setBackgroundColor(Integer color) {
@ReactProp(name = ViewProps.BACKGROUND_COLOR, customType = "Color")
public void setBackgroundColor(@Nullable Integer color) {
// Background color needs to be handled here for virtual nodes so it can be incorporated into
// the span. However, it doesn't need to be applied to non-virtual nodes because non-virtual
// nodes get mapped to native views and native views get their background colors get set via
@@ -487,14 +487,12 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
@ReactProp(name = ViewProps.FONT_WEIGHT)
public void setFontWeight(@Nullable String fontWeightString) {
int fontWeightNumeric =
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : -1;
int fontWeight = UNSET;
if (fontWeightNumeric >= 500 || "bold".equals(fontWeightString)) {
fontWeight = Typeface.BOLD;
} else if ("normal".equals(fontWeightString)
|| (fontWeightNumeric != -1 && fontWeightNumeric < 500)) {
fontWeight = Typeface.NORMAL;
}
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : UNSET;
int fontWeight = fontWeightNumeric != UNSET ? fontWeightNumeric : Typeface.NORMAL;
if (fontWeight == 700 || "bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
else if (fontWeight == 400 || "normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;
if (fontWeight != mFontWeight) {
mFontWeight = fontWeight;
markUpdated();
@@ -13,6 +13,7 @@ import java.util.Map;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.os.Build;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -54,23 +55,32 @@ public class ReactFontManager {
return sReactFontManagerInstance;
}
public @Nullable Typeface getTypeface(
String fontFamilyName,
int style,
AssetManager assetManager) {
return getTypeface(fontFamilyName, style, 0, assetManager);
}
public @Nullable Typeface getTypeface(
String fontFamilyName,
int style,
int weight,
AssetManager assetManager) {
if(mCustomTypefaceCache.containsKey(fontFamilyName)) {
Typeface typeface = mCustomTypefaceCache.get(fontFamilyName);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && weight >= 100 && weight <= 1000) {
return Typeface.create(typeface, weight, (style & Typeface.ITALIC) != 0);
}
return Typeface.create(typeface, style);
}
FontFamily fontFamily = mFontCache.get(fontFamilyName);
if (fontFamily == null) {
fontFamily = new FontFamily();
mFontCache.put(fontFamilyName, fontFamily);
}
if(mCustomTypefaceCache.containsKey(fontFamilyName)) {
return Typeface.create(
mCustomTypefaceCache.get(fontFamilyName),
style
);
}
Typeface typeface = fontFamily.getTypeface(style);
if (typeface == null) {
typeface = createTypeface(fontFamilyName, style, assetManager);
@@ -115,6 +115,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setJustificationMode(mJustificationMode);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
builder.setUseLineSpacingFromFallbacks(true);
}
layout = builder.build();
}
@@ -139,14 +142,18 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
new StaticLayout(
text, textPaint, (int) width, alignment, 1.f, 0.f, mIncludeFontPadding);
} else {
layout =
StaticLayout.Builder builder =
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, (int) width)
.setAlignment(alignment)
.setLineSpacing(0.f, 1.f)
.setIncludePad(mIncludeFontPadding)
.setBreakStrategy(mTextBreakStrategy)
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
.build();
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
builder.setUseLineSpacingFromFallbacks(true);
}
layout = builder.build();
}
}
+2 -2
View File
@@ -26,9 +26,9 @@ NDK_MODULE_PATH := $(APP_MK_DIR)$(HOST_DIRSEP)$(THIRD_PARTY_NDK_DIR)$(HOST_DIRSE
APP_STL := c++_shared
# Make sure every shared lib includes a .note.gnu.build-id header
APP_CFLAGS := -Wall -Werror -fexceptions -frtti
APP_CFLAGS := -Wall -Werror -fexceptions -frtti -DWITH_INSPECTOR=1
APP_CPPFLAGS := -std=c++1y
# Make sure every shared lib includes a .note.gnu.build-id header
APP_LDFLAGS := -Wl,--build-id
NDK_TOOLCHAIN_VERSION := clang
@@ -0,0 +1,5 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= hermes
LOCAL_SRC_FILES := jni/$(TARGET_ARCH_ABI)/libhermes.so
include $(PREBUILT_SHARED_LIBRARY)
@@ -60,6 +60,7 @@ $(call import-module,cxxreact)
$(call import-module,jsi)
$(call import-module,jsiexecutor)
$(call import-module,jscallinvoker)
$(call import-module,hermes)
include $(REACT_SRC_DIR)/turbomodule/core/jni/Android.mk
@@ -68,3 +69,4 @@ include $(REACT_SRC_DIR)/turbomodule/core/jni/Android.mk
# $(call import-module,jscexecutor)
include $(REACT_SRC_DIR)/jscexecutor/Android.mk
include $(REACT_SRC_DIR)/../hermes/reactexecutor/Android.mk
@@ -199,6 +199,8 @@ void CatalystInstanceImpl::jniLoadScriptFromAssets(
sourceURL,
loadSynchronously);
return;
} else if (Instance::isIndexedRAMBundle(&script)) {
instance_->loadRAMBundleFromString(std::move(script), sourceURL);
} else {
instance_->loadScriptFromString(std::move(script), sourceURL, loadSynchronously);
}
+1 -1
View File
@@ -65,7 +65,7 @@ LOCAL_SRC_FILES := \
folly/memory/MallctlHelper.cpp \
folly/portability/SysMembarrier.cpp \
folly/synchronization/AsymmetricMemoryBarrier.cpp \
folly/synchronization/HazPtr.cpp \
folly/synchronization/Hazptr.cpp \
folly/synchronization/ParkingLot.cpp \
folly/synchronization/WaitOptions.cpp
@@ -9,8 +9,8 @@ rn_android_prebuilt_aar(
fb_native.remote_file(
name = "fresco-binary-aar",
sha1 = "0369d4ac5a48cbd748854ea9043c88b807940fb3",
url = "mvn:com.facebook.fresco:fresco:aar:1.13.0",
sha1 = "d473020b37b7cdd3171154942b55021a55a9d990",
url = "mvn:com.facebook.fresco:fresco:aar:2.0.0",
)
rn_android_prebuilt_aar(
@@ -21,8 +21,8 @@ rn_android_prebuilt_aar(
fb_native.remote_file(
name = "drawee-binary-aar",
sha1 = "b846ceec4b708b630693fedb79c85aabd1dbdeed",
url = "mvn:com.facebook.fresco:drawee:aar:1.13.0",
sha1 = "a85bfaeb87a9c8d1521c70edf6ded91ff9999475",
url = "mvn:com.facebook.fresco:drawee:aar:2.0.0",
)
rn_android_library(
@@ -44,8 +44,8 @@ rn_android_prebuilt_aar(
fb_native.remote_file(
name = "imagepipeline-base-aar",
sha1 = "3c4b6613a59825951d3c2b3a5accdbdfd667d9cb",
url = "mvn:com.facebook.fresco:imagepipeline-base:aar:1.13.0",
sha1 = "d27635390665d433f987177c548d25d0473eadbe",
url = "mvn:com.facebook.fresco:imagepipeline-base:aar:2.0.0",
)
rn_android_prebuilt_aar(
@@ -56,8 +56,8 @@ rn_android_prebuilt_aar(
fb_native.remote_file(
name = "imagepipeline-aar",
sha1 = "405fa064f139b495e0e857661a5706cfb22eafdf",
url = "mvn:com.facebook.fresco:imagepipeline:aar:1.13.0",
sha1 = "7bc59327fb4895c465cbfeede700daf349ea56da",
url = "mvn:com.facebook.fresco:imagepipeline:aar:2.0.0",
)
rn_android_prebuilt_aar(
@@ -69,7 +69,7 @@ rn_android_prebuilt_aar(
remote_file(
name = "nativeimagefilters-aar",
sha1 = "f49525db580abc4d2fb0a74fac771fc6c69f2adb",
url = "mvn:com.facebook.fresco:nativeimagefilters:aar:1.13.0",
url = "mvn:com.facebook.fresco:nativeimagefilters:aar:2.0.0",
)
rn_prebuilt_jar(
@@ -92,8 +92,8 @@ rn_android_prebuilt_aar(
fb_native.remote_file(
name = "fbcore-aar",
sha1 = "f8dd8ba9d7ea60dc54b5fba4ed5f6feacc5e596f",
url = "mvn:com.facebook.fresco:fbcore:aar:1.13.0",
sha1 = "8de91f71e8aa84a4d9be4dd88d1a0ac51600ad60",
url = "mvn:com.facebook.fresco:fbcore:aar:2.0.0",
)
rn_android_prebuilt_aar(
@@ -105,5 +105,5 @@ rn_android_prebuilt_aar(
fb_native.remote_file(
name = "imagepipeline-okhttp3-binary-aar",
sha1 = "bc1212ca66cd09678b416894ea8bd04102d26c5f",
url = "mvn:com.facebook.fresco:imagepipeline-okhttp3:aar:1.13.0",
url = "mvn:com.facebook.fresco:imagepipeline-okhttp3:aar:2.0.0",
)
+9
View File
@@ -36,4 +36,13 @@ rn_android_resource(
],
)
rn_android_resource(
name = "systeminfo",
package = "com.facebook.react",
res = "systeminfo",
visibility = [
"PUBLIC",
],
)
# New resource directories must be added to react-native-github/ReactAndroid/build.gradle
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="react_native_dev_server_port">8081</integer>
<integer name="react_native_inspector_proxy_port">@integer/react_native_dev_server_port</integer>
</resources>
+1
View File
@@ -30,3 +30,4 @@ $(call import-module,jsc)
$(call import-module,glog)
$(call import-module,jsi)
$(call import-module,jsinspector)
$(call import-module,hermes/inspector)
+18
View File
@@ -108,6 +108,24 @@ bool Instance::isIndexedRAMBundle(const char *sourcePath) {
return parseTypeFromHeader(header) == ScriptTag::RAMBundle;
}
bool Instance::isIndexedRAMBundle(std::unique_ptr<const JSBigString>* script) {
BundleHeader header;
strncpy(reinterpret_cast<char *>(&header), script->get()->c_str(), sizeof(header));
return parseTypeFromHeader(header) == ScriptTag::RAMBundle;
}
void Instance::loadRAMBundleFromString(std::unique_ptr<const JSBigString> script, const std::string& sourceURL) {
auto bundle = folly::make_unique<JSIndexedRAMBundle>(std::move(script));
auto startupScript = bundle->getStartupCode();
auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
loadRAMBundle(
std::move(registry),
std::move(startupScript),
sourceURL,
true);
}
void Instance::loadRAMBundleFromFile(const std::string& sourcePath,
const std::string& sourceURL,
bool loadSynchronously) {
+2
View File
@@ -47,6 +47,8 @@ public:
void loadScriptFromString(std::unique_ptr<const JSBigString> string,
std::string sourceURL, bool loadSynchronously);
static bool isIndexedRAMBundle(const char *sourcePath);
static bool isIndexedRAMBundle(std::unique_ptr<const JSBigString>* string);
void loadRAMBundleFromString(std::unique_ptr<const JSBigString> script, const std::string& sourceURL);
void loadRAMBundleFromFile(const std::string& sourcePath,
const std::string& sourceURL,
bool loadSynchronously);
+26 -9
View File
@@ -6,7 +6,8 @@
#include "JSIndexedRAMBundle.h"
#include <glog/logging.h>
#include <fstream>
#include <sstream>
#include <folly/Memory.h>
namespace facebook {
@@ -18,14 +19,30 @@ std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> JSIndexedRAMBundl
};
}
JSIndexedRAMBundle::JSIndexedRAMBundle(const char *sourcePath) :
m_bundle (sourcePath, std::ios_base::in) {
JSIndexedRAMBundle::JSIndexedRAMBundle(const char *sourcePath) {
m_bundle = std::make_unique<std::ifstream>(sourcePath, std::ifstream::binary);
if (!m_bundle) {
throw std::ios_base::failure(
folly::to<std::string>("Bundle ", sourcePath,
"cannot be opened: ", m_bundle.rdstate()));
"cannot be opened: ", m_bundle->rdstate()));
}
init();
}
JSIndexedRAMBundle::JSIndexedRAMBundle(std::unique_ptr<const JSBigString> script) {
// tmpStream is needed because m_bundle is std::istream type
// which has no member 'write'
std::unique_ptr<std::stringstream> tmpStream = std::make_unique<std::stringstream>();
tmpStream->write(script->c_str(), script->size());
m_bundle = std::move(tmpStream);
if (!m_bundle) {
throw std::ios_base::failure(
folly::to<std::string>("Bundle from string cannot be opened: ", m_bundle->rdstate()));
}
init();
}
void JSIndexedRAMBundle::init() {
// read in magic header, number of entries, and length of the startup section
uint32_t header[3];
static_assert(
@@ -78,12 +95,12 @@ std::string JSIndexedRAMBundle::getModuleCode(const uint32_t id) const {
}
void JSIndexedRAMBundle::readBundle(char *buffer, const std::streamsize bytes) const {
if (!m_bundle.read(buffer, bytes)) {
if (m_bundle.rdstate() & std::ios::eofbit) {
if (!m_bundle->read(buffer, bytes)) {
if (m_bundle->rdstate() & std::ios::eofbit) {
throw std::ios_base::failure("Unexpected end of RAM Bundle file");
}
throw std::ios_base::failure(
folly::to<std::string>("Error reading RAM Bundle: ", m_bundle.rdstate()));
folly::to<std::string>("Error reading RAM Bundle: ", m_bundle->rdstate()));
}
}
@@ -92,9 +109,9 @@ void JSIndexedRAMBundle::readBundle(
const std::streamsize bytes,
const std::ifstream::pos_type position) const {
if (!m_bundle.seekg(position)) {
if (!m_bundle->seekg(position)) {
throw std::ios_base::failure(
folly::to<std::string>("Error reading RAM Bundle: ", m_bundle.rdstate()));
folly::to<std::string>("Error reading RAM Bundle: ", m_bundle->rdstate()));
}
readBundle(buffer, bytes);
}
+5 -3
View File
@@ -5,7 +5,7 @@
#pragma once
#include <fstream>
#include <istream>
#include <memory>
#include <cxxreact/JSBigString.h>
@@ -24,6 +24,7 @@ public:
// Throws std::runtime_error on failure.
JSIndexedRAMBundle(const char *sourceURL);
JSIndexedRAMBundle(std::unique_ptr<const JSBigString> script);
// Throws std::runtime_error on failure.
std::unique_ptr<const JSBigString> getStartupCode();
@@ -51,14 +52,15 @@ private:
}
};
void init();
std::string getModuleCode(const uint32_t id) const;
void readBundle(char *buffer, const std::streamsize bytes) const;
void readBundle(
char *buffer, const
std::streamsize bytes,
const std::ifstream::pos_type position) const;
const std::istream::pos_type position) const;
mutable std::ifstream m_bundle;
mutable std::unique_ptr<std::istream> m_bundle;
ModuleTable m_table;
size_t m_baseOffset;
std::unique_ptr<JSBigBufferString> m_startupCode;
@@ -0,0 +1,87 @@
---
AccessModifierOffset: -1
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: true
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, ]
IncludeCategories:
- Regex: '^<.*\.h(pp)?>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IndentCaseLabels: true
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
...
+26
View File
@@ -0,0 +1,26 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
REACT_NATIVE := $(LOCAL_PATH)/../../..
LOCAL_MODULE := hermes-inspector
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp $(LOCAL_PATH)/detail/*.cpp $(LOCAL_PATH)/chrome/*.cpp)
LOCAL_C_ROOT := $(LOCAL_PATH)/../..
LOCAL_CFLAGS := -DHERMES_ENABLE_DEBUGGER=1
LOCAL_C_INCLUDES := $(LOCAL_C_ROOT) $(REACT_NATIVE)/ReactCommon/jsi $(REACT_NATIVE)/node_modules/hermesvm/android/include
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_ROOT)
LOCAL_CPP_FEATURES := exceptions
LOCAL_STATIC_LIBRARIES := libjsi
LOCAL_SHARED_LIBRARIES := jsinspector libfb libfolly_futures libfolly_json libhermes
include $(BUILD_SHARED_LIBRARY)
@@ -0,0 +1,29 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
namespace facebook {
namespace hermes {
namespace inspector {
/**
* AsyncPauseState is used to track whether we requested an async pause from a
* running VM, and whether the pause was initiated by us or by the client.
*/
enum class AsyncPauseState {
/// None means there is no pending async pause in the VM.
None,
/// Implicit means we requested an async pause from the VM to service an op
/// that can only be performed while paused, like setting a breakpoint. An
/// impliict pause can be upgraded to an explicit pause if the client later
/// explicitly requests a pause.
Implicit,
/// Explicit means that the client requested the pause by calling pause().
Explicit
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
+221
View File
@@ -0,0 +1,221 @@
# Copyright 2004-present Facebook. All Rights Reserved.
load("@fbsource//tools/build_defs:default_platform_defs.bzl", "APPLE", "CXX")
load("@fbsource//tools/build_defs:fb_xplat_cxx_binary.bzl", "fb_xplat_cxx_binary")
load("@fbsource//tools/build_defs:fb_xplat_cxx_library.bzl", "fb_xplat_cxx_library")
load("@fbsource//tools/build_defs:fb_xplat_cxx_test.bzl", "fb_xplat_cxx_test")
load("@fbsource//tools/build_defs/oss:rn_defs.bzl", "react_native_xplat_target")
load("@fbsource//xplat/hermes/defs:hermes.bzl", "hermes_build_mode", "hermes_optimize_flag")
CFLAGS_BY_MODE = {
"dbg": [
"-fexceptions",
"-frtti",
hermes_optimize_flag("dbg"),
"-g",
],
"dev": [
"-fexceptions",
"-frtti",
hermes_optimize_flag("dev"),
"-g",
],
"opt": [
"-fexceptions",
"-frtti",
hermes_optimize_flag("opt"),
],
}
CHROME_EXPORTED_HEADERS = [
"chrome/AutoAttachUtils.h",
"chrome/Connection.h",
"chrome/ConnectionDemux.h",
"chrome/MessageConverters.h",
"chrome/MessageInterfaces.h",
"chrome/MessageTypes.h",
"chrome/Registration.h",
"chrome/RemoteObjectsTable.h",
]
fb_xplat_cxx_library(
name = "chrome",
srcs = glob(["chrome/*.cpp"]),
headers = glob(
[
"chrome/*.h",
],
exclude = CHROME_EXPORTED_HEADERS,
),
header_namespace = "hermes/inspector",
exported_headers = CHROME_EXPORTED_HEADERS,
compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()],
fbobjc_header_path_prefix = "hermes/inspector/chrome",
macosx_tests_override = [],
tests = [":chrome-tests"],
visibility = [
"PUBLIC",
],
xcode_public_headers_symlinks = True,
deps = [
react_native_xplat_target("jsinspector:jsinspector"),
"fbsource//xplat/folly:futures",
"fbsource//xplat/folly:molly",
"fbsource//xplat/hermes/API:HermesAPI",
"fbsource//xplat/jsi:jsi",
"fbsource//xplat/third-party/glog:glog",
":detail",
":inspectorlib",
],
)
fb_xplat_cxx_test(
name = "chrome-tests",
srcs = glob([
"chrome/tests/*.cpp",
]),
headers = glob([
"chrome/tests/*.h",
]),
compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()],
cxx_deps = [react_native_xplat_target("jsinspector:jsinspector")],
fbandroid_deps = [react_native_xplat_target("jsinspector:jsinspector")],
fbobjc_deps = [react_native_xplat_target("jsinspector:jsinspector")],
visibility = [
"PUBLIC",
],
deps = [
"fbsource//xplat/third-party/gmock:gtest",
":chrome",
":detail",
],
)
DETAIL_EXPORTED_HEADERS = [
"detail/SerialExecutor.h",
"detail/Thread.h",
]
fb_xplat_cxx_library(
name = "detail",
srcs = glob(["detail/*.cpp"]),
headers = glob(
[
"detail/*.h",
],
exclude = DETAIL_EXPORTED_HEADERS,
),
header_namespace = "hermes/inspector",
exported_headers = DETAIL_EXPORTED_HEADERS,
compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()],
# This is required by lint, but must be False, because there is
# no JNI_Onload.
fbandroid_allow_jni_merging = False,
fbandroid_deps = [
"fbandroid//native/fb:fb",
],
fbobjc_header_path_prefix = "hermes/inspector/detail",
macosx_tests_override = [],
tests = [":detail-tests"],
visibility = [
"PUBLIC",
],
xcode_public_headers_symlinks = True,
deps = [
"fbsource//xplat/folly:molly",
],
)
fb_xplat_cxx_test(
name = "detail-tests",
srcs = glob([
"detail/tests/*.cpp",
]),
headers = glob([
"detail/tests/*.h",
]),
compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()],
visibility = [
"PUBLIC",
],
deps = [
"fbsource//xplat/third-party/gmock:gtest",
":detail",
],
)
fb_xplat_cxx_binary(
name = "hermes-chrome-debug-server",
srcs = glob([
"chrome/cli/*.cpp",
]),
compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()],
cxx_deps = [react_native_xplat_target("jsinspector:jsinspector")],
fbandroid_deps = [react_native_xplat_target("jsinspector:jsinspector")],
fbobjc_deps = [react_native_xplat_target("jsinspector:jsinspector")],
visibility = [
"PUBLIC",
],
deps = [
"fbsource//xplat/hermes/API:HermesAPI",
":chrome",
":inspectorlib",
],
)
INSPECTOR_EXPORTED_HEADERS = [
"AsyncPauseState.h",
"Exceptions.h",
"Inspector.h",
"RuntimeAdapter.h",
]
# can't be named "inspector" since JSC already uses it, causing a buck rulekey
# collision: P58794155
fb_xplat_cxx_library(
name = "inspectorlib",
srcs = glob(["*.cpp"]),
headers = glob(
[
"*.h",
],
exclude = INSPECTOR_EXPORTED_HEADERS,
),
header_namespace = "hermes/inspector",
exported_headers = INSPECTOR_EXPORTED_HEADERS,
compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()],
fbobjc_header_path_prefix = "hermes/inspector",
macosx_tests_override = [],
cxx_tests = [":inspector-tests"],
visibility = [
"PUBLIC",
],
xcode_public_headers_symlinks = True,
deps = [
"fbsource//xplat/folly:futures",
"fbsource//xplat/folly:molly",
"fbsource//xplat/hermes/API:HermesAPI",
"fbsource//xplat/jsi:jsi",
"fbsource//xplat/third-party/glog:glog",
":detail",
],
)
fb_xplat_cxx_test(
name = "inspector-tests",
srcs = glob([
"tests/*.cpp",
]),
headers = glob([
"tests/*.h",
]),
compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()],
platforms = (CXX, APPLE),
visibility = [
"PUBLIC",
],
deps = [
"fbsource//xplat/third-party/gmock:gtest",
":inspectorlib",
],
)
+7
View File
@@ -0,0 +1,7 @@
load("@fbsource//xplat/hermes/defs:hermes.bzl", "hermes_is_debugger_enabled")
def hermes_inspector_dep_list():
return [
"fbsource//xplat/hermes-inspector:chrome",
"fbsource//xplat/hermes-inspector:inspectorlib",
] if hermes_is_debugger_enabled() else []
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <stdexcept>
namespace facebook {
namespace hermes {
namespace inspector {
class AlreadyEnabledException : public std::runtime_error {
public:
AlreadyEnabledException()
: std::runtime_error("can't enable: debugger already enabled") {}
};
class NotEnabledException : public std::runtime_error {
public:
NotEnabledException(const std::string &cmd)
: std::runtime_error("debugger can't perform " + cmd + ": not enabled") {}
};
class InvalidStateException : public std::runtime_error {
public:
InvalidStateException(
const std::string &cmd,
const std::string &curState,
const std::string &expectedState)
: std::runtime_error(
"debugger can't perform " + cmd + ": in " + curState +
", expected " + expectedState) {}
};
class MultipleCommandsPendingException : public std::runtime_error {
public:
MultipleCommandsPendingException(const std::string &cmd)
: std::runtime_error(
"debugger can't perform " + cmd +
": a step or resume is already pending") {}
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
+582
View File
@@ -0,0 +1,582 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "Inspector.h"
#include "Exceptions.h"
#include "InspectorState.h"
#include <functional>
#include <string>
#include <glog/logging.h>
#include <hermes/inspector/detail/SerialExecutor.h>
#include <hermes/inspector/detail/Thread.h>
// <kludge> This is here, instead of linking against
// folly/futures/Future.cpp, to avoid pulling in another pile of
// dependencies, including the separate dependency libevent. This is
// likely specific to the version of folly RN uses, so may need to be
// changed. Even better, perhaps folly can be refactored to simplify
// this.
template class folly::Future<folly::Unit>;
namespace folly {
namespace futures {
Future<Unit> sleep(Duration dur, Timekeeper* tk) {
LOG(FATAL) << "folly::futures::sleep() not implemented";
}
}
namespace detail {
std::shared_ptr<Timekeeper> getTimekeeperSingleton() {
LOG(FATAL) << "folly::detail::getTimekeeperSingleton() not implemented";
}
}
}
// </kludge>
namespace facebook {
namespace hermes {
namespace inspector {
using folly::Unit;
namespace debugger = ::facebook::hermes::debugger;
/**
* Threading notes:
*
* 1. mutex_ must be held before using state_ or any InspectorState methods.
* 2. Methods that are callable by the client (like enable, resume, etc.) call
* various InspectorState methods via state_. This implies that they must
* acquire mutex_.
* 3. Since some InspectorState methods call back out to the client (e.g. via
* fulfilling promises, or via the InspectorObserver callbacks), we have to
* be careful about reentrancy from a callback causing a deadlock when (1)
* and (2) interact. Consider:
*
* 1) Debugger pauses, which causes InspectorObserve::onPause to fire.
* onPause is called by InspectorState::Paused::onEnter on the JS
* thread with mutex_ held.
* 2) Client calls setBreakpoint from the onPause callback.
* 3) If setBreakpoint directly tried to acquire mutex_ here, we would
* deadlock since our thread already owns the mutex_ (see 1).
*
* For this reason, all client-facing methods are executed on executor_, which
* runs on its own thread. The pattern is:
*
* 1. The client-facing method foo (e.g. enable) enqueues a call to
* fooOnExecutor (e.g. enableOnExecutor) on executor_.
* 2. fooOnExecutor is responsible for acquiring mutex_.
*
*/
// TODO: read this out of an env variable or config
static constexpr bool kShouldLog = true;
// Logging state transitions is done outside of transition() in a macro so that
// function and line numbers in the log will be accurate.
#define TRANSITION(nextState) \
do { \
if (kShouldLog) { \
if (state_ == nullptr) { \
LOG(INFO) << "Inspector::" << __func__ \
<< " transitioning to initial state " << *(nextState); \
} else { \
LOG(INFO) << "Inspector::" << __func__ << " transitioning from " \
<< *state_ << " to " << *(nextState); \
} \
} \
transition((nextState)); \
} while (0)
Inspector::Inspector(
std::shared_ptr<RuntimeAdapter> adapter,
InspectorObserver &observer,
bool pauseOnFirstStatement)
: adapter_(adapter),
debugger_(adapter->getRuntime().getDebugger()),
observer_(observer),
executor_(std::make_unique<detail::SerialExecutor>("hermes-inspector")) {
// TODO (t26491391): make tickleJs a real Hermes runtime API
const char *src = "function __tickleJs() { return Math.random(); }";
adapter->getRuntime().debugJavaScript(src, "__tickleJsHackUrl", {});
{
std::lock_guard<std::mutex> lock(mutex_);
if (pauseOnFirstStatement) {
TRANSITION(std::make_unique<InspectorState::RunningWaitEnable>(*this));
} else {
TRANSITION(std::make_unique<InspectorState::RunningDetached>(*this));
}
}
debugger_.setShouldPauseOnScriptLoad(true);
debugger_.setEventObserver(this);
}
Inspector::~Inspector() {
// TODO: think about expected detach flow
debugger_.setEventObserver(nullptr);
}
void Inspector::installConsoleFunction(
jsi::Object &console,
const std::string &name,
const std::string &chromeTypeDefault = "") {
jsi::Runtime &rt = adapter_->getRuntime();
auto chromeType = chromeTypeDefault == "" ? name : chromeTypeDefault;
auto nameID = jsi::PropNameID::forUtf8(rt, name);
auto weakInspector = std::weak_ptr<Inspector>(shared_from_this());
console.setProperty(
rt,
nameID,
jsi::Function::createFromHostFunction(
rt,
nameID,
1,
[weakInspector, chromeType](
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
if (auto inspector = weakInspector.lock()) {
jsi::Array argsArray(runtime, count);
for (size_t index = 0; index < count; ++index)
argsArray.setValueAtIndex(runtime, index, args[index]);
inspector->logMessage(
ConsoleMessageInfo{chromeType, std::move(argsArray)});
}
return jsi::Value::undefined();
}));
}
void Inspector::installLogHandler() {
jsi::Runtime &rt = adapter_->getRuntime();
auto console = jsi::Object(rt);
installConsoleFunction(console, "assert");
installConsoleFunction(console, "clear");
installConsoleFunction(console, "debug");
installConsoleFunction(console, "dir");
installConsoleFunction(console, "dirxml");
installConsoleFunction(console, "error");
installConsoleFunction(console, "group", "startGroup");
installConsoleFunction(console, "groupCollapsed", "startGroupCollapsed");
installConsoleFunction(console, "groupEnd", "endGroup");
installConsoleFunction(console, "info");
installConsoleFunction(console, "log");
installConsoleFunction(console, "profile");
installConsoleFunction(console, "profileEnd");
installConsoleFunction(console, "table");
installConsoleFunction(console, "trace");
installConsoleFunction(console, "warn", "warning");
rt.global().setProperty(rt, "console", console);
}
void Inspector::triggerAsyncPause(bool andTickle) {
// In order to ensure that we pause soon, we both set the async pause flag on
// the runtime, and we run a bit of dummy JS to ensure we enter the Hermes
// interpreter loop.
debugger_.triggerAsyncPause();
if (andTickle) {
// We run the dummy JS on a background thread to avoid any reentrancy issues
// in case this thread is called with the inspector mutex held.
std::shared_ptr<RuntimeAdapter> adapter = adapter_;
detail::Thread tickleJsLater(
"inspectorTickleJs", [adapter]() { adapter->tickleJs(); });
tickleJsLater.detach();
}
}
void Inspector::notifyContextCreated() {
observer_.onContextCreated(*this);
}
ScriptInfo Inspector::getScriptInfoFromTopCallFrame() {
ScriptInfo info{};
auto stackTrace = debugger_.getProgramState().getStackTrace();
if (stackTrace.callFrameCount() > 0) {
uint32_t i = stackTrace.callFrameCount() - 1;
debugger::SourceLocation loc = stackTrace.callFrameForIndex(i).location;
info.fileId = loc.fileId;
info.fileName = loc.fileName;
info.sourceMappingUrl = debugger_.getSourceMappingUrl(info.fileId);
}
return info;
}
void Inspector::addCurrentScriptToLoadedScripts() {
ScriptInfo info = getScriptInfoFromTopCallFrame();
if (!loadedScripts_.count(info.fileId)) {
loadedScripts_[info.fileId] = LoadedScriptInfo{std::move(info), false};
}
}
void Inspector::removeAllBreakpoints() {
debugger_.deleteAllBreakpoints();
}
void Inspector::resetScriptsLoaded() {
for (auto &it : loadedScripts_) {
it.second.notifiedClient = false;
}
}
void Inspector::notifyScriptsLoaded() {
for (auto &it : loadedScripts_) {
LoadedScriptInfo &loadedScriptInfo = it.second;
if (!loadedScriptInfo.notifiedClient) {
loadedScriptInfo.notifiedClient = true;
observer_.onScriptParsed(*this, loadedScriptInfo.info);
}
}
}
folly::Future<Unit> Inspector::disable() {
auto promise = std::make_shared<folly::Promise<Unit>>();
executor_->add([this, promise] { disableOnExecutor(promise); });
return promise->getFuture();
}
folly::Future<Unit> Inspector::enable() {
auto promise = std::make_shared<folly::Promise<Unit>>();
executor_->add([this, promise] { enableOnExecutor(promise); });
return promise->getFuture();
}
folly::Future<Unit> Inspector::executeIfEnabled(
const std::string &description,
folly::Function<void(const debugger::ProgramState &)> func) {
auto promise = std::make_shared<folly::Promise<Unit>>();
executor_->add(
[this, description, func = std::move(func), promise]() mutable {
executeIfEnabledOnExecutor(description, std::move(func), promise);
});
return promise->getFuture();
}
folly::Future<debugger::BreakpointInfo> Inspector::setBreakpoint(
debugger::SourceLocation loc,
folly::Optional<std::string> condition) {
auto promise = std::make_shared<folly::Promise<debugger::BreakpointInfo>>();
executor_->add([this, loc, condition, promise] {
setBreakpointOnExecutor(loc, condition, promise);
});
return promise->getFuture();
}
folly::Future<folly::Unit> Inspector::removeBreakpoint(
debugger::BreakpointID breakpointId) {
auto promise = std::make_shared<folly::Promise<folly::Unit>>();
executor_->add([this, breakpointId, promise] {
removeBreakpointOnExecutor(breakpointId, promise);
});
return promise->getFuture();
}
folly::Future<folly::Unit> Inspector::logMessage(ConsoleMessageInfo info) {
auto promise = std::make_shared<folly::Promise<folly::Unit>>();
executor_->add([this,
pInfo = std::make_unique<ConsoleMessageInfo>(std::move(info)),
promise] { logOnExecutor(std::move(*pInfo), promise); });
return promise->getFuture();
}
folly::Future<Unit> Inspector::setPendingCommand(debugger::Command command) {
auto promise = std::make_shared<folly::Promise<Unit>>();
executor_->add([this, promise, cmd = std::move(command)]() mutable {
setPendingCommandOnExecutor(std::move(cmd), promise);
});
return promise->getFuture();
}
folly::Future<Unit> Inspector::resume() {
return setPendingCommand(debugger::Command::continueExecution());
}
folly::Future<Unit> Inspector::stepIn() {
return setPendingCommand(debugger::Command::step(debugger::StepMode::Into));
}
folly::Future<Unit> Inspector::stepOver() {
return setPendingCommand(debugger::Command::step(debugger::StepMode::Over));
}
folly::Future<Unit> Inspector::stepOut() {
return setPendingCommand(debugger::Command::step(debugger::StepMode::Out));
}
folly::Future<Unit> Inspector::pause() {
auto promise = std::make_shared<folly::Promise<Unit>>();
executor_->add([this, promise]() { pauseOnExecutor(promise); });
return promise->getFuture();
}
folly::Future<debugger::EvalResult> Inspector::evaluate(
uint32_t frameIndex,
const std::string &src,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) {
auto promise = std::make_shared<folly::Promise<debugger::EvalResult>>();
executor_->add([this,
frameIndex,
src,
promise,
resultTransformer = std::move(resultTransformer)]() mutable {
evaluateOnExecutor(frameIndex, src, promise, std::move(resultTransformer));
});
return promise->getFuture();
}
folly::Future<folly::Unit> Inspector::setPauseOnExceptions(
const debugger::PauseOnThrowMode &mode) {
auto promise = std::make_shared<folly::Promise<Unit>>();
executor_->add([this, mode, promise]() mutable {
setPauseOnExceptionsOnExecutor(mode, promise);
});
return promise->getFuture();
};
debugger::Command Inspector::didPause(debugger::Debugger &debugger) {
std::unique_lock<std::mutex> lock(mutex_);
if (kShouldLog) {
LOG(INFO) << "received didPause for reason: "
<< static_cast<int>(debugger.getProgramState().getPauseReason())
<< " in state: " << *state_;
}
while (true) {
/*
* Keep sending the onPause event to the current state until we get a
* command to return. For instance, this handles the transition from
* Running to Paused to Running:
*
* 1) (R => P) We're currently in Running, so we call Running::didPause,
* which returns {nextState: Paused, command: null}. There isn't a
* command to return yet.
* 2) (P => R) Now we're in Paused, so we call Paused::didPause, which
* returns {nextState: Running, command: someCommand} where someCommand
* is non-null (e.g. continue or step over). This terminates the loop.
*/
auto result = state_->didPause(lock);
std::unique_ptr<InspectorState> nextState = std::move(result.first);
if (nextState) {
TRANSITION(std::move(nextState));
}
std::unique_ptr<debugger::Command> command = std::move(result.second);
if (command) {
return std::move(*command);
}
}
}
void Inspector::breakpointResolved(
debugger::Debugger &debugger,
debugger::BreakpointID breakpointId) {
std::unique_lock<std::mutex> lock(mutex_);
debugger::BreakpointInfo info = debugger.getBreakpointInfo(breakpointId);
observer_.onBreakpointResolved(*this, info);
}
void Inspector::transition(std::unique_ptr<InspectorState> nextState) {
assert(nextState);
assert(state_ != nextState);
std::unique_ptr<InspectorState> prevState = std::move(state_);
state_ = std::move(nextState);
state_->onEnter(prevState.get());
}
void Inspector::disableOnExecutor(
std::shared_ptr<folly::Promise<Unit>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
debugger_.setIsDebuggerAttached(false);
state_->detach(promise);
}
void Inspector::enableOnExecutor(
std::shared_ptr<folly::Promise<Unit>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
auto result = state_->enable();
/**
* We fulfill the promise before changing state because fulfilling the promise
* responds to the Debugger.enable request, and changing state could send a
* notification (like Debugger.paused). It seems like a good idea to respond
* to enable before sending out any notifications.
*/
bool enabled = result.second;
if (enabled) {
debugger_.setIsDebuggerAttached(true);
promise->setValue();
} else {
promise->setException(AlreadyEnabledException());
}
std::unique_ptr<InspectorState> nextState = std::move(result.first);
if (nextState) {
TRANSITION(std::move(nextState));
}
}
void Inspector::executeIfEnabledOnExecutor(
const std::string &description,
folly::Function<void(const debugger::ProgramState &)> func,
std::shared_ptr<folly::Promise<Unit>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
if (!state_->isPaused() && !state_->isRunning()) {
promise->setException(InvalidStateException(
description, state_->description(), "paused or running"));
return;
}
folly::Func wrappedFunc = [this, func = std::move(func)]() mutable {
func(debugger_.getProgramState());
};
state_->pushPendingFunc(
[wrappedFunc = std::move(wrappedFunc), promise]() mutable {
wrappedFunc();
promise->setValue();
});
}
void Inspector::setBreakpointOnExecutor(
debugger::SourceLocation loc,
folly::Optional<std::string> condition,
std::shared_ptr<folly::Promise<debugger::BreakpointInfo>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
bool pushed = state_->pushPendingFunc([this, loc, condition, promise] {
debugger::BreakpointID id = debugger_.setBreakpoint(loc);
debugger::BreakpointInfo info{debugger::kInvalidBreakpoint};
if (id != debugger::kInvalidBreakpoint) {
info = debugger_.getBreakpointInfo(id);
if (condition) {
debugger_.setBreakpointCondition(id, condition.value());
}
}
promise->setValue(std::move(info));
});
if (!pushed) {
promise->setException(NotEnabledException("setBreakpoint"));
}
}
void Inspector::removeBreakpointOnExecutor(
debugger::BreakpointID breakpointId,
std::shared_ptr<folly::Promise<folly::Unit>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
bool pushed = state_->pushPendingFunc([this, breakpointId, promise] {
debugger_.deleteBreakpoint(breakpointId);
promise->setValue();
});
if (!pushed) {
promise->setException(NotEnabledException("removeBreakpoint"));
}
}
void Inspector::logOnExecutor(
ConsoleMessageInfo info,
std::shared_ptr<folly::Promise<folly::Unit>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
state_->pushPendingFunc([this, info = std::move(info)] {
observer_.onMessageAdded(*this, info);
});
promise->setValue();
}
void Inspector::setPendingCommandOnExecutor(
debugger::Command command,
std::shared_ptr<folly::Promise<Unit>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
state_->setPendingCommand(std::move(command), promise);
}
void Inspector::pauseOnExecutor(std::shared_ptr<folly::Promise<Unit>> promise) {
std::lock_guard<std::mutex> lock(mutex_);
bool canPause = state_->pause();
if (canPause) {
promise->setValue();
} else {
promise->setException(NotEnabledException("pause"));
}
}
void Inspector::evaluateOnExecutor(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<debugger::EvalResult>> promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) {
std::lock_guard<std::mutex> lock(mutex_);
state_->pushPendingEval(
frameIndex, src, promise, std::move(resultTransformer));
}
void Inspector::setPauseOnExceptionsOnExecutor(
const debugger::PauseOnThrowMode &mode,
std::shared_ptr<folly::Promise<folly::Unit>> promise) {
std::lock_guard<std::mutex> local(mutex_);
state_->pushPendingFunc([this, mode, promise] {
debugger_.setPauseOnThrowMode(mode);
promise->setValue();
});
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
+306
View File
@@ -0,0 +1,306 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <queue>
#include <unordered_map>
#include <folly/Executor.h>
#include <folly/Unit.h>
#include <folly/futures/Future.h>
#include <hermes/DebuggerAPI.h>
#include <hermes/hermes.h>
#include <hermes/inspector/AsyncPauseState.h>
#include <hermes/inspector/RuntimeAdapter.h>
namespace facebook {
namespace hermes {
namespace inspector {
class Inspector;
class InspectorState;
/**
* ScriptInfo contains info about loaded scripts.
*/
struct ScriptInfo {
uint32_t fileId{};
std::string fileName;
std::string sourceMappingUrl;
};
struct ConsoleMessageInfo {
std::string source;
std::string level;
std::string url;
int line;
int column;
jsi::Array args;
ConsoleMessageInfo(std::string level, jsi::Array args)
: source("console-api"),
level(level),
url(""),
line(-1),
column(-1),
args(std::move(args)) {}
};
/**
* InspectorObserver notifies the observer of events that occur in the VM.
*/
class InspectorObserver {
public:
virtual ~InspectorObserver() = default;
/// onContextCreated fires when the VM is created.
virtual void onContextCreated(Inspector &inspector) = 0;
/// onBreakpointResolve fires when a lazy breakpoint is resolved.
virtual void onBreakpointResolved(
Inspector &inspector,
const facebook::hermes::debugger::BreakpointInfo &info) = 0;
/// onPause fires when VM transitions from running to paused state. This is
/// called directly on the JS thread while the VM is paused, so the receiver
/// can call debugger::ProgramState methods safely.
virtual void onPause(
Inspector &inspector,
const facebook::hermes::debugger::ProgramState &state) = 0;
/// onResume fires when VM transitions from paused to running state.
virtual void onResume(Inspector &inspector) = 0;
/// onScriptParsed fires when after the VM parses a script.
virtual void onScriptParsed(Inspector &inspector, const ScriptInfo &info) = 0;
// onMessageAdded fires when new console message is added.
virtual void onMessageAdded(
Inspector &inspector,
const ConsoleMessageInfo &info) = 0;
};
/**
* Inspector implements a future-based interface over the low-level Hermes
* debugging API.
*/
class Inspector : public facebook::hermes::debugger::EventObserver,
public std::enable_shared_from_this<Inspector> {
public:
/**
* Inspector's constructor should be used to install the inspector on the
* provided runtime before any JS executes in the runtime.
*/
Inspector(
std::shared_ptr<RuntimeAdapter> adapter,
InspectorObserver &observer,
bool pauseOnFirstStatement);
~Inspector();
/**
* disable turns off the inspector. All of the subsequent methods will not do
* anything unless the inspector is enabled.
*/
folly::Future<folly::Unit> disable();
/**
* enable turns on the inspector. All of the subsequent methods will not do
* anything unless the inspector is enabled. The returned future succeeds when
* the debugger is enabled, or fails with AlreadyEnabledException if the
* debugger was already enabled.
*/
folly::Future<folly::Unit> enable();
/**
* installs console log handler. Ideally this should be done inside
* constructor, but because it uses shared_from_this we can't do this
* in constructor.
*/
void installLogHandler();
/**
* executeIfEnabled executes the provided callback *on the JS thread with the
* inspector lock held*. Execution can be implicitly requested while running.
* The inspector lock:
*
* 1) Protects VM state transitions. This means that the VM is guaranteed to
* stay in the paused or running state for the duration of the callback.
* 2) Protects InspectorObserver callbacks. This means that if some shared
* data is accessed only in InspectorObserver and executeIfEnabled
* callbacks, it does not need to be locked, since it's already protected
* by the inspector lock.
*
* The returned future resolves to true in the VM can be paused, or
* fails with IllegalStateException otherwise. The description is only used
* to populate the IllegalStateException with more useful info on failure.
*/
folly::Future<folly::Unit> executeIfEnabled(
const std::string &description,
folly::Function<void(const facebook::hermes::debugger::ProgramState &)>
func);
/**
* setBreakpoint can be called at any time after the debugger is enabled to
* set a breakpoint in the VM. The future is fulfilled with the resolved
* breakpoint info.
*
* Resolving a breakpoint takes an indeterminate amount of time since Hermes
* only resolves breakpoints when the debugger is able to actively pause JS
* execution.
*/
folly::Future<facebook::hermes::debugger::BreakpointInfo> setBreakpoint(
facebook::hermes::debugger::SourceLocation loc,
folly::Optional<std::string> condition = folly::none);
folly::Future<folly::Unit> removeBreakpoint(
facebook::hermes::debugger::BreakpointID loc);
/**
* logs console message.
*/
folly::Future<folly::Unit> logMessage(ConsoleMessageInfo info);
/**
* resume and step methods are only valid when the VM is currently paused. The
* returned future suceeds when the VM resumes execution, or fails with an
* InvalidStateException otherwise.
*/
folly::Future<folly::Unit> resume();
folly::Future<folly::Unit> stepIn();
folly::Future<folly::Unit> stepOver();
folly::Future<folly::Unit> stepOut();
/**
* pause can be issued at any time while the inspector is enabled. It requests
* the VM to asynchronously break execution. The returned future suceeds if
* the VM can be paused in this state and fails with InvalidStateException if
* otherwise.
*/
folly::Future<folly::Unit> pause();
/**
* evaluate runs JavaScript code within the context of a call frame. The
* returned promise is fulfilled with an eval result if it's possible to
* evaluate code in the current state or fails with InvalidStateException
* otherwise.
*/
folly::Future<facebook::hermes::debugger::EvalResult> evaluate(
uint32_t frameIndex,
const std::string &src,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer);
folly::Future<folly::Unit> setPauseOnExceptions(
const facebook::hermes::debugger::PauseOnThrowMode &mode);
/**
* didPause implements the pause callback from Hermes. This callback arrives
* on the JS thread.
*/
facebook::hermes::debugger::Command didPause(
facebook::hermes::debugger::Debugger &debugger) override;
/**
* breakpointResolved implements the breakpointResolved callback from Hermes.
*/
void breakpointResolved(
facebook::hermes::debugger::Debugger &debugger,
facebook::hermes::debugger::BreakpointID breakpointId) override;
private:
friend class InspectorState;
void triggerAsyncPause(bool andTickle);
void notifyContextCreated();
ScriptInfo getScriptInfoFromTopCallFrame();
void addCurrentScriptToLoadedScripts();
void removeAllBreakpoints();
void resetScriptsLoaded();
void notifyScriptsLoaded();
folly::Future<folly::Unit> setPendingCommand(debugger::Command command);
void transition(std::unique_ptr<InspectorState> nextState);
// All methods that end with OnExecutor run on executor_.
void disableOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void enableOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void executeIfEnabledOnExecutor(
const std::string &description,
folly::Function<void(const facebook::hermes::debugger::ProgramState &)>
func,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void setBreakpointOnExecutor(
debugger::SourceLocation loc,
folly::Optional<std::string> condition,
std::shared_ptr<
folly::Promise<facebook::hermes::debugger::BreakpointInfo>> promise);
void removeBreakpointOnExecutor(
debugger::BreakpointID breakpointId,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void logOnExecutor(
ConsoleMessageInfo info,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void setPendingCommandOnExecutor(
facebook::hermes::debugger::Command command,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void pauseOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void evaluateOnExecutor(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer);
void setPauseOnExceptionsOnExecutor(
const facebook::hermes::debugger::PauseOnThrowMode &mode,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void installConsoleFunction(
jsi::Object &console,
const std::string &name,
const std::string &chromeType);
std::shared_ptr<RuntimeAdapter> adapter_;
facebook::hermes::debugger::Debugger &debugger_;
InspectorObserver &observer_;
// All client methods (e.g. enable, setBreakpoint, resume, etc.) are executed
// on executor_ to prevent deadlocking on mutex_. See the implementation for
// more comments on the threading invariants used in this class.
std::unique_ptr<folly::Executor> executor_;
// All of the following member variables are guarded by mutex_.
std::mutex mutex_;
std::unique_ptr<InspectorState> state_;
// See the InspectorState::Running implementation for an explanation for why
// this state is here rather than in the Running class.
AsyncPauseState pendingPauseState_ = AsyncPauseState::None;
// All scripts loaded in to the VM, along with whether we've notified the
// client about the script yet.
struct LoadedScriptInfo {
ScriptInfo info;
bool notifiedClient;
};
std::unordered_map<int, LoadedScriptInfo> loadedScripts_;
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,476 @@
#include "InspectorState.h"
#include <glog/logging.h>
namespace facebook {
namespace hermes {
namespace inspector {
using folly::Unit;
namespace debugger = ::facebook::hermes::debugger;
namespace {
std::unique_ptr<debugger::Command> makeContinueCommand() {
return std::make_unique<debugger::Command>(
debugger::Command::continueExecution());
}
} // namespace
std::ostream &operator<<(std::ostream &os, const InspectorState &state) {
return os << state.description();
}
/*
* InspectorState::RunningDetached
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::RunningDetached::didPause(
MonitorLock &lock) {
debugger::PauseReason reason = getPauseReason();
if (reason == debugger::PauseReason::DebuggerStatement) {
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::PausedWaitEnable::make(inspector_), nullptr);
}
if (reason == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
}
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, makeContinueCommand());
}
std::pair<NextStatePtr, bool> InspectorState::RunningDetached::enable() {
return std::make_pair<NextStatePtr, bool>(
InspectorState::Running::make(inspector_), true);
}
/*
* InspectorState::RunningWaitEnable
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::RunningWaitEnable::didPause(
MonitorLock &lock) {
// If we started in RWE, then we asked for the VM to break on the first
// statement, and the first pause should be because of a script load.
assert(getPauseReason() == debugger::PauseReason::ScriptLoaded);
inspector_.addCurrentScriptToLoadedScripts();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::PausedWaitEnable::make(inspector_), nullptr);
}
std::pair<NextStatePtr, bool> InspectorState::RunningWaitEnable::enable() {
return std::make_pair<NextStatePtr, bool>(
InspectorState::RunningWaitPause::make(inspector_), true);
}
/*
* InspectorState::RunningWaitPause
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::RunningWaitPause::didPause(
MonitorLock &lock) {
// If we are in RWP, then we asked for the VM to break on the first
// statement, and the first pause should be because of a script load.
assert(getPauseReason() == debugger::PauseReason::ScriptLoaded);
inspector_.addCurrentScriptToLoadedScripts();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
/*
* InspectorState::PausedWaitEnable
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::PausedWaitEnable::didPause(
MonitorLock &lock) {
if (getPauseReason() == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
}
while (!enabled_) {
/*
* The call to wait temporarily relinquishes the inspector mutex. This is
* safe because no other PausedWaitEnable event handler directly transitions
* out of PausedWaitEnable. So we know that our state is the active state
* both before and after the call to wait. This preserves the invariant that
* the inspector state is not modified during the execution of this method.
*
* Instead, PausedWaitEnable::enable indirectly induces the state transition
* out of PausedWaitEnable by signaling us via enabledCondition_.
*/
enabledCondition_.wait(lock);
assert(inspector_.state_.get() == this);
}
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
std::pair<NextStatePtr, bool> InspectorState::PausedWaitEnable::enable() {
if (enabled_) {
// Someone already called enable before and we're just waiting for the
// condition variable to wake up didPause.
return std::make_pair<NextStatePtr, bool>(nullptr, false);
}
enabled_ = true;
enabledCondition_.notify_one();
return std::make_pair<NextStatePtr, bool>(nullptr, true);
}
/*
* InspectorState::Running
*
* # Async Pauses
*
* We distinguish between implicit and explicit async pauses. An implicit async
* pause is requested by the inspector itself to service a request that requires
* the VM to be paused (e.g. to set a breakpoint). This is different from an
* explicit async pause requested by the user by hitting the pause button in the
* debugger UI.
*
* The async pause state must live in the Inspector class instead of the Running
* class because of potential races between when the implicit pause is requested
* and when it's serviced. Consider:
*
* 1. We request an implicit pause (e.g. to set a breakpoint).
* 2. An existing breakpoint fires, moving us from Running => Paused.
* 3. Client resumes execution, moving us from Paused => Running.
* 4. Now the debugger notices the async pause flag we set in (1), which pauses
* us again, causing Running::didPause to run.
*
* In this case, the Running state instance from (1) is no longer the same as
* the Running state instance in (4). But the running state instance in (4)
* needs to know that we requested the async break sometime in the past so it
* knows to automatically continue in the didPause callback. Therefore the async
* break state has to be stored in the long-lived Inspector class rather than in
* the short-lived Running class.
*/
void InspectorState::Running::onEnter(InspectorState *prevState) {
if (prevState) {
if (prevState->isPaused()) {
inspector_.observer_.onResume(inspector_);
} else {
// send context created and script load notifications if we just enabled
// the debugger
inspector_.notifyContextCreated();
inspector_.notifyScriptsLoaded();
}
}
}
void InspectorState::Running::detach(
std::shared_ptr<folly::Promise<Unit>> promise) {
pushPendingFunc([this, promise] {
pendingDetach_ = promise;
inspector_.removeAllBreakpoints();
inspector_.resetScriptsLoaded();
});
}
std::pair<NextStatePtr, CommandPtr> InspectorState::Running::didPause(
MonitorLock &lock) {
debugger::PauseReason reason = getPauseReason();
for (auto &func : pendingFuncs_) {
func();
}
pendingFuncs_.clear();
if (pendingDetach_) {
// Clear any pending pause state back to no requests for the next attach
inspector_.pendingPauseState_ = AsyncPauseState::None;
// Ensure we fulfill any pending ScriptLoaded requests
if (reason == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
}
// Fail any in-flight Eval requests
if (pendingEvalPromise_) {
pendingEvalPromise_->setException(NotEnabledException("eval"));
}
// if we requested the break implicitly to clear state and detach,
// transition to RunningDetached
pendingDetach_->setValue();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::RunningDetached::make(inspector_),
makeContinueCommand());
}
if (reason == debugger::PauseReason::AsyncTrigger) {
AsyncPauseState &pendingPauseState = inspector_.pendingPauseState_;
switch (pendingPauseState) {
case AsyncPauseState::None:
// shouldn't ever async break without us asking first
assert(false);
break;
case AsyncPauseState::Implicit:
pendingPauseState = AsyncPauseState::None;
break;
case AsyncPauseState::Explicit:
// explicit break was requested by user, so go to Paused state
pendingPauseState = AsyncPauseState::None;
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
} else if (reason == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
inspector_.notifyScriptsLoaded();
} else if (reason == debugger::PauseReason::EvalComplete) {
assert(pendingEvalPromise_);
pendingEvalResultTransformer_(
inspector_.debugger_.getProgramState().getEvalResult());
pendingEvalPromise_->setValue(
inspector_.debugger_.getProgramState().getEvalResult());
pendingEvalPromise_.reset();
} else /* other cases imply a transition to Pause */ {
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
if (!pendingEvals_.empty()) {
assert(!pendingEvalPromise_);
auto eval = std::make_unique<PendingEval>(std::move(pendingEvals_.front()));
pendingEvals_.pop();
pendingEvalPromise_ = eval->promise;
pendingEvalResultTransformer_ = std::move(eval->resultTransformer);
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, std::make_unique<debugger::Command>(std::move(eval->command)));
}
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, makeContinueCommand());
}
bool InspectorState::Running::pushPendingFunc(folly::Func func) {
pendingFuncs_.emplace_back(std::move(func));
if (inspector_.pendingPauseState_ == AsyncPauseState::None) {
inspector_.pendingPauseState_ = AsyncPauseState::Implicit;
inspector_.triggerAsyncPause(true);
}
return true;
}
void InspectorState::Running::pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<debugger::EvalResult>> promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) {
PendingEval pendingEval{debugger::Command::eval(src, frameIndex),
promise,
std::move(resultTransformer)};
pendingEvals_.emplace(std::move(pendingEval));
if (inspector_.pendingPauseState_ == AsyncPauseState::None) {
inspector_.pendingPauseState_ = AsyncPauseState::Implicit;
}
inspector_.triggerAsyncPause(true);
}
bool InspectorState::Running::pause() {
AsyncPauseState &pendingPauseState = inspector_.pendingPauseState_;
bool canPause = false;
switch (pendingPauseState) {
case AsyncPauseState::None:
// haven't yet requested a pause, so do it now
inspector_.triggerAsyncPause(false);
pendingPauseState = AsyncPauseState::Explicit;
canPause = true;
break;
case AsyncPauseState::Implicit:
// already requested an implicit pause on our own, upgrade it to an
// explicit pause
pendingPauseState = AsyncPauseState::Explicit;
canPause = true;
break;
case AsyncPauseState::Explicit:
// client already requested a pause that hasn't occurred yet
canPause = false;
break;
}
return canPause;
}
/*
* InspectorState::Paused
*/
void InspectorState::Paused::onEnter(InspectorState *prevState) {
// send script load notifications if we just enabled the debugger
if (prevState && !prevState->isRunning()) {
inspector_.notifyContextCreated();
inspector_.notifyScriptsLoaded();
}
const debugger::ProgramState &state = inspector_.debugger_.getProgramState();
inspector_.observer_.onPause(inspector_, state);
}
std::pair<NextStatePtr, CommandPtr> InspectorState::Paused::didPause(
std::unique_lock<std::mutex> &lock) {
switch (getPauseReason()) {
case debugger::PauseReason::AsyncTrigger:
inspector_.pendingPauseState_ = AsyncPauseState::None;
break;
case debugger::PauseReason::EvalComplete: {
assert(pendingEvalPromise_);
pendingEvalResultTransformer_(
inspector_.debugger_.getProgramState().getEvalResult());
pendingEvalPromise_->setValue(
inspector_.debugger_.getProgramState().getEvalResult());
pendingEvalPromise_.reset();
} break;
case debugger::PauseReason::ScriptLoaded:
inspector_.addCurrentScriptToLoadedScripts();
inspector_.notifyScriptsLoaded();
break;
default:
break;
}
std::unique_ptr<PendingEval> eval;
std::unique_ptr<PendingCommand> resumeOrStep;
while (!eval && !resumeOrStep && !pendingDetach_) {
{
while (!pendingCommand_ && pendingEvals_.empty() &&
pendingFuncs_.empty()) {
/*
* The call to wait temporarily relinquishes the inspector mutex. This
* is safe because no other Paused event handler directly transitions
* out of Paused. So we know that our state is the active state both
* before and after the call to wait. This preserves the invariant that
* the inspector state is not modified during the execution of this
* method.
*/
hasPendingWork_.wait(lock);
}
assert(inspector_.state_.get() == this);
}
if (!pendingEvals_.empty()) {
eval = std::make_unique<PendingEval>(std::move(pendingEvals_.front()));
pendingEvals_.pop();
} else if (pendingCommand_) {
resumeOrStep.swap(pendingCommand_);
}
for (auto &func : pendingFuncs_) {
func();
}
pendingFuncs_.clear();
}
if (pendingDetach_) {
if (pendingEvalPromise_) {
pendingEvalPromise_->setException(NotEnabledException("eval"));
}
if (resumeOrStep) {
resumeOrStep->promise->setValue();
}
pendingDetach_->setValue();
// Send resume so client-side UI doesn't stay stuck at the breakpoint UI
inspector_.observer_.onResume(inspector_);
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::RunningDetached::make(inspector_),
makeContinueCommand());
}
if (eval) {
assert(!pendingEvalPromise_);
pendingEvalPromise_ = eval->promise;
pendingEvalResultTransformer_ = std::move(eval->resultTransformer);
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, std::make_unique<debugger::Command>(std::move(eval->command)));
}
assert(resumeOrStep);
resumeOrStep->promise->setValue();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Running::make(inspector_),
std::make_unique<debugger::Command>(std::move(resumeOrStep->command)));
}
void InspectorState::Paused::detach(
std::shared_ptr<folly::Promise<Unit>> promise) {
pushPendingFunc([this, promise] {
pendingDetach_ = promise;
inspector_.removeAllBreakpoints();
inspector_.resetScriptsLoaded();
});
}
bool InspectorState::Paused::pushPendingFunc(folly::Func func) {
pendingFuncs_.emplace_back(std::move(func));
hasPendingWork_.notify_one();
return true;
}
void InspectorState::Paused::pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<debugger::EvalResult>> promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) {
// Shouldn't allow the client to eval if there's already a pending resume/step
if (pendingCommand_) {
promise->setException(MultipleCommandsPendingException("eval"));
return;
}
PendingEval pendingEval{debugger::Command::eval(src, frameIndex),
promise,
std::move(resultTransformer)};
pendingEvals_.emplace(std::move(pendingEval));
hasPendingWork_.notify_one();
}
void InspectorState::Paused::setPendingCommand(
debugger::Command command,
std::shared_ptr<folly::Promise<Unit>> promise) {
if (pendingCommand_) {
promise->setException(MultipleCommandsPendingException("cmd"));
return;
}
pendingCommand_ =
std::make_unique<PendingCommand>(std::move(command), promise);
hasPendingWork_.notify_one();
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,400 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <utility>
#include <folly/Unit.h>
#include <hermes/inspector/Exceptions.h>
#include <hermes/inspector/Inspector.h>
namespace facebook {
namespace hermes {
namespace inspector {
using NextStatePtr = std::unique_ptr<InspectorState>;
using CommandPtr = std::unique_ptr<facebook::hermes::debugger::Command>;
using MonitorLock = std::unique_lock<std::mutex>;
/**
* InspectorState encapsulates a single state in the Inspector FSM. Events in
* the FSM are modeled as methods in InspectorState.
*
* Some events may cause state transitions. The next state is returned via a
* pointer to the next InspectorState.
*
* We assume that the Inspector's mutex is held across all calls to
* InspectorState methods. For more threading notes, see the Inspector
* implementation.
*/
class InspectorState {
public:
InspectorState(Inspector &inspector) : inspector_(inspector) {}
virtual ~InspectorState() = default;
/**
* onEnter is called when entering the state. prevState may be null when
* transitioning into an initial state.
*/
virtual void onEnter(InspectorState *prevState) {}
/*
* Events that may cause a state transition.
*/
/**
* detach clears all debuger state and transitions to RunningDetached.
*/
virtual void detach(std::shared_ptr<folly::Promise<folly::Unit>> promise) {
// As we're not attached we'd like for the operation to be idempotent
promise->setValue();
}
/**
* didPause handles the didPause callback from the debugger. It takes the lock
* associated with the Inspector's mutex by reference in case we need to
* temporarily relinquish the lock (e.g. via condition_variable::wait).
*/
virtual std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) = 0;
/**
* enable handles the enable event from the client.
*/
virtual std::pair<NextStatePtr, bool> enable() {
return std::make_pair<NextStatePtr, bool>(nullptr, false);
}
/*
* Events that don't cause a state transition.
*/
/**
* pushPendingFunc appends a function to run the next time the debugger
* pauses, either explicitly while paused or implicitly while running.
* Returns false if it's not possible to push a func in this state.
*/
virtual bool pushPendingFunc(folly::Func func) {
return false;
}
/**
* pushPendingEval appends an eval request to run the next time the debugger
* pauses, either explicitly while paused or implicitly while running.
* resultTransformer function will be called with EvalResult before returning
* result so that we can manipulate EvalResult while the VM is paused.
*/
virtual void pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) {
promise->setException(
InvalidStateException("eval", description(), "paused or running"));
}
/**
* setPendingCommand sets a command to break the debugger out of the didPause
* run loop. If it's not possible to set a pending command in this state, the
* promise fails with InvalidStateException. Otherwise, the promise resolves
* to true when the command actually executes.
*/
virtual void setPendingCommand(
debugger::Command command,
std::shared_ptr<folly::Promise<folly::Unit>> promise) {
promise->setException(
InvalidStateException("cmd", description(), "paused"));
}
/**
* pause requests an async pause from the VM.
*/
virtual bool pause() {
return false;
}
/*
* Convenience functions for determining the concrete type and description
* for a state instance without RTTI.
*/
virtual bool isRunningDetached() const {
return false;
}
virtual bool isRunningWaitEnable() const {
return false;
}
virtual bool isRunningWaitPause() const {
return false;
}
virtual bool isPausedWaitEnable() const {
return false;
}
virtual bool isRunning() const {
return false;
}
virtual bool isPaused() const {
return false;
}
virtual const char *description() const = 0;
friend std::ostream &operator<<(
std::ostream &os,
const InspectorState &state);
class RunningDetached;
class RunningWaitEnable;
class RunningWaitPause;
class PausedWaitEnable;
class Running;
class Paused;
protected:
debugger::PauseReason getPauseReason() {
return inspector_.debugger_.getProgramState().getPauseReason();
}
private:
Inspector &inspector_;
};
extern std::ostream &operator<<(std::ostream &os, const InspectorState &state);
/**
* RunningDetached is the initial state when we're associated with a VM that
* initially has no breakpoints.
*/
class InspectorState::RunningDetached : public InspectorState {
public:
static std::unique_ptr<InspectorState> make(Inspector &inspector) {
return std::make_unique<RunningDetached>(inspector);
}
RunningDetached(Inspector &inspector) : InspectorState(inspector) {}
~RunningDetached() {}
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
std::pair<NextStatePtr, bool> enable() override;
bool isRunningDetached() const override {
return true;
}
const char *description() const override {
return "RunningDetached";
}
};
/**
* RunningWaitEnable is the initial state when we're associated with a VM that
* has a breakpoint on the first statement.
*/
class InspectorState::RunningWaitEnable : public InspectorState {
public:
static std::unique_ptr<InspectorState> make(Inspector &inspector) {
return std::make_unique<RunningWaitEnable>(inspector);
}
RunningWaitEnable(Inspector &inspector) : InspectorState(inspector) {}
~RunningWaitEnable() {}
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
std::pair<NextStatePtr, bool> enable() override;
bool isRunningWaitEnable() const override {
return true;
}
const char *description() const override {
return "RunningWaitEnable";
}
};
/**
* RunningWaitPause is the state when we've received enable call, but
* waiting for didPause because we need to pause on the first statement.
*/
class InspectorState::RunningWaitPause : public InspectorState {
public:
static std::unique_ptr<InspectorState> make(Inspector &inspector) {
return std::make_unique<RunningWaitPause>(inspector);
}
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
RunningWaitPause(Inspector &inspector) : InspectorState(inspector) {}
~RunningWaitPause() {}
bool isRunningWaitPause() const override {
return true;
}
const char *description() const override {
return "RunningWaitPause";
}
};
/**
* PausedWaitEnable is the state when we're in a didPause callback and we're
* waiting for the client to call enable.
*/
class InspectorState::PausedWaitEnable : public InspectorState {
public:
static std::unique_ptr<InspectorState> make(Inspector &inspector) {
return std::make_unique<PausedWaitEnable>(inspector);
}
PausedWaitEnable(Inspector &inspector) : InspectorState(inspector) {}
~PausedWaitEnable() {}
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
std::pair<NextStatePtr, bool> enable() override;
bool isPausedWaitEnable() const override {
return true;
}
const char *description() const override {
return "PausedWaitEnable";
}
private:
bool enabled_ = false;
std::condition_variable enabledCondition_;
};
/**
* PendingEval holds an eval command and a promise that is fulfilled with the
* eval result.
*/
struct PendingEval {
debugger::Command command;
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise;
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer;
};
/**
* Running is the state when we're enabled and not currently paused, e.g. when
* we're actively executing JS.
*
* Note that we can be in the running state even if we're not actively running
* JS. For instance, React Native could be blocked in a native message queue
* waiting for the next message to process outside of the call in to Hermes.
* That still counts as Running in this FSM.
*/
class InspectorState::Running : public InspectorState {
public:
static std::unique_ptr<InspectorState> make(Inspector &inspector) {
return std::make_unique<Running>(inspector);
}
Running(Inspector &inspector) : InspectorState(inspector) {}
~Running() {}
void onEnter(InspectorState *prevState) override;
void detach(std::shared_ptr<folly::Promise<folly::Unit>> promise) override;
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
bool pushPendingFunc(folly::Func func) override;
void pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) override;
bool pause() override;
bool isRunning() const override {
return true;
}
const char *description() const override {
return "Running";
}
private:
std::vector<folly::Func> pendingFuncs_;
std::queue<PendingEval> pendingEvals_;
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
pendingEvalPromise_;
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
pendingEvalResultTransformer_;
std::shared_ptr<folly::Promise<folly::Unit>> pendingDetach_;
};
/**
* PendingCommand holds a resume or step command and a promise that is fulfilled
* just before the debugger resumes or steps.
*/
struct PendingCommand {
PendingCommand(
debugger::Command command,
std::shared_ptr<folly::Promise<folly::Unit>> promise)
: command(std::move(command)), promise(promise) {}
debugger::Command command;
std::shared_ptr<folly::Promise<folly::Unit>> promise;
};
/**
* Paused is the state when we're enabled and and currently in a didPause
* callback.
*/
class InspectorState::Paused : public InspectorState {
public:
static std::unique_ptr<InspectorState> make(Inspector &inspector) {
return std::make_unique<Paused>(inspector);
}
Paused(Inspector &inspector) : InspectorState(inspector) {}
~Paused() {}
void onEnter(InspectorState *prevState) override;
void detach(std::shared_ptr<folly::Promise<folly::Unit>> promise) override;
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
bool pushPendingFunc(folly::Func func) override;
void pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) override;
void setPendingCommand(
debugger::Command command,
std::shared_ptr<folly::Promise<folly::Unit>> promise) override;
bool isPaused() const override {
return true;
}
const char *description() const override {
return "Paused";
}
private:
std::condition_variable hasPendingWork_;
std::vector<folly::Func> pendingFuncs_;
std::queue<PendingEval> pendingEvals_;
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
pendingEvalPromise_;
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
pendingEvalResultTransformer_;
std::unique_ptr<PendingCommand> pendingCommand_;
std::shared_ptr<folly::Promise<folly::Unit>> pendingDetach_;
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
+113
View File
@@ -0,0 +1,113 @@
hermes-inspector provides a bridge between the low-level debugging API exposed
by Hermes and higher-level debugging protocols such as the Chrome DevTools
protocol.
# Targets
- chrome: classes that implement the Chrome DevTools Protocol adapter. Sits on
top of classes provided by the inspector target.
- detail: utility classes and functions
- inspector: protocol-independent classes that sit on top of the low-level
Hermes debugging API.
# Testing
Tests are implemented using gtest. Debug logging is enabled for tests, and you
can get debug logs to show even when tests are passing by running the test
executable directly:
```
$ buck build fbsource//xplat/hermes-inspector:chrome-tests
$ buck-out/gen/hermes-inspector/chrome-tests
[...]
```
You can use standard gtest filters to only execute a particular set of tests:
```
$ buck-out/gen/hermes-inspector/chrome-tests \
--gtest_filter='ConnectionTests.testSetBreakpoint'
```
You can debug the tests using lldb or gdb:
```
$ lldb buck-out/gen/hermes-inspector/chrome-tests
$ gdb buck-out/gen/hermes-inspector/chrome-tests
```
# Formatting
Make sure the code is formatted using the hermes clang-format rules before
committing:
```
$ xplat/hermes-inspector/tools/format
```
We follow the clang format rules used by the rest of the Hermes project.
# Adding Support For New Message Types
To add support for a new Chrome DevTools protocol message, add the message you
want to add to tools/message_types.txt, and re-run the message types generator:
```
$ xplat/hermes-inspector/tools/run_msggen
```
This will generate C++ structs for the new message type in
`chrome/MessageTypes.{h,cpp}`.
You'll then need to:
1. Implement a message handler for the new message type in `chrome::Connection`.
2. Implement a public API for the new message type in `Inspector`. This will
most likely return a `folly::Future` that the message handler in (1) can use
for chaining.
3. Implement a private API for the new message type in `Inspector` that performs
the logic in Inspector's executor. (Inspector.cpp contains a comment
explaining why the executor is necessary.)
4. Optionally, implement a method for the new message type in `InspectorState`.
In most cases this is probably not necessary--one of the existing methods in
`InspectorState` will work.
For a diff that illustrates these steps, take a look at D6601459.
# Testing Integration With Nuclide and Apps
For now, the quickest way to use hermes-inspector in an app is with Eats. First,
make sure the packager is running:
```
$ js1 run
```
Then, on Android, build the fbeats target:
```
$ buck install --run fbeats
```
On iOS, build the `//Apps/Internal/Eats:Eats` target:
```
$ buck install --run //Apps/Internal/Eats:Eats
```
You can also build `Eats` in Xcode using `arc focus` if you prefer an
IDE:
```
$ arc focus --force-build \
-b //Apps/Internal/Eats:Eats \
cxxreact fbsource//xplat/hermes/API:HermesAPI fbsource//xplat/hermes/lib/VM:VM jsi \
jsinspector hermes-inspector FBReactKit FBReactModule FBCatalystWrapper \
fbsource//xplat/js:React fbsource//xplat/js/react-native-github:ReactInternal
```
For all the above commands, if you want to build the inspector `-O0` for better
debug info, add the argument `--config hermes.build_mode=dbg`.
You should then be able to launch the app and see it listed in the list of
Mobile JS contexts in the Nuclide debugger.
@@ -0,0 +1,25 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "RuntimeAdapter.h"
namespace facebook {
namespace hermes {
namespace inspector {
RuntimeAdapter::~RuntimeAdapter() = default;
void RuntimeAdapter::tickleJs() {}
SharedRuntimeAdapter::SharedRuntimeAdapter(
std::shared_ptr<HermesRuntime> runtime)
: runtime_(std::move(runtime)) {}
SharedRuntimeAdapter::~SharedRuntimeAdapter() = default;
HermesRuntime &SharedRuntimeAdapter::getRuntime() {
return *runtime_;
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,59 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <hermes/hermes.h>
namespace facebook {
namespace hermes {
namespace inspector {
/**
* RuntimeAdapter encapsulates a HermesRuntime object. The underlying Hermes
* runtime object should stay alive for at least as long as the RuntimeAdapter
* is alive.
*/
class RuntimeAdapter {
public:
virtual ~RuntimeAdapter() = 0;
/// getRuntime should return the Hermes runtime encapsulated by this adapter.
virtual HermesRuntime &getRuntime() = 0;
/// tickleJs is a method that subclasses can choose to override to make the
/// inspector more responsive. If overridden, it should call the "__tickleJs"
/// function. The call should occur with appropriate locking (e.g. via a
/// thread-safe runtime instance, or by enqueuing the call on to a dedicated
/// JS thread).
///
/// This makes the inspector more responsive because it gives the inspector
/// the ability to force the process to enter the Hermes interpreter loop
/// soon. This is important because the inspector can only do a number of
/// important operations (like manipulating breakpoints) within the context of
/// a Hermes interperter loop.
///
/// The default implementation does nothing.
virtual void tickleJs();
};
/**
* SharedRuntimeAdapter is a simple implementation of RuntimeAdapter that
* uses shared_ptr to hold on to the runtime. It's generally only used in tests,
* since it does not implement tickleJs.
*/
class SharedRuntimeAdapter : public RuntimeAdapter {
public:
SharedRuntimeAdapter(std::shared_ptr<HermesRuntime> runtime);
virtual ~SharedRuntimeAdapter();
HermesRuntime &getRuntime() override;
private:
std::shared_ptr<HermesRuntime> runtime_;
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,127 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "AutoAttachUtils.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <folly/String.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
// The following code is copied from
// https://phabricator.intern.facebook.com/diffusion/FBS/browse/master/xplat/js/react-native-github/ReactCommon/cxxreact/JSCExecutor.cpp;431c4d01b7072d9a1a52f8bd6c6ba2ff3e47e25d$250
bool isNetworkInspected(
const std::string &owner,
const std::string &app,
const std::string &device) {
auto connect_socket = [](int socket_desc, std::string address, int port) {
if (socket_desc < 0) {
close(socket_desc);
return false;
}
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
auto sock_opt_rcv_resp = setsockopt(
socket_desc,
SOL_SOCKET,
SO_RCVTIMEO,
(const char *)&tv,
sizeof(struct timeval));
if (sock_opt_rcv_resp < 0) {
close(socket_desc);
return false;
}
auto sock_opt_snd_resp = setsockopt(
socket_desc,
SOL_SOCKET,
SO_SNDTIMEO,
(const char *)&tv,
sizeof(struct timeval));
if (sock_opt_snd_resp < 0) {
close(socket_desc);
return false;
}
struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr(address.c_str());
server.sin_family = AF_INET;
server.sin_port = htons(port);
auto connect_resp =
::connect(socket_desc, (struct sockaddr *)&server, sizeof(server));
if (connect_resp < 0) {
::close(socket_desc);
return false;
}
return true;
};
int socket_desc = socket(AF_INET, SOCK_STREAM, 0);
if (!connect_socket(socket_desc, "127.0.0.1", 8082)) {
#if defined(__ANDROID__)
socket_desc = socket(AF_INET, SOCK_STREAM, 0);
if (!connect_socket(socket_desc, "10.0.2.2", 8082) /* emulator */) {
socket_desc = socket(AF_INET, SOCK_STREAM, 0);
if (!connect_socket(socket_desc, "10.0.3.2", 8082) /* genymotion */) {
return false;
}
}
#else //! defined(__ANDROID__)
return false;
#endif // defined(__ANDROID__)
}
std::string escapedOwner =
folly::uriEscape<std::string>(owner, folly::UriEscapeMode::QUERY);
std::string escapedApp =
folly::uriEscape<std::string>(app, folly::UriEscapeMode::QUERY);
std::string escapedDevice =
folly::uriEscape<std::string>(device, folly::UriEscapeMode::QUERY);
std::string msg = folly::to<std::string>(
"GET /autoattach?title=",
escapedOwner,
"&app=",
escapedApp,
"&device=",
escapedDevice,
" HTTP/1.1\r\n\r\n");
auto send_resp = ::send(socket_desc, msg.c_str(), msg.length(), 0);
if (send_resp < 0) {
close(socket_desc);
return false;
}
char server_reply[200];
server_reply[199] = '\0';
auto recv_resp =
::recv(socket_desc, server_reply, sizeof(server_reply) - 1, 0);
if (recv_resp < 0) {
close(socket_desc);
return false;
}
std::string response(server_reply);
if (response.size() < 25) {
close(socket_desc);
return false;
}
auto responseCandidate = response.substr(response.size() - 25);
auto found =
responseCandidate.find("{\"autoattach\":true}") != std::string::npos;
close(socket_desc);
return found;
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,19 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <string>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
bool isNetworkInspected(
const std::string &owner,
const std::string &app,
const std::string &device);
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,668 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "Connection.h"
#include <cstdlib>
#include <mutex>
#include <folly/Conv.h>
#include <folly/Executor.h>
#include <folly/Function.h>
#include <glog/logging.h>
#include <hermes/inspector/Inspector.h>
#include <hermes/inspector/chrome/MessageConverters.h>
#include <hermes/inspector/chrome/RemoteObjectsTable.h>
#include <hermes/inspector/detail/SerialExecutor.h>
#include <hermes/inspector/detail/Thread.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
using ::facebook::react::ILocalConnection;
using ::facebook::react::IRemoteConnection;
using ::folly::Unit;
namespace debugger = ::facebook::hermes::debugger;
namespace inspector = ::facebook::hermes::inspector;
namespace m = ::facebook::hermes::inspector::chrome::message;
/*
* Connection::Impl
*/
class Connection::Impl : public inspector::InspectorObserver,
public message::RequestHandler {
public:
Impl(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title,
bool waitForDebugger);
~Impl();
HermesRuntime &getRuntime();
std::string getTitle() const;
bool connect(std::unique_ptr<IRemoteConnection> remoteConn);
bool disconnect();
void sendMessage(std::string str);
/* InspectorObserver overrides */
void onBreakpointResolved(
Inspector &inspector,
const debugger::BreakpointInfo &info) override;
void onContextCreated(Inspector &inspector) override;
void onPause(Inspector &inspector, const debugger::ProgramState &state)
override;
void onResume(Inspector &inspector) override;
void onScriptParsed(Inspector &inspector, const ScriptInfo &info) override;
void onMessageAdded(Inspector &inspector, const ConsoleMessageInfo &info)
override;
/* RequestHandler overrides */
void handle(const m::UnknownRequest &req) override;
void handle(const m::debugger::DisableRequest &req) override;
void handle(const m::debugger::EnableRequest &req) override;
void handle(const m::debugger::EvaluateOnCallFrameRequest &req) override;
void handle(const m::debugger::PauseRequest &req) override;
void handle(const m::debugger::RemoveBreakpointRequest &req) override;
void handle(const m::debugger::ResumeRequest &req) override;
void handle(const m::debugger::SetBreakpointByUrlRequest &req) override;
void handle(const m::debugger::SetPauseOnExceptionsRequest &req) override;
void handle(const m::debugger::StepIntoRequest &req) override;
void handle(const m::debugger::StepOutRequest &req) override;
void handle(const m::debugger::StepOverRequest &req) override;
void handle(const m::runtime::EvaluateRequest &req) override;
void handle(const m::runtime::GetPropertiesRequest &req) override;
private:
std::vector<m::runtime::PropertyDescriptor> makePropsFromScope(
std::pair<uint32_t, uint32_t> frameAndScopeIndex,
const std::string &objectGroup,
const debugger::ProgramState &state);
std::vector<m::runtime::PropertyDescriptor> makePropsFromValue(
const jsi::Value &value,
const std::string &objectGroup,
bool onlyOwnProperties);
void sendToClient(const std::string &str);
void sendResponseToClient(const m::Response &resp);
folly::Function<void(const std::exception &)> sendErrorToClient(int id);
void sendResponseToClientViaExecutor(int id);
void sendResponseToClientViaExecutor(folly::Future<Unit> future, int id);
void sendNotificationToClientViaExecutor(const m::Notification &note);
std::shared_ptr<RuntimeAdapter> runtimeAdapter_;
std::string title_;
// connected_ is protected by connectionMutex_.
std::mutex connectionMutex_;
bool connected_;
// parsedScripts_ list stores file names of all scripts that have been
// parsed so that we could find script's file name by regex.
// This is similar to Inspector's loadedScripts_ map but we want to
// store this info here because searching file name that matches
// given regex (on setBreakpointByUrl command) is more related to Chrome
// protocol than to Hermes inspector.
// Access is protected by parsedScriptsMutex_.
std::mutex parsedScriptsMutex_;
std::vector<std::string> parsedScripts_;
// The rest of these member variables are only accessed via executor_.
std::unique_ptr<folly::Executor> executor_;
std::unique_ptr<IRemoteConnection> remoteConn_;
std::shared_ptr<inspector::Inspector> inspector_;
// objTable_ is protected by the inspector lock. It should only be accessed
// when the VM is paused, e.g. in an InspectorObserver callback or in an
// executeIfEnabled callback.
RemoteObjectsTable objTable_;
};
Connection::Impl::Impl(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title,
bool waitForDebugger)
: runtimeAdapter_(std::move(adapter)),
title_(title),
connected_(false),
executor_(std::make_unique<inspector::detail::SerialExecutor>(
"hermes-chrome-inspector-conn")),
remoteConn_(nullptr),
inspector_(std::make_shared<inspector::Inspector>(
runtimeAdapter_,
*this,
waitForDebugger)) {
inspector_->installLogHandler();
}
Connection::Impl::~Impl() = default;
HermesRuntime &Connection::Impl::getRuntime() {
return runtimeAdapter_->getRuntime();
}
std::string Connection::Impl::getTitle() const {
return title_;
}
bool Connection::Impl::connect(std::unique_ptr<IRemoteConnection> remoteConn) {
assert(remoteConn);
std::lock_guard<std::mutex> lock(connectionMutex_);
if (connected_) {
return false;
}
connected_ = true;
executor_->add([this, remoteConn = std::move(remoteConn)]() mutable {
remoteConn_ = std::move(remoteConn);
});
return true;
}
bool Connection::Impl::disconnect() {
std::lock_guard<std::mutex> lock(connectionMutex_);
if (!connected_) {
return false;
}
connected_ = false;
inspector_->disable().via(executor_.get()).thenValue([this](auto &&) {
// HACK: We purposely call RemoteConnection::onDisconnect on a *different*
// rather than on this thread (the executor thread). This is to prevent this
// scenario:
//
// 1. RemoteConnection::onDisconnect runs on the executor thread
// 2. onDisconnect through a long chain of calls causes the Connection
// destructor to run
// 3. The Connection destructor causes the SerialExecutor destructor to run.
// 4. The SerialExecutor destructor waits for all outstanding work items to
// finish via a call to join().
// 5. join() fails, since the executor thread is trying to join against
// itself.
//
// To prevent this chain of events, we always call onDisconnect on a
// different thread.
//
// See P59135203 for an example stack trace.
//
// One more hack: we use release() and delete instead of unique_ptr because
// detail::Thread expects a std::function, and std::function cannot capture
// move-only types like unique_ptr.
auto conn = remoteConn_.release();
inspector::detail::Thread disconnectLaterThread{
"hermes-chrome-inspector-conn-disconnect", [conn] {
conn->onDisconnect();
delete conn;
}};
disconnectLaterThread.detach();
});
return true;
}
void Connection::Impl::sendMessage(std::string str) {
executor_->add([this, str = std::move(str)]() mutable {
folly::Try<std::unique_ptr<m::Request>> maybeReq =
m::Request::fromJson(str);
if (maybeReq.hasException()) {
LOG(ERROR) << "Invalid request `" << str
<< "`: " << maybeReq.exception().what();
return;
}
auto &req = maybeReq.value();
if (req) {
req->accept(*this);
}
});
}
/*
* InspectorObserver overrides
*/
void Connection::Impl::onBreakpointResolved(
Inspector &inspector,
const debugger::BreakpointInfo &info) {
m::debugger::BreakpointResolvedNotification note;
note.breakpointId = folly::to<std::string>(info.id);
note.location = m::debugger::makeLocation(info.resolvedLocation);
sendNotificationToClientViaExecutor(note);
}
void Connection::Impl::onContextCreated(Inspector &inspector) {
// Right now, Hermes only has the notion of one JS context per VM instance,
// so we just always name the single JS context with id=1 and name=hermes.
m::runtime::ExecutionContextCreatedNotification note;
note.context.id = 1;
note.context.name = "hermes";
// isDefault and isPageContext are custom properties that the legacy RN to
// JSC adapter set for some unknown reason.
note.context.isDefault = true;
note.context.isPageContext = true;
sendNotificationToClientViaExecutor(note);
}
void Connection::Impl::onPause(
Inspector &inspector,
const debugger::ProgramState &state) {
m::debugger::PausedNotification note;
note.callFrames = m::debugger::makeCallFrames(state, objTable_, getRuntime());
switch (state.getPauseReason()) {
case debugger::PauseReason::Breakpoint:
// use other, chrome protocol has no reason specifically for breakpoints
note.reason = "other";
// TODO: hermes hasn't implemented ProgramState::getBreakpoint yet
#if HERMES_SUPPORTS_STATE_GET_BREAKPOINT
note.hitBreakpoints = std::vector<m::debugger::BreakpointId>();
note.hitBreakpoints->emplace_back(
folly::to<std::string>(state.getBreakpoint()));
#endif
break;
case debugger::PauseReason::Exception:
note.reason = "exception";
break;
default:
note.reason = "other";
break;
}
sendNotificationToClientViaExecutor(note);
}
void Connection::Impl::onResume(Inspector &inspector) {
objTable_.releaseObjectGroup(BacktraceObjectGroup);
m::debugger::ResumedNotification note;
sendNotificationToClientViaExecutor(note);
}
void Connection::Impl::onScriptParsed(
Inspector &inspector,
const ScriptInfo &info) {
m::debugger::ScriptParsedNotification note;
note.scriptId = folly::to<std::string>(info.fileId);
note.url = info.fileName;
if (!info.sourceMappingUrl.empty()) {
note.sourceMapURL = info.sourceMappingUrl;
}
{
std::lock_guard<std::mutex> lock(parsedScriptsMutex_);
parsedScripts_.push_back(info.fileName);
}
sendNotificationToClientViaExecutor(note);
}
void Connection::Impl::onMessageAdded(
facebook::hermes::inspector::Inspector &inspector,
const ConsoleMessageInfo &info) {
m::runtime::ConsoleAPICalledNotification apiCalledNote;
apiCalledNote.type = info.level;
size_t argsSize = info.args.size(getRuntime());
for (size_t index = 0; index < argsSize; ++index) {
apiCalledNote.args.push_back(m::runtime::makeRemoteObject(
getRuntime(),
info.args.getValueAtIndex(getRuntime(), index),
objTable_,
"ConsoleObjectGroup"));
}
sendNotificationToClientViaExecutor(apiCalledNote);
}
/*
* RequestHandler overrides
*/
void Connection::Impl::handle(const m::UnknownRequest &req) {
LOG(INFO) << "responding ok to unknown request: " << req.toDynamic();
sendResponseToClientViaExecutor(req.id);
}
void Connection::Impl::handle(const m::debugger::DisableRequest &req) {
sendResponseToClientViaExecutor(inspector_->disable(), req.id);
}
void Connection::Impl::handle(const m::debugger::EnableRequest &req) {
sendResponseToClientViaExecutor(inspector_->enable(), req.id);
}
void Connection::Impl::handle(
const m::debugger::EvaluateOnCallFrameRequest &req) {
auto remoteObjPtr = std::make_shared<m::runtime::RemoteObject>();
inspector_
->evaluate(
atoi(req.callFrameId.c_str()),
req.expression,
[this, remoteObjPtr, objectGroup = req.objectGroup](
const facebook::hermes::debugger::EvalResult
&evalResult) mutable {
*remoteObjPtr = m::runtime::makeRemoteObject(
getRuntime(),
evalResult.value,
objTable_,
objectGroup.value_or(""));
})
.via(executor_.get())
.thenValue(
[this, id = req.id, remoteObjPtr](debugger::EvalResult result) {
m::debugger::EvaluateOnCallFrameResponse resp;
resp.id = id;
if (result.isException) {
resp.exceptionDetails =
m::runtime::makeExceptionDetails(result.exceptionDetails);
} else {
resp.result = *remoteObjPtr;
}
sendResponseToClient(resp);
})
.thenError<std::exception>(sendErrorToClient(req.id));
}
void Connection::Impl::handle(const m::runtime::EvaluateRequest &req) {
auto remoteObjPtr = std::make_shared<m::runtime::RemoteObject>();
inspector_
->evaluate(
0, // Top of the stackframe
req.expression,
[this, remoteObjPtr, objectGroup = req.objectGroup](
const facebook::hermes::debugger::EvalResult
&evalResult) mutable {
*remoteObjPtr = m::runtime::makeRemoteObject(
getRuntime(),
evalResult.value,
objTable_,
objectGroup.value_or("ConsoleObjectGroup"));
})
.via(executor_.get())
.thenValue(
[this, id = req.id, remoteObjPtr](debugger::EvalResult result) {
m::debugger::EvaluateOnCallFrameResponse resp;
resp.id = id;
if (result.isException) {
resp.exceptionDetails =
m::runtime::makeExceptionDetails(result.exceptionDetails);
} else {
resp.result = *remoteObjPtr;
}
sendResponseToClient(resp);
})
.thenError<std::exception>(sendErrorToClient(req.id));
}
void Connection::Impl::handle(const m::debugger::PauseRequest &req) {
sendResponseToClientViaExecutor(inspector_->pause(), req.id);
}
void Connection::Impl::handle(const m::debugger::RemoveBreakpointRequest &req) {
auto breakpointId = folly::to<debugger::BreakpointID>(req.breakpointId);
sendResponseToClientViaExecutor(
inspector_->removeBreakpoint(breakpointId), req.id);
}
void Connection::Impl::handle(const m::debugger::ResumeRequest &req) {
sendResponseToClientViaExecutor(inspector_->resume(), req.id);
}
void Connection::Impl::handle(
const m::debugger::SetBreakpointByUrlRequest &req) {
debugger::SourceLocation loc;
{
std::lock_guard<std::mutex> lock(parsedScriptsMutex_);
setHermesLocation(loc, req, parsedScripts_);
}
inspector_->setBreakpoint(loc, req.condition)
.via(executor_.get())
.thenValue([this, id = req.id](debugger::BreakpointInfo info) {
m::debugger::SetBreakpointByUrlResponse resp;
resp.id = id;
resp.breakpointId = folly::to<std::string>(info.id);
if (info.resolved) {
resp.locations.emplace_back(
m::debugger::makeLocation(info.resolvedLocation));
}
sendResponseToClient(resp);
})
.thenError<std::exception>(sendErrorToClient(req.id));
}
void Connection::Impl::handle(
const m::debugger::SetPauseOnExceptionsRequest &req) {
debugger::PauseOnThrowMode mode = debugger::PauseOnThrowMode::None;
if (req.state == "none") {
mode = debugger::PauseOnThrowMode::None;
} else if (req.state == "all") {
mode = debugger::PauseOnThrowMode::All;
} else if (req.state == "uncaught") {
mode = debugger::PauseOnThrowMode::Uncaught;
} else {
sendErrorToClient(req.id);
}
sendResponseToClientViaExecutor(
inspector_->setPauseOnExceptions(mode), req.id);
}
void Connection::Impl::handle(const m::debugger::StepIntoRequest &req) {
sendResponseToClientViaExecutor(inspector_->stepIn(), req.id);
}
void Connection::Impl::handle(const m::debugger::StepOutRequest &req) {
sendResponseToClientViaExecutor(inspector_->stepOut(), req.id);
}
void Connection::Impl::handle(const m::debugger::StepOverRequest &req) {
sendResponseToClientViaExecutor(inspector_->stepOver(), req.id);
}
std::vector<m::runtime::PropertyDescriptor>
Connection::Impl::makePropsFromScope(
std::pair<uint32_t, uint32_t> frameAndScopeIndex,
const std::string &objectGroup,
const debugger::ProgramState &state) {
std::vector<m::runtime::PropertyDescriptor> result;
uint32_t frameIndex = frameAndScopeIndex.first;
uint32_t scopeIndex = frameAndScopeIndex.second;
debugger::LexicalInfo lexicalInfo = state.getLexicalInfo(frameIndex);
uint32_t varCount = lexicalInfo.getVariablesCountInScope(scopeIndex);
for (uint32_t varIndex = 0; varIndex < varCount; varIndex++) {
debugger::VariableInfo varInfo =
state.getVariableInfo(frameIndex, scopeIndex, varIndex);
m::runtime::PropertyDescriptor desc;
desc.name = varInfo.name;
desc.value = m::runtime::makeRemoteObject(
getRuntime(), varInfo.value, objTable_, objectGroup);
result.emplace_back(std::move(desc));
}
return result;
}
std::vector<m::runtime::PropertyDescriptor>
Connection::Impl::makePropsFromValue(
const jsi::Value &value,
const std::string &objectGroup,
bool onlyOwnProperties) {
std::vector<m::runtime::PropertyDescriptor> result;
if (value.isObject()) {
HermesRuntime &runtime = getRuntime();
jsi::Object obj = value.getObject(runtime);
// TODO(hypuk): obj.getPropertyNames only returns enumerable properties.
jsi::Array propNames = onlyOwnProperties
? runtime.global()
.getPropertyAsObject(runtime, "Object")
.getPropertyAsFunction(runtime, "getOwnPropertyNames")
.call(runtime, obj)
.getObject(runtime)
.getArray(runtime)
: obj.getPropertyNames(runtime);
size_t propCount = propNames.length(runtime);
for (size_t i = 0; i < propCount; i++) {
jsi::String propName =
propNames.getValueAtIndex(runtime, i).getString(runtime);
m::runtime::PropertyDescriptor desc;
desc.name = propName.utf8(runtime);
jsi::Value propValue = obj.getProperty(runtime, propName);
desc.value = m::runtime::makeRemoteObject(
runtime, propValue, objTable_, objectGroup);
result.emplace_back(std::move(desc));
}
if (onlyOwnProperties) {
jsi::Value proto = runtime.global()
.getPropertyAsObject(runtime, "Object")
.getPropertyAsFunction(runtime, "getPrototypeOf")
.call(runtime, obj);
if (!proto.isNull()) {
m::runtime::PropertyDescriptor desc;
desc.name = "__proto__";
desc.value = m::runtime::makeRemoteObject(
runtime, proto, objTable_, objectGroup);
result.emplace_back(std::move(desc));
}
}
}
return result;
}
void Connection::Impl::handle(const m::runtime::GetPropertiesRequest &req) {
auto resp = std::make_shared<m::runtime::GetPropertiesResponse>();
resp->id = req.id;
inspector_
->executeIfEnabled(
"Runtime.getProperties",
[this, req, resp](const debugger::ProgramState &state) {
std::string objGroup = objTable_.getObjectGroup(req.objectId);
auto scopePtr = objTable_.getScope(req.objectId);
auto valuePtr = objTable_.getValue(req.objectId);
if (scopePtr != nullptr) {
resp->result = makePropsFromScope(*scopePtr, objGroup, state);
} else if (valuePtr != nullptr) {
resp->result = makePropsFromValue(
*valuePtr, objGroup, req.ownProperties.value_or(true));
}
})
.via(executor_.get())
.thenValue([this, resp](auto &&) { sendResponseToClient(*resp); })
.thenError<std::exception>(sendErrorToClient(req.id));
}
/*
* Send-to-client methods
*/
void Connection::Impl::sendToClient(const std::string &str) {
if (remoteConn_) {
remoteConn_->onMessage(str);
}
}
void Connection::Impl::sendResponseToClient(const m::Response &resp) {
sendToClient(resp.toJson());
}
folly::Function<void(const std::exception &)>
Connection::Impl::sendErrorToClient(int id) {
return [this, id](const std::exception &e) {
sendResponseToClient(
m::makeErrorResponse(id, m::ErrorCode::ServerError, e.what()));
};
}
void Connection::Impl::sendResponseToClientViaExecutor(int id) {
sendResponseToClientViaExecutor(folly::makeFuture(), id);
}
void Connection::Impl::sendResponseToClientViaExecutor(
folly::Future<Unit> future,
int id) {
future.via(executor_.get())
.thenValue([this, id](const Unit &unit) {
sendResponseToClient(m::makeOkResponse(id));
})
.thenError<std::exception>(sendErrorToClient(id));
}
void Connection::Impl::sendNotificationToClientViaExecutor(
const m::Notification &note) {
executor_->add(
[this, noteJson = note.toJson()]() { sendToClient(noteJson); });
}
/*
* Connection
*/
Connection::Connection(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title,
bool waitForDebugger)
: impl_(
std::make_unique<Impl>(std::move(adapter), title, waitForDebugger)) {}
Connection::~Connection() = default;
HermesRuntime &Connection::getRuntime() {
return impl_->getRuntime();
}
std::string Connection::getTitle() const {
return impl_->getTitle();
}
bool Connection::connect(std::unique_ptr<IRemoteConnection> remoteConn) {
return impl_->connect(std::move(remoteConn));
}
bool Connection::disconnect() {
return impl_->disconnect();
}
void Connection::sendMessage(std::string str) {
impl_->sendMessage(std::move(str));
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,58 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/MessageTypes.h>
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
/// Connection is a duplex connection between the client and the debugger.
class Connection {
public:
/// Connection constructor enables the debugger on the provided runtime. This
/// should generally called before you start running any JS in the runtime.
Connection(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title,
bool waitForDebugger = false);
~Connection();
/// getRuntime returns the underlying runtime being debugged.
HermesRuntime &getRuntime();
/// getTitle returns the name of the friendly name of the runtime that's shown
/// to users in Nuclide.
std::string getTitle() const;
/// connect attaches this connection to the runtime's debugger. Requests to
/// the debugger sent via send(). Replies and notifications from the debugger
/// are sent back to the client via IRemoteConnection::onMessage.
bool connect(
std::unique_ptr<::facebook::react::IRemoteConnection> remoteConn);
/// disconnect disconnects this connection from the runtime's debugger
bool disconnect();
/// sendMessage delivers a JSON-encoded Chrome DevTools Protocol request to
/// the debugger.
void sendMessage(std::string str);
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,137 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "ConnectionDemux.h"
#include "AutoAttachUtils.h"
#include "Connection.h"
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
using ::facebook::react::IInspector;
using ::facebook::react::ILocalConnection;
using ::facebook::react::IRemoteConnection;
namespace {
class LocalConnection : public ILocalConnection {
public:
LocalConnection(
std::shared_ptr<Connection> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts);
~LocalConnection();
void sendMessage(std::string message) override;
void disconnect() override;
private:
std::shared_ptr<Connection> conn_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
LocalConnection::LocalConnection(
std::shared_ptr<Connection> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts)
: conn_(conn), inspectedContexts_(inspectedContexts) {
inspectedContexts_->insert(conn->getTitle());
}
LocalConnection::~LocalConnection() = default;
void LocalConnection::sendMessage(std::string str) {
conn_->sendMessage(std::move(str));
}
void LocalConnection::disconnect() {
inspectedContexts_->erase(conn_->getTitle());
conn_->disconnect();
}
} // namespace
ConnectionDemux::ConnectionDemux(facebook::react::IInspector &inspector)
: globalInspector_(inspector),
inspectedContexts_(std::make_shared<std::unordered_set<std::string>>()) {}
ConnectionDemux::~ConnectionDemux() = default;
int ConnectionDemux::enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title) {
std::lock_guard<std::mutex> lock(mutex_);
// TODO(#22976087): workaround for ComponentScript contexts never being
// destroyed.
//
// After a reload, the old ComponentScript VM instance stays alive. When we
// register the new CS VM instance, check for any previous CS VM (via strcmp
// of title) and remove them.
std::vector<int> pagesToDelete;
for (auto it = conns_.begin(); it != conns_.end(); ++it) {
if (it->second->getTitle() == title) {
pagesToDelete.push_back(it->first);
}
}
for (auto pageId : pagesToDelete) {
removePage(pageId);
}
// TODO(hypuk): Provide real app and device names.
auto waitForDebugger =
(inspectedContexts_->find(title) != inspectedContexts_->end()) ||
isNetworkInspected(title, "app_name", "device_name");
return addPage(
std::make_shared<Connection>(std::move(adapter), title, waitForDebugger));
}
void ConnectionDemux::disableDebugging(HermesRuntime &runtime) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto &it : conns_) {
int pageId = it.first;
auto &conn = it.second;
if (&(conn->getRuntime()) == &runtime) {
removePage(pageId);
// must break here. removePage mutates conns_, so range-for iterator is
// now invalid.
break;
}
}
}
int ConnectionDemux::addPage(std::shared_ptr<Connection> conn) {
auto connectFunc = [conn, this](std::unique_ptr<IRemoteConnection> remoteConn)
-> std::unique_ptr<ILocalConnection> {
if (!conn->connect(std::move(remoteConn))) {
return nullptr;
}
return std::make_unique<LocalConnection>(conn, inspectedContexts_);
};
int pageId = globalInspector_.addPage(
conn->getTitle(), "Hermes", std::move(connectFunc));
conns_[pageId] = std::move(conn);
return pageId;
}
void ConnectionDemux::removePage(int pageId) {
globalInspector_.removePage(pageId);
auto conn = conns_.at(pageId);
conn->disconnect();
conns_.erase(pageId);
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,52 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/Connection.h>
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
/*
* ConnectionDemux keeps track of all debuggable Hermes runtimes (called
* "pages" in the higher-level React Native API) in this process. See
* Registration.h for documentation of the public API.
*/
class ConnectionDemux {
public:
explicit ConnectionDemux(facebook::react::IInspector &inspector);
~ConnectionDemux();
ConnectionDemux(const ConnectionDemux &) = delete;
ConnectionDemux &operator=(const ConnectionDemux &) = delete;
int enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title);
void disableDebugging(HermesRuntime &runtime);
private:
int addPage(std::shared_ptr<Connection> conn);
void removePage(int pageId);
facebook::react::IInspector &globalInspector_;
std::mutex mutex_;
std::unordered_map<int, std::shared_ptr<Connection>> conns_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,215 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "MessageConverters.h"
#include <cmath>
#include <limits>
#include <folly/Conv.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace h = ::facebook::hermes;
namespace m = ::facebook::hermes::inspector::chrome::message;
m::ErrorResponse
m::makeErrorResponse(int id, m::ErrorCode code, const std::string &message) {
m::ErrorResponse resp;
resp.id = id;
resp.code = static_cast<int>(code);
resp.message = message;
return resp;
}
m::OkResponse m::makeOkResponse(int id) {
m::OkResponse resp;
resp.id = id;
return resp;
}
/*
* debugger message conversion helpers
*/
m::debugger::Location m::debugger::makeLocation(
const h::debugger::SourceLocation &loc) {
m::debugger::Location result;
result.scriptId = folly::to<std::string>(loc.fileId);
m::setChromeLocation(result, loc);
return result;
}
m::debugger::CallFrame m::debugger::makeCallFrame(
uint32_t callFrameIndex,
const h::debugger::CallFrameInfo &callFrameInfo,
const h::debugger::LexicalInfo &lexicalInfo,
RemoteObjectsTable &objTable,
HermesRuntime &runtime,
const facebook::hermes::debugger::ProgramState &state) {
m::debugger::CallFrame result;
result.callFrameId = folly::to<std::string>(callFrameIndex);
result.functionName = callFrameInfo.functionName;
result.location = makeLocation(callFrameInfo.location);
uint32_t scopeCount = lexicalInfo.getScopesCount();
for (uint32_t scopeIndex = 0; scopeIndex < scopeCount; scopeIndex++) {
m::debugger::Scope scope;
if (scopeIndex == scopeCount - 1) {
scope.type = "global";
scope.name = "Global Scope";
scope.object.objectId =
objTable.addValue(runtime.global(), BacktraceObjectGroup);
} else {
scope.type = "local";
scope.name = "Scope " + folly::to<std::string>(scopeIndex);
scope.object.objectId = objTable.addScope(
std::make_pair(callFrameIndex, scopeIndex), BacktraceObjectGroup);
}
scope.object.type = "object";
scope.object.className = "Object";
result.scopeChain.emplace_back(std::move(scope));
}
result.thisObj.type = "object";
result.thisObj.objectId = objTable.addValue(
state.getVariableInfoForThis(callFrameIndex).value, BacktraceObjectGroup);
return result;
}
std::vector<m::debugger::CallFrame> m::debugger::makeCallFrames(
const h::debugger::ProgramState &state,
RemoteObjectsTable &objTable,
HermesRuntime &runtime) {
const h::debugger::StackTrace &stackTrace = state.getStackTrace();
uint32_t count = stackTrace.callFrameCount();
std::vector<m::debugger::CallFrame> result;
result.reserve(count);
for (uint32_t i = 0; i < count; i++) {
h::debugger::CallFrameInfo callFrameInfo = stackTrace.callFrameForIndex(i);
h::debugger::LexicalInfo lexicalInfo = state.getLexicalInfo(i);
result.emplace_back(
makeCallFrame(i, callFrameInfo, lexicalInfo, objTable, runtime, state));
}
return result;
}
/*
* runtime message conversion helpers
*/
m::runtime::CallFrame m::runtime::makeCallFrame(
const h::debugger::CallFrameInfo &info) {
m::runtime::CallFrame result;
result.functionName = info.functionName;
result.scriptId = folly::to<std::string>(info.location.fileId);
result.url = info.location.fileName;
m::setChromeLocation(result, info.location);
return result;
}
std::vector<m::runtime::CallFrame> m::runtime::makeCallFrames(
const facebook::hermes::debugger::StackTrace &stackTrace) {
std::vector<m::runtime::CallFrame> result;
result.reserve(stackTrace.callFrameCount());
for (size_t i = 0; i < stackTrace.callFrameCount(); i++) {
h::debugger::CallFrameInfo info = stackTrace.callFrameForIndex(i);
result.emplace_back(makeCallFrame(info));
}
return result;
}
m::runtime::ExceptionDetails m::runtime::makeExceptionDetails(
const h::debugger::ExceptionDetails &details) {
m::runtime::ExceptionDetails result;
result.text = details.text;
result.scriptId = folly::to<std::string>(details.location.fileId);
result.url = details.location.fileName;
result.stackTrace = m::runtime::StackTrace();
result.stackTrace->callFrames = makeCallFrames(details.getStackTrace());
m::setChromeLocation(result, details.location);
return result;
}
m::runtime::RemoteObject m::runtime::makeRemoteObject(
facebook::jsi::Runtime &runtime,
const facebook::jsi::Value &value,
RemoteObjectsTable &objTable,
const std::string &objectGroup) {
m::runtime::RemoteObject result;
if (value.isUndefined()) {
result.type = "undefined";
} else if (value.isNull()) {
result.type = "object";
result.subtype = "null";
result.value = "null";
} else if (value.isBool()) {
result.type = "boolean";
result.value = value.getBool();
} else if (value.isNumber()) {
double numberValue = value.getNumber();
result.type = "number";
if (std::isnan(numberValue)) {
result.description = result.unserializableValue = "NaN";
} else if (numberValue == -std::numeric_limits<double>::infinity()) {
result.description = result.unserializableValue = "-Infinity";
} else if (numberValue == std::numeric_limits<double>::infinity()) {
result.description = result.unserializableValue = "Infinity";
} else if (numberValue == 0.0 && std::signbit(numberValue)) {
result.description = result.unserializableValue = "-0";
} else {
result.value = numberValue;
}
} else if (value.isString()) {
result.type = "string";
result.value = value.getString(runtime).utf8(runtime);
} else if (value.isObject()) {
jsi::Object obj = value.getObject(runtime);
if (obj.isFunction(runtime)) {
result.type = "function";
result.value = "";
} else if (obj.isArray(runtime)) {
auto array = obj.getArray(runtime);
size_t arrayCount = array.length(runtime);
result.type = "object";
result.subtype = "array";
result.className = "Array";
result.description = "Array(" + folly::to<std::string>(arrayCount) + ")";
} else {
result.type = "object";
result.description = result.className = "Object";
}
result.objectId =
objTable.addValue(jsi::Value(std::move(obj)), objectGroup);
}
return result;
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,121 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <regex>
#include <string>
#include <vector>
#include <hermes/DebuggerAPI.h>
#include <hermes/hermes.h>
#include <hermes/inspector/chrome/MessageTypes.h>
#include <hermes/inspector/chrome/RemoteObjectsTable.h>
#include <jsi/jsi.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
template <typename T>
void setHermesLocation(
facebook::hermes::debugger::SourceLocation &hermesLoc,
const T &chromeLoc,
const std::vector<std::string> &parsedScripts) {
hermesLoc.line = chromeLoc.lineNumber + 1;
if (chromeLoc.columnNumber.hasValue()) {
if (chromeLoc.columnNumber.value() == 0) {
// TODO: When CDTP sends a column number of 0, we send Hermes a column
// number of 1. For some reason, this causes Hermes to not be
// able to resolve breakpoints.
hermesLoc.column = ::facebook::hermes::debugger::kInvalidLocation;
} else {
hermesLoc.column = chromeLoc.columnNumber.value() + 1;
}
}
if (chromeLoc.url.hasValue()) {
hermesLoc.fileName = chromeLoc.url.value();
} else if (chromeLoc.urlRegex.hasValue()) {
const std::regex regex(chromeLoc.urlRegex.value());
for (const auto &fileName : parsedScripts) {
if (std::regex_match(fileName, regex)) {
hermesLoc.fileName = fileName;
break;
}
}
}
}
template <typename T>
void setChromeLocation(
T &chromeLoc,
const facebook::hermes::debugger::SourceLocation &hermesLoc) {
if (hermesLoc.line != facebook::hermes::debugger::kInvalidLocation) {
chromeLoc.lineNumber = hermesLoc.line - 1;
}
if (hermesLoc.column != facebook::hermes::debugger::kInvalidLocation) {
chromeLoc.columnNumber = hermesLoc.column - 1;
}
}
/// ErrorCode magic numbers match JSC's (see InspectorBackendDispatcher.cpp)
enum class ErrorCode {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
ServerError = -32000
};
ErrorResponse
makeErrorResponse(int id, ErrorCode code, const std::string &message);
OkResponse makeOkResponse(int id);
namespace debugger {
Location makeLocation(const facebook::hermes::debugger::SourceLocation &loc);
CallFrame makeCallFrame(
uint32_t callFrameIndex,
const facebook::hermes::debugger::CallFrameInfo &callFrameInfo,
const facebook::hermes::debugger::LexicalInfo &lexicalInfo,
facebook::hermes::inspector::chrome::RemoteObjectsTable &objTable,
HermesRuntime &runtime,
const facebook::hermes::debugger::ProgramState &state);
std::vector<CallFrame> makeCallFrames(
const facebook::hermes::debugger::ProgramState &state,
facebook::hermes::inspector::chrome::RemoteObjectsTable &objTable,
HermesRuntime &runtime);
} // namespace debugger
namespace runtime {
CallFrame makeCallFrame(const facebook::hermes::debugger::CallFrameInfo &info);
std::vector<CallFrame> makeCallFrames(
const facebook::hermes::debugger::StackTrace &stackTrace);
ExceptionDetails makeExceptionDetails(
const facebook::hermes::debugger::ExceptionDetails &details);
RemoteObject makeRemoteObject(
facebook::jsi::Runtime &runtime,
const facebook::jsi::Value &value,
facebook::hermes::inspector::chrome::RemoteObjectsTable &objTable,
const std::string &objectGroup);
} // namespace runtime
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,69 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <folly/Try.h>
#include <folly/dynamic.h>
#include <folly/json.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
struct RequestHandler;
/// Serializable is an interface for objects that can be serialized to and from
/// JSON.
struct Serializable {
virtual ~Serializable() = default;
virtual folly::dynamic toDynamic() const = 0;
std::string toJson() const {
return folly::toJson(toDynamic());
}
};
/// Requests are sent from the debugger to the target.
struct Request : public Serializable {
static std::unique_ptr<Request> fromJsonThrowOnError(const std::string &str);
static folly::Try<std::unique_ptr<Request>> fromJson(const std::string &str);
Request() = default;
explicit Request(std::string method) : method(method) {}
// accept dispatches to the appropriate handler method in RequestHandler based
// on the type of the request.
virtual void accept(RequestHandler &handler) const = 0;
int id = 0;
std::string method;
};
/// Responses are sent from the target to the debugger in response to a Request.
struct Response : public Serializable {
Response() = default;
int id = 0;
};
/// Notifications are sent from the target to the debugger. This is used to
/// notify the debugger about events that occur in the target, e.g. stopping
/// at a breakpoint.
struct Notification : public Serializable {
Notification() = default;
explicit Notification(std::string method) : method(method) {}
std::string method;
};
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,902 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// @generated <<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>
#include "MessageTypes.h"
#include "MessageTypesInlines.h"
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
using RequestBuilder = std::unique_ptr<Request> (*)(const dynamic &);
namespace {
template <typename T>
std::unique_ptr<Request> makeUnique(const dynamic &obj) {
return std::make_unique<T>(obj);
}
} // namespace
std::unique_ptr<Request> Request::fromJsonThrowOnError(const std::string &str) {
static std::unordered_map<std::string, RequestBuilder> builders = {
{"Debugger.disable", makeUnique<debugger::DisableRequest>},
{"Debugger.enable", makeUnique<debugger::EnableRequest>},
{"Debugger.evaluateOnCallFrame",
makeUnique<debugger::EvaluateOnCallFrameRequest>},
{"Debugger.pause", makeUnique<debugger::PauseRequest>},
{"Debugger.removeBreakpoint",
makeUnique<debugger::RemoveBreakpointRequest>},
{"Debugger.resume", makeUnique<debugger::ResumeRequest>},
{"Debugger.setBreakpointByUrl",
makeUnique<debugger::SetBreakpointByUrlRequest>},
{"Debugger.setPauseOnExceptions",
makeUnique<debugger::SetPauseOnExceptionsRequest>},
{"Debugger.stepInto", makeUnique<debugger::StepIntoRequest>},
{"Debugger.stepOut", makeUnique<debugger::StepOutRequest>},
{"Debugger.stepOver", makeUnique<debugger::StepOverRequest>},
{"Runtime.evaluate", makeUnique<runtime::EvaluateRequest>},
{"Runtime.getProperties", makeUnique<runtime::GetPropertiesRequest>},
};
dynamic obj = folly::parseJson(str);
std::string method = obj.at("method").asString();
auto it = builders.find(method);
if (it == builders.end()) {
return std::make_unique<UnknownRequest>(obj);
}
auto builder = it->second;
return builder(obj);
}
folly::Try<std::unique_ptr<Request>> Request::fromJson(const std::string &str) {
return folly::makeTryWith(
[&str] { return Request::fromJsonThrowOnError(str); });
}
/// Types
debugger::Location::Location(const dynamic &obj) {
assign(scriptId, obj, "scriptId");
assign(lineNumber, obj, "lineNumber");
assign(columnNumber, obj, "columnNumber");
}
dynamic debugger::Location::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "scriptId", scriptId);
put(obj, "lineNumber", lineNumber);
put(obj, "columnNumber", columnNumber);
return obj;
}
runtime::RemoteObject::RemoteObject(const dynamic &obj) {
assign(type, obj, "type");
assign(subtype, obj, "subtype");
assign(className, obj, "className");
assign(value, obj, "value");
assign(unserializableValue, obj, "unserializableValue");
assign(description, obj, "description");
assign(objectId, obj, "objectId");
}
dynamic runtime::RemoteObject::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "type", type);
put(obj, "subtype", subtype);
put(obj, "className", className);
put(obj, "value", value);
put(obj, "unserializableValue", unserializableValue);
put(obj, "description", description);
put(obj, "objectId", objectId);
return obj;
}
runtime::CallFrame::CallFrame(const dynamic &obj) {
assign(functionName, obj, "functionName");
assign(scriptId, obj, "scriptId");
assign(url, obj, "url");
assign(lineNumber, obj, "lineNumber");
assign(columnNumber, obj, "columnNumber");
}
dynamic runtime::CallFrame::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "functionName", functionName);
put(obj, "scriptId", scriptId);
put(obj, "url", url);
put(obj, "lineNumber", lineNumber);
put(obj, "columnNumber", columnNumber);
return obj;
}
runtime::StackTrace::StackTrace(const dynamic &obj) {
assign(description, obj, "description");
assign(callFrames, obj, "callFrames");
assign(parent, obj, "parent");
}
dynamic runtime::StackTrace::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "description", description);
put(obj, "callFrames", callFrames);
put(obj, "parent", parent);
return obj;
}
runtime::ExceptionDetails::ExceptionDetails(const dynamic &obj) {
assign(exceptionId, obj, "exceptionId");
assign(text, obj, "text");
assign(lineNumber, obj, "lineNumber");
assign(columnNumber, obj, "columnNumber");
assign(scriptId, obj, "scriptId");
assign(url, obj, "url");
assign(stackTrace, obj, "stackTrace");
assign(exception, obj, "exception");
assign(executionContextId, obj, "executionContextId");
}
dynamic runtime::ExceptionDetails::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "exceptionId", exceptionId);
put(obj, "text", text);
put(obj, "lineNumber", lineNumber);
put(obj, "columnNumber", columnNumber);
put(obj, "scriptId", scriptId);
put(obj, "url", url);
put(obj, "stackTrace", stackTrace);
put(obj, "exception", exception);
put(obj, "executionContextId", executionContextId);
return obj;
}
debugger::Scope::Scope(const dynamic &obj) {
assign(type, obj, "type");
assign(object, obj, "object");
assign(name, obj, "name");
assign(startLocation, obj, "startLocation");
assign(endLocation, obj, "endLocation");
}
dynamic debugger::Scope::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "type", type);
put(obj, "object", object);
put(obj, "name", name);
put(obj, "startLocation", startLocation);
put(obj, "endLocation", endLocation);
return obj;
}
debugger::CallFrame::CallFrame(const dynamic &obj) {
assign(callFrameId, obj, "callFrameId");
assign(functionName, obj, "functionName");
assign(location, obj, "location");
assign(url, obj, "url");
assign(scopeChain, obj, "scopeChain");
assign(thisObj, obj, "this");
assign(returnValue, obj, "returnValue");
}
dynamic debugger::CallFrame::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "callFrameId", callFrameId);
put(obj, "functionName", functionName);
put(obj, "location", location);
put(obj, "url", url);
put(obj, "scopeChain", scopeChain);
put(obj, "this", thisObj);
put(obj, "returnValue", returnValue);
return obj;
}
runtime::ExecutionContextDescription::ExecutionContextDescription(
const dynamic &obj) {
assign(id, obj, "id");
assign(origin, obj, "origin");
assign(name, obj, "name");
assign(auxData, obj, "auxData");
assign(isPageContext, obj, "isPageContext");
assign(isDefault, obj, "isDefault");
}
dynamic runtime::ExecutionContextDescription::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "origin", origin);
put(obj, "name", name);
put(obj, "auxData", auxData);
put(obj, "isPageContext", isPageContext);
put(obj, "isDefault", isDefault);
return obj;
}
runtime::PropertyDescriptor::PropertyDescriptor(const dynamic &obj) {
assign(name, obj, "name");
assign(value, obj, "value");
assign(writable, obj, "writable");
assign(get, obj, "get");
assign(set, obj, "set");
assign(configurable, obj, "configurable");
assign(enumerable, obj, "enumerable");
assign(wasThrown, obj, "wasThrown");
assign(isOwn, obj, "isOwn");
assign(symbol, obj, "symbol");
}
dynamic runtime::PropertyDescriptor::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "name", name);
put(obj, "value", value);
put(obj, "writable", writable);
put(obj, "get", get);
put(obj, "set", set);
put(obj, "configurable", configurable);
put(obj, "enumerable", enumerable);
put(obj, "wasThrown", wasThrown);
put(obj, "isOwn", isOwn);
put(obj, "symbol", symbol);
return obj;
}
runtime::InternalPropertyDescriptor::InternalPropertyDescriptor(
const dynamic &obj) {
assign(name, obj, "name");
assign(value, obj, "value");
}
dynamic runtime::InternalPropertyDescriptor::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "name", name);
put(obj, "value", value);
return obj;
}
/// Requests
UnknownRequest::UnknownRequest() {}
UnknownRequest::UnknownRequest(const dynamic &obj) {
assign(id, obj, "id");
assign(method, obj, "method");
assign(params, obj, "params");
}
dynamic UnknownRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", params);
return obj;
}
void UnknownRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::DisableRequest::DisableRequest() : Request("Debugger.disable") {}
debugger::DisableRequest::DisableRequest(const dynamic &obj)
: Request("Debugger.disable") {
assign(id, obj, "id");
assign(method, obj, "method");
}
dynamic debugger::DisableRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
return obj;
}
void debugger::DisableRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::EnableRequest::EnableRequest() : Request("Debugger.enable") {}
debugger::EnableRequest::EnableRequest(const dynamic &obj)
: Request("Debugger.enable") {
assign(id, obj, "id");
assign(method, obj, "method");
}
dynamic debugger::EnableRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
return obj;
}
void debugger::EnableRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::EvaluateOnCallFrameRequest::EvaluateOnCallFrameRequest()
: Request("Debugger.evaluateOnCallFrame") {}
debugger::EvaluateOnCallFrameRequest::EvaluateOnCallFrameRequest(
const dynamic &obj)
: Request("Debugger.evaluateOnCallFrame") {
assign(id, obj, "id");
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(callFrameId, params, "callFrameId");
assign(expression, params, "expression");
assign(objectGroup, params, "objectGroup");
assign(includeCommandLineAPI, params, "includeCommandLineAPI");
assign(silent, params, "silent");
assign(returnByValue, params, "returnByValue");
}
dynamic debugger::EvaluateOnCallFrameRequest::toDynamic() const {
dynamic params = dynamic::object;
put(params, "callFrameId", callFrameId);
put(params, "expression", expression);
put(params, "objectGroup", objectGroup);
put(params, "includeCommandLineAPI", includeCommandLineAPI);
put(params, "silent", silent);
put(params, "returnByValue", returnByValue);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
void debugger::EvaluateOnCallFrameRequest::accept(
RequestHandler &handler) const {
handler.handle(*this);
}
debugger::PauseRequest::PauseRequest() : Request("Debugger.pause") {}
debugger::PauseRequest::PauseRequest(const dynamic &obj)
: Request("Debugger.pause") {
assign(id, obj, "id");
assign(method, obj, "method");
}
dynamic debugger::PauseRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
return obj;
}
void debugger::PauseRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::RemoveBreakpointRequest::RemoveBreakpointRequest()
: Request("Debugger.removeBreakpoint") {}
debugger::RemoveBreakpointRequest::RemoveBreakpointRequest(const dynamic &obj)
: Request("Debugger.removeBreakpoint") {
assign(id, obj, "id");
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(breakpointId, params, "breakpointId");
}
dynamic debugger::RemoveBreakpointRequest::toDynamic() const {
dynamic params = dynamic::object;
put(params, "breakpointId", breakpointId);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
void debugger::RemoveBreakpointRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::ResumeRequest::ResumeRequest() : Request("Debugger.resume") {}
debugger::ResumeRequest::ResumeRequest(const dynamic &obj)
: Request("Debugger.resume") {
assign(id, obj, "id");
assign(method, obj, "method");
}
dynamic debugger::ResumeRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
return obj;
}
void debugger::ResumeRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::SetBreakpointByUrlRequest::SetBreakpointByUrlRequest()
: Request("Debugger.setBreakpointByUrl") {}
debugger::SetBreakpointByUrlRequest::SetBreakpointByUrlRequest(
const dynamic &obj)
: Request("Debugger.setBreakpointByUrl") {
assign(id, obj, "id");
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(lineNumber, params, "lineNumber");
assign(url, params, "url");
assign(urlRegex, params, "urlRegex");
assign(columnNumber, params, "columnNumber");
assign(condition, params, "condition");
}
dynamic debugger::SetBreakpointByUrlRequest::toDynamic() const {
dynamic params = dynamic::object;
put(params, "lineNumber", lineNumber);
put(params, "url", url);
put(params, "urlRegex", urlRegex);
put(params, "columnNumber", columnNumber);
put(params, "condition", condition);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
void debugger::SetBreakpointByUrlRequest::accept(
RequestHandler &handler) const {
handler.handle(*this);
}
debugger::SetPauseOnExceptionsRequest::SetPauseOnExceptionsRequest()
: Request("Debugger.setPauseOnExceptions") {}
debugger::SetPauseOnExceptionsRequest::SetPauseOnExceptionsRequest(
const dynamic &obj)
: Request("Debugger.setPauseOnExceptions") {
assign(id, obj, "id");
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(state, params, "state");
}
dynamic debugger::SetPauseOnExceptionsRequest::toDynamic() const {
dynamic params = dynamic::object;
put(params, "state", state);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
void debugger::SetPauseOnExceptionsRequest::accept(
RequestHandler &handler) const {
handler.handle(*this);
}
debugger::StepIntoRequest::StepIntoRequest() : Request("Debugger.stepInto") {}
debugger::StepIntoRequest::StepIntoRequest(const dynamic &obj)
: Request("Debugger.stepInto") {
assign(id, obj, "id");
assign(method, obj, "method");
}
dynamic debugger::StepIntoRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
return obj;
}
void debugger::StepIntoRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::StepOutRequest::StepOutRequest() : Request("Debugger.stepOut") {}
debugger::StepOutRequest::StepOutRequest(const dynamic &obj)
: Request("Debugger.stepOut") {
assign(id, obj, "id");
assign(method, obj, "method");
}
dynamic debugger::StepOutRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
return obj;
}
void debugger::StepOutRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
debugger::StepOverRequest::StepOverRequest() : Request("Debugger.stepOver") {}
debugger::StepOverRequest::StepOverRequest(const dynamic &obj)
: Request("Debugger.stepOver") {
assign(id, obj, "id");
assign(method, obj, "method");
}
dynamic debugger::StepOverRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
return obj;
}
void debugger::StepOverRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
runtime::EvaluateRequest::EvaluateRequest() : Request("Runtime.evaluate") {}
runtime::EvaluateRequest::EvaluateRequest(const dynamic &obj)
: Request("Runtime.evaluate") {
assign(id, obj, "id");
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(expression, params, "expression");
assign(objectGroup, params, "objectGroup");
assign(includeCommandLineAPI, params, "includeCommandLineAPI");
assign(silent, params, "silent");
assign(contextId, params, "contextId");
assign(returnByValue, params, "returnByValue");
assign(awaitPromise, params, "awaitPromise");
}
dynamic runtime::EvaluateRequest::toDynamic() const {
dynamic params = dynamic::object;
put(params, "expression", expression);
put(params, "objectGroup", objectGroup);
put(params, "includeCommandLineAPI", includeCommandLineAPI);
put(params, "silent", silent);
put(params, "contextId", contextId);
put(params, "returnByValue", returnByValue);
put(params, "awaitPromise", awaitPromise);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
void runtime::EvaluateRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
runtime::GetPropertiesRequest::GetPropertiesRequest()
: Request("Runtime.getProperties") {}
runtime::GetPropertiesRequest::GetPropertiesRequest(const dynamic &obj)
: Request("Runtime.getProperties") {
assign(id, obj, "id");
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(objectId, params, "objectId");
assign(ownProperties, params, "ownProperties");
}
dynamic runtime::GetPropertiesRequest::toDynamic() const {
dynamic params = dynamic::object;
put(params, "objectId", objectId);
put(params, "ownProperties", ownProperties);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
void runtime::GetPropertiesRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
/// Responses
ErrorResponse::ErrorResponse(const dynamic &obj) {
assign(id, obj, "id");
dynamic error = obj.at("error");
assign(code, error, "code");
assign(message, error, "message");
assign(data, error, "data");
}
dynamic ErrorResponse::toDynamic() const {
dynamic error = dynamic::object;
put(error, "code", code);
put(error, "message", message);
put(error, "data", data);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "error", std::move(error));
return obj;
}
OkResponse::OkResponse(const dynamic &obj) {
assign(id, obj, "id");
}
dynamic OkResponse::toDynamic() const {
dynamic result = dynamic::object;
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(result));
return obj;
}
debugger::EvaluateOnCallFrameResponse::EvaluateOnCallFrameResponse(
const dynamic &obj) {
assign(id, obj, "id");
dynamic res = obj.at("result");
assign(result, res, "result");
assign(exceptionDetails, res, "exceptionDetails");
}
dynamic debugger::EvaluateOnCallFrameResponse::toDynamic() const {
dynamic res = dynamic::object;
put(res, "result", result);
put(res, "exceptionDetails", exceptionDetails);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(res));
return obj;
}
debugger::SetBreakpointByUrlResponse::SetBreakpointByUrlResponse(
const dynamic &obj) {
assign(id, obj, "id");
dynamic res = obj.at("result");
assign(breakpointId, res, "breakpointId");
assign(locations, res, "locations");
}
dynamic debugger::SetBreakpointByUrlResponse::toDynamic() const {
dynamic res = dynamic::object;
put(res, "breakpointId", breakpointId);
put(res, "locations", locations);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(res));
return obj;
}
runtime::EvaluateResponse::EvaluateResponse(const dynamic &obj) {
assign(id, obj, "id");
dynamic res = obj.at("result");
assign(result, res, "result");
assign(exceptionDetails, res, "exceptionDetails");
}
dynamic runtime::EvaluateResponse::toDynamic() const {
dynamic res = dynamic::object;
put(res, "result", result);
put(res, "exceptionDetails", exceptionDetails);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(res));
return obj;
}
runtime::GetPropertiesResponse::GetPropertiesResponse(const dynamic &obj) {
assign(id, obj, "id");
dynamic res = obj.at("result");
assign(result, res, "result");
assign(internalProperties, res, "internalProperties");
assign(exceptionDetails, res, "exceptionDetails");
}
dynamic runtime::GetPropertiesResponse::toDynamic() const {
dynamic res = dynamic::object;
put(res, "result", result);
put(res, "internalProperties", internalProperties);
put(res, "exceptionDetails", exceptionDetails);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(res));
return obj;
}
/// Notifications
debugger::BreakpointResolvedNotification::BreakpointResolvedNotification()
: Notification("Debugger.breakpointResolved") {}
debugger::BreakpointResolvedNotification::BreakpointResolvedNotification(
const dynamic &obj)
: Notification("Debugger.breakpointResolved") {
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(breakpointId, params, "breakpointId");
assign(location, params, "location");
}
dynamic debugger::BreakpointResolvedNotification::toDynamic() const {
dynamic params = dynamic::object;
put(params, "breakpointId", breakpointId);
put(params, "location", location);
dynamic obj = dynamic::object;
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
debugger::PausedNotification::PausedNotification()
: Notification("Debugger.paused") {}
debugger::PausedNotification::PausedNotification(const dynamic &obj)
: Notification("Debugger.paused") {
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(callFrames, params, "callFrames");
assign(reason, params, "reason");
assign(data, params, "data");
assign(hitBreakpoints, params, "hitBreakpoints");
assign(asyncStackTrace, params, "asyncStackTrace");
}
dynamic debugger::PausedNotification::toDynamic() const {
dynamic params = dynamic::object;
put(params, "callFrames", callFrames);
put(params, "reason", reason);
put(params, "data", data);
put(params, "hitBreakpoints", hitBreakpoints);
put(params, "asyncStackTrace", asyncStackTrace);
dynamic obj = dynamic::object;
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
debugger::ResumedNotification::ResumedNotification()
: Notification("Debugger.resumed") {}
debugger::ResumedNotification::ResumedNotification(const dynamic &obj)
: Notification("Debugger.resumed") {
assign(method, obj, "method");
}
dynamic debugger::ResumedNotification::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "method", method);
return obj;
}
debugger::ScriptParsedNotification::ScriptParsedNotification()
: Notification("Debugger.scriptParsed") {}
debugger::ScriptParsedNotification::ScriptParsedNotification(const dynamic &obj)
: Notification("Debugger.scriptParsed") {
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(scriptId, params, "scriptId");
assign(url, params, "url");
assign(startLine, params, "startLine");
assign(startColumn, params, "startColumn");
assign(endLine, params, "endLine");
assign(endColumn, params, "endColumn");
assign(executionContextId, params, "executionContextId");
assign(hash, params, "hash");
assign(executionContextAuxData, params, "executionContextAuxData");
assign(sourceMapURL, params, "sourceMapURL");
}
dynamic debugger::ScriptParsedNotification::toDynamic() const {
dynamic params = dynamic::object;
put(params, "scriptId", scriptId);
put(params, "url", url);
put(params, "startLine", startLine);
put(params, "startColumn", startColumn);
put(params, "endLine", endLine);
put(params, "endColumn", endColumn);
put(params, "executionContextId", executionContextId);
put(params, "hash", hash);
put(params, "executionContextAuxData", executionContextAuxData);
put(params, "sourceMapURL", sourceMapURL);
dynamic obj = dynamic::object;
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
runtime::ConsoleAPICalledNotification::ConsoleAPICalledNotification()
: Notification("Runtime.consoleAPICalled") {}
runtime::ConsoleAPICalledNotification::ConsoleAPICalledNotification(
const dynamic &obj)
: Notification("Runtime.consoleAPICalled") {
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(type, params, "type");
assign(args, params, "args");
assign(executionContextId, params, "executionContextId");
assign(timestamp, params, "timestamp");
assign(stackTrace, params, "stackTrace");
}
dynamic runtime::ConsoleAPICalledNotification::toDynamic() const {
dynamic params = dynamic::object;
put(params, "type", type);
put(params, "args", args);
put(params, "executionContextId", executionContextId);
put(params, "timestamp", timestamp);
put(params, "stackTrace", stackTrace);
dynamic obj = dynamic::object;
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
runtime::ExecutionContextCreatedNotification::
ExecutionContextCreatedNotification()
: Notification("Runtime.executionContextCreated") {}
runtime::ExecutionContextCreatedNotification::
ExecutionContextCreatedNotification(const dynamic &obj)
: Notification("Runtime.executionContextCreated") {
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(context, params, "context");
}
dynamic runtime::ExecutionContextCreatedNotification::toDynamic() const {
dynamic params = dynamic::object;
put(params, "context", context);
dynamic obj = dynamic::object;
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,499 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// @generated <<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>
#pragma once
#include <hermes/inspector/chrome/MessageInterfaces.h>
#include <vector>
#include <folly/Optional.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
struct UnknownRequest;
namespace debugger {
using BreakpointId = std::string;
struct BreakpointResolvedNotification;
struct CallFrame;
using CallFrameId = std::string;
struct DisableRequest;
struct EnableRequest;
struct EvaluateOnCallFrameRequest;
struct EvaluateOnCallFrameResponse;
struct Location;
struct PauseRequest;
struct PausedNotification;
struct RemoveBreakpointRequest;
struct ResumeRequest;
struct ResumedNotification;
struct Scope;
struct ScriptParsedNotification;
struct SetBreakpointByUrlRequest;
struct SetBreakpointByUrlResponse;
struct SetPauseOnExceptionsRequest;
struct StepIntoRequest;
struct StepOutRequest;
struct StepOverRequest;
} // namespace debugger
namespace runtime {
struct CallFrame;
struct ConsoleAPICalledNotification;
struct EvaluateRequest;
struct EvaluateResponse;
struct ExceptionDetails;
struct ExecutionContextCreatedNotification;
struct ExecutionContextDescription;
using ExecutionContextId = int;
struct GetPropertiesRequest;
struct GetPropertiesResponse;
struct InternalPropertyDescriptor;
struct PropertyDescriptor;
struct RemoteObject;
using RemoteObjectId = std::string;
using ScriptId = std::string;
struct StackTrace;
using Timestamp = double;
using UnserializableValue = std::string;
} // namespace runtime
/// RequestHandler handles requests via the visitor pattern.
struct RequestHandler {
virtual ~RequestHandler() = default;
virtual void handle(const UnknownRequest &req) = 0;
virtual void handle(const debugger::DisableRequest &req) = 0;
virtual void handle(const debugger::EnableRequest &req) = 0;
virtual void handle(const debugger::EvaluateOnCallFrameRequest &req) = 0;
virtual void handle(const debugger::PauseRequest &req) = 0;
virtual void handle(const debugger::RemoveBreakpointRequest &req) = 0;
virtual void handle(const debugger::ResumeRequest &req) = 0;
virtual void handle(const debugger::SetBreakpointByUrlRequest &req) = 0;
virtual void handle(const debugger::SetPauseOnExceptionsRequest &req) = 0;
virtual void handle(const debugger::StepIntoRequest &req) = 0;
virtual void handle(const debugger::StepOutRequest &req) = 0;
virtual void handle(const debugger::StepOverRequest &req) = 0;
virtual void handle(const runtime::EvaluateRequest &req) = 0;
virtual void handle(const runtime::GetPropertiesRequest &req) = 0;
};
/// NoopRequestHandler can be subclassed to only handle some requests.
struct NoopRequestHandler : public RequestHandler {
void handle(const UnknownRequest &req) override {}
void handle(const debugger::DisableRequest &req) override {}
void handle(const debugger::EnableRequest &req) override {}
void handle(const debugger::EvaluateOnCallFrameRequest &req) override {}
void handle(const debugger::PauseRequest &req) override {}
void handle(const debugger::RemoveBreakpointRequest &req) override {}
void handle(const debugger::ResumeRequest &req) override {}
void handle(const debugger::SetBreakpointByUrlRequest &req) override {}
void handle(const debugger::SetPauseOnExceptionsRequest &req) override {}
void handle(const debugger::StepIntoRequest &req) override {}
void handle(const debugger::StepOutRequest &req) override {}
void handle(const debugger::StepOverRequest &req) override {}
void handle(const runtime::EvaluateRequest &req) override {}
void handle(const runtime::GetPropertiesRequest &req) override {}
};
/// Types
struct debugger::Location : public Serializable {
Location() = default;
explicit Location(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
runtime::ScriptId scriptId{};
int lineNumber{};
folly::Optional<int> columnNumber;
};
struct runtime::RemoteObject : public Serializable {
RemoteObject() = default;
explicit RemoteObject(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::string type;
folly::Optional<std::string> subtype;
folly::Optional<std::string> className;
folly::Optional<folly::dynamic> value;
folly::Optional<runtime::UnserializableValue> unserializableValue;
folly::Optional<std::string> description;
folly::Optional<runtime::RemoteObjectId> objectId;
};
struct runtime::CallFrame : public Serializable {
CallFrame() = default;
explicit CallFrame(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::string functionName;
runtime::ScriptId scriptId{};
std::string url;
int lineNumber{};
int columnNumber{};
};
struct runtime::StackTrace : public Serializable {
StackTrace() = default;
explicit StackTrace(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
folly::Optional<std::string> description;
std::vector<runtime::CallFrame> callFrames;
std::unique_ptr<runtime::StackTrace> parent;
};
struct runtime::ExceptionDetails : public Serializable {
ExceptionDetails() = default;
explicit ExceptionDetails(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
int exceptionId{};
std::string text;
int lineNumber{};
int columnNumber{};
folly::Optional<runtime::ScriptId> scriptId;
folly::Optional<std::string> url;
folly::Optional<runtime::StackTrace> stackTrace;
folly::Optional<runtime::RemoteObject> exception;
folly::Optional<runtime::ExecutionContextId> executionContextId;
};
struct debugger::Scope : public Serializable {
Scope() = default;
explicit Scope(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::string type;
runtime::RemoteObject object{};
folly::Optional<std::string> name;
folly::Optional<debugger::Location> startLocation;
folly::Optional<debugger::Location> endLocation;
};
struct debugger::CallFrame : public Serializable {
CallFrame() = default;
explicit CallFrame(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
debugger::CallFrameId callFrameId{};
std::string functionName;
debugger::Location location{};
std::string url;
std::vector<debugger::Scope> scopeChain;
runtime::RemoteObject thisObj{};
folly::Optional<runtime::RemoteObject> returnValue;
};
struct runtime::ExecutionContextDescription : public Serializable {
ExecutionContextDescription() = default;
explicit ExecutionContextDescription(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
runtime::ExecutionContextId id{};
std::string origin;
std::string name;
folly::Optional<folly::dynamic> auxData;
folly::Optional<bool> isPageContext;
folly::Optional<bool> isDefault;
};
struct runtime::PropertyDescriptor : public Serializable {
PropertyDescriptor() = default;
explicit PropertyDescriptor(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::string name;
folly::Optional<runtime::RemoteObject> value;
folly::Optional<bool> writable;
folly::Optional<runtime::RemoteObject> get;
folly::Optional<runtime::RemoteObject> set;
bool configurable{};
bool enumerable{};
folly::Optional<bool> wasThrown;
folly::Optional<bool> isOwn;
folly::Optional<runtime::RemoteObject> symbol;
};
struct runtime::InternalPropertyDescriptor : public Serializable {
InternalPropertyDescriptor() = default;
explicit InternalPropertyDescriptor(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::string name;
folly::Optional<runtime::RemoteObject> value;
};
/// Requests
struct UnknownRequest : public Request {
UnknownRequest();
explicit UnknownRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
folly::Optional<folly::dynamic> params;
};
struct debugger::DisableRequest : public Request {
DisableRequest();
explicit DisableRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
};
struct debugger::EnableRequest : public Request {
EnableRequest();
explicit EnableRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
};
struct debugger::EvaluateOnCallFrameRequest : public Request {
EvaluateOnCallFrameRequest();
explicit EvaluateOnCallFrameRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
debugger::CallFrameId callFrameId{};
std::string expression;
folly::Optional<std::string> objectGroup;
folly::Optional<bool> includeCommandLineAPI;
folly::Optional<bool> silent;
folly::Optional<bool> returnByValue;
};
struct debugger::PauseRequest : public Request {
PauseRequest();
explicit PauseRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
};
struct debugger::RemoveBreakpointRequest : public Request {
RemoveBreakpointRequest();
explicit RemoveBreakpointRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
debugger::BreakpointId breakpointId{};
};
struct debugger::ResumeRequest : public Request {
ResumeRequest();
explicit ResumeRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
};
struct debugger::SetBreakpointByUrlRequest : public Request {
SetBreakpointByUrlRequest();
explicit SetBreakpointByUrlRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
int lineNumber{};
folly::Optional<std::string> url;
folly::Optional<std::string> urlRegex;
folly::Optional<int> columnNumber;
folly::Optional<std::string> condition;
};
struct debugger::SetPauseOnExceptionsRequest : public Request {
SetPauseOnExceptionsRequest();
explicit SetPauseOnExceptionsRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
std::string state;
};
struct debugger::StepIntoRequest : public Request {
StepIntoRequest();
explicit StepIntoRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
};
struct debugger::StepOutRequest : public Request {
StepOutRequest();
explicit StepOutRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
};
struct debugger::StepOverRequest : public Request {
StepOverRequest();
explicit StepOverRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
};
struct runtime::EvaluateRequest : public Request {
EvaluateRequest();
explicit EvaluateRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
std::string expression;
folly::Optional<std::string> objectGroup;
folly::Optional<bool> includeCommandLineAPI;
folly::Optional<bool> silent;
folly::Optional<runtime::ExecutionContextId> contextId;
folly::Optional<bool> returnByValue;
folly::Optional<bool> awaitPromise;
};
struct runtime::GetPropertiesRequest : public Request {
GetPropertiesRequest();
explicit GetPropertiesRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
runtime::RemoteObjectId objectId{};
folly::Optional<bool> ownProperties;
};
/// Responses
struct ErrorResponse : public Response {
ErrorResponse() = default;
explicit ErrorResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
int code;
std::string message;
folly::Optional<folly::dynamic> data;
};
struct OkResponse : public Response {
OkResponse() = default;
explicit OkResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
};
struct debugger::EvaluateOnCallFrameResponse : public Response {
EvaluateOnCallFrameResponse() = default;
explicit EvaluateOnCallFrameResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
runtime::RemoteObject result{};
folly::Optional<runtime::ExceptionDetails> exceptionDetails;
};
struct debugger::SetBreakpointByUrlResponse : public Response {
SetBreakpointByUrlResponse() = default;
explicit SetBreakpointByUrlResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
debugger::BreakpointId breakpointId{};
std::vector<debugger::Location> locations;
};
struct runtime::EvaluateResponse : public Response {
EvaluateResponse() = default;
explicit EvaluateResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
runtime::RemoteObject result{};
folly::Optional<runtime::ExceptionDetails> exceptionDetails;
};
struct runtime::GetPropertiesResponse : public Response {
GetPropertiesResponse() = default;
explicit GetPropertiesResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::vector<runtime::PropertyDescriptor> result;
folly::Optional<std::vector<runtime::InternalPropertyDescriptor>>
internalProperties;
folly::Optional<runtime::ExceptionDetails> exceptionDetails;
};
/// Notifications
struct debugger::BreakpointResolvedNotification : public Notification {
BreakpointResolvedNotification();
explicit BreakpointResolvedNotification(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
debugger::BreakpointId breakpointId{};
debugger::Location location{};
};
struct debugger::PausedNotification : public Notification {
PausedNotification();
explicit PausedNotification(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::vector<debugger::CallFrame> callFrames;
std::string reason;
folly::Optional<folly::dynamic> data;
folly::Optional<std::vector<std::string>> hitBreakpoints;
folly::Optional<runtime::StackTrace> asyncStackTrace;
};
struct debugger::ResumedNotification : public Notification {
ResumedNotification();
explicit ResumedNotification(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
};
struct debugger::ScriptParsedNotification : public Notification {
ScriptParsedNotification();
explicit ScriptParsedNotification(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
runtime::ScriptId scriptId{};
std::string url;
int startLine{};
int startColumn{};
int endLine{};
int endColumn{};
runtime::ExecutionContextId executionContextId{};
std::string hash;
folly::Optional<folly::dynamic> executionContextAuxData;
folly::Optional<std::string> sourceMapURL;
};
struct runtime::ConsoleAPICalledNotification : public Notification {
ConsoleAPICalledNotification();
explicit ConsoleAPICalledNotification(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::string type;
std::vector<runtime::RemoteObject> args;
runtime::ExecutionContextId executionContextId{};
runtime::Timestamp timestamp{};
folly::Optional<runtime::StackTrace> stackTrace;
};
struct runtime::ExecutionContextCreatedNotification : public Notification {
ExecutionContextCreatedNotification();
explicit ExecutionContextCreatedNotification(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
runtime::ExecutionContextDescription context{};
};
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -0,0 +1,154 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <hermes/inspector/chrome/MessageInterfaces.h>
#include <memory>
#include <type_traits>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
using dynamic = folly::dynamic;
template <typename T>
using optional = folly::Optional<T>;
template <typename>
struct is_vector : std::false_type {};
template <typename T>
struct is_vector<std::vector<T>> : std::true_type {};
/// valueFromDynamic
template <typename T>
typename std::enable_if<std::is_base_of<Serializable, T>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return T(obj);
}
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type valueFromDynamic(
const dynamic &obj) {
return obj.asInt();
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return obj.asDouble();
}
template <typename T>
typename std::enable_if<std::is_same<T, std::string>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return obj.asString();
}
template <typename T>
typename std::enable_if<std::is_same<T, dynamic>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return obj;
}
template <typename T>
typename std::enable_if<is_vector<T>::value, T>::type valueFromDynamic(
const dynamic &items) {
T result;
result.reserve(items.size());
for (const auto &item : items) {
result.push_back(valueFromDynamic<typename T::value_type>(item));
}
return result;
}
/// assign(lhs, obj, key) is a wrapper for:
///
/// lhs = obj[key]
///
/// It mainly exists so that we can choose the right version of valueFromDynamic
/// based on the type of lhs.
template <typename T, typename U>
void assign(T &lhs, const dynamic &obj, const U &key) {
lhs = valueFromDynamic<T>(obj.at(key));
}
template <typename T, typename U>
void assign(optional<T> &lhs, const dynamic &obj, const U &key) {
auto it = obj.find(key);
if (it != obj.items().end()) {
lhs = valueFromDynamic<T>(it->second);
} else {
lhs.clear();
}
}
template <typename T, typename U>
void assign(std::unique_ptr<T> &lhs, const dynamic &obj, const U &key) {
auto it = obj.find(key);
if (it != obj.items().end()) {
lhs = std::make_unique<T>(valueFromDynamic<T>(it->second));
} else {
lhs.reset();
}
}
/// valueToDynamic
inline dynamic valueToDynamic(const Serializable &value) {
return value.toDynamic();
}
template <typename T>
typename std::enable_if<!std::is_base_of<Serializable, T>::value, dynamic>::type
valueToDynamic(const T &item) {
return dynamic(item);
}
template <typename T>
dynamic valueToDynamic(const std::vector<T> &items) {
dynamic result = dynamic::array;
for (const auto &item : items) {
result.push_back(valueToDynamic(item));
}
return result;
}
/// put(obj, key, value) is a wrapper for:
///
/// obj[key] = valueToDynamic(value);
template <typename K, typename V>
void put(dynamic &obj, const K &key, const V &value) {
obj[key] = valueToDynamic(value);
}
template <typename K, typename V>
void put(dynamic &obj, const K &key, const optional<V> &optValue) {
if (optValue.hasValue()) {
obj[key] = valueToDynamic(optValue.value());
} else {
obj.erase(key);
}
}
template <typename K, typename V>
void put(dynamic &obj, const K &key, const std::unique_ptr<V> &ptr) {
if (ptr.get()) {
obj[key] = valueToDynamic(*ptr);
} else {
obj.erase(key);
}
}
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook

Some files were not shown because too many files have changed in this diff Show More