Release React Native for Android

This is an early release and there are several things that are known
not to work if you're porting your iOS app to Android.

See the Known Issues guide on the website.

We will work with the community to reach platform parity with iOS.
This commit is contained in:
Martin Konicek
2015-09-14 15:35:58 +01:00
parent c372dab213
commit 42eb5464fd
571 changed files with 44550 additions and 116 deletions

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<0a1e3b1f834f027e7a5bc5303f945b0e>>
package com.facebook.csslayout;
public enum CSSAlign {
AUTO,
FLEX_START,
CENTER,
FLEX_END,
STRETCH,
}

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<755069c4747cc9fc5624d70e5130e3d1>>
package com.facebook.csslayout;
public class CSSConstants {
public static final float UNDEFINED = Float.NaN;
public static boolean isUndefined(float value) {
return Float.compare(value, UNDEFINED) == 0;
}
}

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<5dc7f205706089599859188712b3bd8a>>
package com.facebook.csslayout;
public enum CSSDirection {
INHERIT,
LTR,
RTL,
}

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<6183a87290f3acd1caef7b6301bbf3a7>>
package com.facebook.csslayout;
public enum CSSFlexDirection {
COLUMN,
COLUMN_REVERSE,
ROW,
ROW_REVERSE
}

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<619fbefba1cfee797bbc7dd18e22f50c>>
package com.facebook.csslayout;
public enum CSSJustify {
FLEX_START,
CENTER,
FLEX_END,
SPACE_BETWEEN,
SPACE_AROUND,
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<153b6759d2dd8fe8cf6d58a422450b96>>
package com.facebook.csslayout;
/**
* Where the output of {@link LayoutEngine#layoutNode(CSSNode, float)} will go in the CSSNode.
*/
public class CSSLayout {
public float top;
public float left;
public float right;
public float bottom;
public float width = CSSConstants.UNDEFINED;
public float height = CSSConstants.UNDEFINED;
public CSSDirection direction = CSSDirection.LTR;
/**
* This should always get called before calling {@link LayoutEngine#layoutNode(CSSNode, float)}
*/
public void resetResult() {
left = 0;
top = 0;
right = 0;
bottom = 0;
width = CSSConstants.UNDEFINED;
height = CSSConstants.UNDEFINED;
direction = CSSDirection.LTR;
}
public void copy(CSSLayout layout) {
left = layout.left;
top = layout.top;
right = layout.right;
bottom = layout.bottom;
width = layout.width;
height = layout.height;
direction = layout.direction;
}
@Override
public String toString() {
return "layout: {" +
"left: " + left + ", " +
"top: " + top + ", " +
"width: " + width + ", " +
"height: " + height +
"direction: " + direction +
"}";
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<9d48f3d4330e7b6cba0fff7d8f1e8b0c>>
package com.facebook.csslayout;
/**
* A context for holding values local to a given instance of layout computation.
*
* This is necessary for making layout thread-safe. A separate instance should
* be used when {@link CSSNode#calculateLayout} is called concurrently on
* different node hierarchies.
*/
public class CSSLayoutContext {
/*package*/ final MeasureOutput measureOutput = new MeasureOutput();
}

View File

@@ -0,0 +1,396 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<c3a1298e36789dcda4cc2776d48646a7>>
package com.facebook.csslayout;
import javax.annotation.Nullable;
import java.util.ArrayList;
import com.facebook.infer.annotation.Assertions;
/**
* A CSS Node. It has a style object you can manipulate at {@link #style}. After calling
* {@link #calculateLayout()}, {@link #layout} will be filled with the results of the layout.
*/
public class CSSNode {
private static enum LayoutState {
/**
* Some property of this node or its children has changes and the current values in
* {@link #layout} are not valid.
*/
DIRTY,
/**
* This node has a new layout relative to the last time {@link #markLayoutSeen()} was called.
*/
HAS_NEW_LAYOUT,
/**
* {@link #layout} is valid for the node's properties and this layout has been marked as
* having been seen.
*/
UP_TO_DATE,
}
public static interface MeasureFunction {
/**
* Should measure the given node and put the result in the given MeasureOutput.
*
* NB: measure is NOT guaranteed to be threadsafe/re-entrant safe!
*/
public void measure(CSSNode node, float width, MeasureOutput measureOutput);
}
// VisibleForTesting
/*package*/ final CSSStyle style = new CSSStyle();
/*package*/ final CSSLayout layout = new CSSLayout();
/*package*/ final CachedCSSLayout lastLayout = new CachedCSSLayout();
public int lineIndex = 0;
private @Nullable ArrayList<CSSNode> mChildren;
private @Nullable CSSNode mParent;
private @Nullable MeasureFunction mMeasureFunction = null;
private LayoutState mLayoutState = LayoutState.DIRTY;
public int getChildCount() {
return mChildren == null ? 0 : mChildren.size();
}
public CSSNode getChildAt(int i) {
Assertions.assertNotNull(mChildren);
return mChildren.get(i);
}
public void addChildAt(CSSNode child, int i) {
if (child.mParent != null) {
throw new IllegalStateException("Child already has a parent, it must be removed first.");
}
if (mChildren == null) {
// 4 is kinda arbitrary, but the default of 10 seems really high for an average View.
mChildren = new ArrayList<>(4);
}
mChildren.add(i, child);
child.mParent = this;
dirty();
}
public CSSNode removeChildAt(int i) {
Assertions.assertNotNull(mChildren);
CSSNode removed = mChildren.remove(i);
removed.mParent = null;
dirty();
return removed;
}
public @Nullable CSSNode getParent() {
return mParent;
}
/**
* @return the index of the given child, or -1 if the child doesn't exist in this node.
*/
public int indexOf(CSSNode child) {
Assertions.assertNotNull(mChildren);
return mChildren.indexOf(child);
}
public void setMeasureFunction(MeasureFunction measureFunction) {
if (!valuesEqual(mMeasureFunction, measureFunction)) {
mMeasureFunction = measureFunction;
dirty();
}
}
public boolean isMeasureDefined() {
return mMeasureFunction != null;
}
/*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width) {
if (!isMeasureDefined()) {
throw new RuntimeException("Measure function isn't defined!");
}
measureOutput.height = CSSConstants.UNDEFINED;
measureOutput.width = CSSConstants.UNDEFINED;
Assertions.assertNotNull(mMeasureFunction).measure(this, width, measureOutput);
return measureOutput;
}
/**
* Performs the actual layout and saves the results in {@link #layout}
*/
public void calculateLayout(CSSLayoutContext layoutContext) {
layout.resetResult();
LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, null);
}
/**
* See {@link LayoutState#DIRTY}.
*/
protected boolean isDirty() {
return mLayoutState == LayoutState.DIRTY;
}
/**
* See {@link LayoutState#HAS_NEW_LAYOUT}.
*/
public boolean hasNewLayout() {
return mLayoutState == LayoutState.HAS_NEW_LAYOUT;
}
protected void dirty() {
if (mLayoutState == LayoutState.DIRTY) {
return;
} else if (mLayoutState == LayoutState.HAS_NEW_LAYOUT) {
throw new IllegalStateException("Previous layout was ignored! markLayoutSeen() never called");
}
mLayoutState = LayoutState.DIRTY;
if (mParent != null) {
mParent.dirty();
}
}
/*package*/ void markHasNewLayout() {
mLayoutState = LayoutState.HAS_NEW_LAYOUT;
}
/**
* Tells the node that the current values in {@link #layout} have been seen. Subsequent calls
* to {@link #hasNewLayout()} will return false until this node is laid out with new parameters.
* You must call this each time the layout is generated if the node has a new layout.
*/
public void markLayoutSeen() {
if (!hasNewLayout()) {
throw new IllegalStateException("Expected node to have a new layout to be seen!");
}
mLayoutState = LayoutState.UP_TO_DATE;
}
private void toStringWithIndentation(StringBuilder result, int level) {
// Spaces and tabs are dropped by IntelliJ logcat integration, so rely on __ instead.
StringBuilder indentation = new StringBuilder();
for (int i = 0; i < level; ++i) {
indentation.append("__");
}
result.append(indentation.toString());
result.append(layout.toString());
if (getChildCount() == 0) {
return;
}
result.append(", children: [\n");
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).toStringWithIndentation(result, level + 1);
result.append("\n");
}
result.append(indentation + "]");
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
this.toStringWithIndentation(sb, 0);
return sb.toString();
}
protected boolean valuesEqual(float f1, float f2) {
return FloatUtil.floatsEqual(f1, f2);
}
protected <T> boolean valuesEqual(@Nullable T o1, @Nullable T o2) {
if (o1 == null) {
return o2 == null;
}
return o1.equals(o2);
}
public void setDirection(CSSDirection direction) {
if (!valuesEqual(style.direction, direction)) {
style.direction = direction;
dirty();
}
}
public void setFlexDirection(CSSFlexDirection flexDirection) {
if (!valuesEqual(style.flexDirection, flexDirection)) {
style.flexDirection = flexDirection;
dirty();
}
}
public void setJustifyContent(CSSJustify justifyContent) {
if (!valuesEqual(style.justifyContent, justifyContent)) {
style.justifyContent = justifyContent;
dirty();
}
}
public void setAlignItems(CSSAlign alignItems) {
if (!valuesEqual(style.alignItems, alignItems)) {
style.alignItems = alignItems;
dirty();
}
}
public void setAlignSelf(CSSAlign alignSelf) {
if (!valuesEqual(style.alignSelf, alignSelf)) {
style.alignSelf = alignSelf;
dirty();
}
}
public void setPositionType(CSSPositionType positionType) {
if (!valuesEqual(style.positionType, positionType)) {
style.positionType = positionType;
dirty();
}
}
public void setWrap(CSSWrap flexWrap) {
if (!valuesEqual(style.flexWrap, flexWrap)) {
style.flexWrap = flexWrap;
dirty();
}
}
public void setFlex(float flex) {
if (!valuesEqual(style.flex, flex)) {
style.flex = flex;
dirty();
}
}
public void setMargin(int spacingType, float margin) {
if (style.margin.set(spacingType, margin)) {
dirty();
}
}
public void setPadding(int spacingType, float padding) {
if (style.padding.set(spacingType, padding)) {
dirty();
}
}
public void setBorder(int spacingType, float border) {
if (style.border.set(spacingType, border)) {
dirty();
}
}
public void setPositionTop(float positionTop) {
if (!valuesEqual(style.positionTop, positionTop)) {
style.positionTop = positionTop;
dirty();
}
}
public void setPositionBottom(float positionBottom) {
if (!valuesEqual(style.positionBottom, positionBottom)) {
style.positionBottom = positionBottom;
dirty();
}
}
public void setPositionLeft(float positionLeft) {
if (!valuesEqual(style.positionLeft, positionLeft)) {
style.positionLeft = positionLeft;
dirty();
}
}
public void setPositionRight(float positionRight) {
if (!valuesEqual(style.positionRight, positionRight)) {
style.positionRight = positionRight;
dirty();
}
}
public void setStyleWidth(float width) {
if (!valuesEqual(style.width, width)) {
style.width = width;
dirty();
}
}
public void setStyleHeight(float height) {
if (!valuesEqual(style.height, height)) {
style.height = height;
dirty();
}
}
public float getLayoutX() {
return layout.left;
}
public float getLayoutY() {
return layout.top;
}
public float getLayoutWidth() {
return layout.width;
}
public float getLayoutHeight() {
return layout.height;
}
public CSSDirection getLayoutDirection() {
return layout.direction;
}
/**
* Get this node's padding, as defined by style + default padding.
*/
public Spacing getStylePadding() {
return style.padding;
}
/**
* Get this node's width, as defined in the style.
*/
public float getStyleWidth() {
return style.width;
}
/**
* Get this node's height, as defined in the style.
*/
public float getStyleHeight() {
return style.height;
}
/**
* Get this node's direction, as defined in the style.
*/
public CSSDirection getStyleDirection() {
return style.direction;
}
/**
* Set a default padding (left/top/right/bottom) for this node.
*/
public void setDefaultPadding(int spacingType, float padding) {
if (style.padding.setDefault(spacingType, padding)) {
dirty();
}
}
}

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<a53da9b13bd6e7b03fd743adf0e536b3>>
package com.facebook.csslayout;
public enum CSSPositionType {
RELATIVE,
ABSOLUTE,
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<ec76950a22dda1c6e98eafc15ccf7cd3>>
package com.facebook.csslayout;
/**
* The CSS style definition for a {@link CSSNode}.
*/
public class CSSStyle {
public CSSDirection direction = CSSDirection.INHERIT;
public CSSFlexDirection flexDirection = CSSFlexDirection.COLUMN;
public CSSJustify justifyContent = CSSJustify.FLEX_START;
public CSSAlign alignContent = CSSAlign.FLEX_START;
public CSSAlign alignItems = CSSAlign.STRETCH;
public CSSAlign alignSelf = CSSAlign.AUTO;
public CSSPositionType positionType = CSSPositionType.RELATIVE;
public CSSWrap flexWrap = CSSWrap.NOWRAP;
public float flex;
public Spacing margin = new Spacing();
public Spacing padding = new Spacing();
public Spacing border = new Spacing();
public float positionTop = CSSConstants.UNDEFINED;
public float positionBottom = CSSConstants.UNDEFINED;
public float positionLeft = CSSConstants.UNDEFINED;
public float positionRight = CSSConstants.UNDEFINED;
public float width = CSSConstants.UNDEFINED;
public float height = CSSConstants.UNDEFINED;
public float minWidth = CSSConstants.UNDEFINED;
public float minHeight = CSSConstants.UNDEFINED;
public float maxWidth = CSSConstants.UNDEFINED;
public float maxHeight = CSSConstants.UNDEFINED;
}

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<21dab9bd1acf5892ad09370b69b7dd71>>
package com.facebook.csslayout;
public enum CSSWrap {
NOWRAP,
WRAP,
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<8276834951a75286a0b6d4a980bc43ce>>
package com.facebook.csslayout;
/**
* CSSLayout with additional information about the conditions under which it was generated.
* {@link #requestedWidth} and {@link #requestedHeight} are the width and height the parent set on
* this node before calling layout visited us.
*/
public class CachedCSSLayout extends CSSLayout {
public float requestedWidth = CSSConstants.UNDEFINED;
public float requestedHeight = CSSConstants.UNDEFINED;
public float parentMaxWidth = CSSConstants.UNDEFINED;
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<fc074ec7db63f2eebf1e9cbc626f280d>>
package com.facebook.csslayout;
public class FloatUtil {
private static final float EPSILON = .00001f;
public static boolean floatsEqual(float f1, float f2) {
if (Float.isNaN(f1) || Float.isNaN(f2)) {
return Float.isNaN(f1) && Float.isNaN(f2);
}
return Math.abs(f2 - f1) < EPSILON;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<177254872216bd05e2c0667dc29ef032>>
package com.facebook.csslayout;
/**
* POJO to hold the output of the measure function.
*/
public class MeasureOutput {
public float width;
public float height;
}

View File

@@ -0,0 +1,12 @@
The source of truth for css-layout is: https://github.com/facebook/css-layout
The code here should be kept in sync with GitHub.
HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/7104f7c8eb6c160a8d10badc08f9b981bb65e19c
There is generated code in:
- README (this file)
- java/com/facebook/csslayout (this folder)
- javatests/com/facebook/csslayout
The code was generated by running 'make' in the css-layout folder and copied to React Native.

View File

@@ -0,0 +1,14 @@
The source of truth for css-layout is: https://github.com/facebook/css-layout
The code here should be kept in sync with GitHub.
HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/7104f7c8eb6c160a8d10badc08f9b981bb65e19c
There is generated code in:
- README.facebook (this file)
- java/com/facebook/csslayout (this folder)
- javatests/com/facebook/csslayout
The code was generated by running 'make' in the css-layout folder and running:
./syncFromGithub.sh <pathToGithubRepo> <pathToFbAndroid>

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// NOTE: this file is auto-copied from https://github.com/facebook/css-layout
// @generated SignedSource<<6853e87a84818f1abb6573aee7d6bd55>>
package com.facebook.csslayout;
import javax.annotation.Nullable;
/**
* Class representing CSS spacing (padding, margin, and borders). This is mostly necessary to
* properly implement interactions and updates for properties like margin, marginLeft, and
* marginHorizontal.
*/
public class Spacing {
/**
* Spacing type that represents the left direction. E.g. {@code marginLeft}.
*/
public static final int LEFT = 0;
/**
* Spacing type that represents the top direction. E.g. {@code marginTop}.
*/
public static final int TOP = 1;
/**
* Spacing type that represents the right direction. E.g. {@code marginRight}.
*/
public static final int RIGHT = 2;
/**
* Spacing type that represents the bottom direction. E.g. {@code marginBottom}.
*/
public static final int BOTTOM = 3;
/**
* Spacing type that represents vertical direction (top and bottom). E.g. {@code marginVertical}.
*/
public static final int VERTICAL = 4;
/**
* Spacing type that represents horizontal direction (left and right). E.g.
* {@code marginHorizontal}.
*/
public static final int HORIZONTAL = 5;
/**
* Spacing type that represents start direction e.g. left in left-to-right, right in right-to-left.
*/
public static final int START = 6;
/**
* Spacing type that represents end direction e.g. right in left-to-right, left in right-to-left.
*/
public static final int END = 7;
/**
* Spacing type that represents all directions (left, top, right, bottom). E.g. {@code margin}.
*/
public static final int ALL = 8;
private final float[] mSpacing = newFullSpacingArray();
@Nullable private float[] mDefaultSpacing = null;
/**
* Set a spacing value.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM},
* {@link #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
* @param value the value for this direction
* @return {@code true} if the spacing has changed, or {@code false} if the same value was already
* set
*/
public boolean set(int spacingType, float value) {
if (!FloatUtil.floatsEqual(mSpacing[spacingType], value)) {
mSpacing[spacingType] = value;
return true;
}
return false;
}
/**
* Set a default spacing value. This is used as a fallback when no spacing has been set for a
* particular direction.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}
* @param value the default value for this direction
* @return
*/
public boolean setDefault(int spacingType, float value) {
if (mDefaultSpacing == null) {
mDefaultSpacing = newSpacingResultArray();
}
if (!FloatUtil.floatsEqual(mDefaultSpacing[spacingType], value)) {
mDefaultSpacing[spacingType] = value;
return true;
}
return false;
}
/**
* Get the spacing for a direction. This takes into account any default values that have been set.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}
*/
public float get(int spacingType) {
int secondType = spacingType == TOP || spacingType == BOTTOM ? VERTICAL : HORIZONTAL;
float defaultValue = spacingType == START || spacingType == END ? CSSConstants.UNDEFINED : 0;
return
!CSSConstants.isUndefined(mSpacing[spacingType])
? mSpacing[spacingType]
: !CSSConstants.isUndefined(mSpacing[secondType])
? mSpacing[secondType]
: !CSSConstants.isUndefined(mSpacing[ALL])
? mSpacing[ALL]
: mDefaultSpacing != null
? mDefaultSpacing[spacingType]
: defaultValue;
}
/**
* Get the raw value (that was set using {@link #set(int, float)}), without taking into account
* any default values.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM},
* {@link #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
*/
public float getRaw(int spacingType) {
return mSpacing[spacingType];
}
private static float[] newFullSpacingArray() {
return new float[] {
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
};
}
private static float[] newSpacingResultArray() {
return newSpacingResultArray(0);
}
private static float[] newSpacingResultArray(float defaultValue) {
return new float[] {
defaultValue,
defaultValue,
defaultValue,
defaultValue,
defaultValue,
defaultValue,
CSSConstants.UNDEFINED,
CSSConstants.UNDEFINED,
defaultValue,
};
}
}

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env bash
function usage {
echo "usage: syncFromGithub.sh <pathToGithubRepo> <pathToFbAndroidRepo>";
}
function patchfile {
# Add React Native copyright
printf "/**\n" >> /tmp/csslayoutsync.tmp
printf " * Copyright (c) 2014-present, Facebook, Inc.\n" >> /tmp/csslayoutsync.tmp
printf " * All rights reserved.\n" >> /tmp/csslayoutsync.tmp
printf " * This source code is licensed under the BSD-style license found in the\n" >> /tmp/csslayoutsync.tmp
printf " * LICENSE file in the root directory of this source tree. An additional grant\n" >> /tmp/csslayoutsync.tmp
printf " * of patent rights can be found in the PATENTS file in the same directory.\n" >> /tmp/csslayoutsync.tmp
printf " */\n\n" >> /tmp/csslayoutsync.tmp
printf "// NOTE: this file is auto-copied from https://github.com/facebook/css-layout\n" >> /tmp/csslayoutsync.tmp
# The following is split over four lines so Phabricator doesn't think this file is generated
printf "// @g" >> /tmp/csslayoutsync.tmp
printf "enerated <<S" >> /tmp/csslayoutsync.tmp
printf "ignedSource::*O*zOeWoEQle#+L" >> /tmp/csslayoutsync.tmp
printf "!plEphiEmie@IsG>>\n\n" >> /tmp/csslayoutsync.tmp
tail -n +9 $1 >> /tmp/csslayoutsync.tmp
mv /tmp/csslayoutsync.tmp $1
$ROOT/scripts/signedsource.py sign $1
}
if [ -z $1 ]; then
usage
exit 1
fi
if [ -z $2 ]; then
usage
exit 1
fi
GITHUB=$1
ROOT=$2
set -e # exit if any command fails
echo "Making github project..."
pushd $GITHUB
COMMIT_ID=$(git rev-parse HEAD)
make
popd
SRC=$GITHUB/src/java/src/com/facebook/csslayout
TESTS=$GITHUB/src/java/tests/com/facebook/csslayout
FBA_SRC=.
FBA_TESTS=$ROOT/javatests/com/facebook/csslayout
echo "Copying src files over..."
cp $SRC/*.java $FBA_SRC
echo "Copying test files over..."
cp $TESTS/*.java $FBA_TESTS
echo "Patching files..."
for sourcefile in $FBA_SRC/*.java; do
patchfile $sourcefile
done
for testfile in $FBA_TESTS/*.java; do
patchfile $testfile
done
echo "Writing README.facebook"
echo "The source of truth for css-layout is: https://github.com/facebook/css-layout
The code here should be kept in sync with GitHub.
HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/$COMMIT_ID
There is generated code in:
- README.facebook (this file)
- java/com/facebook/csslayout (this folder)
- javatests/com/facebook/csslayout
The code was generated by running 'make' in the css-layout folder and running:
./syncFromGithub.sh <pathToGithubRepo> <pathToFbAndroid>
" > $FBA_SRC/README.facebook
echo "Writing README"
echo "The source of truth for css-layout is: https://github.com/facebook/css-layout
The code here should be kept in sync with GitHub.
HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/$COMMIT_ID
There is generated code in:
- README (this file)
- java/com/facebook/csslayout (this folder)
- javatests/com/facebook/csslayout
The code was generated by running 'make' in the css-layout folder and copied to React Native.
" > $FBA_SRC/README
echo "Done."
echo "Please run buck test //javatests/com/facebook/csslayout"

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* A Java Object that has native memory allocated corresponding to this instance.
*
* NB: THREAD SAFETY (this comment also exists at Countable.cpp)
*
* {@link #dispose} deletes the corresponding native object on whatever thread the method is called
* on. In the common case when this is called by Countable#finalize(), this will be called on the
* system finalizer thread. If you manually call dispose on the Java object, the native object
* will be deleted synchronously on that thread.
*/
@DoNotStrip
public class Countable {
// Private C++ instance
@DoNotStrip
private long mInstance = 0;
public Countable() {
Prerequisites.ensure();
}
public native void dispose();
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
}

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class CppException extends RuntimeException {
@DoNotStrip
public CppException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class CppSystemErrorException extends CppException {
int errorCode;
@DoNotStrip
public CppSystemErrorException(String message, int errorCode) {
super(message);
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* This object holds a native C++ member for hybrid Java/C++ objects.
*
* NB: THREAD SAFETY
*
* {@link #dispose} deletes the corresponding native object on whatever thread
* the method is called on. In the common case when this is called by
* HybridData#finalize(), this will be called on the system finalizer
* thread. If you manually call resetNative() on the Java object, the C++
* object will be deleted synchronously on that thread.
*/
@DoNotStrip
public class HybridData {
// Private C++ instance
@DoNotStrip
private long mNativePointer = 0;
public HybridData() {
Prerequisites.ensure();
}
/**
* To explicitly delete the instance, call resetNative(). If the C++
* instance is referenced after this is called, a NullPointerException will
* be thrown. resetNative() may be called multiple times safely. Because
* {@link #finalize} calls resetNative, the instance will not leak if this is
* not called, but timing of deletion and the thread the C++ dtor is called
* on will be at the whim of the Java GC. If you want to control the thread
* and timing of the destructor, you should call resetNative() explicitly.
*/
public native void resetNative();
protected void finalize() throws Throwable {
resetNative();
super.finalize();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.jni;
import com.facebook.soloader.SoLoader;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
public class Prerequisites {
private static final int EGL_OPENGL_ES2_BIT = 0x0004;
public static void ensure() {
SoLoader.loadLibrary("fbjni");
}
// Code is simplified version of getDetectedVersion()
// from cts/tests/tests/graphics/src/android/opengl/cts/OpenGlEsVersionTest.java
static public boolean supportsOpenGL20() {
EGL10 egl = (EGL10) EGLContext.getEGL();
EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] numConfigs = new int[1];
if (egl.eglInitialize(display, null)) {
try {
if (egl.eglGetConfigs(display, null, 0, numConfigs)) {
EGLConfig[] configs = new EGLConfig[numConfigs[0]];
if (egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) {
int[] value = new int[1];
for (int i = 0; i < numConfigs[0]; i++) {
if (egl.eglGetConfigAttrib(display, configs[i],
EGL10.EGL_RENDERABLE_TYPE, value)) {
if ((value[0] & EGL_OPENGL_ES2_BIT) == EGL_OPENGL_ES2_BIT) {
return true;
}
}
}
}
}
} finally {
egl.eglTerminate(display);
}
}
return false;
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class UnknownCppException extends CppException {
@DoNotStrip
public UnknownCppException() {
super("Unknown");
}
@DoNotStrip
public UnknownCppException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.proguard.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Add this annotation to a class, method, or field to instruct Proguard to not strip it out.
*
* This is useful for methods called via reflection that could appear as unused to Proguard.
*/
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR })
@Retention(CLASS)
public @interface DoNotStrip {
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.proguard.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Add this annotation to a class, to keep all "void set*(***)" and get* methods.
*
* <p>This is useful for classes that are controlled by animator-like classes that control
* various properties with reflection.
*
* <p><b>NOTE:</b> This is <em>not</em> needed for Views because their getters and setters
* are automatically kept by the default Android SDK ProGuard config.
*/
@Target({ElementType.TYPE})
@Retention(CLASS)
public @interface KeepGettersAndSetters {
}

View File

@@ -0,0 +1,15 @@
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}

View File

@@ -0,0 +1,88 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
/**
* {@code CompositeReactPackage} allows to create a single package composed of views and modules
* from several other packages.
*/
public class CompositeReactPackage implements ReactPackage {
private final List<ReactPackage> mChildReactPackages = new ArrayList<>();
/**
* The order in which packages are passed matters. It may happen that a NativeModule or
* or a ViewManager exists in two or more ReactPackages. In that case the latter will win
* i.e. the latter will overwrite the former. This re-occurrence is detected by
* comparing a name of a module.
*/
public CompositeReactPackage(ReactPackage arg1, ReactPackage arg2, ReactPackage... args) {
mChildReactPackages.add(arg1);
mChildReactPackages.add(arg2);
for (ReactPackage reactPackage: args) {
mChildReactPackages.add(reactPackage);
}
}
/**
* {@inheritDoc}
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
final Map<String, NativeModule> moduleMap = new HashMap<>();
for (ReactPackage reactPackage: mChildReactPackages) {
for (NativeModule nativeModule: reactPackage.createNativeModules(reactContext)) {
moduleMap.put(nativeModule.getName(), nativeModule);
}
}
return new ArrayList(moduleMap.values());
}
/**
* {@inheritDoc}
*/
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
final Set<Class<? extends JavaScriptModule>> moduleSet = new HashSet<>();
for (ReactPackage reactPackage: mChildReactPackages) {
for (Class<? extends JavaScriptModule> jsModule: reactPackage.createJSModules()) {
moduleSet.add(jsModule);
}
}
return new ArrayList(moduleSet);
}
/**
* {@inheritDoc}
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
final Map<String, ViewManager> viewManagerMap = new HashMap<>();
for (ReactPackage reactPackage: mChildReactPackages) {
for (ViewManager viewManager: reactPackage.createViewManagers(reactContext)) {
viewManagerMap.put(viewManager.getName(), viewManager);
}
}
return new ArrayList(viewManagerMap.values());
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.facebook.catalyst.uimanager.debug.DebugComponentOwnershipModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ExceptionsManagerModule;
import com.facebook.react.modules.core.JSTimersExecution;
import com.facebook.react.modules.core.Timing;
import com.facebook.react.modules.debug.AnimationsDebugModule;
import com.facebook.react.modules.debug.SourceCodeModule;
import com.facebook.react.modules.systeminfo.AndroidInfoModule;
import com.facebook.react.uimanager.AppRegistry;
import com.facebook.react.uimanager.ReactNative;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Package defining core framework modules (e.g. UIManager). It should be used for modules that
* require special integration with other framework parts (e.g. with the list of packages to load
* view managers from).
*/
/* package */ class CoreModulesPackage implements ReactPackage {
private final ReactInstanceManager mReactInstanceManager;
private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler;
CoreModulesPackage(
ReactInstanceManager reactInstanceManager,
DefaultHardwareBackBtnHandler hardwareBackBtnHandler) {
mReactInstanceManager = reactInstanceManager;
mHardwareBackBtnHandler = hardwareBackBtnHandler;
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext catalystApplicationContext) {
return Arrays.<NativeModule>asList(
new AnimationsDebugModule(
catalystApplicationContext,
mReactInstanceManager.getDevSupportManager().getDevSettings()),
new AndroidInfoModule(),
new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()),
new Timing(catalystApplicationContext),
new SourceCodeModule(
mReactInstanceManager.getDevSupportManager().getSourceUrl(),
mReactInstanceManager.getDevSupportManager().getSourceMapUrl()),
new UIManagerModule(
catalystApplicationContext,
mReactInstanceManager.createAllViewManagers(catalystApplicationContext)),
new DebugComponentOwnershipModule(catalystApplicationContext));
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Arrays.asList(
DeviceEventManagerModule.RCTDeviceEventEmitter.class,
JSTimersExecution.class,
RCTEventEmitter.class,
AppRegistry.class,
ReactNative.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return new ArrayList<>(0);
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react;
/**
* Lifecycle state for an Activity. The state right after pause and right before resume are the
* basically the same so this enum is in terms of the forward lifecycle progression (onResume, etc).
* Eventually, if necessary, it could contain something like:
*
* BEFORE_CREATE,
* CREATED,
* VIEW_CREATED,
* STARTED,
* RESUMED
*/
public enum LifecycleState {
BEFORE_RESUME,
RESUMED,
}

View File

@@ -0,0 +1,564 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSCJavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.JavaScriptModulesConfig;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleRegistry;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.bridge.ProxyJavaScriptExecutor;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.devsupport.DevSupportManager;
import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.AppRegistry;
import com.facebook.react.uimanager.ReactNative;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.soloader.SoLoader;
/**
* This class is managing instances of {@link CatalystInstance}. It expose a way to configure
* catalyst instance using {@link ReactPackage} and keeps track of the lifecycle of that
* instance. It also sets up connection between the instance and developers support functionality
* of the framework.
*
* An instance of this manager is required to start JS application in {@link ReactRootView} (see
* {@link ReactRootView#startReactApplication} for more info).
*
* The lifecycle of the instance of {@link ReactInstanceManager} should be bound to the activity
* that owns the {@link ReactRootView} that is used to render react application using this
* instance manager (see {@link ReactRootView#startReactApplication}). It's required tp pass
* owning activity's lifecycle events to the instance manager (see {@link #onPause},
* {@link #onDestroy} and {@link #onResume}).
*
* To instantiate an instance of this class use {@link #builder}.
*/
public class ReactInstanceManager {
/* should only be accessed from main thread (UI thread) */
private final List<ReactRootView> mAttachedRootViews = new ArrayList<>();
private LifecycleState mLifecycleState;
/* accessed from any thread */
private final @Nullable String mBundleAssetName; /* name of JS bundle file in assets folder */
private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */
private final List<ReactPackage> mPackages;
private final DevSupportManager mDevSupportManager;
private final boolean mUseDeveloperSupport;
private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
private @Nullable volatile ReactContext mCurrentReactContext;
private final Context mApplicationContext;
private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl;
private final ReactInstanceDevCommandsHandler mDevInterface =
new ReactInstanceDevCommandsHandler() {
@Override
public void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) {
ReactInstanceManager.this.onReloadWithJSDebugger(proxyExecutor);
}
@Override
public void onJSBundleLoadedFromServer() {
ReactInstanceManager.this.onJSBundleLoadedFromServer();
}
@Override
public void toggleElementInspector() {
ReactInstanceManager.this.toggleElementInspector();
}
};
private final DefaultHardwareBackBtnHandler mBackBtnHandler =
new DefaultHardwareBackBtnHandler() {
@Override
public void invokeDefaultOnBackPressed() {
ReactInstanceManager.this.invokeDefaultOnBackPressed();
}
};
private ReactInstanceManager(
Context applicationContext,
@Nullable String bundleAssetName,
@Nullable String jsMainModuleName,
List<ReactPackage> packages,
boolean useDeveloperSupport,
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState) {
initializeSoLoaderIfNecessary(applicationContext);
mApplicationContext = applicationContext;
mBundleAssetName = bundleAssetName;
mJSMainModuleName = jsMainModuleName;
mPackages = packages;
mUseDeveloperSupport = useDeveloperSupport;
// We need to instantiate DevSupportManager regardless to the useDeveloperSupport option,
// although will prevent dev support manager from displaying any options or dialogs by
// checking useDeveloperSupport option before calling setDevSupportEnabled on this manager
// TODO(6803830): Don't instantiate devsupport manager when useDeveloperSupport is false
mDevSupportManager = new DevSupportManager(
applicationContext,
mDevInterface,
mJSMainModuleName,
useDeveloperSupport);
mBridgeIdleDebugListener = bridgeIdleDebugListener;
mLifecycleState = initialLifecycleState;
}
public DevSupportManager getDevSupportManager() {
return mDevSupportManager;
}
/**
* Creates a builder that is capable of creating an instance of {@link ReactInstanceManager}.
*/
public static Builder builder() {
return new Builder();
}
private static void initializeSoLoaderIfNecessary(Context applicationContext) {
// Call SoLoader.initialize here, this is required for apps that does not use exopackage and
// does not use SoLoader for loading other native code except from the one used by React Native
// This way we don't need to require others to have additional initialization code and to
// subclass android.app.Application.
// Method SoLoader.init is idempotent, so if you wish to use native exopackage, just call
// SoLoader.init with appropriate args before initializing ReactInstanceManager
SoLoader.init(applicationContext, /* native exopackage */ false);
}
/**
* This method will give JS the opportunity to consume the back button event. If JS does not
* consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip
* to JS.
*/
public void onBackPressed() {
UiThreadUtil.assertOnUiThread();
ReactContext reactContext = mCurrentReactContext;
if (mCurrentReactContext == null) {
// Invoke without round trip to JS.
FLog.w(ReactConstants.TAG, "Instance detached from instance manager");
invokeDefaultOnBackPressed();
} else {
DeviceEventManagerModule deviceEventManagerModule =
Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class);
deviceEventManagerModule.emitHardwareBackPressed();
}
}
private void invokeDefaultOnBackPressed() {
UiThreadUtil.assertOnUiThread();
if (mDefaultBackButtonImpl != null) {
mDefaultBackButtonImpl.invokeDefaultOnBackPressed();
}
}
private void toggleElementInspector() {
if (mCurrentReactContext != null) {
mCurrentReactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("toggleElementInspector", null);
}
}
public void onPause() {
UiThreadUtil.assertOnUiThread();
mLifecycleState = LifecycleState.BEFORE_RESUME;
mDefaultBackButtonImpl = null;
if (mUseDeveloperSupport) {
mDevSupportManager.setDevSupportEnabled(false);
}
if (mCurrentReactContext != null) {
mCurrentReactContext.onPause();
}
}
/**
* Use this method when the activity resumes to enable invoking the back button directly from JS.
*
* This method retains an instance to provided mDefaultBackButtonImpl. Thus it's
* important to pass from the activity instance that owns this particular instance of {@link
* ReactInstanceManager}, so that once this instance receive {@link #onDestroy} event it will
* clear the reference to that defaultBackButtonImpl.
*
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns
* this instance of {@link ReactInstanceManager}.
*/
public void onResume(DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
UiThreadUtil.assertOnUiThread();
mLifecycleState = LifecycleState.RESUMED;
mDefaultBackButtonImpl = defaultBackButtonImpl;
if (mUseDeveloperSupport) {
mDevSupportManager.setDevSupportEnabled(true);
}
if (mCurrentReactContext != null) {
mCurrentReactContext.onResume();
}
}
public void onDestroy() {
UiThreadUtil.assertOnUiThread();
if (mUseDeveloperSupport) {
mDevSupportManager.setDevSupportEnabled(false);
}
if (mCurrentReactContext != null) {
mCurrentReactContext.onDestroy();
}
}
public void showDevOptionsDialog() {
UiThreadUtil.assertOnUiThread();
mDevSupportManager.showDevOptionsDialog();
}
/**
* Attach given {@param rootView} to a catalyst instance manager and start JS application using
* JS module provided by {@link ReactRootView#getJSModuleName}. This view will then be tracked
* by this manager and in case of catalyst instance restart it will be re-attached.
*/
/* package */ void attachMeasuredRootView(ReactRootView rootView) {
UiThreadUtil.assertOnUiThread();
mAttachedRootViews.add(rootView);
if (mCurrentReactContext == null) {
initializeReactContext();
} else {
attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
}
}
/**
* Detach given {@param rootView} from current catalyst instance. It's safe to call this method
* multiple times on the same {@param rootView} - in that case view will be detached with the
* first call.
*/
/* package */ void detachRootView(ReactRootView rootView) {
UiThreadUtil.assertOnUiThread();
if (mAttachedRootViews.remove(rootView)) {
if (mCurrentReactContext != null && mCurrentReactContext.hasActiveCatalystInstance()) {
detachViewFromInstance(rootView, mCurrentReactContext.getCatalystInstance());
}
}
}
/**
* Uses configured {@link ReactPackage} instances to create all view managers
*/
/* package */ List<ViewManager> createAllViewManagers(
ReactApplicationContext catalystApplicationContext) {
List<ViewManager> allViewManagers = new ArrayList<>();
for (ReactPackage reactPackage : mPackages) {
allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext));
}
return allViewManagers;
}
@VisibleForTesting
public @Nullable ReactContext getCurrentReactContext() {
return mCurrentReactContext;
}
private void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) {
recreateReactContext(
proxyExecutor,
JSBundleLoader.createRemoteDebuggerBundleLoader(
mDevSupportManager.getJSBundleURLForRemoteDebugging()));
}
private void onJSBundleLoadedFromServer() {
recreateReactContext(
new JSCJavaScriptExecutor(),
JSBundleLoader.createCachedBundleFromNetworkLoader(
mDevSupportManager.getSourceUrl(),
mDevSupportManager.getDownloadedJSBundleFile()));
}
private void initializeReactContext() {
if (mUseDeveloperSupport) {
if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
// If there is a up-to-date bundle downloaded from server, always use that
onJSBundleLoadedFromServer();
return;
} else if (mBundleAssetName == null ||
!mDevSupportManager.hasBundleInAssets(mBundleAssetName)) {
// Bundle not available in assets, fetch from the server
mDevSupportManager.handleReloadJS();
return;
}
}
// Use JS file from assets
recreateReactContext(
new JSCJavaScriptExecutor(),
JSBundleLoader.createAssetLoader(
mApplicationContext.getAssets(),
mBundleAssetName));
}
private void recreateReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
UiThreadUtil.assertOnUiThread();
if (mCurrentReactContext != null) {
tearDownReactContext(mCurrentReactContext);
}
mCurrentReactContext = createReactContext(jsExecutor, jsBundleLoader);
for (ReactRootView rootView : mAttachedRootViews) {
attachMeasuredRootViewToInstance(
rootView,
mCurrentReactContext.getCatalystInstance());
}
}
private void attachMeasuredRootViewToInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) {
UiThreadUtil.assertOnUiThread();
// Reset view content as it's going to be populated by the application content from JS
rootView.removeAllViews();
rootView.setId(View.NO_ID);
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
int rootTag = uiManagerModule.addMeasuredRootView(rootView);
@Nullable Bundle launchOptions = rootView.getLaunchOptions();
WritableMap initialProps = launchOptions != null
? Arguments.fromBundle(launchOptions)
: Arguments.createMap();
String jsAppModuleName = rootView.getJSModuleName();
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", rootTag);
appParams.putMap("initialProps", initialProps);
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
}
private void detachViewFromInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) {
UiThreadUtil.assertOnUiThread();
catalystInstance.getJSModule(ReactNative.class)
.unmountComponentAtNodeAndRemoveContainer(rootView.getId());
}
private void tearDownReactContext(ReactContext reactContext) {
UiThreadUtil.assertOnUiThread();
if (mLifecycleState == LifecycleState.RESUMED) {
reactContext.onPause();
}
for (ReactRootView rootView : mAttachedRootViews) {
detachViewFromInstance(rootView, reactContext.getCatalystInstance());
}
reactContext.onDestroy();
mDevSupportManager.onReactInstanceDestroyed(reactContext);
}
/**
* @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
*/
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
if (mUseDeveloperSupport) {
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
}
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler);
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
// TODO(6818138): Solve use-case of native/js modules overriding
for (ReactPackage reactPackage : mPackages) {
processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
}
CatalystInstance.Builder catalystInstanceBuilder = new CatalystInstance.Builder()
.setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor)
.setRegistry(nativeRegistryBuilder.build())
.setJSModulesConfig(jsModulesBuilder.build())
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(mDevSupportManager);
CatalystInstance catalystInstance = catalystInstanceBuilder.build();
if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
reactContext.initializeWithInstance(catalystInstance);
catalystInstance.initialize();
mDevSupportManager.onNewReactContextCreated(reactContext);
moveReactContextToCurrentLifecycleState(reactContext);
return reactContext;
}
private void processPackage(
ReactPackage reactPackage,
ReactApplicationContext reactContext,
NativeModuleRegistry.Builder nativeRegistryBuilder,
JavaScriptModulesConfig.Builder jsModulesBuilder) {
for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
nativeRegistryBuilder.add(nativeModule);
}
for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
jsModulesBuilder.add(jsModuleClass);
}
}
private void moveReactContextToCurrentLifecycleState(ReactApplicationContext reactContext) {
if (mLifecycleState == LifecycleState.RESUMED) {
reactContext.onResume();
}
}
/**
* Builder class for {@link ReactInstanceManager}
*/
public static class Builder {
private final List<ReactPackage> mPackages = new ArrayList<>();
private @Nullable String mBundleAssetName;
private @Nullable String mJSMainModuleName;
private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
private @Nullable Application mApplication;
private boolean mUseDeveloperSupport;
private @Nullable LifecycleState mInitialLifecycleState;
private Builder() {
}
/**
* Name of the JS budle file to be loaded from application's raw assets.
* Example: {@code "index.android.js"}
*/
public Builder setBundleAssetName(String bundleAssetName) {
mBundleAssetName = bundleAssetName;
return this;
}
/**
* Path to your app's main module on the packager server. This is used when
* reloading JS during development. All paths are relative to the root folder
* the packager is serving files from.
* Examples:
* {@code "index.android"} or
* {@code "subdirectory/index.android"}
*/
public Builder setJSMainModuleName(String jsMainModuleName) {
mJSMainModuleName = jsMainModuleName;
return this;
}
public Builder addPackage(ReactPackage reactPackage) {
mPackages.add(reactPackage);
return this;
}
public Builder setBridgeIdleDebugListener(
NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener) {
mBridgeIdleDebugListener = bridgeIdleDebugListener;
return this;
}
/**
* Required. This must be your {@code Application} instance.
*/
public Builder setApplication(Application application) {
mApplication = application;
return this;
}
/**
* When {@code true}, developer options such as JS reloading and debugging are enabled.
* Note you still have to call {@link #showDevOptionsDialog} to show the dev menu,
* e.g. when the device Menu button is pressed.
*/
public Builder setUseDeveloperSupport(boolean useDeveloperSupport) {
mUseDeveloperSupport = useDeveloperSupport;
return this;
}
/**
* Sets the initial lifecycle state of the host. For example, if the host is already resumed at
* creation time, we wouldn't expect an onResume call until we get an onPause call.
*/
public Builder setInitialLifecycleState(LifecycleState initialLifecycleState) {
mInitialLifecycleState = initialLifecycleState;
return this;
}
/**
* Instantiates a new {@link ReactInstanceManager}.
* Before calling {@code build}, the following must be called:
* <ul>
* <li> {@link #setApplication}
* <li> {@link #setBundleAssetName} or {@link #setJSMainModuleName}
* </ul>
*/
public ReactInstanceManager build() {
Assertions.assertCondition(
mUseDeveloperSupport || mBundleAssetName != null,
"JS Bundle has to be provided in app assets when dev support is disabled");
Assertions.assertCondition(
mBundleAssetName != null || mJSMainModuleName != null,
"Either BundleAssetName or MainModuleName needs to be provided");
return new ReactInstanceManager(
Assertions.assertNotNull(
mApplication,
"Application property has not been set with this builder"),
mBundleAssetName,
mJSMainModuleName,
mPackages,
mUseDeveloperSupport,
mBridgeIdleDebugListener,
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"));
}
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react;
import java.util.List;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
/**
* Main interface for providing additional capabilities to the catalyst framework by couple of
* different means:
* 1) Registering new native modules
* 2) Registering new JS modules that may be accessed from native modules or from other parts of the
* native code (requiring JS modules from the package doesn't mean it will automatically be included
* as a part of the JS bundle, so there should be a corresponding piece of code on JS side that will
* require implementation of that JS module so that it gets bundled)
* 3) Registering custom native views (view managers) and custom event types
* 4) Registering natively packaged assets/resources (e.g. images) exposed to JS
*
* TODO(6788500, 6788507): Implement support for adding custom views, events and resources
*/
public interface ReactPackage {
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
List<NativeModule> createNativeModules(ReactApplicationContext reactContext);
/**
* @return list of JS modules to register with the newly created catalyst instance.
*
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
List<Class<? extends JavaScriptModule>> createJSModules();
/**
* @return a list of view managers that should be registered with {@link UIManagerModule}
*/
List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}

View File

@@ -0,0 +1,374 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react;
import javax.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.RootView;
import com.facebook.react.uimanager.SizeMonitoringFrameLayout;
import com.facebook.react.uimanager.TouchTargetHelper;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.TouchEvent;
import com.facebook.react.uimanager.events.TouchEventType;
/**
* Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI
* manager can re-layout its elements.
* It is also responsible for handling touch events passed to any of it's child view's and sending
* those events to JS via RCTEventEmitter module. This view is overriding
* {@link ViewGroup#onInterceptTouchEvent} method in order to be notified about the events for all
* of it's children and it's also overriding {@link ViewGroup#requestDisallowInterceptTouchEvent}
* to make sure that {@link ViewGroup#onInterceptTouchEvent} will get events even when some child
* view start intercepting it. In case when no child view is interested in handling some particular
* touch event this view's {@link View#onTouchEvent} will still return true in order to be notified
* about all subsequent touch events related to that gesture (in case when JS code want to handle
* that gesture).
*/
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
private final KeyboardListener mKeyboardListener = new KeyboardListener();
private @Nullable ReactInstanceManager mReactInstanceManager;
private @Nullable String mJSModuleName;
private @Nullable Bundle mLaunchOptions;
private int mTargetTag = -1;
private boolean mChildIsHandlingNativeGesture = false;
private boolean mWasMeasured = false;
private boolean mAttachScheduled = false;
private boolean mIsAttachedToWindow = false;
private boolean mIsAttachedToInstance = false;
public ReactRootView(Context context) {
super(context);
}
public ReactRootView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReactRootView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
throw new IllegalStateException(
"The root catalyst view must have a width and height given to it by it's parent view. " +
"You can do this by specifying MATCH_PARENT or explicit width and height in the layout.");
}
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
mWasMeasured = true;
if (mAttachScheduled && mReactInstanceManager != null && mIsAttachedToWindow) {
// Scheduled from {@link #startReactApplication} call in case when the view measurements are
// not available
mAttachScheduled = false;
// Enqueue it to UIThread not to block onMeasure waiting for the catalyst instance creation
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
Assertions.assertNotNull(mReactInstanceManager)
.attachMeasuredRootView(ReactRootView.this);
mIsAttachedToInstance = true;
getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener);
}
});
}
}
/**
* Main catalyst view is responsible for collecting and sending touch events to JS. This method
* reacts for an incoming android native touch events ({@link MotionEvent}) and calls into
* {@link com.facebook.react.uimanager.events.EventDispatcher} when appropriate.
* It uses {@link com.facebook.react.uimanager.TouchTargetManagerHelper#findTouchTargetView}
* helper method for figuring out a react view ID in the case of ACTION_DOWN
* event (when the gesture starts).
*/
private void handleTouchEvent(MotionEvent ev) {
if (mReactInstanceManager == null || !mIsAttachedToInstance ||
mReactInstanceManager.getCurrentReactContext() == null) {
FLog.w(
ReactConstants.TAG,
"Unable to handle touch in JS as the catalyst instance has not been attached");
return;
}
int action = ev.getAction() & MotionEvent.ACTION_MASK;
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class)
.getEventDispatcher();
if (action == MotionEvent.ACTION_DOWN) {
if (mTargetTag != -1) {
FLog.e(
ReactConstants.TAG,
"Got DOWN touch before receiving UP or CANCEL from last gesture");
}
// First event for this gesture. We expect tag to be set to -1, and we use helper method
// {@link #findTargetTagForTouch} to find react view ID that will be responsible for handling
// this gesture
mChildIsHandlingNativeGesture = false;
mTargetTag = TouchTargetHelper.findTargetTagForTouch(ev.getRawY(), ev.getRawX(), this);
eventDispatcher.dispatchEvent(new TouchEvent(mTargetTag, TouchEventType.START, ev));
} else if (mChildIsHandlingNativeGesture) {
// If the touch was intercepted by a child, we've already sent a cancel event to JS for this
// gesture, so we shouldn't send any more touches related to it.
return;
} else if (mTargetTag == -1) {
// All the subsequent action types are expected to be called after ACTION_DOWN thus target
// is supposed to be set for them.
FLog.e(
ReactConstants.TAG,
"Unexpected state: received touch event but didn't get starting ACTION_DOWN for this " +
"gesture before");
} else if (action == MotionEvent.ACTION_UP) {
// End of the gesture. We reset target tag to -1 and expect no further event associated with
// this gesture.
eventDispatcher.dispatchEvent(new TouchEvent(mTargetTag, TouchEventType.END, ev));
mTargetTag = -1;
} else if (action == MotionEvent.ACTION_MOVE) {
// Update pointer position for current gesture
eventDispatcher.dispatchEvent(new TouchEvent(mTargetTag, TouchEventType.MOVE, ev));
} else if (action == MotionEvent.ACTION_POINTER_DOWN) {
// New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
eventDispatcher.dispatchEvent(new TouchEvent(mTargetTag, TouchEventType.START, ev));
} else if (action == MotionEvent.ACTION_POINTER_UP) {
// Exactly onw of the pointers goes up
eventDispatcher.dispatchEvent(new TouchEvent(mTargetTag, TouchEventType.END, ev));
} else if (action == MotionEvent.ACTION_CANCEL) {
dispatchCancelEvent(ev);
mTargetTag = -1;
} else {
FLog.w(
ReactConstants.TAG,
"Warning : touch event was ignored. Action=" + action + " Target=" + mTargetTag);
}
}
@Override
public void onChildStartedNativeGesture(MotionEvent androidEvent) {
if (mChildIsHandlingNativeGesture) {
// This means we previously had another child start handling this native gesture and now a
// different native parent of that child has decided to intercept the touch stream and handle
// the gesture itself. Example where this can happen: HorizontalScrollView in a ScrollView.
return;
}
dispatchCancelEvent(androidEvent);
mChildIsHandlingNativeGesture = true;
mTargetTag = -1;
}
private void dispatchCancelEvent(MotionEvent androidEvent) {
// This means the gesture has already ended, via some other CANCEL or UP event. This is not
// expected to happen very often as it would mean some child View has decided to intercept the
// touch stream and start a native gesture only upon receiving the UP/CANCEL event.
if (mTargetTag == -1) {
FLog.w(
ReactConstants.TAG,
"Can't cancel already finished gesture. Is a child View trying to start a gesture from " +
"an UP/CANCEL event?");
return;
}
EventDispatcher eventDispatcher = mReactInstanceManager.getCurrentReactContext()
.getNativeModule(UIManagerModule.class)
.getEventDispatcher();
Assertions.assertCondition(
!mChildIsHandlingNativeGesture,
"Expected to not have already sent a cancel for this gesture");
Assertions.assertNotNull(eventDispatcher).dispatchEvent(
new TouchEvent(mTargetTag, TouchEventType.CANCEL, androidEvent));
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
super.onTouchEvent(ev);
// In case when there is no children interested in handling touch event, we return true from
// the root view in order to receive subsequent events related to that gesture
return true;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// No-op - override in order to still receive events to onInterceptTouchEvent
// even when some other view disallow that
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// No-op since UIManagerModule handles actually laying out children.
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mIsAttachedToWindow = false;
if (mReactInstanceManager != null && !mAttachScheduled) {
mReactInstanceManager.detachRootView(this);
mIsAttachedToInstance = false;
getViewTreeObserver().removeOnGlobalLayoutListener(mKeyboardListener);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mIsAttachedToWindow = true;
// If the view re-attached and catalyst instance has been set before, we'd attach again to the
// catalyst instance (expecting measure to be called after {@link onAttachedToWindow})
if (mReactInstanceManager != null) {
mAttachScheduled = true;
}
}
/**
* {@see #startReactApplication(ReactInstanceManager, String, android.os.Bundle)}
*/
public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName) {
startReactApplication(reactInstanceManager, moduleName, null);
}
/**
* Schedule rendering of the react component rendered by the JS application from the given JS
* module (@{param moduleName}) using provided {@param reactInstanceManager} to attach to the
* JS context of that manager. Extra parameter {@param launchOptions} can be used to pass initial
* properties for the react component.
*/
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle launchOptions) {
// TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap
// here as it may be deallocated in native after passing via JNI bridge, but we want to reuse
// it in the case of re-creating the catalyst instance
Assertions.assertCondition(
mReactInstanceManager == null,
"This root view has already " +
"been attached to a catalyst instance manager");
mReactInstanceManager = reactInstanceManager;
mJSModuleName = moduleName;
mLaunchOptions = launchOptions;
// We need to wait for the initial onMeasure, if this view has not yet been measured, we set
// mAttachScheduled flag, which will make this view startReactApplication itself to instance
// manager once onMeasure is called.
if (mWasMeasured && mIsAttachedToWindow) {
mReactInstanceManager.attachMeasuredRootView(this);
mIsAttachedToInstance = true;
getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener);
} else {
mAttachScheduled = true;
}
}
/* package */ String getJSModuleName() {
return Assertions.assertNotNull(mJSModuleName);
}
/* package */ @Nullable Bundle getLaunchOptions() {
return mLaunchOptions;
}
/**
* Is used by unit test to setup mWasMeasured and mIsAttachedToWindow flags, that will let this
* view to be properly attached to catalyst instance by startReactApplication call
*/
@VisibleForTesting
/* package */ void simulateAttachForTesting() {
mIsAttachedToWindow = true;
mIsAttachedToInstance = true;
mWasMeasured = true;
}
private class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener {
private int mKeyboardHeight = 0;
private final Rect mVisibleViewArea = new Rect();
@Override
public void onGlobalLayout() {
if (mReactInstanceManager == null || !mIsAttachedToInstance ||
mReactInstanceManager.getCurrentReactContext() == null) {
FLog.w(
ReactConstants.TAG,
"Unable to dispatch keyboard events in JS as the react instance has not been attached");
return;
}
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
final int heightDiff =
DisplayMetricsHolder.getDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
if (mKeyboardHeight != heightDiff && heightDiff > 0) {
// keyboard is now showing, or the keyboard height has changed
mKeyboardHeight = heightDiff;
WritableMap params = Arguments.createMap();
WritableMap coordinates = Arguments.createMap();
coordinates.putDouble("screenY", PixelUtil.toDIPFromPixel(mVisibleViewArea.bottom));
coordinates.putDouble("screenX", PixelUtil.toDIPFromPixel(mVisibleViewArea.left));
coordinates.putDouble("width", PixelUtil.toDIPFromPixel(mVisibleViewArea.width()));
coordinates.putDouble("height", PixelUtil.toDIPFromPixel(mKeyboardHeight));
params.putMap("endCoordinates", coordinates);
sendEvent("keyboardDidShow", params);
} else if (mKeyboardHeight != 0 && heightDiff == 0) {
// keyboard is now hidden
mKeyboardHeight = heightDiff;
sendEvent("keyboardDidHide", null);
}
}
private void sendEvent(String eventName, @Nullable WritableMap params) {
if (mReactInstanceManager != null) {
mReactInstanceManager.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Base class for {@link AnimationPropertyUpdater} subclasses that updates a pair of float property
* values. It helps to handle convertion from animation progress to the actual values as
* well as the quite common case when no starting value is provided.
*/
public abstract class AbstractFloatPairPropertyUpdater implements AnimationPropertyUpdater {
private final float[] mFromValues = new float[2];
private final float[] mToValues = new float[2];
private final float[] mUpdateValues = new float[2];
private boolean mFromSource;
protected AbstractFloatPairPropertyUpdater(float toFirst, float toSecond) {
mToValues[0] = toFirst;
mToValues[1] = toSecond;
mFromSource = true;
}
protected AbstractFloatPairPropertyUpdater(
float fromFirst,
float fromSecond,
float toFirst,
float toSecond) {
this(toFirst, toSecond);
mFromValues[0] = fromFirst;
mFromValues[1] = fromSecond;
mFromSource = false;
}
protected abstract void getProperty(View view, float[] returnValues);
protected abstract void setProperty(View view, float[] propertyValues);
@Override
public void prepare(View view) {
if (mFromSource) {
getProperty(view, mFromValues);
}
}
@Override
public void onUpdate(View view, float progress) {
mUpdateValues[0] = mFromValues[0] + (mToValues[0] - mFromValues[0]) * progress;
mUpdateValues[1] = mFromValues[1] + (mToValues[1] - mFromValues[1]) * progress;
setProperty(view, mUpdateValues);
}
@Override
public void onFinish(View view) {
setProperty(view, mToValues);
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Base class for {@link AnimationPropertyUpdater} subclasses that updates a single float property
* value. It helps to handle convertion from animation progress to the actual value as well as the
* quite common case when no starting value is provided.
*/
public abstract class AbstractSingleFloatProperyUpdater implements AnimationPropertyUpdater {
private float mFromValue, mToValue;
private boolean mFromSource;
protected AbstractSingleFloatProperyUpdater(float toValue) {
mToValue = toValue;
mFromSource = true;
}
protected AbstractSingleFloatProperyUpdater(float fromValue, float toValue) {
this(toValue);
mFromValue = fromValue;
mFromSource = false;
}
protected abstract float getProperty(View view);
protected abstract void setProperty(View view, float propertyValue);
@Override
public final void prepare(View view) {
if (mFromSource) {
mFromValue = getProperty(view);
}
}
@Override
public final void onUpdate(View view, float progress) {
setProperty(view, mFromValue + (mToValue - mFromValue) * progress);
}
@Override
public void onFinish(View view) {
setProperty(view, mToValue);
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import javax.annotation.Nullable;
import android.view.View;
import com.facebook.infer.annotation.Assertions;
/**
* Base class for various catalyst animation engines. Subclasses of this class should implement
* {@link #run} method which should bootstrap the animation. Then in each animation frame we expect
* animation engine to call {@link #onUpdate} with a float progress which then will be transferred
* to the underlying {@link AnimationPropertyUpdater} instance.
*
* Animation engine should support animation cancelling by monitoring the returned value of
* {@link #onUpdate}. In case of returning false, animation should be considered cancelled and
* engine should not attempt to call {@link #onUpdate} again.
*/
public abstract class Animation {
private final int mAnimationID;
private final AnimationPropertyUpdater mPropertyUpdater;
private volatile boolean mCancelled = false;
private volatile boolean mIsFinished = false;
private @Nullable AnimationListener mAnimationListener;
private @Nullable View mAnimatedView;
public Animation(int animationID, AnimationPropertyUpdater propertyUpdater) {
mAnimationID = animationID;
mPropertyUpdater = propertyUpdater;
}
public void setAnimationListener(AnimationListener animationListener) {
mAnimationListener = animationListener;
}
public final void start(View view) {
mAnimatedView = view;
mPropertyUpdater.prepare(view);
run();
}
public abstract void run();
/**
* Animation engine should call this method for every animation frame passing animation progress
* value as a parameter. Animation progress should be within the range 0..1 (the exception here
* would be a spring animation engine which may slightly exceed start and end progress values).
*
* This method will return false if the animation has been cancelled. In that case animation
* engine should not attempt to call this method again. Otherwise this method will return true
*/
protected final boolean onUpdate(float value) {
Assertions.assertCondition(!mIsFinished, "Animation must not already be finished!");
if (!mCancelled) {
mPropertyUpdater.onUpdate(Assertions.assertNotNull(mAnimatedView), value);
}
return !mCancelled;
}
/**
* Animation engine should call this method when the animation is finished. Should be called only
* once
*/
protected final void finish() {
Assertions.assertCondition(!mIsFinished, "Animation must not already be finished!");
mIsFinished = true;
if (!mCancelled) {
if (mAnimatedView != null) {
mPropertyUpdater.onFinish(mAnimatedView);
}
if (mAnimationListener != null) {
mAnimationListener.onFinished();
}
}
}
/**
* Cancels the animation.
*
* It is possible for this to be called after finish() and should handle that gracefully.
*/
public final void cancel() {
if (mIsFinished || mCancelled) {
// If we were already finished, ignore
return;
}
mCancelled = true;
if (mAnimationListener != null) {
mAnimationListener.onCancel();
}
}
public int getAnimationID() {
return mAnimationID;
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
/**
* Interface for getting animation lifecycle updates. It is guaranteed that for a given animation,
* only one of onFinished and onCancel will be called, and it will be called exactly once.
*/
public interface AnimationListener {
/**
* Called once animation is finished
*/
public void onFinished();
/**
* Called in case when animation was cancelled
*/
public void onCancel();
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Interface used to update particular property types during animation. While animation is in
* progress {@link Animation} instance will call {@link #onUpdate} several times with a value
* representing animation progress. Normally value will be from 0..1 range, but for spring animation
* it can slightly exceed that limit due to bounce effect at the start/end of animation.
*/
public interface AnimationPropertyUpdater {
/**
* This method will be called before animation starts.
*
* @param view view that will be animated
*/
public void prepare(View view);
/**
* This method will be called for each animation frame
*
* @param view view to update property
* @param progress animation progress from 0..1 range (may slightly exceed that limit in case of
* spring engine) retrieved from {@link Animation} engine.
*/
public void onUpdate(View view, float progress);
/**
* This method will be called at the end of animation. It should be used to set the final values
* for animated properties in order to avoid floating point inacurracy calculated in
* {@link #onUpdate} by passing value close to 1.0 or in a case some frames got dropped.
*
* @param view view to update property
*/
public void onFinish(View view);
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.util.SparseArray;
import com.facebook.react.bridge.UiThreadUtil;
/**
* Coordinates catalyst animations driven by {@link UIManagerModule} and
* {@link AnimationManagerModule}
*/
public class AnimationRegistry {
private final SparseArray<Animation> mRegistry = new SparseArray<Animation>();
public void registerAnimation(Animation animation) {
UiThreadUtil.assertOnUiThread();
mRegistry.put(animation.getAnimationID(), animation);
}
public Animation getAnimation(int animationID) {
UiThreadUtil.assertOnUiThread();
return mRegistry.get(animationID);
}
public Animation removeAnimation(int animationID) {
UiThreadUtil.assertOnUiThread();
Animation animation = mRegistry.get(animationID);
if (animation != null) {
mRegistry.delete(animationID);
}
return animation;
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
/**
* Ignores duration and immediately jump to the end of animation. This is a temporal solution for
* the lack of real animation engines implemented.
*/
public class ImmediateAnimation extends Animation {
public ImmediateAnimation(int animationID, AnimationPropertyUpdater propertyUpdater) {
super(animationID, propertyUpdater);
}
@Override
public void run() {
onUpdate(1.0f);
finish();
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Empty {@link AnimationPropertyUpdater} that can be used as a stub for unsupported property types
*/
public class NoopAnimationPropertyUpdater implements AnimationPropertyUpdater {
@Override
public void prepare(View view) {
}
@Override
public void onUpdate(View view, float value) {
}
@Override
public void onFinish(View view) {
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Subclass of {@link AnimationPropertyUpdater} for animating view's opacity
*/
public class OpacityAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater {
public OpacityAnimationPropertyUpdater(float toOpacity) {
super(toOpacity);
}
public OpacityAnimationPropertyUpdater(float fromOpacity, float toOpacity) {
super(fromOpacity, toOpacity);
}
@Override
protected float getProperty(View view) {
return view.getAlpha();
}
@Override
protected void setProperty(View view, float propertyValue) {
view.setAlpha(propertyValue);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Subclass of {@link AnimationPropertyUpdater} for animating center position of a view
*/
public class PositionAnimationPairPropertyUpdater extends AbstractFloatPairPropertyUpdater {
public PositionAnimationPairPropertyUpdater(float toFirst, float toSecond) {
super(toFirst, toSecond);
}
public PositionAnimationPairPropertyUpdater(
float fromFirst,
float fromSecond,
float toFirst,
float toSecond) {
super(fromFirst, fromSecond, toFirst, toSecond);
}
@Override
protected void getProperty(View view, float[] returnValues) {
returnValues[0] = view.getX() + 0.5f * view.getWidth();
returnValues[1] = view.getY() + 0.5f * view.getHeight();
}
@Override
protected void setProperty(View view, float[] propertyValues) {
view.setX(propertyValues[0] - 0.5f * view.getWidth());
view.setY(propertyValues[1] - 0.5f * view.getHeight());
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Subclass of {@link AnimationPropertyUpdater} for animating view's rotation
*/
public class RotationAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater {
public RotationAnimationPropertyUpdater(float toValue) {
super(toValue);
}
@Override
protected float getProperty(View view) {
return view.getRotation();
}
@Override
protected void setProperty(View view, float propertyValue) {
view.setRotation((float) Math.toDegrees(propertyValue));
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Subclass of {@link AnimationPropertyUpdater} for animating view's X scale
*/
public class ScaleXAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater {
public ScaleXAnimationPropertyUpdater(float toValue) {
super(toValue);
}
public ScaleXAnimationPropertyUpdater(float fromValue, float toValue) {
super(fromValue, toValue);
}
@Override
protected float getProperty(View view) {
return view.getScaleX();
}
@Override
protected void setProperty(View view, float propertyValue) {
view.setScaleX(propertyValue);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Subclass of {@link AnimationPropertyUpdater} for animating view's X and Y scale
*/
public class ScaleXYAnimationPairPropertyUpdater extends AbstractFloatPairPropertyUpdater {
public ScaleXYAnimationPairPropertyUpdater(float toFirst, float toSecond) {
super(toFirst, toSecond);
}
public ScaleXYAnimationPairPropertyUpdater(
float fromFirst,
float fromSecond,
float toFirst,
float toSecond) {
super(fromFirst, fromSecond, toFirst, toSecond);
}
@Override
protected void getProperty(View view, float[] returnValues) {
returnValues[0] = view.getScaleX();
returnValues[1] = view.getScaleY();
}
@Override
protected void setProperty(View view, float[] propertyValues) {
view.setScaleX(propertyValues[0]);
view.setScaleY(propertyValues[1]);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animation;
import android.view.View;
/**
* Subclass of {@link AnimationPropertyUpdater} for animating view's Y scale
*/
public class ScaleYAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater {
public ScaleYAnimationPropertyUpdater(float toValue) {
super(toValue);
}
public ScaleYAnimationPropertyUpdater(float fromValue, float toValue) {
super(fromValue, toValue);
}
@Override
protected float getProperty(View view) {
return view.getScaleY();
}
@Override
protected void setProperty(View view, float propertyValue) {
view.setScaleY(propertyValue);
}
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import android.os.Bundle;
public class Arguments {
/**
* This method should be used when you need to stub out creating NativeArrays in unit tests.
*/
public static WritableArray createArray() {
return new WritableNativeArray();
}
/**
* This method should be used when you need to stub out creating NativeMaps in unit tests.
*/
public static WritableMap createMap() {
return new WritableNativeMap();
}
public static WritableNativeArray fromJavaArgs(Object[] args) {
WritableNativeArray arguments = new WritableNativeArray();
for (int i = 0; i < args.length; i++) {
Object argument = args[i];
if (argument == null) {
arguments.pushNull();
continue;
}
Class argumentClass = argument.getClass();
if (argumentClass == Boolean.class) {
arguments.pushBoolean(((Boolean) argument).booleanValue());
} else if (argumentClass == Integer.class) {
arguments.pushDouble(((Integer) argument).doubleValue());
} else if (argumentClass == Double.class) {
arguments.pushDouble(((Double) argument).doubleValue());
} else if (argumentClass == Float.class) {
arguments.pushDouble(((Float) argument).doubleValue());
} else if (argumentClass == String.class) {
arguments.pushString(argument.toString());
} else if (argumentClass == WritableNativeMap.class) {
arguments.pushMap((WritableNativeMap) argument);
} else if (argumentClass == WritableNativeArray.class) {
arguments.pushArray((WritableNativeArray) argument);
} else {
throw new RuntimeException("Cannot convert argument of type " + argumentClass);
}
}
return arguments;
}
/**
* Convert an array to a {@link WritableArray}.
*
* @param array the array to convert. Supported types are: {@code String[]}, {@code Bundle[]},
* {@code int[]}, {@code float[]}, {@code double[]}, {@code boolean[]}.
*
* @return the converted {@link WritableArray}
* @throws IllegalArgumentException if the passed object is none of the above types
*/
public static WritableArray fromArray(Object array) {
WritableArray catalystArray = createArray();
if (array instanceof String[]) {
for (String v: (String[]) array) {
catalystArray.pushString(v);
}
} else if (array instanceof Bundle[]) {
for (Bundle v: (Bundle[]) array) {
catalystArray.pushMap(fromBundle(v));
}
} else if (array instanceof int[]) {
for (int v: (int[]) array) {
catalystArray.pushInt(v);
}
} else if (array instanceof float[]) {
for (float v: (float[]) array) {
catalystArray.pushDouble(v);
}
} else if (array instanceof double[]) {
for (double v: (double[]) array) {
catalystArray.pushDouble(v);
}
} else if (array instanceof boolean[]) {
for (boolean v: (boolean[]) array) {
catalystArray.pushBoolean(v);
}
} else {
throw new IllegalArgumentException("Unknown array type " + array.getClass());
}
return catalystArray;
}
/**
* Convert a {@link Bundle} to a {@link WritableMap}. Supported key types in the bundle
* are:
*
* <ul>
* <li>primitive types: int, float, double, boolean</li>
* <li>arrays supported by {@link #fromArray(Object)}</li>
* <li>{@link Bundle} objects that are recursively converted to maps</li>
* </ul>
*
* @param bundle the {@link Bundle} to convert
* @return the converted {@link WritableMap}
* @throws IllegalArgumentException if there are keys of unsupported types
*/
public static WritableMap fromBundle(Bundle bundle) {
WritableMap map = createMap();
for (String key: bundle.keySet()) {
Object value = bundle.get(key);
if (value == null) {
map.putNull(key);
} else if (value.getClass().isArray()) {
map.putArray(key, fromArray(value));
} else if (value instanceof String) {
map.putString(key, (String) value);
} else if (value instanceof Number) {
if (value instanceof Integer) {
map.putInt(key, (Integer) value);
} else {
map.putDouble(key, ((Number) value).doubleValue());
}
} else if (value instanceof Boolean) {
map.putBoolean(key, (Boolean) value);
} else if (value instanceof Bundle) {
map.putMap(key, fromBundle((Bundle) value));
} else {
throw new IllegalArgumentException("Could not convert " + value.getClass());
}
}
return map;
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Like {@link AssertionError} but extends RuntimeException so that it may be caught by a
* {@link NativeModuleCallExceptionHandler}. See that class for more details. Used in
* conjunction with {@link SoftAssertions}.
*/
public class AssertionException extends RuntimeException {
public AssertionException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -0,0 +1,181 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.fasterxml.jackson.core.JsonGenerator;
import com.facebook.systrace.Systrace;
import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Base class for Catalyst native modules whose implementations are written in Java. Default
* implementations for {@link #initialize} and {@link #onCatalystInstanceDestroy} are provided for
* convenience. Subclasses which override these don't need to call {@code super} in case of
* overriding those methods as implementation of those methods is empty.
*
* BaseJavaModules can be linked to Fragments' lifecycle events, {@link CatalystInstance} creation
* and destruction, by being called on the appropriate method when a life cycle event occurs.
*
* Native methods can be exposed to JS with {@link ReactMethod} annotation. Those methods may
* only use limited number of types for their arguments:
* 1/ primitives (boolean, int, float, double
* 2/ {@link String} mapped from JS string
* 3/ {@link ReadableArray} mapped from JS Array
* 4/ {@link ReadableMap} mapped from JS Object
* 5/ {@link Callback} mapped from js function and can be used only as a last parameter or in the
* case when it express success & error callback pair as two last arguments respecively.
*
* All methods exposed as native to JS with {@link ReactMethod} annotation must return
* {@code void}.
*
* Please note that it is not allowed to have multiple methods annotated with {@link ReactMethod}
* with the same name.
*/
public abstract class BaseJavaModule implements NativeModule {
private class JavaMethod implements NativeMethod {
private Method method;
public JavaMethod(Method method) {
this.method = method;
}
@Override
public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod");
try {
Class[] types = method.getParameterTypes();
if (types.length != parameters.size()) {
throw new NativeArgumentsParseException(
BaseJavaModule.this.getName() + "." + method.getName() + " got " + parameters.size() +
" arguments, expected " + types.length);
}
Object[] arguments = new Object[types.length];
int i = 0;
try {
for (; i < types.length; i++) {
Class argumentClass = types[i];
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
arguments[i] = Boolean.valueOf(parameters.getBoolean(i));
} else if (argumentClass == Integer.class || argumentClass == int.class) {
arguments[i] = Integer.valueOf((int) parameters.getDouble(i));
} else if (argumentClass == Double.class || argumentClass == double.class) {
arguments[i] = Double.valueOf(parameters.getDouble(i));
} else if (argumentClass == Float.class || argumentClass == float.class) {
arguments[i] = Float.valueOf((float) parameters.getDouble(i));
} else if (argumentClass == String.class) {
arguments[i] = parameters.getString(i);
} else if (argumentClass == Callback.class) {
if (parameters.isNull(i)) {
arguments[i] = null;
} else {
int id = (int) parameters.getDouble(i);
arguments[i] = new CallbackImpl(catalystInstance, id);
}
} else if (argumentClass == ReadableMap.class) {
arguments[i] = parameters.getMap(i);
} else if (argumentClass == ReadableArray.class) {
arguments[i] = parameters.getArray(i);
} else {
throw new RuntimeException(
"Got unknown argument class: " + argumentClass.getSimpleName());
}
}
} catch (UnexpectedNativeTypeException e) {
throw new NativeArgumentsParseException(
e.getMessage() + " (constructing arguments for " + BaseJavaModule.this.getName() +
"." + method.getName() + " at argument index " + i + ")",
e);
}
try {
method.invoke(BaseJavaModule.this, arguments);
} catch (IllegalArgumentException ie) {
throw new RuntimeException(
"Could not invoke " + BaseJavaModule.this.getName() + "." + method.getName(), ie);
} catch (IllegalAccessException iae) {
throw new RuntimeException(
"Could not invoke " + BaseJavaModule.this.getName() + "." + method.getName(), iae);
} catch (InvocationTargetException ite) {
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
// which just make traces harder to read and bump out useful information
if (ite.getCause() instanceof RuntimeException) {
throw (RuntimeException) ite.getCause();
}
throw new RuntimeException(
"Could not invoke " + BaseJavaModule.this.getName() + "." + method.getName(), ite);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
@Override
public final Map<String, NativeMethod> getMethods() {
Map<String, NativeMethod> methods = new HashMap<String, NativeMethod>();
Method[] targetMethods = getClass().getDeclaredMethods();
for (int i = 0; i < targetMethods.length; i++) {
Method targetMethod = targetMethods[i];
if (targetMethod.getAnnotation(ReactMethod.class) != null) {
String methodName = targetMethod.getName();
if (methods.containsKey(methodName)) {
// We do not support method overloading since js sees a function as an object regardless
// of number of params.
throw new IllegalArgumentException(
"Java Module " + getName() + " method name already registered: " + methodName);
}
methods.put(methodName, new JavaMethod(targetMethod));
}
}
return methods;
}
/**
* @return a map of constants this module exports to JS. Supports JSON types.
*/
public @Nullable Map<String, Object> getConstants() {
return null;
}
@Override
public final void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException {
Map<String, Object> constants = getConstants();
if (constants == null || constants.isEmpty()) {
return;
}
jg.writeObjectFieldStart(fieldName);
for (Map.Entry<String, Object> constant : constants.entrySet()) {
JsonGeneratorHelper.writeObjectField(
jg,
constant.getKey(),
constant.getValue());
}
jg.writeEndObject();
}
@Override
public void initialize() {
// do nothing
}
@Override
public void onCatalystInstanceDestroy() {
// do nothing
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface that represent javascript callback function which can be passed to the native module
* as a method parameter.
*/
public interface Callback {
/**
* Schedule javascript function execution represented by this {@link Callback} instance
*
* @param args arguments passed to javascript callback method via bridge
*/
public void invoke(Object... args);
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Implementation of javascript callback function that use Bridge to schedule method execution
*/
public final class CallbackImpl implements Callback {
private final CatalystInstance mCatalystInstance;
private final int mCallbackId;
public CallbackImpl(CatalystInstance bridge, int callbackId) {
mCatalystInstance = bridge;
mCallbackId = callbackId;
}
@Override
public void invoke(Object... args) {
mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
}
}

View File

@@ -0,0 +1,419 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec;
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.infer.annotation.Assertions;
import com.facebook.systrace.Systrace;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* A higher level API on top of the asynchronous JSC bridge. This provides an
* environment allowing the invocation of JavaScript methods and lets a set of
* Java APIs be invokable from JavaScript as well.
*/
@DoNotStrip
public class CatalystInstance {
private static final int BRIDGE_SETUP_TIMEOUT_MS = 15000;
private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
// Access from any thread
private final CatalystQueueConfiguration mCatalystQueueConfiguration;
private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
private final AtomicInteger mPendingJSCalls = new AtomicInteger(0);
private final String mJsPendingCallsTitleForTrace =
"pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement();
private volatile boolean mDestroyed = false;
// Access from native modules thread
private final NativeModuleRegistry mJavaRegistry;
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private boolean mInitialized = false;
// Access from JS thread
private @Nullable ReactBridge mBridge;
private @Nullable JavaScriptModuleRegistry mJSModuleRegistry;
private CatalystInstance(
final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModulesConfig jsModulesConfig,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
mCatalystQueueConfiguration = CatalystQueueConfiguration.create(
catalystQueueConfigurationSpec,
new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener>();
mJavaRegistry = registry;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
final CountDownLatch initLatch = new CountDownLatch(1);
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
initializeBridge(jsExecutor, registry, jsModulesConfig, jsBundleLoader);
mJSModuleRegistry =
new JavaScriptModuleRegistry(CatalystInstance.this, jsModulesConfig);
initLatch.countDown();
}
});
try {
Assertions.assertCondition(
initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS),
"Timed out waiting for bridge to initialize!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private void initializeBridge(
JavaScriptExecutor jsExecutor,
NativeModuleRegistry registry,
JavaScriptModulesConfig jsModulesConfig,
JSBundleLoader jsBundleLoader) {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
mBridge = new ReactBridge(
jsExecutor,
new NativeModulesReactCallback(),
mCatalystQueueConfiguration.getNativeModulesQueueThread());
mBridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(registry, jsModulesConfig));
jsBundleLoader.loadScript(mBridge);
}
/* package */ void callFunction(
final int moduleId,
final int methodId,
final NativeArray arguments,
final String tracingName) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
return;
}
incrementPendingJSCalls();
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
if (mDestroyed) {
return;
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName);
try {
Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
});
}
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
// which this prevents.
@DoNotStrip
/* package */ void invokeCallback(final int callbackID, final NativeArray arguments) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
return;
}
incrementPendingJSCalls();
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
if (mDestroyed) {
return;
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "<callback>");
try {
Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
});
}
/**
* Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
* fully shut down other threads.
*/
/* package */ void destroy() {
UiThreadUtil.assertOnUiThread();
if (mDestroyed) {
return;
}
// TODO: tell all APIs to shut down
mDestroyed = true;
mJavaRegistry.notifyCatalystInstanceDestroy();
mCatalystQueueConfiguration.destroy();
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
listener.onTransitionToBridgeIdle();
}
}
// We can access the Bridge from any thread now because we know either we are on the JS thread
// or the JS thread has finished via CatalystQueueConfiguration#destroy()
Assertions.assertNotNull(mBridge).dispose();
}
public boolean isDestroyed() {
return mDestroyed;
}
/**
* Initialize all the native modules
*/
@VisibleForTesting
public void initialize() {
UiThreadUtil.assertOnUiThread();
Assertions.assertCondition(
!mInitialized,
"This catalyst instance has already been initialized");
mInitialized = true;
mJavaRegistry.notifyCatalystInstanceInitialized();
}
public CatalystQueueConfiguration getCatalystQueueConfiguration() {
return mCatalystQueueConfiguration;
}
@VisibleForTesting
public @Nullable
ReactBridge getBridge() {
return mBridge;
}
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
}
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
return mJavaRegistry.getModule(nativeModuleInterface);
}
public Collection<NativeModule> getNativeModules() {
return mJavaRegistry.getAllModules();
}
/**
* Adds a idle listener for this Catalyst instance. The listener will receive notifications
* whenever the bridge transitions from idle to busy and vice-versa, where the busy state is
* defined as there being some non-zero number of calls to JS that haven't resolved via a
* onBatchCompleted call. The listener should be purely passive and not affect application logic.
*/
public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
mBridgeIdleListeners.add(listener);
}
/**
* Removes a NotThreadSafeBridgeIdleDebugListener previously added with
* {@link #addBridgeIdleDebugListener}
*/
public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
mBridgeIdleListeners.remove(listener);
}
private String buildModulesConfigJSONProperty(
NativeModuleRegistry nativeModuleRegistry,
JavaScriptModulesConfig jsModulesConfig) {
// TODO(5300733): Serialize config using single json generator
JsonFactory jsonFactory = new JsonFactory();
StringWriter writer = new StringWriter();
try {
JsonGenerator jg = jsonFactory.createGenerator(writer);
jg.writeStartObject();
jg.writeFieldName("remoteModuleConfig");
jg.writeRawValue(nativeModuleRegistry.moduleDescriptions());
jg.writeFieldName("localModulesConfig");
jg.writeRawValue(jsModulesConfig.moduleDescriptions());
jg.writeEndObject();
jg.close();
} catch (IOException ioe) {
throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
}
return writer.getBuffer().toString();
}
private void incrementPendingJSCalls() {
int oldPendingCalls = mPendingJSCalls.getAndIncrement();
boolean wasIdle = oldPendingCalls == 0;
Systrace.traceCounter(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
mJsPendingCallsTitleForTrace,
oldPendingCalls + 1);
if (wasIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
listener.onTransitionToBridgeBusy();
}
}
}
private void decrementPendingJSCalls() {
int newPendingCalls = mPendingJSCalls.decrementAndGet();
boolean isNowIdle = newPendingCalls == 0;
Systrace.traceCounter(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
mJsPendingCallsTitleForTrace,
newPendingCalls);
if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
listener.onTransitionToBridgeIdle();
}
}
}
private class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstance.this, moduleId, methodId, parameters);
}
@Override
public void onBatchComplete() {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// The bridge may have been destroyed due to an exception during the batch. In that case
// native modules could be in a bad state so we don't want to call anything on them. We
// still want to trigger the debug listener since it allows instrumentation tests to end and
// check their assertions without waiting for a timeout.
if (!mDestroyed) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
try {
mJavaRegistry.onBatchComplete();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
decrementPendingJSCalls();
}
}
private class NativeExceptionHandler implements QueueThreadExceptionHandler {
@Override
public void handleException(Exception e) {
// Any Exception caught here is because of something in JS. Even if it's a bug in the
// framework/native code, it was triggered by JS and theoretically since we were able
// to set up the bridge, JS could change its logic, reload, and not trigger that crash.
mNativeModuleCallExceptionHandler.handleException(e);
mCatalystQueueConfiguration.getUIQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
destroy();
}
});
}
}
public static class Builder {
private @Nullable CatalystQueueConfigurationSpec mCatalystQueueConfigurationSpec;
private @Nullable JSBundleLoader mJSBundleLoader;
private @Nullable NativeModuleRegistry mRegistry;
private @Nullable JavaScriptModulesConfig mJSModulesConfig;
private @Nullable JavaScriptExecutor mJSExecutor;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
public Builder setCatalystQueueConfigurationSpec(
CatalystQueueConfigurationSpec catalystQueueConfigurationSpec) {
mCatalystQueueConfigurationSpec = catalystQueueConfigurationSpec;
return this;
}
public Builder setRegistry(NativeModuleRegistry registry) {
mRegistry = registry;
return this;
}
public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) {
mJSModulesConfig = jsModulesConfig;
return this;
}
public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
mJSBundleLoader = jsBundleLoader;
return this;
}
public Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
mJSExecutor = jsExecutor;
return this;
}
public Builder setNativeModuleCallExceptionHandler(
NativeModuleCallExceptionHandler handler) {
mNativeModuleCallExceptionHandler = handler;
return this;
}
public CatalystInstance build() {
return new CatalystInstance(
Assertions.assertNotNull(mCatalystQueueConfigurationSpec),
Assertions.assertNotNull(mJSExecutor),
Assertions.assertNotNull(mRegistry),
Assertions.assertNotNull(mJSModulesConfig),
Assertions.assertNotNull(mJSBundleLoader),
Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
}
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import android.os.AsyncTask;
/**
* Abstract base for a AsyncTask that should have any RuntimeExceptions it throws
* handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if
* the app is in dev mode.
*
* This class doesn't allow doInBackground to return a results. This is mostly because when this
* class was written that functionality wasn't used and it would require some extra code to make
* work correctly with caught exceptions. Don't let that stop you from adding it if you need it :)
*/
public abstract class GuardedAsyncTask<Params, Progress>
extends AsyncTask<Params, Progress, Void> {
private final ReactContext mReactContext;
protected GuardedAsyncTask(ReactContext reactContext) {
mReactContext = reactContext;
}
@Override
protected final Void doInBackground(Params... params) {
try {
doInBackgroundGuarded(params);
} catch (RuntimeException e) {
mReactContext.handleException(e);
}
return null;
}
protected abstract void doInBackgroundGuarded(Params... params);
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Exception thrown by {@link ReadableMapKeySeyIterator#nextKey()} when the iterator tries
* to iterate over elements after the end of the key set.
*/
@DoNotStrip
public class InvalidIteratorException extends RuntimeException {
@DoNotStrip
public InvalidIteratorException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
/**
* A special RuntimeException that should be thrown by native code if it has reached an exceptional
* state due to a, or a sequence of, bad commands.
*
* A good rule of thumb for whether a native Exception should extend this interface is 1) Can a
* developer make a change or correction in JS to keep this Exception from being thrown? 2) Is the
* app outside of this catalyst instance still in a good state to allow reloading and restarting
* this catalyst instance?
*
* Examples where this class is appropriate to throw:
* - JS tries to update a view with a tag that hasn't been created yet
* - JS tries to show a static image that isn't in resources
* - JS tries to use an unsupported view class
*
* Examples where this class **isn't** appropriate to throw:
* - Failed to write to localStorage because disk is full
* - Assertions about internal state (e.g. that child.getParent().indexOf(child) != -1)
*/
public class JSApplicationCausedNativeException extends RuntimeException {
public JSApplicationCausedNativeException(String detailMessage) {
super(detailMessage);
}
public JSApplicationCausedNativeException(
@Nullable String detailMessage,
@Nullable Throwable throwable) {
super(detailMessage, throwable);
}
}

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* An illegal argument Exception caused by an argument passed from JS.
*/
public class JSApplicationIllegalArgumentException extends JSApplicationCausedNativeException {
public JSApplicationIllegalArgumentException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import android.content.res.AssetManager;
/**
* A class that stores JS bundle information and allows {@link CatalystInstance} to load a correct
* bundle through {@link ReactBridge}.
*/
public abstract class JSBundleLoader {
/**
* This loader is recommended one for release version of your app. In that case local JS executor
* should be used. JS bundle will be read from assets directory in native code to save on passing
* large strings from java to native memory.
*/
public static JSBundleLoader createAssetLoader(
final AssetManager assetManager,
final String assetFileName) {
return new JSBundleLoader() {
@Override
public void loadScript(ReactBridge bridge) {
bridge.loadScriptFromAssets(assetManager, assetFileName);
}
};
}
/**
* This loader is used when bundle gets reloaded from dev server. In that case loader expect JS
* bundle to be prefetched and stored in local file. We do that to avoid passing large strings
* between java and native code and avoid allocating memory in java to fit whole JS bundle in it.
* Providing correct {@param sourceURL} of downloaded bundle is required for JS stacktraces to
* work correctly and allows for source maps to correctly symbolize those.
*/
public static JSBundleLoader createCachedBundleFromNetworkLoader(
final String sourceURL,
final String cachedFileLocation) {
return new JSBundleLoader() {
@Override
public void loadScript(ReactBridge bridge) {
bridge.loadScriptFromNetworkCached(sourceURL, cachedFileLocation);
}
};
}
/**
* This loader is used when proxy debugging is enabled. In that case there is no point in fetching
* the bundle from device as remote executor will have to do it anyway.
*/
public static JSBundleLoader createRemoteDebuggerBundleLoader(
final String sourceURL) {
return new JSBundleLoader() {
@Override
public void loadScript(ReactBridge bridge) {
bridge.loadScriptFromNetworkCached(sourceURL, null);
}
};
}
public abstract void loadScript(ReactBridge bridge);
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
@DoNotStrip
public class JSCJavaScriptExecutor extends JavaScriptExecutor {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
public JSCJavaScriptExecutor() {
initialize();
}
private native void initialize();
}

View File

@@ -0,0 +1,269 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.TimeUnit;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ws.WebSocket;
import com.squareup.okhttp.ws.WebSocketCall;
import com.squareup.okhttp.ws.WebSocketListener;
import okio.Buffer;
import okio.BufferedSource;
/**
* A wrapper around WebSocketClient that recognizes RN debugging message format.
*/
public class JSDebuggerWebSocketClient implements WebSocketListener {
private static final String TAG = "JSDebuggerWebSocketClient";
private static final JsonFactory mJsonFactory = new JsonFactory();
public interface JSDebuggerCallback {
void onSuccess(@Nullable String response);
void onFailure(Throwable cause);
}
private @Nullable WebSocket mWebSocket;
private @Nullable OkHttpClient mHttpClient;
private @Nullable JSDebuggerCallback mConnectCallback;
private final AtomicInteger mRequestID = new AtomicInteger();
private final ConcurrentHashMap<Integer, JSDebuggerCallback> mCallbacks =
new ConcurrentHashMap<>();
public void connect(String url, JSDebuggerCallback callback) {
if (mHttpClient != null) {
throw new IllegalStateException("JSDebuggerWebSocketClient is already initialized.");
}
mConnectCallback = callback;
mHttpClient = new OkHttpClient();
mHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
mHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
// Disable timeouts for read
mHttpClient.setReadTimeout(0, TimeUnit.MINUTES);
Request request = new Request.Builder().url(url).build();
WebSocketCall call = WebSocketCall.create(mHttpClient, request);
call.enqueue(this);
}
/**
* Creates the next JSON message to send to remote JS executor, with request ID pre-filled in.
*/
private JsonGenerator startMessageObject(int requestID) throws IOException {
JsonGenerator jg = mJsonFactory.createGenerator(new StringWriter());
jg.writeStartObject();
jg.writeNumberField("id", requestID);
return jg;
}
/**
* Takes in a JsonGenerator created by {@link #startMessageObject} and returns the stringified
* JSON
*/
private String endMessageObject(JsonGenerator jg) throws IOException {
jg.writeEndObject();
jg.flush();
return ((StringWriter) jg.getOutputTarget()).getBuffer().toString();
}
public void prepareJSRuntime(JSDebuggerCallback callback) {
int requestID = mRequestID.getAndIncrement();
mCallbacks.put(requestID, callback);
try {
JsonGenerator jg = startMessageObject(requestID);
jg.writeStringField("method", "prepareJSRuntime");
sendMessage(requestID, endMessageObject(jg));
} catch (IOException e) {
triggerRequestFailure(requestID, e);
}
}
public void executeApplicationScript(
String sourceURL,
HashMap<String, String> injectedObjects,
JSDebuggerCallback callback) {
int requestID = mRequestID.getAndIncrement();
mCallbacks.put(requestID, callback);
try {
JsonGenerator jg = startMessageObject(requestID);
jg.writeStringField("method", "executeApplicationScript");
jg.writeStringField("url", sourceURL);
jg.writeObjectFieldStart("inject");
for (String key : injectedObjects.keySet()) {
jg.writeObjectField(key, injectedObjects.get(key));
}
jg.writeEndObject();
sendMessage(requestID, endMessageObject(jg));
} catch (IOException e) {
triggerRequestFailure(requestID, e);
}
}
public void executeJSCall(
String moduleName,
String methodName,
String jsonArgsArray,
JSDebuggerCallback callback) {
int requestID = mRequestID.getAndIncrement();
mCallbacks.put(requestID, callback);
try {
JsonGenerator jg = startMessageObject(requestID);
jg.writeStringField("method","executeJSCall");
jg.writeStringField("moduleName", moduleName);
jg.writeStringField("moduleMethod", methodName);
jg.writeFieldName("arguments");
jg.writeRawValue(jsonArgsArray);
sendMessage(requestID, endMessageObject(jg));
} catch (IOException e) {
triggerRequestFailure(requestID, e);
}
}
public void closeQuietly() {
if (mWebSocket != null) {
try {
mWebSocket.close(1000, "End of session");
} catch (IOException e) {
// swallow, no need to handle it here
}
mWebSocket = null;
}
}
private void sendMessage(int requestID, String message) {
if (mWebSocket == null) {
triggerRequestFailure(
requestID,
new IllegalStateException("WebSocket connection no longer valid"));
return;
}
Buffer messageBuffer = new Buffer();
messageBuffer.writeUtf8(message);
try {
mWebSocket.sendMessage(WebSocket.PayloadType.TEXT, messageBuffer);
} catch (IOException e) {
triggerRequestFailure(requestID, e);
}
}
private void triggerRequestFailure(int requestID, Throwable cause) {
JSDebuggerCallback callback = mCallbacks.get(requestID);
if (callback != null) {
mCallbacks.remove(requestID);
callback.onFailure(cause);
}
}
private void triggerRequestSuccess(int requestID, @Nullable String response) {
JSDebuggerCallback callback = mCallbacks.get(requestID);
if (callback != null) {
mCallbacks.remove(requestID);
callback.onSuccess(response);
}
}
@Override
public void onMessage(BufferedSource payload, WebSocket.PayloadType type) throws IOException {
if (type != WebSocket.PayloadType.TEXT) {
FLog.w(TAG, "Websocket received unexpected message with payload of type " + type);
return;
}
String message = null;
try {
message = payload.readUtf8();
} finally {
payload.close();
}
Integer replyID = null;
try {
JsonParser parser = new JsonFactory().createParser(message);
String result = null;
while (parser.nextToken() != JsonToken.END_OBJECT) {
String field = parser.getCurrentName();
if ("replyID".equals(field)) {
parser.nextToken();
replyID = parser.getIntValue();
} else if ("result".equals(field)) {
parser.nextToken();
result = parser.getText();
}
}
if (replyID != null) {
triggerRequestSuccess(replyID, result);
}
} catch (IOException e) {
if (replyID != null) {
triggerRequestFailure(replyID, e);
} else {
abort("Parsing response message from websocket failed", e);
}
}
}
@Override
public void onFailure(IOException e, Response response) {
abort("Websocket exception", e);
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
mWebSocket = webSocket;
Assertions.assertNotNull(mConnectCallback).onSuccess(null);
mConnectCallback = null;
}
@Override
public void onClose(int code, String reason) {
mWebSocket = null;
}
@Override
public void onPong(Buffer payload) {
// ignore
}
private void abort(String message, Throwable cause) {
FLog.e(TAG, "Error occurred, shutting down websocket connection: " + message, cause);
closeQuietly();
// Trigger failure callbacks
if (mConnectCallback != null) {
mConnectCallback.onFailure(cause);
mConnectCallback = null;
}
for (JSDebuggerCallback callback : mCallbacks.values()) {
callback.onFailure(cause);
}
mCallbacks.clear();
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.jni.Countable;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public abstract class JavaScriptExecutor extends Countable {
/**
* Close this executor and cleanup any resources that it was using. No further calls are
* expected after this.
*/
public void close() {
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Interface denoting that a class is the interface to a module with the same name in JS. Calling
* functions on this interface will result in corresponding methods in JS being called.
*
* When extending JavaScriptModule and registering it with a CatalystInstance, all public methods
* are assumed to be implemented on a JS module with the same name as this class. Calling methods
* on the object returned from {@link ReactContext#getJSModule} or
* {@link CatalystInstance#getJSModule} will result in the methods with those names exported by
* that module being called in JS.
*
* NB: JavaScriptModule does not allow method name overloading because JS does not allow method name
* overloading.
*/
@DoNotStrip
public interface JavaScriptModule {
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.concurrent.Immutable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import com.facebook.react.common.MapBuilder;
import com.facebook.infer.annotation.Assertions;
/**
* Registration info for a {@link JavaScriptModule}. Maps its methods to method ids.
*/
@Immutable
class JavaScriptModuleRegistration {
private final int mModuleId;
private final Class<? extends JavaScriptModule> mModuleInterface;
private final Map<Method, Integer> mMethodsToIds;
private final Map<Method, String> mMethodsToTracingNames;
JavaScriptModuleRegistration(int moduleId, Class<? extends JavaScriptModule> moduleInterface) {
mModuleId = moduleId;
mModuleInterface = moduleInterface;
mMethodsToIds = MapBuilder.newHashMap();
mMethodsToTracingNames = MapBuilder.newHashMap();
final Method[] declaredMethods = mModuleInterface.getDeclaredMethods();
Arrays.sort(declaredMethods, new Comparator<Method>() {
@Override
public int compare(Method lhs, Method rhs) {
return lhs.getName().compareTo(rhs.getName());
}
});
// Methods are sorted by name so we can dupe check and have obvious ordering
String previousName = null;
for (int i = 0; i < declaredMethods.length; i++) {
Method method = declaredMethods[i];
String name = method.getName();
Assertions.assertCondition(
!name.equals(previousName),
"Method overloading is unsupported: " + mModuleInterface.getName() + "#" + name);
previousName = name;
mMethodsToIds.put(method, i);
mMethodsToTracingNames.put(method, "JSCall__" + getName() + "_" + method.getName());
}
}
public int getModuleId() {
return mModuleId;
}
public int getMethodId(Method method) {
final Integer id = mMethodsToIds.get(method);
Assertions.assertNotNull(id, "Unknown method: " + method.getName());
return id.intValue();
}
public String getTracingName(Method method) {
return Assertions.assertNotNull(mMethodsToTracingNames.get(method));
}
public Class<? extends JavaScriptModule> getModuleInterface() {
return mModuleInterface;
}
public String getName() {
// With proguard obfuscation turned on, proguard apparently (poorly) emulates inner classes or
// something because Class#getSimpleName() no longer strips the outer class name. We manually
// strip it here if necessary.
String name = mModuleInterface.getSimpleName();
int dollarSignIndex = name.lastIndexOf('$');
if (dollarSignIndex != -1) {
name = name.substring(dollarSignIndex + 1);
}
return name;
}
public Set<Method> getMethods() {
return mMethodsToIds.keySet();
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import java.lang.Class;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import com.facebook.infer.annotation.Assertions;
/**
* Class responsible for holding all the {@link JavaScriptModule}s registered to this
* {@link CatalystInstance}. Uses Java proxy objects to dispatch method calls on JavaScriptModules
* to the bridge using the corresponding module and method ids so the proper function is executed in
* JavaScript.
*/
/*package*/ class JavaScriptModuleRegistry {
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
public JavaScriptModuleRegistry(
CatalystInstance instance,
JavaScriptModulesConfig config) {
mModuleInstances = new HashMap<>();
for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface();
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(instance, registration));
mModuleInstances.put(moduleInterface, interfaceProxy);
}
}
public <T extends JavaScriptModule> T getJavaScriptModule(Class<T> moduleInterface) {
return (T) Assertions.assertNotNull(
mModuleInstances.get(moduleInterface),
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
}
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstance mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String tracingName = mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),
tracingName);
return null;
}
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* Class stores configuration of javascript modules that can be used across the bridge
*/
public class JavaScriptModulesConfig {
private final List<JavaScriptModuleRegistration> mModules;
private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
mModules = modules;
}
/*package*/ List<JavaScriptModuleRegistration> getModuleDefinitions() {
return mModules;
}
/*package*/ String moduleDescriptions() {
JsonFactory jsonFactory = new JsonFactory();
StringWriter writer = new StringWriter();
try {
JsonGenerator jg = jsonFactory.createGenerator(writer);
jg.writeStartObject();
for (JavaScriptModuleRegistration registration : mModules) {
jg.writeObjectFieldStart(registration.getName());
appendJSModuleToJSONObject(jg, registration);
jg.writeEndObject();
}
jg.writeEndObject();
jg.close();
} catch (IOException ioe) {
throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
}
return writer.getBuffer().toString();
}
private void appendJSModuleToJSONObject(
JsonGenerator jg,
JavaScriptModuleRegistration registration) throws IOException {
jg.writeObjectField("moduleID", registration.getModuleId());
jg.writeObjectFieldStart("methods");
for (Method method : registration.getMethods()) {
jg.writeObjectFieldStart(method.getName());
jg.writeObjectField("methodID", registration.getMethodId(method));
jg.writeEndObject();
}
jg.writeEndObject();
}
public static class Builder {
private int mLastJSModuleId = 0;
private List<JavaScriptModuleRegistration> mModules =
new ArrayList<JavaScriptModuleRegistration>();
public Builder add(Class<? extends JavaScriptModule> moduleInterfaceClass) {
int moduleId = mLastJSModuleId++;
mModules.add(new JavaScriptModuleRegistration(moduleId, moduleInterfaceClass));
return this;
}
public JavaScriptModulesConfig build() {
return new JavaScriptModulesConfig(mModules);
}
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* Helper for generating JSON for lists and maps.
*/
public class JsonGeneratorHelper {
/**
* Like {@link JsonGenerator#writeObjectField(String, Object)} but supports Maps and Lists.
*/
public static void writeObjectField(JsonGenerator jg, String name, Object object)
throws IOException {
if (object instanceof Map) {
writeMap(jg, name, (Map) object);
} else if (object instanceof List) {
writeList(jg, name, (List) object);
} else {
jg.writeObjectField(name, object);
}
}
private static void writeMap(JsonGenerator jg, String name, Map map) throws IOException {
jg.writeObjectFieldStart(name);
Set<Map.Entry> entries = map.entrySet();
for (Map.Entry entry : entries) {
writeObjectField(jg, entry.getKey().toString(), entry.getValue());
}
jg.writeEndObject();
}
private static void writeList(JsonGenerator jg, String name, List list) throws IOException {
jg.writeArrayFieldStart(name);
for (Object item : list) {
jg.writeObject(item);
}
jg.writeEndArray();
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Listener for receiving activity/service lifecycle events.
*/
public interface LifecycleEventListener {
/**
* Called when host (activity/service) receives resume event (e.g. {@link Activity#onResume}
*/
void onHostResume();
/**
* Called when host (activity/service) receives pause event (e.g. {@link Activity#onPause}
*/
void onHostPause();
/**
* Called when host (activity/service) receives destroy event (e.g. {@link Activity#onDestroy}
*/
void onHostDestroy();
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
/**
* Exception thrown when a native module method call receives unexpected arguments from JS.
*/
public class NativeArgumentsParseException extends JSApplicationCausedNativeException {
public NativeArgumentsParseException(String detailMessage) {
super(detailMessage);
}
public NativeArgumentsParseException(@Nullable String detailMessage, @Nullable Throwable t) {
super(detailMessage, t);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
/**
* Base class for an array whose members are stored in native code (C++).
*/
@DoNotStrip
public abstract class NativeArray {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
public NativeArray() {
mHybridData = initHybrid();
}
@Override
public native String toString();
private native HybridData initHybrid();
@DoNotStrip
private HybridData mHybridData;
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.jni.Countable;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
/**
* Base class for a Map whose keys and values are stored in native code (C++).
*/
@DoNotStrip
public abstract class NativeMap extends Countable {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
public NativeMap() {
initialize();
}
@Override
public native String toString();
private native void initialize();
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import java.io.IOException;
import java.util.Map;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* A native module whose API can be provided to JS catalyst instances. {@link NativeModule}s whose
* implementation is written in Java should extend {@link BaseJavaModule} or {@link
* ReactContextBaseJavaModule}. {@link NativeModule}s whose implementation is written in C++
* must not provide any Java code (so they can be reused on other platforms), and instead should
* register themselves using {@link CxxModuleWrapper}.
*/
public interface NativeModule {
public static interface NativeMethod {
void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters);
}
/**
* @return the name of this module. This will be the name used to {@code require()} this module
* from javascript.
*/
public String getName();
/**
* @return methods callable from JS on this module
*/
public Map<String, NativeMethod> getMethods();
/**
* Append a field which represents the constants this module exports
* to JS. If no constants are exported this should do nothing.
*/
public void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException;
/**
* This is called at the end of {@link CatalystApplicationFragment#createCatalystInstance()}
* after the CatalystInstance has been created, in order to initialize NativeModules that require
* the CatalystInstance or JS modules.
*/
public void initialize();
/**
* Called before {CatalystInstance#onHostDestroy}
*/
public void onCatalystInstanceDestroy();
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface for a class that knows how to handle an Exception thrown by a native module invoked
* from JS. Since these Exceptions are triggered by JS calls (and can be fixed in JS), a
* common way to handle one is to show a error dialog and allow the developer to change and reload
* JS.
*
* We should also note that we have a unique stance on what 'caused' means: even if there's a bug in
* the framework/native code, it was triggered by JS and theoretically since we were able to set up
* the bridge, JS could change its logic, reload, and not trigger that crash.
*/
public interface NativeModuleCallExceptionHandler {
/**
* Do something to display or log the exception.
*/
void handleException(Exception e);
}

View File

@@ -0,0 +1,200 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.SetBuilder;
import com.facebook.infer.annotation.Assertions;
import com.facebook.systrace.Systrace;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* A set of Java APIs to expose to a particular JavaScript instance.
*/
public class NativeModuleRegistry {
private final ArrayList<ModuleDefinition> mModuleTable;
private final Map<Class<NativeModule>, NativeModule> mModuleInstances;
private final String mModuleDescriptions;
private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
private NativeModuleRegistry(
ArrayList<ModuleDefinition> moduleTable,
Map<Class<NativeModule>, NativeModule> moduleInstances,
String moduleDescriptions) {
mModuleTable = moduleTable;
mModuleInstances = moduleInstances;
mModuleDescriptions = moduleDescriptions;
mBatchCompleteListenerModules = new ArrayList<OnBatchCompleteListener>(mModuleTable.size());
for (int i = 0; i < mModuleTable.size(); i++) {
ModuleDefinition definition = mModuleTable.get(i);
if (definition.target instanceof OnBatchCompleteListener) {
mBatchCompleteListenerModules.add((OnBatchCompleteListener) definition.target);
}
}
}
/* package */ void call(
CatalystInstance catalystInstance,
int moduleId,
int methodId,
ReadableNativeArray parameters) {
ModuleDefinition definition = mModuleTable.get(moduleId);
if (definition == null) {
throw new RuntimeException("Call to unknown module: " + moduleId);
}
definition.call(catalystInstance, methodId, parameters);
}
/* package */ String moduleDescriptions() {
return mModuleDescriptions;
}
/* package */ void notifyCatalystInstanceDestroy() {
UiThreadUtil.assertOnUiThread();
for (NativeModule nativeModule : mModuleInstances.values()) {
nativeModule.onCatalystInstanceDestroy();
}
}
/* package */ void notifyCatalystInstanceInitialized() {
UiThreadUtil.assertOnUiThread();
for (NativeModule nativeModule : mModuleInstances.values()) {
nativeModule.initialize();
}
}
public void onBatchComplete() {
for (int i = 0; i < mBatchCompleteListenerModules.size(); i++) {
mBatchCompleteListenerModules.get(i).onBatchComplete();
}
}
public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
return (T) Assertions.assertNotNull(mModuleInstances.get(moduleInterface));
}
public Collection<NativeModule> getAllModules() {
return mModuleInstances.values();
}
private static class ModuleDefinition {
public final int id;
public final String name;
public final NativeModule target;
public final ArrayList<MethodRegistration> methods;
public ModuleDefinition(int id, String name, NativeModule target) {
this.id = id;
this.name = name;
this.target = target;
this.methods = new ArrayList<MethodRegistration>();
for (Map.Entry<String, NativeModule.NativeMethod> entry : target.getMethods().entrySet()) {
this.methods.add(
new MethodRegistration(
entry.getKey(), "NativeCall__" + target.getName() + "_" + entry.getKey(),
entry.getValue()));
}
}
public void call(
CatalystInstance catalystInstance,
int methodId,
ReadableNativeArray parameters) {
MethodRegistration method = this.methods.get(methodId);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, method.tracingName);
try {
this.methods.get(methodId).method.invoke(catalystInstance, parameters);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
private static class MethodRegistration {
public MethodRegistration(String name, String tracingName, NativeModule.NativeMethod method) {
this.name = name;
this.tracingName = tracingName;
this.method = method;
}
public String name;
public String tracingName;
public NativeModule.NativeMethod method;
}
public static class Builder {
private ArrayList<ModuleDefinition> mModuleDefinitions;
private Map<Class<NativeModule>, NativeModule> mModuleInstances;
private Set<String> mSeenModuleNames;
public Builder() {
mModuleDefinitions = new ArrayList<ModuleDefinition>();
mModuleInstances = MapBuilder.newHashMap();
mSeenModuleNames = SetBuilder.newHashSet();
}
public Builder add(NativeModule module) {
ModuleDefinition registration = new ModuleDefinition(
mModuleDefinitions.size(),
module.getName(),
module);
Assertions.assertCondition(
!mSeenModuleNames.contains(module.getName()),
"Module " + module.getName() + " was already registered!");
mSeenModuleNames.add(module.getName());
mModuleDefinitions.add(registration);
mModuleInstances.put((Class<NativeModule>) module.getClass(), module);
return this;
}
public NativeModuleRegistry build() {
JsonFactory jsonFactory = new JsonFactory();
StringWriter writer = new StringWriter();
try {
JsonGenerator jg = jsonFactory.createGenerator(writer);
jg.writeStartObject();
for (ModuleDefinition module : mModuleDefinitions) {
jg.writeObjectFieldStart(module.name);
jg.writeNumberField("moduleID", module.id);
jg.writeObjectFieldStart("methods");
for (int i = 0; i < module.methods.size(); i++) {
MethodRegistration method = module.methods.get(i);
jg.writeObjectFieldStart(method.name);
jg.writeNumberField("methodID", i);
jg.writeEndObject();
}
jg.writeEndObject();
module.target.writeConstantsField(jg, "constants");
jg.writeEndObject();
}
jg.writeEndObject();
jg.close();
} catch (IOException ioe) {
throw new RuntimeException("Unable to serialize Java module configuration", ioe);
}
String moduleDefinitionJson = writer.getBuffer().toString();
return new NativeModuleRegistry(mModuleDefinitions, mModuleInstances, moduleDefinitionJson);
}
}
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Exception thrown by {@link ReadableNativeMap} when a key that does not exist is requested.
*/
@DoNotStrip
public class NoSuchKeyException extends RuntimeException {
@DoNotStrip
public NoSuchKeyException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface for receiving notification for bridge idle/busy events. Should not affect application
* logic and should only be used for debug/monitoring/testing purposes. Call
* {@link CatalystInstance#addBridgeIdleDebugListener} to start monitoring.
*
* NB: onTransitionToBridgeIdle and onTransitionToBridgeBusy may be called from different threads,
* and those threads may not be the same thread on which the listener was originally registered.
*/
public interface NotThreadSafeBridgeIdleDebugListener {
/**
* Called once all pending JS calls have resolved via an onBatchComplete call in the bridge and
* the requested native module calls have also run. The bridge will not become busy again until
* a timer, touch event, etc. causes a Java->JS call to be enqueued again.
*/
void onTransitionToBridgeIdle();
/**
* Called when the bridge was in an idle state and executes a JS call or callback.
*/
void onTransitionToBridgeBusy();
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Exception thrown when a caller attempts to modify or use a {@link WritableNativeArray} or
* {@link WritableNativeMap} after it has already been added to a parent array or map. This is
* unsafe since we reuse the native memory so the underlying array/map is no longer valid.
*/
@DoNotStrip
public class ObjectAlreadyConsumedException extends RuntimeException {
@DoNotStrip
public ObjectAlreadyConsumedException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface for a module that will be notified when a batch of JS->Java calls has finished.
*/
public interface OnBatchCompleteListener {
void onBatchComplete();
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import com.facebook.soloader.SoLoader;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* JavaScript executor that delegates JS calls processed by native code back to a java version
* of the native executor interface.
*
* When set as a executor with {@link CatalystInstance.Builder}, catalyst native code will delegate
* low level javascript calls to the implementation of {@link JavaJSExecutor} interface provided
* with the constructor of this class.
*/
@DoNotStrip
public class ProxyJavaScriptExecutor extends JavaScriptExecutor {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
public static class ProxyExecutorException extends Exception {
public ProxyExecutorException(Throwable cause) {
super(cause);
}
}
/**
* This is class represents java version of native js executor interface. When set through
* {@link ProxyJavaScriptExecutor} as a {@link CatalystInstance} executor, native code will
* delegate js calls to the given implementation of this interface.
*/
@DoNotStrip
public interface JavaJSExecutor {
/**
* Close this executor and cleanup any resources that it was using. No further calls are
* expected after this.
*/
void close();
/**
* Load javascript into the js context
* @param script script contet to be executed
* @param sourceURL url or file location from which script content was loaded
*/
@DoNotStrip
void executeApplicationScript(String script, String sourceURL) throws ProxyExecutorException;
/**
* Execute javascript method within js context
* @param modulename name of the common-js like module to execute the method from
* @param methodName name of the method to be executed
* @param jsonArgsArray json encoded array of arguments provided for the method call
* @return json encoded value returned from the method call
*/
@DoNotStrip
String executeJSCall(String modulename, String methodName, String jsonArgsArray)
throws ProxyExecutorException;
@DoNotStrip
void setGlobalVariable(String propertyName, String jsonEncodedValue);
}
private @Nullable JavaJSExecutor mJavaJSExecutor;
/**
* Create {@link ProxyJavaScriptExecutor} instance
* @param executor implementation of {@link JavaJSExecutor} which will be responsible for handling
* javascript calls
*/
public ProxyJavaScriptExecutor(JavaJSExecutor executor) {
mJavaJSExecutor = executor;
initialize(executor);
}
@Override
public void close() {
if (mJavaJSExecutor != null) {
mJavaJSExecutor.close();
mJavaJSExecutor = null;
}
}
private native void initialize(JavaJSExecutor executor);
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import android.content.Context;
/**
* A context wrapper that always wraps Android Application {@link Context} and
* {@link CatalystInstance} by extending {@link ReactContext}
*/
public class ReactApplicationContext extends ReactContext {
// We want to wrap ApplicationContext, since there is no easy way to verify that application
// context is passed as a param, we use {@link Context#getApplicationContext} to ensure that
// the context we're wrapping is in fact an application context.
public ReactApplicationContext(Context context) {
super(context.getApplicationContext());
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import android.content.res.AssetManager;
import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.jni.Countable;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
/**
* Interface to the JS execution environment and means of transport for messages Java<->JS.
*/
@DoNotStrip
public class ReactBridge extends Countable {
/* package */ static final String REACT_NATIVE_LIB = "reactnativejni";
static {
SoLoader.loadLibrary(REACT_NATIVE_LIB);
}
private final ReactCallback mCallback;
private final JavaScriptExecutor mJSExecutor;
private final MessageQueueThread mNativeModulesQueueThread;
/**
* @param jsExecutor the JS executor to use to run JS
* @param callback the callback class used to invoke native modules
* @param nativeModulesQueueThread the MessageQueueThread the callbacks should be invoked on
*/
public ReactBridge(
JavaScriptExecutor jsExecutor,
ReactCallback callback,
MessageQueueThread nativeModulesQueueThread) {
mJSExecutor = jsExecutor;
mCallback = callback;
mNativeModulesQueueThread = nativeModulesQueueThread;
initialize(jsExecutor, callback, mNativeModulesQueueThread);
}
@Override
public void dispose() {
mJSExecutor.close();
mJSExecutor.dispose();
super.dispose();
}
private native void initialize(
JavaScriptExecutor jsExecutor,
ReactCallback callback,
MessageQueueThread nativeModulesQueueThread);
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
public native void loadScriptFromNetworkCached(String sourceURL, @Nullable String tempFileName);
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
public native void invokeCallback(int callbackID, NativeArray arguments);
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
public native boolean supportsProfiling();
public native void startProfiler(String title);
public native void stopProfiler(String title, String filename);
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public interface ReactCallback {
@DoNotStrip
void call(int moduleId, int methodId, ReadableNativeArray parameters);
@DoNotStrip
void onBatchComplete();
}

View File

@@ -0,0 +1,202 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import java.util.concurrent.CopyOnWriteArraySet;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.LayoutInflater;
import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.infer.annotation.Assertions;
/**
* Abstract ContextWrapper for Android applicaiton or activity {@link Context} and
* {@link CatalystInstance}
*/
public class ReactContext extends ContextWrapper {
private final CopyOnWriteArraySet<LifecycleEventListener> mLifecycleEventListeners =
new CopyOnWriteArraySet<>();
private @Nullable CatalystInstance mCatalystInstance;
private @Nullable LayoutInflater mInflater;
private @Nullable MessageQueueThread mUiMessageQueueThread;
private @Nullable MessageQueueThread mNativeModulesMessageQueueThread;
private @Nullable MessageQueueThread mJSMessageQueueThread;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
public ReactContext(Context base) {
super(base);
}
/**
* Set and initialize CatalystInstance for this Context. This should be called exactly once.
*/
public void initializeWithInstance(CatalystInstance catalystInstance) {
if (catalystInstance == null) {
throw new IllegalArgumentException("CatalystInstance cannot be null.");
}
if (mCatalystInstance != null) {
throw new IllegalStateException("ReactContext has been already initialized");
}
mCatalystInstance = catalystInstance;
CatalystQueueConfiguration queueConfig = catalystInstance.getCatalystQueueConfiguration();
mUiMessageQueueThread = queueConfig.getUIQueueThread();
mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
mJSMessageQueueThread = queueConfig.getJSQueueThread();
}
public void setNativeModuleCallExceptionHandler(
@Nullable NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
}
// We override the following method so that views inflated with the inflater obtained from this
// context return the ReactContext in #getContext(). The default implementation uses the base
// context instead, so it couldn't be cast to ReactContext.
// TODO: T7538796 Check requirement for Override of getSystemService ReactContext
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
/**
* @return handle to the specified JS module for the CatalystInstance associated with this Context
*/
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
if (mCatalystInstance == null) {
throw new RuntimeException("Trying to invoke JS before CatalystInstance has been set!");
}
return mCatalystInstance.getJSModule(jsInterface);
}
/**
* @return the instance of the specified module interface associated with this ReactContext.
*/
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
if (mCatalystInstance == null) {
throw new RuntimeException("Trying to invoke JS before CatalystInstance has been set!");
}
return mCatalystInstance.getNativeModule(nativeModuleInterface);
}
public CatalystInstance getCatalystInstance() {
return Assertions.assertNotNull(mCatalystInstance);
}
public boolean hasActiveCatalystInstance() {
return mCatalystInstance != null && !mCatalystInstance.isDestroyed();
}
public void addLifecycleEventListener(LifecycleEventListener listener) {
mLifecycleEventListeners.add(listener);
}
public void removeLifecycleEventListener(LifecycleEventListener listener) {
mLifecycleEventListeners.remove(listener);
}
/**
* Should be called by the hosting Fragment in {@link Fragment#onResume}
*/
public void onResume() {
UiThreadUtil.assertOnUiThread();
for (LifecycleEventListener listener : mLifecycleEventListeners) {
listener.onHostResume();
}
}
/**
* Should be called by the hosting Fragment in {@link Fragment#onPause}
*/
public void onPause() {
UiThreadUtil.assertOnUiThread();
for (LifecycleEventListener listener : mLifecycleEventListeners) {
listener.onHostPause();
}
}
/**
* Should be called by the hosting Fragment in {@link Fragment#onDestroy}
*/
public void onDestroy() {
UiThreadUtil.assertOnUiThread();
for (LifecycleEventListener listener : mLifecycleEventListeners) {
listener.onHostDestroy();
}
if (mCatalystInstance != null) {
mCatalystInstance.destroy();
}
}
public void assertOnUiQueueThread() {
Assertions.assertNotNull(mUiMessageQueueThread).assertIsOnThread();
}
public boolean isOnUiQueueThread() {
return Assertions.assertNotNull(mUiMessageQueueThread).isOnThread();
}
public void runOnUiQueueThread(Runnable runnable) {
Assertions.assertNotNull(mUiMessageQueueThread).runOnQueue(runnable);
}
public void assertOnNativeModulesQueueThread() {
Assertions.assertNotNull(mNativeModulesMessageQueueThread).assertIsOnThread();
}
public boolean isOnNativeModulesQueueThread() {
return Assertions.assertNotNull(mNativeModulesMessageQueueThread).isOnThread();
}
public void runOnNativeModulesQueueThread(Runnable runnable) {
Assertions.assertNotNull(mNativeModulesMessageQueueThread).runOnQueue(runnable);
}
public void assertOnJSQueueThread() {
Assertions.assertNotNull(mJSMessageQueueThread).assertIsOnThread();
}
public boolean isOnJSQueueThread() {
return Assertions.assertNotNull(mJSMessageQueueThread).isOnThread();
}
public void runOnJSQueueThread(Runnable runnable) {
Assertions.assertNotNull(mJSMessageQueueThread).runOnQueue(runnable);
}
/**
* Passes the given exception to the current
* {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} if one exists, rethrowing
* otherwise.
*/
public void handleException(RuntimeException e) {
if (mCatalystInstance != null &&
!mCatalystInstance.isDestroyed() &&
mNativeModuleCallExceptionHandler != null) {
mNativeModuleCallExceptionHandler.handleException(e);
} else {
throw e;
}
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Base class for Catalyst native modules that require access to the {@link ReactContext}
* instance.
*/
public abstract class ReactContextBaseJavaModule extends BaseJavaModule {
private final ReactApplicationContext mReactApplicationContext;
public ReactContextBaseJavaModule(ReactApplicationContext reactContext) {
mReactApplicationContext = reactContext;
}
/**
* Subclasses can use this method to access catalyst context passed as a constructor
*/
protected final ReactApplicationContext getReactApplicationContext() {
return mReactApplicationContext;
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation which is used to mark methods that are exposed to
* Catalyst. This applies to derived classes of {@link
* BaseJavaModule}, which will generate a list of exported methods by
* searching for those which are annotated with this annotation and
* adding a JS callback for each.
*/
@Retention(RUNTIME)
public @interface ReactMethod {
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface for an array that allows typed access to its members. Used to pass parameters from JS
* to Java.
*/
public interface ReadableArray {
int size();
boolean isNull(int index);
boolean getBoolean(int index);
double getDouble(int index);
int getInt(int index);
String getString(int index);
ReadableArray getArray(int index);
ReadableMap getMap(int index);
ReadableType getType(int index);
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface for a map that allows typed access to its members. Used to pass parameters from JS to
* Java.
*/
public interface ReadableMap {
boolean hasKey(String name);
boolean isNull(String name);
boolean getBoolean(String name);
double getDouble(String name);
int getInt(String name);
String getString(String name);
ReadableArray getArray(String name);
ReadableMap getMap(String name);
ReadableType getType(String name);
ReadableMapKeySeyIterator keySetIterator();
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Interface of a iterator for a {@link NativeMap}'s key set.
*/
@DoNotStrip
public interface ReadableMapKeySeyIterator {
boolean hasNextKey();
String nextKey();
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
/**
* Implementation of a NativeArray that allows read-only access to its members. This will generally
* be constructed and filled in native code so you shouldn't construct one yourself.
*/
@DoNotStrip
public class ReadableNativeArray extends NativeArray implements ReadableArray {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
@Override
public native int size();
@Override
public native boolean isNull(int index);
@Override
public native boolean getBoolean(int index);
@Override
public native double getDouble(int index);
@Override
public native String getString(int index);
@Override
public native ReadableNativeArray getArray(int index);
@Override
public native ReadableNativeMap getMap(int index);
@Override
public native ReadableType getType(int index);
@Override
public int getInt(int index) {
return (int) getDouble(index);
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.jni.Countable;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
/**
* Implementation of a read-only map in native memory. This will generally be constructed and filled
* in native code so you shouldn't construct one yourself.
*/
@DoNotStrip
public class ReadableNativeMap extends NativeMap implements ReadableMap {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
@Override
public native boolean hasKey(String name);
@Override
public native boolean isNull(String name);
@Override
public native boolean getBoolean(String name);
@Override
public native double getDouble(String name);
@Override
public native String getString(String name);
@Override
public native ReadableNativeArray getArray(String name);
@Override
public native ReadableNativeMap getMap(String name);
@Override
public native ReadableType getType(String name);
@Override
public ReadableMapKeySeyIterator keySetIterator() {
return new ReadableNativeMapKeySeyIterator(this);
}
@Override
public int getInt(String name) {
return (int) getDouble(name);
}
/**
* Implementation of a {@link ReadableNativeMap} iterator in native memory.
*/
@DoNotStrip
private static class ReadableNativeMapKeySeyIterator extends Countable
implements ReadableMapKeySeyIterator {
private final ReadableNativeMap mReadableNativeMap;
public ReadableNativeMapKeySeyIterator(ReadableNativeMap readableNativeMap) {
mReadableNativeMap = readableNativeMap;
initialize(mReadableNativeMap);
}
@Override
public native boolean hasNextKey();
@Override
public native String nextKey();
private native void initialize(ReadableNativeMap readableNativeMap);
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Defines the type of an object stored in a {@link ReadableArray} or
* {@link ReadableMap}.
*/
@DoNotStrip
public enum ReadableType {
Null,
Boolean,
Number,
String,
Map,
Array,
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
/**
* Utility class to make assertions that should not hard-crash the app but instead be handled by the
* Catalyst app {@link NativeModuleCallExceptionHandler}. See the javadoc on that class for
* more information about our opinion on when these assertions should be used as opposed to
* assertions that might throw AssertionError Throwables that will cause the app to hard crash.
*/
public class SoftAssertions {
/**
* Asserts the given condition, throwing an {@link AssertionException} if the condition doesn't
* hold.
*/
public static void assertCondition(boolean condition, String message) {
if (!condition) {
throw new AssertionException(message);
}
}
/**
* Asserts that the given Object isn't null, throwing an {@link AssertionException} if it was.
*/
public static <T> T assertNotNull(@Nullable T instance) {
if (instance == null) {
throw new AssertionException("Expected object to not be null!");
}
return instance;
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
/**
* Utility for interacting with the UI thread.
*/
public class UiThreadUtil {
@Nullable private static Handler sMainHandler;
/**
* @return whether the current thread is the UI thread.
*/
public static boolean isOnUiThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
/**
* Throws a {@link AssertionException} if the current thread is not the UI thread.
*/
public static void assertOnUiThread() {
SoftAssertions.assertCondition(isOnUiThread(), "Expected to run on UI thread!");
}
/**
* Throws a {@link AssertionException} if the current thread is the UI thread.
*/
public static void assertNotOnUiThread() {
SoftAssertions.assertCondition(!isOnUiThread(), "Expected not to run on UI thread!");
}
/**
* Runs the given Runnable on the UI thread.
*/
public static void runOnUiThread(Runnable runnable) {
synchronized (UiThreadUtil.class) {
if (sMainHandler == null) {
sMainHandler = new Handler(Looper.getMainLooper());
}
}
sMainHandler.post(runnable);
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Exception thrown from native code when a type retrieved from a map or array (e.g. via
* {@link NativeArrayParameter#getString(int)}) does not match the expected type.
*/
@DoNotStrip
public class UnexpectedNativeTypeException extends RuntimeException {
@DoNotStrip
public UnexpectedNativeTypeException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,183 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import android.os.Handler;
import com.facebook.infer.annotation.Assertions;
/**
* Executes JS remotely via the react nodejs server as a proxy to a browser on the host machine.
*/
public class WebsocketJavaScriptExecutor implements ProxyJavaScriptExecutor.JavaJSExecutor {
private static final long CONNECT_TIMEOUT_MS = 5000;
private static final int CONNECT_RETRY_COUNT = 3;
public interface JSExecutorConnectCallback {
void onSuccess();
void onFailure(Throwable cause);
}
public static class WebsocketExecutorTimeoutException extends Exception {
public WebsocketExecutorTimeoutException(String message) {
super(message);
}
}
private static class JSExecutorCallbackFuture implements
JSDebuggerWebSocketClient.JSDebuggerCallback {
private final Semaphore mSemaphore = new Semaphore(0);
private @Nullable Throwable mCause;
private @Nullable String mResponse;
@Override
public void onSuccess(@Nullable String response) {
mResponse = response;
mSemaphore.release();
}
@Override
public void onFailure(Throwable cause) {
mCause = cause;
mSemaphore.release();
}
/**
* Call only once per object instance!
*/
public @Nullable String get() throws Throwable {
mSemaphore.acquire();
if (mCause != null) {
throw mCause;
}
return mResponse;
}
}
final private HashMap<String, String> mInjectedObjects = new HashMap<>();
private @Nullable JSDebuggerWebSocketClient mWebSocketClient;
public void connect(final String webSocketServerUrl, final JSExecutorConnectCallback callback) {
final AtomicInteger retryCount = new AtomicInteger(CONNECT_RETRY_COUNT);
final JSExecutorConnectCallback retryProxyCallback = new JSExecutorConnectCallback() {
@Override
public void onSuccess() {
callback.onSuccess();
}
@Override
public void onFailure(Throwable cause) {
if (retryCount.decrementAndGet() <= 0) {
callback.onFailure(cause);
} else {
connectInternal(webSocketServerUrl, this);
}
}
};
connectInternal(webSocketServerUrl, retryProxyCallback);
}
private void connectInternal(
String webSocketServerUrl,
final JSExecutorConnectCallback callback) {
final JSDebuggerWebSocketClient client = new JSDebuggerWebSocketClient();
final Handler timeoutHandler = new Handler();
client.connect(
webSocketServerUrl, new JSDebuggerWebSocketClient.JSDebuggerCallback() {
@Override
public void onSuccess(@Nullable String response) {
client.prepareJSRuntime(
new JSDebuggerWebSocketClient.JSDebuggerCallback() {
@Override
public void onSuccess(@Nullable String response) {
timeoutHandler.removeCallbacksAndMessages(null);
mWebSocketClient = client;
callback.onSuccess();
}
@Override
public void onFailure(Throwable cause) {
timeoutHandler.removeCallbacksAndMessages(null);
callback.onFailure(cause);
}
});
}
@Override
public void onFailure(Throwable cause) {
callback.onFailure(cause);
}
});
timeoutHandler.postDelayed(
new Runnable() {
@Override
public void run() {
client.closeQuietly();
callback.onFailure(
new WebsocketExecutorTimeoutException(
"Timeout while connecting to remote debugger"));
}
},
CONNECT_TIMEOUT_MS);
}
@Override
public void close() {
if (mWebSocketClient != null) {
mWebSocketClient.closeQuietly();
}
}
@Override
public void executeApplicationScript(String script, String sourceURL)
throws ProxyJavaScriptExecutor.ProxyExecutorException {
JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
Assertions.assertNotNull(mWebSocketClient).executeApplicationScript(
sourceURL,
mInjectedObjects,
callback);
try {
callback.get();
} catch (Throwable cause) {
throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause);
}
}
@Override
public @Nullable String executeJSCall(String moduleName, String methodName, String jsonArgsArray)
throws ProxyJavaScriptExecutor.ProxyExecutorException {
JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
Assertions.assertNotNull(mWebSocketClient).executeJSCall(
moduleName,
methodName,
jsonArgsArray,
callback);
try {
return callback.get();
} catch (Throwable cause) {
throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause);
}
}
@Override
public void setGlobalVariable(String propertyName, String jsonEncodedValue) {
// Store and use in the next executeApplicationScript() call.
mInjectedObjects.put(propertyName, jsonEncodedValue);
}
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface for a mutable array. Used to pass arguments from Java to JS.
*/
public interface WritableArray extends ReadableArray {
void pushNull();
void pushBoolean(boolean value);
void pushDouble(double value);
void pushInt(int value);
void pushString(String value);
void pushArray(WritableArray array);
void pushMap(WritableMap map);
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
/**
* Interface for a mutable map. Used to pass arguments from Java to JS.
*/
public interface WritableMap extends ReadableMap {
void putNull(String key);
void putBoolean(String key, boolean value);
void putDouble(String key, double value);
void putInt(String key, int value);
void putString(String key, String value);
void putArray(String key, WritableArray value);
void putMap(String key, WritableMap value);
void merge(ReadableMap source);
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.infer.annotation.Assertions;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
/**
* Implementation of a write-only array stored in native memory. Use
* {@link Arguments#createArray()} if you need to stub out creating this class in a test.
* TODO(5815532): Check if consumed on read
*/
@DoNotStrip
public class WritableNativeArray extends ReadableNativeArray implements WritableArray {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
@Override
public native void pushNull();
@Override
public native void pushBoolean(boolean value);
@Override
public native void pushDouble(double value);
@Override
public native void pushString(String value);
@Override
public void pushInt(int value) {
pushDouble(value);
}
// Note: this consumes the map so do not reuse it.
@Override
public void pushArray(WritableArray array) {
Assertions.assertCondition(
array == null || array instanceof WritableNativeArray, "Illegal type provided");
pushNativeArray((WritableNativeArray) array);
}
// Note: this consumes the map so do not reuse it.
@Override
public void pushMap(WritableMap map) {
Assertions.assertCondition(
map == null || map instanceof WritableNativeMap, "Illegal type provided");
pushNativeMap((WritableNativeMap) map);
}
private native void pushNativeArray(WritableNativeArray array);
private native void pushNativeMap(WritableNativeMap map);
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.bridge;
import com.facebook.infer.annotation.Assertions;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
/**
* Implementation of a write-only map stored in native memory. Use
* {@link Arguments#createMap()} if you need to stub out creating this class in a test.
* TODO(5815532): Check if consumed on read
*/
@DoNotStrip
public class WritableNativeMap extends ReadableNativeMap implements WritableMap {
static {
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
@Override
public native void putBoolean(String key, boolean value);
@Override
public native void putDouble(String key, double value);
@Override
public native void putString(String key, String value);
@Override
public native void putNull(String key);
@Override
public void putInt(String key, int value) {
putDouble(key, value);
}
// Note: this consumes the map so do not reuse it.
@Override
public void putMap(String key, WritableMap value) {
Assertions.assertCondition(
value == null || value instanceof WritableNativeMap, "Illegal type provided");
putNativeMap(key, (WritableNativeMap) value);
}
// Note: this consumes the map so do not reuse it.
@Override
public void putArray(String key, WritableArray value) {
Assertions.assertCondition(
value == null || value instanceof WritableNativeArray, "Illegal type provided");
putNativeArray(key, (WritableNativeArray) value);
}
// Note: this **DOES NOT** consume the source map
@Override
public void merge(ReadableMap source) {
Assertions.assertCondition(source instanceof ReadableNativeMap, "Illegal type provided");
mergeNativeMap((ReadableNativeMap) source);
}
private native void putNativeMap(String key, WritableNativeMap value);
private native void putNativeArray(String key, WritableNativeArray value);
private native void mergeNativeMap(ReadableNativeMap source);
}

View File

@@ -0,0 +1,14 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import sys
import zipfile
srcs = sys.argv[1:]
with zipfile.ZipFile(sys.stdout, 'w') as jar:
for src in srcs:
archive_name = os.path.join('assets/', os.path.basename(src))
jar.write(src, archive_name, zipfile.ZIP_DEFLATED)

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