mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
46
Commits
v0.74.0-rc.3
...
0.60.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b867cf8974 | ||
|
|
0738fe5738 | ||
|
|
8dd8ec7cb0 | ||
|
|
e857d7066b | ||
|
|
769e35ba5f | ||
|
|
35aeb8c027 | ||
|
|
8fdecf3010 | ||
|
|
ff9855cc3b | ||
|
|
8a43321271 | ||
|
|
db1d60fa95 | ||
|
|
93c83181fb | ||
|
|
9837d2480c | ||
|
|
b68966ec7b | ||
|
|
99bc31cfa6 | ||
|
|
c36c481016 | ||
|
|
13f4fa0245 | ||
|
|
9792f2c9d7 | ||
|
|
53cec2dc1f | ||
|
|
b4f3d4b92e | ||
|
|
e741488659 | ||
|
|
bf4ee6f5c1 | ||
|
|
cecba01b71 | ||
|
|
06fffc2042 | ||
|
|
5ecc87bf3e | ||
|
|
7082c3e449 | ||
|
|
39ce412b25 | ||
|
|
00c7cf3d68 | ||
|
|
a916dd6632 | ||
|
|
eb73dbe24e | ||
|
|
bcc9fcf1c7 | ||
|
|
3e937eac2b | ||
|
|
0d05051f3c | ||
|
|
54471963e0 | ||
|
|
1b8f7e7a36 | ||
|
|
46500b3e36 | ||
|
|
ed40f382e8 | ||
|
|
8d61a4e5f9 | ||
|
|
55332afb29 | ||
|
|
d014fc7153 | ||
|
|
29496ede07 | ||
|
|
f4508a6765 | ||
|
|
53e32a47e4 | ||
|
|
41742b3fe3 | ||
|
|
5be47faff7 | ||
|
|
ea460d6d3e | ||
|
|
edb749f283 |
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
`);
|
||||
});
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 315 KiB |
@@ -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 {
|
||||
|
||||
+5
-1
@@ -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>
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
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>
|
||||
Binary file not shown.
Binary file not shown.
@@ -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">
|
||||
|
||||
@@ -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 />;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ static void __makeVersion()
|
||||
{
|
||||
__rnVersion = @{
|
||||
RCTVersionMajor: @(0),
|
||||
RCTVersionMinor: @(0),
|
||||
RCTVersionPatch: @(0),
|
||||
RCTVersionMinor: @(60),
|
||||
RCTVersionPatch: @(2),
|
||||
RCTVersionPrerelease: [NSNull null],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+14
@@ -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);
|
||||
}
|
||||
+230
@@ -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
|
||||
+34
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+29
-8
@@ -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
|
||||
|
||||
+13
-4
@@ -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"),
|
||||
],
|
||||
)
|
||||
|
||||
+2
-2
@@ -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
-5
@@ -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);
|
||||
|
||||
+9
-11
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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
|
||||
...
|
||||
@@ -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
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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 []
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¬e);
|
||||
|
||||
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 ¬e) {
|
||||
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
Reference in New Issue
Block a user