mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
65
Commits
nc/react-sync
...
v0.60.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14c03298d8 | ||
|
|
b6f6c5080e | ||
|
|
35300147ca | ||
|
|
1bb197afb1 | ||
|
|
683908dedf | ||
|
|
72473c7c3e | ||
|
|
feb931f378 | ||
|
|
95c747e8a7 | ||
|
|
f4d5e8c233 | ||
|
|
5a4fac77fb | ||
|
|
7a0a11316f | ||
|
|
b476ca0b1c | ||
|
|
812abfdbba | ||
|
|
ffdf3f22c6 | ||
|
|
eec4dc6b72 | ||
|
|
60e75dc1ab | ||
|
|
b1f81be4bc | ||
|
|
0190c9c97b | ||
|
|
348b8f0082 | ||
|
|
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}
|
||||
|
||||
@@ -252,6 +252,7 @@ type AndroidProps = $ReadOnly<{|
|
||||
| 'yes'
|
||||
| 'yesExcludeDescendants'
|
||||
),
|
||||
showSoftInputOnFocus?: ?boolean,
|
||||
|}>;
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
@@ -925,6 +926,12 @@ const TextInput = createReactClass({
|
||||
'newPassword',
|
||||
'oneTimeCode',
|
||||
]),
|
||||
/**
|
||||
* When `false`, it will prevent the soft keyboard from showing when the field is focused.
|
||||
* Defaults to `true`.
|
||||
* @platform android
|
||||
*/
|
||||
showSoftInputOnFocus: PropTypes.bool,
|
||||
},
|
||||
getDefaultProps() {
|
||||
return {
|
||||
|
||||
@@ -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: 6,
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,72 +10,10 @@
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTDefines.h>
|
||||
|
||||
#if __has_include(<React/fishhook.h>)
|
||||
#import <React/fishhook.h>
|
||||
#else
|
||||
#import <fishhook/fishhook.h>
|
||||
#endif
|
||||
|
||||
#if __has_include(<os/log.h>) && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100300 /* __IPHONE_10_3 */
|
||||
#import <os/log.h>
|
||||
#endif /* __IPHONE_10_3 */
|
||||
|
||||
#import "RCTSRWebSocket.h"
|
||||
|
||||
#if RCT_DEV // Only supported in dev mode
|
||||
|
||||
#if __has_include(<os/log.h>) && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100300 /* __IPHONE_10_3 */
|
||||
|
||||
// From https://github.com/apple/swift/blob/ad40c770bfe372f879b530443a3d94761fe258a6/stdlib/public/SDK/os/os_log.m
|
||||
typedef struct os_log_pack_s {
|
||||
uint64_t olp_continuous_time;
|
||||
struct timespec olp_wall_time;
|
||||
const void *olp_mh;
|
||||
const void *olp_pc;
|
||||
const char *olp_format;
|
||||
uint8_t olp_data[0];
|
||||
} os_log_pack_s, *os_log_pack_t;
|
||||
|
||||
static void (*orig__nwlog_pack)(os_log_pack_t pack, os_log_type_t logType);
|
||||
|
||||
static void my__nwlog_pack(os_log_pack_t pack, os_log_type_t logType)
|
||||
{
|
||||
if (logType == OS_LOG_TYPE_ERROR && strstr(pack->olp_format, "Connection has no connected handler") == NULL) {
|
||||
orig__nwlog_pack(pack, logType);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __IPHONE_10_3 */
|
||||
|
||||
static void (*orig_nwlog_legacy_v)(int, char*, va_list);
|
||||
|
||||
static void my_nwlog_legacy_v(int level, char *format, va_list args) {
|
||||
static const uint buffer_size = 256;
|
||||
static char buffer[buffer_size];
|
||||
va_list copy;
|
||||
va_copy(copy, args);
|
||||
vsnprintf(buffer, buffer_size, format, copy);
|
||||
va_end(copy);
|
||||
|
||||
if (strstr(buffer, "nw_connection_get_connected_socket_block_invoke") == NULL &&
|
||||
strstr(buffer, "Connection has no connected handler") == NULL) {
|
||||
orig_nwlog_legacy_v(level, format, args);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
||||
|
||||
static void (*orig_os_log_error_impl)(void *dso, os_log_t log, os_log_type_t type, const char *format, uint8_t *buf, uint32_t size);
|
||||
|
||||
static void my_os_log_error_impl(void *dso, os_log_t log, os_log_type_t type, const char *format, uint8_t *buf, uint32_t size)
|
||||
{
|
||||
if (strstr(format, "TCP Conn %p Failed : error %ld:%d") == NULL) {
|
||||
orig_os_log_error_impl(dso, log, type, format, buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __IPHONE_11_0 */
|
||||
|
||||
@interface RCTReconnectingWebSocket () <RCTSRWebSocketDelegate>
|
||||
@end
|
||||
|
||||
@@ -84,26 +22,6 @@ static void my_os_log_error_impl(void *dso, os_log_t log, os_log_type_t type, co
|
||||
RCTSRWebSocket *_socket;
|
||||
}
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
rebind_symbols((struct rebinding[1]){
|
||||
{"nwlog_legacy_v", my_nwlog_legacy_v, (void *)&orig_nwlog_legacy_v}
|
||||
}, 1);
|
||||
#if __has_include(<os/log.h>) && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100300 /* __IPHONE_10_3 */
|
||||
rebind_symbols((struct rebinding[1]){
|
||||
{"__nwlog_pack", my__nwlog_pack, (void *)&orig__nwlog_pack}
|
||||
}, 1);
|
||||
#endif /* __IPHONE_10_3 */
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
||||
rebind_symbols((struct rebinding[1]){
|
||||
{"_os_log_error_impl", my_os_log_error_impl, (void *)&orig_os_log_error_impl}
|
||||
}, 1);
|
||||
#endif /* __IPHONE_11_0 */
|
||||
});
|
||||
}
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url queue:(dispatch_queue_t)queue
|
||||
{
|
||||
if (self = [super init]) {
|
||||
|
||||
@@ -9,113 +9,31 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
1338BBE01B04ACC80064A9C9 /* RCTSRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDD1B04ACC80064A9C9 /* RCTSRWebSocket.m */; };
|
||||
1338BBE11B04ACC80064A9C9 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDF1B04ACC80064A9C9 /* RCTWebSocketExecutor.m */; };
|
||||
2D3ABDC220C7206E00DF56E9 /* libfishhook.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DBE0D001F3B181A0099AA32 /* libfishhook.a */; };
|
||||
2D3B5F3D1D9B165B00451313 /* RCTSRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDD1B04ACC80064A9C9 /* RCTSRWebSocket.m */; };
|
||||
2D3B5F3E1D9B165B00451313 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDF1B04ACC80064A9C9 /* RCTWebSocketExecutor.m */; };
|
||||
2D3B5F401D9B165B00451313 /* RCTWebSocketModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */; };
|
||||
3C86DF7C1ADF695F0047B81A /* RCTWebSocketModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */; };
|
||||
3DBE0D141F3B185A0099AA32 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DBE0D121F3B185A0099AA32 /* fishhook.c */; };
|
||||
3DBE0D151F3B185A0099AA32 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DBE0D121F3B185A0099AA32 /* fishhook.c */; };
|
||||
3DBE0D801F3B1AF00099AA32 /* fishhook.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3DBE0D131F3B185A0099AA32 /* fishhook.h */; };
|
||||
3DBE0D821F3B1B0C0099AA32 /* fishhook.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3DBE0D131F3B185A0099AA32 /* fishhook.h */; };
|
||||
A12E9E2E1E5DEC4E0029001B /* RCTReconnectingWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = A12E9E2D1E5DEC4E0029001B /* RCTReconnectingWebSocket.m */; };
|
||||
A12E9E2F1E5DEC550029001B /* RCTReconnectingWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = A12E9E2D1E5DEC4E0029001B /* RCTReconnectingWebSocket.m */; };
|
||||
ED297176215062BA00B7C4FE /* libfishhook-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DBE0D0D1F3B181C0099AA32 /* libfishhook-tvOS.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
3DBE0D0E1F3B18490099AA32 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3C86DF3E1ADF2C930047B81A /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3DBE0CF41F3B181A0099AA32;
|
||||
remoteInfo = fishhook;
|
||||
};
|
||||
3DBE0D101F3B184D0099AA32 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3C86DF3E1ADF2C930047B81A /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3DBE0D011F3B181C0099AA32;
|
||||
remoteInfo = "fishhook-tvOS";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
3DBE0D7F1F3B1AEC0099AA32 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = include/fishhook;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
3DBE0D801F3B1AF00099AA32 /* fishhook.h in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3DBE0D811F3B1B010099AA32 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = include/fishhook;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
3DBE0D821F3B1B0C0099AA32 /* fishhook.h in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1338BBDC1B04ACC80064A9C9 /* RCTSRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSRWebSocket.h; sourceTree = "<group>"; };
|
||||
1338BBDD1B04ACC80064A9C9 /* RCTSRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSRWebSocket.m; sourceTree = "<group>"; };
|
||||
1338BBDE1B04ACC80064A9C9 /* RCTWebSocketExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketExecutor.h; sourceTree = "<group>"; };
|
||||
1338BBDF1B04ACC80064A9C9 /* RCTWebSocketExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketExecutor.m; sourceTree = "<group>"; };
|
||||
13526A511F362F7F0008EF00 /* libfishhook.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfishhook.a; sourceTree = "<group>"; };
|
||||
2D2A28881D9B049200D4039D /* libRCTWebSocket-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTWebSocket-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2DC5E5271F3A6CFD000EE84B /* libfishhook-tvOS.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libfishhook-tvOS.a"; path = "../fishhook/build/Debug-appletvos/libfishhook-tvOS.a"; sourceTree = "<group>"; };
|
||||
3C86DF461ADF2C930047B81A /* libRCTWebSocket.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocket.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C86DF7A1ADF695F0047B81A /* RCTWebSocketModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketModule.h; sourceTree = "<group>"; };
|
||||
3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketModule.m; sourceTree = "<group>"; tabWidth = 2; };
|
||||
3DBE0D001F3B181A0099AA32 /* libfishhook.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libfishhook.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3DBE0D0D1F3B181C0099AA32 /* libfishhook-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libfishhook-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3DBE0D121F3B185A0099AA32 /* fishhook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = fishhook.c; path = ../fishhook/fishhook.c; sourceTree = "<group>"; };
|
||||
3DBE0D131F3B185A0099AA32 /* fishhook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fishhook.h; path = ../fishhook/fishhook.h; sourceTree = "<group>"; };
|
||||
A12E9E2C1E5DEC4E0029001B /* RCTReconnectingWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTReconnectingWebSocket.h; sourceTree = "<group>"; };
|
||||
A12E9E2D1E5DEC4E0029001B /* RCTReconnectingWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTReconnectingWebSocket.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
13526A4F1F362F770008EF00 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2D3ABDC220C7206E00DF56E9 /* libfishhook.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
2DC5E5151F3A6C39000EE84B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
ED297176215062BA00B7C4FE /* libfishhook-tvOS.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
13526A501F362F7F0008EF00 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2DC5E5271F3A6CFD000EE84B /* libfishhook-tvOS.a */,
|
||||
13526A511F362F7F0008EF00 /* libfishhook.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3C86DF3D1ADF2C930047B81A = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3DBE0D121F3B185A0099AA32 /* fishhook.c */,
|
||||
3DBE0D131F3B185A0099AA32 /* fishhook.h */,
|
||||
A12E9E2C1E5DEC4E0029001B /* RCTReconnectingWebSocket.h */,
|
||||
A12E9E2D1E5DEC4E0029001B /* RCTReconnectingWebSocket.m */,
|
||||
1338BBDC1B04ACC80064A9C9 /* RCTSRWebSocket.h */,
|
||||
@@ -137,8 +55,6 @@
|
||||
children = (
|
||||
3C86DF461ADF2C930047B81A /* libRCTWebSocket.a */,
|
||||
2D2A28881D9B049200D4039D /* libRCTWebSocket-tvOS.a */,
|
||||
3DBE0D001F3B181A0099AA32 /* libfishhook.a */,
|
||||
3DBE0D0D1F3B181C0099AA32 /* libfishhook-tvOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -180,38 +96,6 @@
|
||||
productReference = 3C86DF461ADF2C930047B81A /* libRCTWebSocket.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
3DBE0CF41F3B181A0099AA32 /* fishhook */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3DBE0CFD1F3B181A0099AA32 /* Build configuration list for PBXNativeTarget "fishhook" */;
|
||||
buildPhases = (
|
||||
3DBE0D7F1F3B1AEC0099AA32 /* CopyFiles */,
|
||||
3DBE0CF51F3B181A0099AA32 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = fishhook;
|
||||
productName = WebSocket;
|
||||
productReference = 3DBE0D001F3B181A0099AA32 /* libfishhook.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
3DBE0D011F3B181C0099AA32 /* fishhook-tvOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3DBE0D0A1F3B181C0099AA32 /* Build configuration list for PBXNativeTarget "fishhook-tvOS" */;
|
||||
buildPhases = (
|
||||
3DBE0D811F3B1B010099AA32 /* CopyFiles */,
|
||||
3DBE0D021F3B181C0099AA32 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "fishhook-tvOS";
|
||||
productName = "RCTWebSocket-tvOS";
|
||||
productReference = 3DBE0D0D1F3B181C0099AA32 /* libfishhook-tvOS.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -244,8 +128,6 @@
|
||||
targets = (
|
||||
3C86DF451ADF2C930047B81A /* RCTWebSocket */,
|
||||
2D2A28871D9B049200D4039D /* RCTWebSocket-tvOS */,
|
||||
3DBE0CF41F3B181A0099AA32 /* fishhook */,
|
||||
3DBE0D011F3B181C0099AA32 /* fishhook-tvOS */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -273,37 +155,8 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3DBE0CF51F3B181A0099AA32 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3DBE0D141F3B185A0099AA32 /* fishhook.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3DBE0D021F3B181C0099AA32 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3DBE0D151F3B185A0099AA32 /* fishhook.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
3DBE0D0F1F3B18490099AA32 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3DBE0CF41F3B181A0099AA32 /* fishhook */;
|
||||
targetProxy = 3DBE0D0E1F3B18490099AA32 /* PBXContainerItemProxy */;
|
||||
};
|
||||
3DBE0D111F3B184D0099AA32 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3DBE0D011F3B181C0099AA32 /* fishhook-tvOS */;
|
||||
targetProxy = 3DBE0D101F3B184D0099AA32 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
2D2A288E1D9B049200D4039D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
@@ -554,24 +407,6 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3DBE0CFD1F3B181A0099AA32 /* Build configuration list for PBXNativeTarget "fishhook" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3DBE0CFE1F3B181A0099AA32 /* Debug */,
|
||||
3DBE0CFF1F3B181A0099AA32 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3DBE0D0A1F3B181C0099AA32 /* Build configuration list for PBXNativeTarget "fishhook-tvOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3DBE0D0B1F3B181C0099AA32 /* Debug */,
|
||||
3DBE0D0C1F3B181C0099AA32 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 3C86DF3E1ADF2C930047B81A /* Project object */;
|
||||
|
||||
@@ -27,11 +27,9 @@ Pod::Spec.new do |s|
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "9.0", :tvos => "9.2" }
|
||||
s.source = source
|
||||
s.source_files = "*.{h,m}",
|
||||
"Libraries/fishhook/*.{h,c}"
|
||||
s.source_files = "*.{h,m}"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.header_dir = "React"
|
||||
|
||||
s.dependency "React-Core", version
|
||||
s.dependency "React-fishhook", version
|
||||
end
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) 2013, Facebook, Inc.
|
||||
// All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// * Neither the name Facebook nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,75 +0,0 @@
|
||||
# fishhook
|
||||
|
||||
__fishhook__ is a very simple library that enables dynamically rebinding symbols in Mach-O binaries running on iOS in the simulator and on device. This provides functionality that is similar to using [`DYLD_INTERPOSE`][interpose] on OS X. At Facebook, we've found it useful as a way to hook calls in libSystem for debugging/tracing purposes (for example, auditing for double-close issues with file descriptors).
|
||||
|
||||
[interpose]: http://opensource.apple.com/source/dyld/dyld-210.2.3/include/mach-o/dyld-interposing.h "<mach-o/dyld-interposing.h>"
|
||||
|
||||
## Usage
|
||||
|
||||
Once you add `fishhook.h`/`fishhook.c` to your project, you can rebind symbols as follows:
|
||||
```Objective-C
|
||||
#import <dlfcn.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "fishhook.h"
|
||||
|
||||
static int (*orig_close)(int);
|
||||
static int (*orig_open)(const char *, int, ...);
|
||||
|
||||
int my_close(int fd) {
|
||||
printf("Calling real close(%d)\n", fd);
|
||||
return orig_close(fd);
|
||||
}
|
||||
|
||||
int my_open(const char *path, int oflag, ...) {
|
||||
va_list ap = {0};
|
||||
mode_t mode = 0;
|
||||
|
||||
if ((oflag & O_CREAT) != 0) {
|
||||
// mode only applies to O_CREAT
|
||||
va_start(ap, oflag);
|
||||
mode = va_arg(ap, int);
|
||||
va_end(ap);
|
||||
printf("Calling real open('%s', %d, %d)\n", path, oflag, mode);
|
||||
return orig_open(path, oflag, mode);
|
||||
} else {
|
||||
printf("Calling real open('%s', %d)\n", path, oflag);
|
||||
return orig_open(path, oflag, mode);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
@autoreleasepool {
|
||||
rebind_symbols((struct rebinding[2]){{"close", my_close, (void *)&orig_close}, {"open", my_open, (void *)&orig_open}}, 2);
|
||||
|
||||
// Open our own binary and print out first 4 bytes (which is the same
|
||||
// for all Mach-O binaries on a given architecture)
|
||||
int fd = open(argv[0], O_RDONLY);
|
||||
uint32_t magic_number = 0;
|
||||
read(fd, &magic_number, 4);
|
||||
printf("Mach-O Magic Number: %x \n", magic_number);
|
||||
close(fd);
|
||||
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
```
|
||||
### Sample output
|
||||
```
|
||||
Calling real open('/var/mobile/Applications/161DA598-5B83-41F5-8A44-675491AF6A2C/Test.app/Test', 0)
|
||||
Mach-O Magic Number: feedface
|
||||
Calling real close(3)
|
||||
...
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
`dyld` binds lazy and non-lazy symbols by updating pointers in particular sections of the `__DATA` segment of a Mach-O binary. __fishhook__ re-binds these symbols by determining the locations to update for each of the symbol names passed to `rebind_symbols` and then writing out the corresponding replacements.
|
||||
|
||||
For a given image, the `__DATA` segment may contain two sections that are relevant for dynamic symbol bindings: `__nl_symbol_ptr` and `__la_symbol_ptr`. `__nl_symbol_ptr` is an array of pointers to non-lazily bound data (these are bound at the time a library is loaded) and `__la_symbol_ptr` is an array of pointers to imported functions that is generally filled by a routine called `dyld_stub_binder` during the first call to that symbol (it's also possible to tell `dyld` to bind these at launch). In order to find the name of the symbol that corresponds to a particular location in one of these sections, we have to jump through several layers of indirection. For the two relevant sections, the section headers (`struct section`s from `<mach-o/loader.h>`) provide an offset (in the `reserved1` field) into what is known as the indirect symbol table. The indirect symbol table, which is located in the `__LINKEDIT` segment of the binary, is just an array of indexes into the symbol table (also in `__LINKEDIT`) whose order is identical to that of the pointers in the non-lazy and lazy symbol sections. So, given `struct section nl_symbol_ptr`, the corresponding index in the symbol table of the first address in that section is `indirect_symbol_table[nl_symbol_ptr->reserved1]`. The symbol table itself is an array of `struct nlist`s (see `<mach-o/nlist.h>`), and each `nlist` contains an index into the string table in `__LINKEDIT` which where the actual symbol names are stored. So, for each pointer `__nl_symbol_ptr` and `__la_symbol_ptr`, we are able to find the corresponding symbol and then the corresponding string to compare against the requested symbol names, and if there is a match, we replace the pointer in the section with the replacement.
|
||||
|
||||
The process of looking up the name of a given entry in the lazy or non-lazy pointer tables looks like this:
|
||||

|
||||
@@ -1,31 +0,0 @@
|
||||
# coding: utf-8
|
||||
# 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.
|
||||
|
||||
require "json"
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
|
||||
version = package['version']
|
||||
|
||||
source = { :git => 'https://github.com/facebook/react-native.git' }
|
||||
if version == '1000.0.0'
|
||||
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
|
||||
source[:commit] = `git rev-parse HEAD`.strip
|
||||
else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-fishhook"
|
||||
s.version = version
|
||||
s.summary = "A very simple library that enables dynamically rebinding symbols in Mach-O binaries running on iOS in the simulator and on device."
|
||||
s.homepage = "http://facebook.github.io/react-native/"
|
||||
s.license = package["license"]
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "9.0", :tvos => "9.2" }
|
||||
s.source = source
|
||||
s.source_files = "*.{c,h}"
|
||||
s.header_dir = "fishhook"
|
||||
end
|
||||
@@ -1,210 +0,0 @@
|
||||
// Copyright (c) 2013, Facebook, Inc.
|
||||
// All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// * Neither the name Facebook nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import "fishhook.h"
|
||||
|
||||
#import <dlfcn.h>
|
||||
#import <stdlib.h>
|
||||
#import <string.h>
|
||||
#import <sys/types.h>
|
||||
#import <mach-o/dyld.h>
|
||||
#import <mach-o/loader.h>
|
||||
#import <mach-o/nlist.h>
|
||||
|
||||
#ifdef __LP64__
|
||||
typedef struct mach_header_64 mach_header_t;
|
||||
typedef struct segment_command_64 segment_command_t;
|
||||
typedef struct section_64 section_t;
|
||||
typedef struct nlist_64 nlist_t;
|
||||
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
|
||||
#else
|
||||
typedef struct mach_header mach_header_t;
|
||||
typedef struct segment_command segment_command_t;
|
||||
typedef struct section section_t;
|
||||
typedef struct nlist nlist_t;
|
||||
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
|
||||
#endif
|
||||
|
||||
#ifndef SEG_DATA_CONST
|
||||
#define SEG_DATA_CONST "__DATA_CONST"
|
||||
#endif
|
||||
|
||||
struct rebindings_entry {
|
||||
struct rebinding *rebindings;
|
||||
size_t rebindings_nel;
|
||||
struct rebindings_entry *next;
|
||||
};
|
||||
|
||||
static struct rebindings_entry *_rebindings_head;
|
||||
|
||||
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
|
||||
struct rebinding rebindings[],
|
||||
size_t nel) {
|
||||
struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
|
||||
if (!new_entry) {
|
||||
return -1;
|
||||
}
|
||||
new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
|
||||
if (!new_entry->rebindings) {
|
||||
free(new_entry);
|
||||
return -1;
|
||||
}
|
||||
memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
|
||||
new_entry->rebindings_nel = nel;
|
||||
new_entry->next = *rebindings_head;
|
||||
*rebindings_head = new_entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
|
||||
section_t *section,
|
||||
intptr_t slide,
|
||||
nlist_t *symtab,
|
||||
char *strtab,
|
||||
uint32_t *indirect_symtab) {
|
||||
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
|
||||
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
|
||||
for (uint i = 0; i < section->size / sizeof(void *); i++) {
|
||||
uint32_t symtab_index = indirect_symbol_indices[i];
|
||||
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
|
||||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
|
||||
continue;
|
||||
}
|
||||
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
|
||||
char *symbol_name = strtab + strtab_offset;
|
||||
if (strnlen(symbol_name, 2) < 2) {
|
||||
continue;
|
||||
}
|
||||
struct rebindings_entry *cur = rebindings;
|
||||
while (cur) {
|
||||
for (uint j = 0; j < cur->rebindings_nel; j++) {
|
||||
if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
|
||||
if (cur->rebindings[j].replaced != NULL &&
|
||||
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
|
||||
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
|
||||
}
|
||||
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
|
||||
goto symbol_loop;
|
||||
}
|
||||
}
|
||||
cur = cur->next;
|
||||
}
|
||||
symbol_loop:;
|
||||
}
|
||||
}
|
||||
|
||||
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
|
||||
const struct mach_header *header,
|
||||
intptr_t slide) {
|
||||
Dl_info info;
|
||||
if (dladdr(header, &info) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
segment_command_t *cur_seg_cmd;
|
||||
segment_command_t *linkedit_segment = NULL;
|
||||
struct symtab_command* symtab_cmd = NULL;
|
||||
struct dysymtab_command* dysymtab_cmd = NULL;
|
||||
|
||||
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
|
||||
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
|
||||
cur_seg_cmd = (segment_command_t *)cur;
|
||||
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
|
||||
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
|
||||
linkedit_segment = cur_seg_cmd;
|
||||
}
|
||||
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
|
||||
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
|
||||
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
|
||||
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
|
||||
}
|
||||
}
|
||||
|
||||
if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
|
||||
!dysymtab_cmd->nindirectsyms) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find base symbol/string table addresses
|
||||
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
|
||||
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
|
||||
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
|
||||
|
||||
// Get indirect symbol table (array of uint32_t indices into symbol table)
|
||||
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
|
||||
|
||||
cur = (uintptr_t)header + sizeof(mach_header_t);
|
||||
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
|
||||
cur_seg_cmd = (segment_command_t *)cur;
|
||||
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
|
||||
if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
|
||||
strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
|
||||
continue;
|
||||
}
|
||||
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
|
||||
section_t *sect =
|
||||
(section_t *)(cur + sizeof(segment_command_t)) + j;
|
||||
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
|
||||
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
|
||||
}
|
||||
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
|
||||
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _rebind_symbols_for_image(const struct mach_header *header,
|
||||
intptr_t slide) {
|
||||
rebind_symbols_for_image(_rebindings_head, header, slide);
|
||||
}
|
||||
|
||||
int rebind_symbols_image(void *header,
|
||||
intptr_t slide,
|
||||
struct rebinding rebindings[],
|
||||
size_t rebindings_nel) {
|
||||
struct rebindings_entry *rebindings_head = NULL;
|
||||
int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
|
||||
rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
|
||||
free(rebindings_head);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
|
||||
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
|
||||
if (retval < 0) {
|
||||
return retval;
|
||||
}
|
||||
// If this was the first call, register callback for image additions (which is also invoked for
|
||||
// existing images, otherwise, just run on existing images
|
||||
if (!_rebindings_head->next) {
|
||||
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
|
||||
} else {
|
||||
uint32_t c = _dyld_image_count();
|
||||
for (uint32_t i = 0; i < c; i++) {
|
||||
_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright (c) 2013, Facebook, Inc.
|
||||
// All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// * Neither the name Facebook nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef fishhook_h
|
||||
#define fishhook_h
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if !defined(FISHHOOK_EXPORT)
|
||||
#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
|
||||
#else
|
||||
#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif //__cplusplus
|
||||
|
||||
/*
|
||||
* A structure representing a particular intended rebinding from a symbol
|
||||
* name to its replacement
|
||||
*/
|
||||
struct rebinding {
|
||||
const char *name;
|
||||
void *replacement;
|
||||
void **replaced;
|
||||
};
|
||||
|
||||
/*
|
||||
* For each rebinding in rebindings, rebinds references to external, indirect
|
||||
* symbols with the specified name to instead point at replacement for each
|
||||
* image in the calling process as well as for all future images that are loaded
|
||||
* by the process. If rebind_functions is called more than once, the symbols to
|
||||
* rebind are added to the existing list of rebindings, and if a given symbol
|
||||
* is rebound more than once, the later rebinding will take precedence.
|
||||
*/
|
||||
FISHHOOK_VISIBILITY
|
||||
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
|
||||
|
||||
/*
|
||||
* Rebinds as above, but only in the specified image. The header should point
|
||||
* to the mach-o header, the slide should be the slide offset. Others as above.
|
||||
*/
|
||||
FISHHOOK_VISIBILITY
|
||||
int rebind_symbols_image(void *header,
|
||||
intptr_t slide,
|
||||
struct rebinding rebindings[],
|
||||
size_t rebindings_nel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif //__cplusplus
|
||||
|
||||
#endif //fishhook_h
|
||||
|
||||
-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);
|
||||
@@ -364,20 +364,6 @@
|
||||
remoteGlobalIDString = 139D7E881E25C6D100323FB7;
|
||||
remoteInfo = "double-conversion";
|
||||
};
|
||||
3DBE0D321F3B18670099AA32 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 3DBE0D001F3B181A0099AA32;
|
||||
remoteInfo = fishhook;
|
||||
};
|
||||
3DBE0D341F3B18670099AA32 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 3DBE0D0D1F3B181C0099AA32;
|
||||
remoteInfo = "fishhook-tvOS";
|
||||
};
|
||||
3DCE53211FEAB1C500613583 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 14AADEFF1AC3DB95002390C9 /* React.xcodeproj */;
|
||||
@@ -753,8 +739,6 @@
|
||||
children = (
|
||||
139FDED91B0651EA00C62182 /* libRCTWebSocket.a */,
|
||||
2DD323D51DA2DD8B000FE1B8 /* libRCTWebSocket-tvOS.a */,
|
||||
3DBE0D331F3B18670099AA32 /* libfishhook.a */,
|
||||
3DBE0D351F3B18670099AA32 /* libfishhook-tvOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -1532,20 +1516,6 @@
|
||||
remoteRef = 3D507F431EBC88B700B56834 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
3DBE0D331F3B18670099AA32 /* libfishhook.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libfishhook.a;
|
||||
remoteRef = 3DBE0D321F3B18670099AA32 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
3DBE0D351F3B18670099AA32 /* libfishhook-tvOS.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = "libfishhook-tvOS.a";
|
||||
remoteRef = 3DBE0D341F3B18670099AA32 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
3DCE53221FEAB1C500613583 /* libjsinspector.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
|
||||
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: @(6),
|
||||
RCTVersionPrerelease: [NSNull null],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
- (NSInteger)bottomSafeViewHeight
|
||||
{
|
||||
if (@available(iOS 11.0, *)) {
|
||||
return [UIApplication sharedApplication].delegate.window.safeAreaInsets.bottom;
|
||||
return RCTSharedApplication().delegate.window.safeAreaInsets.bottom;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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.6
|
||||
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,81 @@
|
||||
/**
|
||||
* 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 com.facebook.proguard.annotations.DoNotStrip;
|
||||
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.
|
||||
@DoNotStrip
|
||||
public class AndroidUnicodeUtils {
|
||||
@DoNotStrip
|
||||
public static int localeCompare(String left, String right) {
|
||||
Collator collator = Collator.getInstance();
|
||||
return collator.compare(left, right);
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
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();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
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", 6,
|
||||
"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() {
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.facebook.react.R;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
@@ -173,23 +176,23 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
final String accessibilityLabel = (String) view.getTag(R.id.accessibility_label);
|
||||
final ReadableArray accessibilityStates = (ReadableArray) view.getTag(R.id.accessibility_states);
|
||||
final String accessibilityHint = (String) view.getTag(R.id.accessibility_hint);
|
||||
StringBuilder contentDescription = new StringBuilder();
|
||||
final List<String> contentDescription = new ArrayList<>();
|
||||
if (accessibilityLabel != null) {
|
||||
contentDescription.append(accessibilityLabel + ", ");
|
||||
contentDescription.add(accessibilityLabel);
|
||||
}
|
||||
if (accessibilityStates != null) {
|
||||
for (int i = 0; i < accessibilityStates.size(); i++) {
|
||||
String state = accessibilityStates.getString(i);
|
||||
if (sStateDescription.containsKey(state)) {
|
||||
contentDescription.append(view.getContext().getString(sStateDescription.get(state)) + ", ");
|
||||
contentDescription.add(view.getContext().getString(sStateDescription.get(state)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accessibilityHint != null) {
|
||||
contentDescription.append(accessibilityHint + ", ");
|
||||
contentDescription.add(accessibilityHint);
|
||||
}
|
||||
if (contentDescription.length() > 0) {
|
||||
view.setContentDescription(contentDescription.toString());
|
||||
if (contentDescription.size() > 0) {
|
||||
view.setContentDescription(TextUtils.join(", ", contentDescription));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,9 @@ public class ReactEditText extends EditText {
|
||||
}
|
||||
setFocusableInTouchMode(true);
|
||||
boolean focused = super.requestFocus(direction, previouslyFocusedRect);
|
||||
showSoftKeyboard();
|
||||
if (getShowSoftInputOnFocus()) {
|
||||
showSoftKeyboard();
|
||||
}
|
||||
return focused;
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -715,6 +715,12 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
view.setBorderStyle(borderStyle);
|
||||
}
|
||||
|
||||
@ReactProp(name = "showSoftInputOnFocus", defaultBoolean = true)
|
||||
public void showKeyboardOnFocus(ReactEditText view, boolean showKeyboardOnFocus) {
|
||||
view.setShowSoftInputOnFocus(showKeyboardOnFocus);
|
||||
}
|
||||
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
ViewProps.BORDER_WIDTH,
|
||||
ViewProps.BORDER_LEFT_WIDTH,
|
||||
|
||||
+24
-24
@@ -646,32 +646,32 @@ public class ReactViewBackgroundDrawable extends Drawable {
|
||||
}
|
||||
|
||||
mPathForBorderRadiusOutline.addRoundRect(
|
||||
mTempRectForBorderRadiusOutline,
|
||||
new float[] {
|
||||
topLeftRadius + extraRadiusForOutline,
|
||||
topLeftRadius + extraRadiusForOutline,
|
||||
topRightRadius + extraRadiusForOutline,
|
||||
topRightRadius + extraRadiusForOutline,
|
||||
bottomRightRadius + extraRadiusForOutline,
|
||||
bottomRightRadius + extraRadiusForOutline,
|
||||
bottomLeftRadius + extraRadiusForOutline,
|
||||
bottomLeftRadius + extraRadiusForOutline
|
||||
},
|
||||
Path.Direction.CW);
|
||||
mTempRectForBorderRadiusOutline,
|
||||
new float[] {
|
||||
topLeftRadius + extraRadiusForOutline,
|
||||
topLeftRadius + extraRadiusForOutline,
|
||||
topRightRadius + extraRadiusForOutline,
|
||||
topRightRadius + extraRadiusForOutline,
|
||||
bottomRightRadius + extraRadiusForOutline,
|
||||
bottomRightRadius + extraRadiusForOutline,
|
||||
bottomLeftRadius + extraRadiusForOutline,
|
||||
bottomLeftRadius + extraRadiusForOutline
|
||||
},
|
||||
Path.Direction.CW);
|
||||
|
||||
mCenterDrawPath.addRoundRect(
|
||||
mTempRectForCenterDrawPath,
|
||||
new float[] {
|
||||
innerTopLeftRadiusX + (innerTopLeftRadiusX > 0 ? extraRadiusForOutline : 0),
|
||||
innerTopLeftRadiusY + (innerTopLeftRadiusY > 0 ? extraRadiusForOutline : 0),
|
||||
innerTopRightRadiusX + (innerTopRightRadiusX > 0 ? extraRadiusForOutline : 0),
|
||||
innerTopRightRadiusY + (innerTopRightRadiusY > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomRightRadiusX + (innerBottomRightRadiusX > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomRightRadiusY + (innerBottomRightRadiusY > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomLeftRadiusX + (innerBottomLeftRadiusX > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomLeftRadiusY + (innerBottomLeftRadiusY > 0 ? extraRadiusForOutline : 0)
|
||||
},
|
||||
Path.Direction.CW);
|
||||
mTempRectForCenterDrawPath,
|
||||
new float[] {
|
||||
innerTopLeftRadiusX + (topLeftRadius > 0 ? extraRadiusForOutline : 0),
|
||||
innerTopLeftRadiusY + (topLeftRadius > 0 ? extraRadiusForOutline : 0),
|
||||
innerTopRightRadiusX + (topRightRadius > 0 ? extraRadiusForOutline : 0),
|
||||
innerTopRightRadiusY + (topRightRadius > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomRightRadiusX + (bottomRightRadius > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomRightRadiusY + (bottomRightRadius > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomLeftRadiusX + (bottomLeftRadius > 0 ? extraRadiusForOutline : 0),
|
||||
innerBottomLeftRadiusY + (bottomLeftRadius > 0 ? extraRadiusForOutline : 0)
|
||||
},
|
||||
Path.Direction.CW);
|
||||
|
||||
/**
|
||||
* Rounded Multi-Colored Border Algorithm:
|
||||
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user