diff --git a/.eslintrc b/.eslintrc index a4578636742e2e..7bd17b585b54d8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,6 +21,7 @@ "__fbBatchedBridgeConfig": false, "alert": false, "cancelAnimationFrame": false, + "cancelIdleCallback": false, "clearImmediate": true, "clearInterval": false, "clearTimeout": false, @@ -40,6 +41,7 @@ "process": false, "Promise": true, "requestAnimationFrame": true, + "requestIdleCallback": true, "require": false, "Set": true, "setImmediate": true, diff --git a/.flowconfig b/.flowconfig index 0bbb11213636bd..1eb8b65b47ebe8 100644 --- a/.flowconfig +++ b/.flowconfig @@ -9,6 +9,21 @@ # Ignore malformed json .*/node_modules/y18n/test/.*\.json +# Ignore the website subdir +<PROJECT_ROOT>/website/.* + +# Ignore BUCK generated dirs +<PROJECT_ROOT>/\.buckd/ + +# Ignore unexpected extra @providesModule +.*/node_modules/commoner/test/source/widget/share.js + +# Ignore duplicate module providers +# For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root +.*/Libraries/react-native/React.js +.*/Libraries/react-native/ReactNative.js +.*/node_modules/jest-runtime/build/__tests__/.* + [include] [libs] @@ -32,9 +47,11 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-7]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-7]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-9]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-9]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +unsafe.enable_getters_and_setters=true + [version] -^0.27.0 +^0.29.0 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 74cf4829c698e4..e66cdff2e7ebd5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,7 @@ Thanks for submitting a pull request! Please provide enough information so that others can review your pull request: +> **Unless you are a React Native release maintainer and cherry-picking an *existing* commit into a current release, ensure your pull request is targeting the `master` React Native branch.** + (You can skip this if you're fixing a typo or adding an app to the Showcase.) Explain the **motivation** for making this change. What existing problem does the pull request solve? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5208e8111d9889..3c08db03db726d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ We will do our best to keep `master` in good shape, with tests passing at all ti The core team will be monitoring for pull requests. When we get one, we'll run some Facebook-specific integration tests on it first. From here, we'll need to get another person to sign off on the changes and then merge the pull request. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process. -Please submit your pull request on the *master* branch. If the fix is critical and should be included in a stable branch please mention it and it will be cherry picked into it. +**Please submit your pull request on the `master` branch**. If the fix is critical and should be included in a stable branch please mention it and it will be cherry picked into it. *Before* submitting a pull request, please make sure the following is done⦠@@ -101,7 +101,7 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe #### JSX -* Prefer `'` over `"` for string literal props +* Prefer `"` over `'` for string literal props * When wrapping opening tags over multiple lines, place one prop per line * `{}` of props should hug their values (no spaces) * Place the closing `>` of opening tags on the same line as the last prop diff --git a/Examples/2048/2048.xcodeproj/project.pbxproj b/Examples/2048/2048.xcodeproj/project.pbxproj index 95ac92bb264f29..a4a6b881109d9c 100644 --- a/Examples/2048/2048.xcodeproj/project.pbxproj +++ b/Examples/2048/2048.xcodeproj/project.pbxproj @@ -143,6 +143,7 @@ 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, + 68ACCCD51D2BE2D2008E368A /* Run Script */, ); buildRules = ( ); @@ -230,6 +231,23 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 68ACCCD51D2BE2D2008E368A /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n$SRCROOT/../../packager/react-native-xcode.sh Examples/2048/Game2048.js\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Examples/2048/2048/AppDelegate.m b/Examples/2048/2048/AppDelegate.m index 9975d7df337c5a..9e3c01b8111ea0 100644 --- a/Examples/2048/2048/AppDelegate.m +++ b/Examples/2048/2048/AppDelegate.m @@ -14,41 +14,14 @@ #import "AppDelegate.h" +#import "RCTBundleURLProvider.h" #import "RCTRootView.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - NSURL *jsCodeLocation; - - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ - - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios&dev=true"]; - - /** - * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` - * to your Xcode project folder in the terminal, and run - * - * $ curl 'http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios' -o main.jsbundle - * - * then add the `main.jsbundle` file to your project and uncomment this line: - */ - -// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"Examples/2048/Game2048" fallbackResource:nil]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"Game2048" diff --git a/Examples/Movies/Movies.xcodeproj/project.pbxproj b/Examples/Movies/Movies.xcodeproj/project.pbxproj index 30b7e6505ddbb7..d6db294ceaa03f 100644 --- a/Examples/Movies/Movies.xcodeproj/project.pbxproj +++ b/Examples/Movies/Movies.xcodeproj/project.pbxproj @@ -200,6 +200,7 @@ 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, + 68ACCCEE1D2BE57F008E368A /* ShellScript */, ); buildRules = ( ); @@ -320,6 +321,22 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 68ACCCEE1D2BE57F008E368A /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n$SRCROOT/../../packager/react-native-xcode.sh Examples/Movies/MoviesApp.ios.js\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Examples/Movies/Movies/AppDelegate.m b/Examples/Movies/Movies/AppDelegate.m index d81599fb7d9b45..94f5d2ae94411f 100644 --- a/Examples/Movies/Movies/AppDelegate.m +++ b/Examples/Movies/Movies/AppDelegate.m @@ -14,6 +14,7 @@ #import "AppDelegate.h" +#import "RCTBundleURLProvider.h" #import "RCTLinkingManager.h" #import "RCTRootView.h" @@ -21,35 +22,7 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - NSURL *jsCodeLocation; - - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ - - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.ios.bundle?platform=ios&dev=true"]; - - /** - * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` - * to your Xcode project folder in the terminal, and run - * - * $ curl 'http://localhost:8081/Examples/Movies/MoviesApp.ios.bundle?platform=ios&dev=true' -o main.jsbundle - * - * then add the `main.jsbundle` file to your project and uncomment this line: - */ - -// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"Examples/Movies/MoviesApp.ios" fallbackResource:nil]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"MoviesApp" diff --git a/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj b/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj index 6517283332655c..99ed2764c78a07 100644 --- a/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj +++ b/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj @@ -181,6 +181,7 @@ 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, + 681C70ED1D2BE73C00E71791 /* ShellScript */, ); buildRules = ( ); @@ -290,6 +291,22 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 681C70ED1D2BE73C00E71791 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n$SRCROOT/../../packager/react-native-xcode.sh Examples/TicTacToe/TicTacToeApp.js\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Examples/TicTacToe/TicTacToe/AppDelegate.m b/Examples/TicTacToe/TicTacToe/AppDelegate.m index 4cca2217df235d..3e7399917b5530 100644 --- a/Examples/TicTacToe/TicTacToe/AppDelegate.m +++ b/Examples/TicTacToe/TicTacToe/AppDelegate.m @@ -14,41 +14,14 @@ #import "AppDelegate.h" +#import "RCTBundleURLProvider.h" #import "RCTRootView.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - NSURL *jsCodeLocation; - - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ - - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios&dev=true"]; - - /** - * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` - * to your Xcode project folder in the terminal, and run - * - * $ curl 'http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios' -o main.jsbundle - * - * then add the `main.jsbundle` file to your project and uncomment this line: - */ - -// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"Examples/TicTacToe/TicTacToeApp" fallbackResource:nil]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"TicTacToeApp" diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index f73a4214d02fc8..777773a5a8bd33 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1861C04AB9F0091BED0 /* flux@3x.png */; }; 1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1871C04AB9F0091BED0 /* hawk.png */; }; 1323F18C1C04AB9F0091BED0 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */; }; - 1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */ = {isa = PBXBuildFile; fileRef = 1323F18E1C04ABEB0091BED0 /* Thumbnails */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; @@ -63,7 +62,9 @@ 27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */; }; 27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; + 3D299BAF1D33EBFA00FA1057 /* RCTLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D299BAE1D33EBFA00FA1057 /* RCTLoggingTests.m */; }; 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; }; + 3DD981D61D33C6FB007DC7BE /* TestBundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 3DD981D51D33C6FB007DC7BE /* TestBundle.js */; }; 68FF44381CF6111500720EFD /* RCTBundleURLProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; }; @@ -191,11 +192,10 @@ 004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = "<group>"; }; 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitNotificationRaceTests.m; sourceTree = "<group>"; }; - 1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bunny.png; sourceTree = "<group>"; }; - 1323F1861C04AB9F0091BED0 /* flux@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "flux@3x.png"; sourceTree = "<group>"; }; - 1323F1871C04AB9F0091BED0 /* hawk.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hawk.png; sourceTree = "<group>"; }; - 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = uie_thumb_big.png; sourceTree = "<group>"; }; - 1323F18E1C04ABEB0091BED0 /* Thumbnails */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Thumbnails; sourceTree = "<group>"; }; + 1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = bunny.png; path = js/bunny.png; sourceTree = "<group>"; }; + 1323F1861C04AB9F0091BED0 /* flux@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "flux@3x.png"; path = "js/flux@3x.png"; sourceTree = "<group>"; }; + 1323F1871C04AB9F0091BED0 /* hawk.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = hawk.png; path = js/hawk.png; sourceTree = "<group>"; }; + 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = js/uie_thumb_big.png; sourceTree = "<group>"; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = "<group>"; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; }; @@ -220,12 +220,6 @@ 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = "<group>"; }; 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = ../../Libraries/NativeAnimation/RCTAnimation.xcodeproj; sourceTree = "<group>"; }; 143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; - 143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = "<group>"; }; - 143BC5821B21E18100462512 /* testSliderExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testSliderExampleSnapshot_1@2x.png"; sourceTree = "<group>"; }; - 143BC5831B21E18100462512 /* testSwitchExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testSwitchExampleSnapshot_1@2x.png"; sourceTree = "<group>"; }; - 143BC5841B21E18100462512 /* testTabBarExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testTabBarExampleSnapshot_1@2x.png"; sourceTree = "<group>"; }; - 143BC5851B21E18100462512 /* testTextExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testTextExampleSnapshot_1@2x.png"; sourceTree = "<group>"; }; - 143BC5861B21E18100462512 /* testViewExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testViewExampleSnapshot_1@2x.png"; sourceTree = "<group>"; }; 143BC5951B21E3E100462512 /* UIExplorerIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 143BC5981B21E3E100462512 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerSnapshotTests.m; sourceTree = "<group>"; }; @@ -256,7 +250,9 @@ 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FlexibleSizeExampleView.m; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m; sourceTree = "<group>"; }; 27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = "<group>"; }; 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; }; + 3D299BAE1D33EBFA00FA1057 /* RCTLoggingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLoggingTests.m; sourceTree = "<group>"; }; 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = "<group>"; }; + 3DD981D51D33C6FB007DC7BE /* TestBundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = TestBundle.js; sourceTree = "<group>"; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; }; 68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBundleURLProviderTests.m; sourceTree = "<group>"; }; 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; }; @@ -418,7 +414,6 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 13B07FB71A68108700A75B9A /* main.m */, - 1323F18E1C04ABEB0091BED0 /* Thumbnails */, 1323F18D1C04ABAC0091BED0 /* Supporting Files */, ); name = UIExplorer; @@ -457,37 +452,17 @@ 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, 13BCE84E1C9C209600DD7AAD /* RCTComponentPropsTests.m */, 143BC57E1B21E18100462512 /* Info.plist */, + 3DD981D51D33C6FB007DC7BE /* TestBundle.js */, 14D6D7101B220EB3001FB087 /* libOCMock.a */, 14D6D7011B220AE3001FB087 /* OCMock */, - 143BC57F1B21E18100462512 /* ReferenceImages */, ); path = UIExplorerUnitTests; sourceTree = "<group>"; }; - 143BC57F1B21E18100462512 /* ReferenceImages */ = { - isa = PBXGroup; - children = ( - 143BC5801B21E18100462512 /* Examples-UIExplorer-UIExplorerApp */, - ); - path = ReferenceImages; - sourceTree = "<group>"; - }; - 143BC5801B21E18100462512 /* Examples-UIExplorer-UIExplorerApp */ = { - isa = PBXGroup; - children = ( - 143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */, - 143BC5821B21E18100462512 /* testSliderExampleSnapshot_1@2x.png */, - 143BC5831B21E18100462512 /* testSwitchExampleSnapshot_1@2x.png */, - 143BC5841B21E18100462512 /* testTabBarExampleSnapshot_1@2x.png */, - 143BC5851B21E18100462512 /* testTextExampleSnapshot_1@2x.png */, - 143BC5861B21E18100462512 /* testViewExampleSnapshot_1@2x.png */, - ); - path = "Examples-UIExplorer-UIExplorerApp"; - sourceTree = "<group>"; - }; 143BC5961B21E3E100462512 /* UIExplorerIntegrationTests */ = { isa = PBXGroup; children = ( + 3D299BAE1D33EBFA00FA1057 /* RCTLoggingTests.m */, 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */, 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */, 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */, @@ -646,6 +621,7 @@ 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, + 68CD48B71D2BCB2C007E06A9 /* Run Script */, ); buildRules = ( ); @@ -887,6 +863,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3DD981D61D33C6FB007DC7BE /* TestBundle.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -895,7 +872,6 @@ buildActionMask = 2147483647; files = ( 1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */, - 1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */, 1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, @@ -913,6 +889,23 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 68CD48B71D2BCB2C007E06A9 /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n$SRCROOT/../../packager/react-native-xcode.sh Examples/UIExplorer/js/UIExplorerApp.ios.js"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 004D289A1AAF61C70097A701 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -958,6 +951,7 @@ files = ( 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */, 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */, + 3D299BAF1D33EBFA00FA1057 /* RCTLoggingTests.m in Sources */, 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */, 27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */, ); diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 1ca2716f27b630..822ea8aef3217f 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -15,6 +15,7 @@ #import "AppDelegate.h" #import "RCTBridge.h" +#import "RCTBundleURLProvider.h" #import "RCTJavaScriptLoader.h" #import "RCTLinkingManager.h" #import "RCTRootView.h" @@ -34,8 +35,7 @@ - (BOOL)application:(__unused UIApplication *)application didFinishLaunchingWith NSDictionary *initProps = nil; NSString *_routeUri = [[NSUserDefaults standardUserDefaults] stringForKey:@"route"]; if (_routeUri) { - initProps = @{@"exampleFromAppetizeParams": - [NSString stringWithFormat:@"rnuiexplorer://example/%@Example", _routeUri]}; + initProps = @{@"exampleFromAppetizeParams": [NSString stringWithFormat:@"rnuiexplorer://example/%@Example", _routeUri]}; } RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge @@ -52,41 +52,13 @@ - (BOOL)application:(__unused UIApplication *)application didFinishLaunchingWith - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge { - NSURL *sourceURL; - - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ - - sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?platform=ios&dev=true"]; - - /** - * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` - * to your Xcode project folder and run - * - * $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?platform=ios' -o main.jsbundle - * - * then add the `main.jsbundle` file to your project and uncomment this line: - */ - - // sourceURL = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"Examples/UIExplorer/js/UIExplorerApp.ios" fallbackResource:nil]; if (!getenv("CI_USE_PACKAGER")) { - sourceURL = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; } - return sourceURL; + return jsCodeLocation; } diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m index b46d2cae91ffa9..866a14f3af983b 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m @@ -36,28 +36,27 @@ - (void)setUp { NSURL *scriptURL; if (getenv("CI_USE_PACKAGER")) { - NSString *app = @"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp"; + NSString *app = @"IntegrationTests/IntegrationTestsApp"; scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]]; - RCTAssert(scriptURL != nil, @"No scriptURL set"); } else { scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; - RCTAssert(scriptURL != nil, @"Could not locate main.jsBundle"); } + RCTAssert(scriptURL != nil, @"No scriptURL set"); _bridge = [[RCTBridge alloc] initWithBundleURL:scriptURL moduleProvider:NULL launchOptions:nil]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:60]; while (date.timeIntervalSinceNow > 0 && _bridge.loading) { [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } XCTAssertFalse(_bridge.loading); _logSem = dispatch_semaphore_create(0); - RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { + RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, __unused NSString *fileName, __unused NSNumber *lineNumber, NSString *message) { if (source == RCTLogSourceJavaScript) { - _lastLogLevel = level; - _lastLogSource = source; - _lastLogMessage = message; - dispatch_semaphore_signal(_logSem); + self->_lastLogLevel = level; + self->_lastLogSource = source; + self->_lastLogMessage = message; + dispatch_semaphore_signal(self->_logSem); } }); } @@ -96,6 +95,11 @@ - (void)testLogging [_bridge enqueueJSCall:@"LoggingTestModule.logErrorToConsole" args:@[@"Invoking console.error"]]; dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + // For local bundles, we'll first get a warning about symbolication + if ([_bridge.bundleURL isFileURL]) { + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + } + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.error"); @@ -103,6 +107,11 @@ - (void)testLogging [_bridge enqueueJSCall:@"LoggingTestModule.throwError" args:@[@"Throwing an error"]]; dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + // For local bundles, we'll first get a warning about symbolication + if ([_bridge.bundleURL isFileURL]) { + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + } + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); XCTAssertEqualObjects(_lastLogMessage, @"Throwing an error"); diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png deleted file mode 100644 index 3e7d79c7ff04a2..00000000000000 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png and /dev/null differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png deleted file mode 100644 index 0da195f5c3fede..00000000000000 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png and /dev/null differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testLayoutExample_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testLayoutExample_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testSliderExample_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testSliderExample_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testSwitchExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testSwitchExample_1@2x.png new file mode 100644 index 00000000000000..1d75533e9f1a59 Binary files /dev/null and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testSwitchExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testTabBarExample_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testTabBarExample_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testTextExample_1@2x.png similarity index 100% rename from Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png rename to Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testTextExample_1@2x.png diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testViewExample_1@2x.png new file mode 100644 index 00000000000000..6b66a8c436ccd4 Binary files /dev/null and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-js-UIExplorerApp.ios/testViewExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m index b2a242c214a46f..9de51c3ed59ef6 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m @@ -32,7 +32,7 @@ @implementation UIExplorerSnapshotTests - (void)setUp { - _runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios", nil); + _runner = RCTInitRunnerForApp(@"Examples/UIExplorer/js/UIExplorerApp.ios", nil); _runner.recordMode = NO; } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index 9f2570bcd25f85..8a6ae4212b54c3 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -92,6 +92,11 @@ - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block block(); } +- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block +{ + block(); +} + - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete @@ -151,8 +156,9 @@ - (void)setUp [super setUp]; _unregisteredTestModule = [UnregisteredTestModule new]; - _bridge = [[RCTBridge alloc] initWithBundleURL:nil - moduleProvider:^{ return @[self, _unregisteredTestModule]; } + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + _bridge = [[RCTBridge alloc] initWithBundleURL:[bundle URLForResource:@"TestBundle" withExtension:@"js"] + moduleProvider:^{ return @[self, self->_unregisteredTestModule]; } launchOptions:nil]; _bridge.executorClass = [TestExecutor class]; @@ -232,7 +238,7 @@ - (void)testCallNativeMethod dispatch_sync(_methodQueue, ^{ // clear the queue - XCTAssertTrue(_testMethodCalled); + XCTAssertTrue(self->_testMethodCalled); }); } @@ -263,7 +269,7 @@ - (void)testCallUnregisteredModuleMethod [_bridge.batchedBridge handleBuffer:buffer]; dispatch_sync(_unregisteredTestModule.methodQueue, ^{ - XCTAssertTrue(_unregisteredTestModule.testMethodCalled); + XCTAssertTrue(self->_unregisteredTestModule.testMethodCalled); }); } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m index bb044887c5e3f1..3f0956b5367f7f 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBundleURLProviderTests.m @@ -60,7 +60,6 @@ - (void)setUp RCTSwapInstanceMethods([NSBundle class], @selector(URLForResource:withExtension:), @selector(RCT_URLForResource:withExtension:)); - [[RCTBundleURLProvider sharedSettings] setDefaults]; } - (void)tearDown diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTComponentPropsTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTComponentPropsTests.m index 75a32bdac70ba2..248a424556c6c2 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTComponentPropsTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTComponentPropsTests.m @@ -95,7 +95,8 @@ - (void)setUp { [super setUp]; - _bridge = [[RCTBridge alloc] initWithBundleURL:nil + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + _bridge = [[RCTBridge alloc] initWithBundleURL:[bundle URLForResource:@"TestBundle" withExtension:@"js"] moduleProvider:nil launchOptions:nil]; } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m index 8de60a1992cc0c..e403daf547eb2d 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m @@ -33,7 +33,15 @@ @interface RCTImageLoaderTests : XCTestCase @end -@implementation RCTImageLoaderTests +@implementation RCTImageLoaderTests { + NSURL *_bundleURL; +} + +- (void)setUp +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + _bundleURL = [bundle URLForResource:@"TestBundle" withExtension:@"js"]; +} - (void)testImageLoading { @@ -47,7 +55,7 @@ - (void)testImageLoading return nil; }]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:^{ return @[loader]; } launchOptions:nil]; NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://facebook.github.io/react/img/logo_og.png"]]; [bridge.imageLoader loadImageWithURLRequest:urlRequest size:CGSizeMake(100, 100) scale:1.0 clipped:YES resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { @@ -78,7 +86,7 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority return nil; }]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil]; NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://facebook.github.io/react/img/logo_og.png"]]; [bridge.imageLoader loadImageWithURLRequest:urlRequest size:CGSizeMake(100, 100) scale:1.0 clipped:YES resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { @@ -103,7 +111,7 @@ - (void)testImageDecoding return nil; }]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:^{ return @[decoder]; } launchOptions:nil]; RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 clipped:NO resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); @@ -132,7 +140,7 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority return nil; }]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil]; RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 clipped:NO resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m index 2e9220d2d6b442..05d73709713435 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m @@ -21,6 +21,7 @@ #import "RCTUtils.h" #import "RCTUIManager.h" #import "RCTViewManager.h" +#import "RCTJavaScriptExecutor.h" #define RUN_RUNLOOP_WHILE(CONDITION) \ { \ @@ -99,7 +100,8 @@ @implementation RCTModuleInitNotificationRaceTests - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge { - return nil; + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + return [bundle URLForResource:@"TestBundle" withExtension:@"js"]; } - (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m index bf05e72bad620e..3e5a350e349709 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m @@ -19,6 +19,7 @@ #import "RCTBridge+Private.h" #import "RCTBridgeModule.h" #import "RCTUtils.h" +#import "RCTJavaScriptExecutor.h" #define RUN_RUNLOOP_WHILE(CONDITION) \ { \ @@ -147,7 +148,8 @@ @implementation RCTModuleInitTests - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge { - return nil; + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + return [bundle URLForResource:@"TestBundle" withExtension:@"js"]; } - (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge @@ -225,7 +227,7 @@ - (void)testCustomSetBridgeModuleInitializedAtBridgeStartup __block RCTTestCustomSetBridgeModule *module; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - module = [_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]]; + module = [self->_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]]; }); RUN_RUNLOOP_WHILE(!module); @@ -253,7 +255,7 @@ - (void)testLazyInitModuleNotInitializedDuringBridgeInit __block RCTLazyInitModule *module; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - module = [_bridge moduleForClass:[RCTLazyInitModule class]]; + module = [self->_bridge moduleForClass:[RCTLazyInitModule class]]; }); RUN_RUNLOOP_WHILE(!module); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index 7a49a86034e757..0d586cc166f70e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -28,10 +28,10 @@ - (void)setUp { [super setUp]; - self.parentView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex_direction = CSS_FLEX_DIRECTION_COLUMN; - style->dimensions[0] = 440; - style->dimensions[1] = 440; + self.parentView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlexDirection(node, CSSFlexDirectionColumn); + CSSNodeStyleSetWidth(node, 440); + CSSNodeStyleSetHeight(node, 440); }]; self.parentView.reactTag = @1; // must be valid rootView tag } @@ -50,43 +50,43 @@ - (void)setUp // - (void)testApplyingLayoutRecursivelyToShadowView { - RCTShadowView *leftView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex = 1; + RCTShadowView *leftView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 1); }]; - RCTShadowView *centerView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex = 2; - style->margin[0] = 10; - style->margin[2] = 10; + RCTShadowView *centerView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 2); + CSSNodeStyleSetMarginLeft(node, 10); + CSSNodeStyleSetMarginRight(node, 10); }]; - RCTShadowView *rightView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex = 1; + RCTShadowView *rightView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 1); }]; - RCTShadowView *mainView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex_direction = CSS_FLEX_DIRECTION_ROW; - style->flex = 2; - style->margin[1] = 10; - style->margin[3] = 10; + RCTShadowView *mainView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlexDirection(node, CSSFlexDirectionRow); + CSSNodeStyleSetFlex(node, 2); + CSSNodeStyleSetMarginTop(node, 10); + CSSNodeStyleSetMarginBottom(node, 10); }]; [mainView insertReactSubview:leftView atIndex:0]; [mainView insertReactSubview:centerView atIndex:1]; [mainView insertReactSubview:rightView atIndex:2]; - RCTShadowView *headerView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex = 1; + RCTShadowView *headerView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 1); }]; - RCTShadowView *footerView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex = 1; + RCTShadowView *footerView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 1); }]; - self.parentView.cssNode->style.padding[0] = 10; - self.parentView.cssNode->style.padding[1] = 10; - self.parentView.cssNode->style.padding[2] = 10; - self.parentView.cssNode->style.padding[3] = 10; + CSSNodeStyleSetPaddingLeft(self.parentView.cssNode, 10); + CSSNodeStyleSetPaddingTop(self.parentView.cssNode, 10); + CSSNodeStyleSetPaddingRight(self.parentView.cssNode, 10); + CSSNodeStyleSetPaddingBottom(self.parentView.cssNode, 10); [self.parentView insertReactSubview:headerView atIndex:0]; [self.parentView insertReactSubview:mainView atIndex:1]; @@ -108,10 +108,10 @@ - (void)testApplyingLayoutRecursivelyToShadowView - (void)testAssignsSuggestedWidthDimension { - [self _withShadowViewWithStyle:^(css_style_t *style) { - style->position[CSS_LEFT] = 0; - style->position[CSS_TOP] = 0; - style->dimensions[CSS_HEIGHT] = 10; + [self _withShadowViewWithStyle:^(CSSNodeRef node) { + CSSNodeStyleSetPositionLeft(node, 0); + CSSNodeStyleSetPositionTop(node, 0); + CSSNodeStyleSetHeight(node, 10); } assertRelativeLayout:CGRectMake(0, 0, 3, 10) withIntrinsicContentSize:CGSizeMake(3, UIViewNoIntrinsicMetric)]; @@ -119,10 +119,10 @@ - (void)testAssignsSuggestedWidthDimension - (void)testAssignsSuggestedHeightDimension { - [self _withShadowViewWithStyle:^(css_style_t *style) { - style->position[CSS_LEFT] = 0; - style->position[CSS_TOP] = 0; - style->dimensions[CSS_WIDTH] = 10; + [self _withShadowViewWithStyle:^(CSSNodeRef node) { + CSSNodeStyleSetPositionLeft(node, 0); + CSSNodeStyleSetPositionTop(node, 0); + CSSNodeStyleSetWidth(node, 10); } assertRelativeLayout:CGRectMake(0, 0, 10, 4) withIntrinsicContentSize:CGSizeMake(UIViewNoIntrinsicMetric, 4)]; @@ -130,11 +130,11 @@ - (void)testAssignsSuggestedHeightDimension - (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions { - [self _withShadowViewWithStyle:^(css_style_t *style) { - style->position[CSS_LEFT] = 0; - style->position[CSS_TOP] = 0; - style->dimensions[CSS_WIDTH] = 10; - style->dimensions[CSS_HEIGHT] = 10; + [self _withShadowViewWithStyle:^(CSSNodeRef node) { + CSSNodeStyleSetPositionLeft(node, 0); + CSSNodeStyleSetPositionTop(node, 0); + CSSNodeStyleSetWidth(node, 10); + CSSNodeStyleSetHeight(node, 10); } assertRelativeLayout:CGRectMake(0, 0, 10, 10) withIntrinsicContentSize:CGSizeMake(3, 4)]; @@ -142,20 +142,20 @@ - (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions - (void)testDoesNotAssignSuggestedDimensionsWhenStyledWithFlexAttribute { - float parentWidth = self.parentView.cssNode->style.dimensions[CSS_WIDTH]; - float parentHeight = self.parentView.cssNode->style.dimensions[CSS_HEIGHT]; - [self _withShadowViewWithStyle:^(css_style_t *style) { - style->flex = 1; + float parentWidth = CSSNodeStyleGetWidth(self.parentView.cssNode); + float parentHeight = CSSNodeStyleGetHeight(self.parentView.cssNode); + [self _withShadowViewWithStyle:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 1); } assertRelativeLayout:CGRectMake(0, 0, parentWidth, parentHeight) withIntrinsicContentSize:CGSizeMake(3, 4)]; } -- (void)_withShadowViewWithStyle:(void(^)(css_style_t *style))styleBlock +- (void)_withShadowViewWithStyle:(void(^)(CSSNodeRef node))configBlock assertRelativeLayout:(CGRect)expectedRect withIntrinsicContentSize:(CGSize)contentSize { - RCTShadowView *view = [self _shadowViewWithStyle:styleBlock]; + RCTShadowView *view = [self _shadowViewWithConfig:configBlock]; [self.parentView insertReactSubview:view atIndex:0]; view.intrinsicContentSize = contentSize; [self.parentView collectViewsWithUpdatedFrames]; @@ -166,14 +166,10 @@ - (void)_withShadowViewWithStyle:(void(^)(css_style_t *style))styleBlock NSStringFromCGRect(actualRect)); } -- (RCTRootShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock +- (RCTRootShadowView *)_shadowViewWithConfig:(void(^)(CSSNodeRef node))configBlock { RCTRootShadowView *shadowView = [RCTRootShadowView new]; - - css_style_t style = shadowView.cssNode->style; - styleBlock(&style); - shadowView.cssNode->style = style; - + configBlock(shadowView.cssNode); return shadowView; } diff --git a/Tools/FBPortForwarding/Apps/iOSApp/main.m b/Examples/UIExplorer/UIExplorerUnitTests/TestBundle.js similarity index 51% rename from Tools/FBPortForwarding/Apps/iOSApp/main.m rename to Examples/UIExplorer/UIExplorerUnitTests/TestBundle.js index 3b145a74555ad1..5919f0f4f0406e 100644 --- a/Tools/FBPortForwarding/Apps/iOSApp/main.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/TestBundle.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -7,13 +7,3 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import <UIKit/UIKit.h> - -#import "PFAppDelegate.h" - -int main(int argc, char *argv[]) -{ - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([PFAppDelegate class])); - } -} diff --git a/Examples/UIExplorer/android/app/build.gradle b/Examples/UIExplorer/android/app/build.gradle index ea65dc6a8cc136..6a24213d1785b7 100644 --- a/Examples/UIExplorer/android/app/build.gradle +++ b/Examples/UIExplorer/android/app/build.gradle @@ -59,7 +59,7 @@ import com.android.build.OutputFile project.ext.react = [ bundleAssetName: "UIExplorerApp.android.bundle", - entryFile: file("../../UIExplorerApp.android.js"), + entryFile: file("../../js/UIExplorerApp.android.js"), root: "../../../../", inputExcludes: ["android/**", "./**"] ] diff --git a/Examples/UIExplorer/android/app/src/main/java/com/facebook/react/uiapp/UIExplorerApplication.java b/Examples/UIExplorer/android/app/src/main/java/com/facebook/react/uiapp/UIExplorerApplication.java index 87dbd3735d4b3d..897f9489dbdec2 100644 --- a/Examples/UIExplorer/android/app/src/main/java/com/facebook/react/uiapp/UIExplorerApplication.java +++ b/Examples/UIExplorer/android/app/src/main/java/com/facebook/react/uiapp/UIExplorerApplication.java @@ -30,7 +30,7 @@ public class UIExplorerApplication extends Application implements ReactApplicati private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public String getJSMainModuleName() { - return "Examples/UIExplorer/UIExplorerApp.android"; + return "Examples/UIExplorer/js/UIExplorerApp.android"; } @Override diff --git a/Examples/UIExplorer/AccessibilityAndroidExample.android.js b/Examples/UIExplorer/js/AccessibilityAndroidExample.android.js similarity index 95% rename from Examples/UIExplorer/AccessibilityAndroidExample.android.js rename to Examples/UIExplorer/js/AccessibilityAndroidExample.android.js index ff1f215dab70cb..9b3c5789ab14b8 100644 --- a/Examples/UIExplorer/AccessibilityAndroidExample.android.js +++ b/Examples/UIExplorer/js/AccessibilityAndroidExample.android.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AccessibilityIOSExample.js b/Examples/UIExplorer/js/AccessibilityIOSExample.js similarity index 86% rename from Examples/UIExplorer/AccessibilityIOSExample.js rename to Examples/UIExplorer/js/AccessibilityIOSExample.js index ca5628d6e407fb..84b49c4b1bbed7 100644 --- a/Examples/UIExplorer/AccessibilityIOSExample.js +++ b/Examples/UIExplorer/js/AccessibilityIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/js/ActionSheetIOSExample.js similarity index 94% rename from Examples/UIExplorer/ActionSheetIOSExample.js rename to Examples/UIExplorer/js/ActionSheetIOSExample.js index 4be6c776071621..183e9cff90615e 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/js/ActionSheetIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ActivityIndicatorExample.js b/Examples/UIExplorer/js/ActivityIndicatorExample.js similarity index 92% rename from Examples/UIExplorer/ActivityIndicatorExample.js rename to Examples/UIExplorer/js/ActivityIndicatorExample.js index 3ebf8c109b9ca1..4832b1d0eb1958 100644 --- a/Examples/UIExplorer/ActivityIndicatorExample.js +++ b/Examples/UIExplorer/js/ActivityIndicatorExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AdSupportIOSExample.js b/Examples/UIExplorer/js/AdSupportIOSExample.js similarity index 88% rename from Examples/UIExplorer/AdSupportIOSExample.js rename to Examples/UIExplorer/js/AdSupportIOSExample.js index 7a98fb1dcbdb36..5f6c4227fe0a8b 100644 --- a/Examples/UIExplorer/AdSupportIOSExample.js +++ b/Examples/UIExplorer/js/AdSupportIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AlertExample.js b/Examples/UIExplorer/js/AlertExample.js similarity index 92% rename from Examples/UIExplorer/AlertExample.js rename to Examples/UIExplorer/js/AlertExample.js index 85416e0696832b..8ee4813afdd3a5 100644 --- a/Examples/UIExplorer/AlertExample.js +++ b/Examples/UIExplorer/js/AlertExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/js/AlertIOSExample.js similarity index 93% rename from Examples/UIExplorer/AlertIOSExample.js rename to Examples/UIExplorer/js/AlertIOSExample.js index 85d66d4a62cc99..445824368bafd5 100644 --- a/Examples/UIExplorer/AlertIOSExample.js +++ b/Examples/UIExplorer/js/AlertIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AnimatedExample.js b/Examples/UIExplorer/js/AnimatedExample.js similarity index 95% rename from Examples/UIExplorer/AnimatedExample.js rename to Examples/UIExplorer/js/AnimatedExample.js index d11abbf9024442..461fd62ac3291f 100644 --- a/Examples/UIExplorer/AnimatedExample.js +++ b/Examples/UIExplorer/js/AnimatedExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExApp.js similarity index 97% rename from Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js rename to Examples/UIExplorer/js/AnimatedGratuitousApp/AnExApp.js index 20153889ab471e..d8e3addcf314e2 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js +++ b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExApp.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExBobble.js similarity index 94% rename from Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js rename to Examples/UIExplorer/js/AnimatedGratuitousApp/AnExBobble.js index ebf26f0138b368..94d2ce1a3ef541 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js +++ b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExBobble.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExChained.js similarity index 93% rename from Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js rename to Examples/UIExplorer/js/AnimatedGratuitousApp/AnExChained.js index aad9c5839c19a2..2707620d8d6c2f 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js +++ b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExChained.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExScroll.js similarity index 92% rename from Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js rename to Examples/UIExplorer/js/AnimatedGratuitousApp/AnExScroll.js index 40ad63e3e34894..b195b107237702 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js +++ b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExScroll.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExSet.js similarity index 93% rename from Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js rename to Examples/UIExplorer/js/AnimatedGratuitousApp/AnExSet.js index 1df1f084c91730..464cf887a6e6a6 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js +++ b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExSet.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExSlides.md b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExSlides.md similarity index 100% rename from Examples/UIExplorer/AnimatedGratuitousApp/AnExSlides.md rename to Examples/UIExplorer/js/AnimatedGratuitousApp/AnExSlides.md diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExTilt.js similarity index 93% rename from Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js rename to Examples/UIExplorer/js/AnimatedGratuitousApp/AnExTilt.js index 31c23c3bcb5d20..5acee72cf1a7a4 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js +++ b/Examples/UIExplorer/js/AnimatedGratuitousApp/AnExTilt.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AppStateExample.js b/Examples/UIExplorer/js/AppStateExample.js similarity index 100% rename from Examples/UIExplorer/AppStateExample.js rename to Examples/UIExplorer/js/AppStateExample.js diff --git a/Examples/UIExplorer/AssetScaledImageExample.js b/Examples/UIExplorer/js/AssetScaledImageExample.js similarity index 88% rename from Examples/UIExplorer/AssetScaledImageExample.js rename to Examples/UIExplorer/js/AssetScaledImageExample.js index 6b68e2aabb04d0..3d5c12a4d06aff 100644 --- a/Examples/UIExplorer/AssetScaledImageExample.js +++ b/Examples/UIExplorer/js/AssetScaledImageExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/AsyncStorageExample.js b/Examples/UIExplorer/js/AsyncStorageExample.js similarity index 91% rename from Examples/UIExplorer/AsyncStorageExample.js rename to Examples/UIExplorer/js/AsyncStorageExample.js index ac8bd78899abff..8d26f7d32e5246 100644 --- a/Examples/UIExplorer/AsyncStorageExample.js +++ b/Examples/UIExplorer/js/AsyncStorageExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/js/BorderExample.js similarity index 95% rename from Examples/UIExplorer/BorderExample.js rename to Examples/UIExplorer/js/BorderExample.js index 231b64f2c9e084..f68a81fc061805 100644 --- a/Examples/UIExplorer/BorderExample.js +++ b/Examples/UIExplorer/js/BorderExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/BoxShadowExample.js b/Examples/UIExplorer/js/BoxShadowExample.js similarity index 88% rename from Examples/UIExplorer/BoxShadowExample.js rename to Examples/UIExplorer/js/BoxShadowExample.js index 81ed1359b5c37d..90e40af2544069 100644 --- a/Examples/UIExplorer/BoxShadowExample.js +++ b/Examples/UIExplorer/js/BoxShadowExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/CameraRollExample.js b/Examples/UIExplorer/js/CameraRollExample.js similarity index 92% rename from Examples/UIExplorer/CameraRollExample.js rename to Examples/UIExplorer/js/CameraRollExample.js index 0dcad8adba5e10..1a665321c6c154 100644 --- a/Examples/UIExplorer/CameraRollExample.js +++ b/Examples/UIExplorer/js/CameraRollExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/CameraRollView.js b/Examples/UIExplorer/js/CameraRollView.js similarity index 94% rename from Examples/UIExplorer/CameraRollView.js rename to Examples/UIExplorer/js/CameraRollView.js index f5f428f95a01f1..8bb9fbd2b07765 100644 --- a/Examples/UIExplorer/CameraRollView.js +++ b/Examples/UIExplorer/js/CameraRollView.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -191,7 +198,7 @@ var CameraRollView = React.createClass({ _renderFooterSpinner: function() { if (!this.state.noMore) { - return <ActivityIndicator style={styles.spinner} />; + return <ActivityIndicator />; } return null; }, diff --git a/Examples/UIExplorer/ClipboardExample.js b/Examples/UIExplorer/js/ClipboardExample.js similarity index 84% rename from Examples/UIExplorer/ClipboardExample.js rename to Examples/UIExplorer/js/ClipboardExample.js index 59373f9fd198d7..0b7399b20ededf 100644 --- a/Examples/UIExplorer/ClipboardExample.js +++ b/Examples/UIExplorer/js/ClipboardExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/DatePickerAndroidExample.js b/Examples/UIExplorer/js/DatePickerAndroidExample.js similarity index 93% rename from Examples/UIExplorer/DatePickerAndroidExample.js rename to Examples/UIExplorer/js/DatePickerAndroidExample.js index 42740cf90229c3..c6abe38a03c324 100644 --- a/Examples/UIExplorer/DatePickerAndroidExample.js +++ b/Examples/UIExplorer/js/DatePickerAndroidExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/DatePickerIOSExample.js b/Examples/UIExplorer/js/DatePickerIOSExample.js similarity index 93% rename from Examples/UIExplorer/DatePickerIOSExample.js rename to Examples/UIExplorer/js/DatePickerIOSExample.js index a3dd7876c7e6f0..8d00dd87776b0b 100644 --- a/Examples/UIExplorer/DatePickerIOSExample.js +++ b/Examples/UIExplorer/js/DatePickerIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ExampleTypes.js b/Examples/UIExplorer/js/ExampleTypes.js similarity index 75% rename from Examples/UIExplorer/ExampleTypes.js rename to Examples/UIExplorer/js/ExampleTypes.js index 691f4c6cf22f72..3863bb5bb6e977 100644 --- a/Examples/UIExplorer/ExampleTypes.js +++ b/Examples/UIExplorer/js/ExampleTypes.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/GeolocationExample.js b/Examples/UIExplorer/js/GeolocationExample.js similarity index 88% rename from Examples/UIExplorer/GeolocationExample.js rename to Examples/UIExplorer/js/GeolocationExample.js index 230127d665adcb..ea4517159b0964 100644 --- a/Examples/UIExplorer/GeolocationExample.js +++ b/Examples/UIExplorer/js/GeolocationExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ImageCapInsetsExample.js b/Examples/UIExplorer/js/ImageCapInsetsExample.js similarity index 87% rename from Examples/UIExplorer/ImageCapInsetsExample.js rename to Examples/UIExplorer/js/ImageCapInsetsExample.js index 48a9a986298f05..fe80a86124cf33 100644 --- a/Examples/UIExplorer/ImageCapInsetsExample.js +++ b/Examples/UIExplorer/js/ImageCapInsetsExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ImageEditingExample.js b/Examples/UIExplorer/js/ImageEditingExample.js similarity index 96% rename from Examples/UIExplorer/ImageEditingExample.js rename to Examples/UIExplorer/js/ImageEditingExample.js index 20e710f9215c9b..32cda68294235f 100644 --- a/Examples/UIExplorer/ImageEditingExample.js +++ b/Examples/UIExplorer/js/ImageEditingExample.js @@ -1,5 +1,11 @@ - -/** +/* + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/js/ImageExample.js similarity index 98% rename from Examples/UIExplorer/ImageExample.js rename to Examples/UIExplorer/js/ImageExample.js index 1e6c6de19b442e..df54d10f9eaf47 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/js/ImageExample.js @@ -165,7 +165,7 @@ var MultipleSourcesExample = React.createClass({ }, render: function() { return ( - <View style={styles.container}> + <View> <View style={{flexDirection: 'row', justifyContent: 'space-between'}}> <Text style={styles.touchableText} @@ -180,7 +180,7 @@ var MultipleSourcesExample = React.createClass({ </View> <Text>Container image size: {this.state.width}x{this.state.height} </Text> <View - style={[styles.imageContainer, {height: this.state.height, width: this.state.width}]} > + style={{height: this.state.height, width: this.state.width}} > <Image style={{flex: 1}} source={[ @@ -589,6 +589,20 @@ exports.examples = [ }, platform: 'android', }, + { + title: 'Legacy local image', + description: + 'Images shipped with the native bundle, but not managed ' + + 'by the JS packager', + render: function() { + return ( + <Image + source={require('image!hawk')} + style={styles.base} + /> + ); + }, + }, ]; var fullImage = {uri: 'http://facebook.github.io/react/img/logo_og.png'}; diff --git a/Examples/UIExplorer/KeyboardAvoidingViewExample.js b/Examples/UIExplorer/js/KeyboardAvoidingViewExample.js similarity index 100% rename from Examples/UIExplorer/KeyboardAvoidingViewExample.js rename to Examples/UIExplorer/js/KeyboardAvoidingViewExample.js diff --git a/Examples/UIExplorer/LayoutAnimationExample.js b/Examples/UIExplorer/js/LayoutAnimationExample.js similarity index 92% rename from Examples/UIExplorer/LayoutAnimationExample.js rename to Examples/UIExplorer/js/LayoutAnimationExample.js index ee2038dbfc9d69..10efb402b58df3 100644 --- a/Examples/UIExplorer/LayoutAnimationExample.js +++ b/Examples/UIExplorer/js/LayoutAnimationExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/LayoutEventsExample.js b/Examples/UIExplorer/js/LayoutEventsExample.js similarity index 93% rename from Examples/UIExplorer/LayoutEventsExample.js rename to Examples/UIExplorer/js/LayoutEventsExample.js index 8325194847c2cc..097b76cc88733b 100644 --- a/Examples/UIExplorer/LayoutEventsExample.js +++ b/Examples/UIExplorer/js/LayoutEventsExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/LayoutExample.js b/Examples/UIExplorer/js/LayoutExample.js similarity index 95% rename from Examples/UIExplorer/LayoutExample.js rename to Examples/UIExplorer/js/LayoutExample.js index 6d1095ae527bce..3b65fcbadce3da 100644 --- a/Examples/UIExplorer/LayoutExample.js +++ b/Examples/UIExplorer/js/LayoutExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/LinkingExample.js b/Examples/UIExplorer/js/LinkingExample.js similarity index 88% rename from Examples/UIExplorer/LinkingExample.js rename to Examples/UIExplorer/js/LinkingExample.js index 390d00314623ba..594209fe112f3b 100644 --- a/Examples/UIExplorer/LinkingExample.js +++ b/Examples/UIExplorer/js/LinkingExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ListViewExample.js b/Examples/UIExplorer/js/ListViewExample.js similarity index 91% rename from Examples/UIExplorer/ListViewExample.js rename to Examples/UIExplorer/js/ListViewExample.js index 42e30de38fb6cb..6c0e0d88c03334 100644 --- a/Examples/UIExplorer/ListViewExample.js +++ b/Examples/UIExplorer/js/ListViewExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -58,7 +65,7 @@ var ListViewSimpleExample = React.createClass({ dataSource={this.state.dataSource} renderRow={this._renderRow} renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />} - renderSeparator={this._renderSeperator} + renderSeparator={this._renderSeparator} /> </UIExplorerPage> ); @@ -100,7 +107,7 @@ var ListViewSimpleExample = React.createClass({ )}); }, - _renderSeperator: function(sectionID: number, rowID: number, adjacentRowHighlighted: bool) { + _renderSeparator: function(sectionID: number, rowID: number, adjacentRowHighlighted: bool) { return ( <View key={`${sectionID}-${rowID}`} diff --git a/Examples/UIExplorer/ListViewGridLayoutExample.js b/Examples/UIExplorer/js/ListViewGridLayoutExample.js similarity index 93% rename from Examples/UIExplorer/ListViewGridLayoutExample.js rename to Examples/UIExplorer/js/ListViewGridLayoutExample.js index eadba937157dc7..4ef2a110d9a95c 100644 --- a/Examples/UIExplorer/ListViewGridLayoutExample.js +++ b/Examples/UIExplorer/js/ListViewGridLayoutExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/js/ListViewPagingExample.js similarity index 96% rename from Examples/UIExplorer/ListViewPagingExample.js rename to Examples/UIExplorer/js/ListViewPagingExample.js index 4d31db208c1bbd..89c6c4b4b80a08 100644 --- a/Examples/UIExplorer/ListViewPagingExample.js +++ b/Examples/UIExplorer/js/ListViewPagingExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/js/MapViewExample.js similarity index 97% rename from Examples/UIExplorer/MapViewExample.js rename to Examples/UIExplorer/js/MapViewExample.js index d393702280a7ec..9851f0d0fca3be 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/js/MapViewExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ModalExample.js b/Examples/UIExplorer/js/ModalExample.js similarity index 94% rename from Examples/UIExplorer/ModalExample.js rename to Examples/UIExplorer/js/ModalExample.js index 1717fb0e098192..deb838960c14e2 100644 --- a/Examples/UIExplorer/ModalExample.js +++ b/Examples/UIExplorer/js/ModalExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/NativeAnimationsExample.js b/Examples/UIExplorer/js/NativeAnimationsExample.js similarity index 100% rename from Examples/UIExplorer/NativeAnimationsExample.js rename to Examples/UIExplorer/js/NativeAnimationsExample.js diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCardStack-NavigationHeader-Tabs-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-NavigationHeader-Tabs-example.js similarity index 100% rename from Examples/UIExplorer/NavigationExperimental/NavigationCardStack-NavigationHeader-Tabs-example.js rename to Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-NavigationHeader-Tabs-example.js diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCardStack-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-example.js similarity index 100% rename from Examples/UIExplorer/NavigationExperimental/NavigationCardStack-example.js rename to Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-example.js diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationExampleRow.js similarity index 84% rename from Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js rename to Examples/UIExplorer/js/NavigationExperimental/NavigationExampleRow.js index 94a874a9a0bf8b..9f6b27a184a954 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js +++ b/Examples/UIExplorer/js/NavigationExperimental/NavigationExampleRow.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationExperimentalExample.js similarity index 100% rename from Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js rename to Examples/UIExplorer/js/NavigationExperimental/NavigationExperimentalExample.js diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js similarity index 99% rename from Examples/UIExplorer/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js rename to Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js index 31cafcb91bbcad..db1b088f5e43cd 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js +++ b/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js @@ -222,7 +222,7 @@ class ExampleScene extends Component { const width = layout.initWidth; const translateX = position.interpolate({ inputRange, - outputRange: [width, 0, -10], + outputRange: ([width, 0, -10]: Array<number>), }); return { diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTransitioner-AnimatedView-pager-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-pager-example.js similarity index 100% rename from Examples/UIExplorer/NavigationExperimental/NavigationTransitioner-AnimatedView-pager-example.js rename to Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-pager-example.js diff --git a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js b/Examples/UIExplorer/js/Navigator/BreadcrumbNavSample.js similarity index 92% rename from Examples/UIExplorer/Navigator/BreadcrumbNavSample.js rename to Examples/UIExplorer/js/Navigator/BreadcrumbNavSample.js index 368944125b83f2..183ff03df368c9 100644 --- a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js +++ b/Examples/UIExplorer/js/Navigator/BreadcrumbNavSample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/Navigator/JumpingNavSample.js b/Examples/UIExplorer/js/Navigator/JumpingNavSample.js similarity index 94% rename from Examples/UIExplorer/Navigator/JumpingNavSample.js rename to Examples/UIExplorer/js/Navigator/JumpingNavSample.js index 4f63f2d477ea77..ef3e09da79c06d 100644 --- a/Examples/UIExplorer/Navigator/JumpingNavSample.js +++ b/Examples/UIExplorer/js/Navigator/JumpingNavSample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/Navigator/NavigationBarSample.js b/Examples/UIExplorer/js/Navigator/NavigationBarSample.js similarity index 94% rename from Examples/UIExplorer/Navigator/NavigationBarSample.js rename to Examples/UIExplorer/js/Navigator/NavigationBarSample.js index a0b4461aa75664..b11629de082c32 100644 --- a/Examples/UIExplorer/Navigator/NavigationBarSample.js +++ b/Examples/UIExplorer/js/Navigator/NavigationBarSample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/Navigator/NavigatorExample.js b/Examples/UIExplorer/js/Navigator/NavigatorExample.js similarity index 94% rename from Examples/UIExplorer/Navigator/NavigatorExample.js rename to Examples/UIExplorer/js/Navigator/NavigatorExample.js index 1da8434b7595f3..45158f6427a78d 100644 --- a/Examples/UIExplorer/Navigator/NavigatorExample.js +++ b/Examples/UIExplorer/js/Navigator/NavigatorExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/js/NavigatorIOSColorsExample.js similarity index 87% rename from Examples/UIExplorer/NavigatorIOSColorsExample.js rename to Examples/UIExplorer/js/NavigatorIOSColorsExample.js index 2f527c1c639695..033d56f5db527c 100644 --- a/Examples/UIExplorer/NavigatorIOSColorsExample.js +++ b/Examples/UIExplorer/js/NavigatorIOSColorsExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/js/NavigatorIOSExample.js similarity index 99% rename from Examples/UIExplorer/NavigatorIOSExample.js rename to Examples/UIExplorer/js/NavigatorIOSExample.js index e45196cf0d91ff..4d8f1f52ac746c 100644 --- a/Examples/UIExplorer/NavigatorIOSExample.js +++ b/Examples/UIExplorer/js/NavigatorIOSExample.js @@ -217,7 +217,6 @@ const NavigatorIOSExample = React.createClass({ component: NavigatorIOSExamplePage, passProps: {onExampleExit}, }} - itemWrapperStyle={styles.itemWrapper} tintColor="#008888" /> ); diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/js/NetInfoExample.js similarity index 93% rename from Examples/UIExplorer/NetInfoExample.js rename to Examples/UIExplorer/js/NetInfoExample.js index e64886ea52e8a8..540a84639e4e72 100644 --- a/Examples/UIExplorer/NetInfoExample.js +++ b/Examples/UIExplorer/js/NetInfoExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/PanResponderExample.js b/Examples/UIExplorer/js/PanResponderExample.js similarity index 100% rename from Examples/UIExplorer/PanResponderExample.js rename to Examples/UIExplorer/js/PanResponderExample.js diff --git a/Examples/UIExplorer/PermissionsExampleAndroid.android.js b/Examples/UIExplorer/js/PermissionsExampleAndroid.android.js similarity index 100% rename from Examples/UIExplorer/PermissionsExampleAndroid.android.js rename to Examples/UIExplorer/js/PermissionsExampleAndroid.android.js diff --git a/Examples/UIExplorer/PickerExample.js b/Examples/UIExplorer/js/PickerExample.js similarity index 93% rename from Examples/UIExplorer/PickerExample.js rename to Examples/UIExplorer/js/PickerExample.js index df29e3178da3aa..c45014d2c0dc52 100644 --- a/Examples/UIExplorer/PickerExample.js +++ b/Examples/UIExplorer/js/PickerExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/PickerIOSExample.js b/Examples/UIExplorer/js/PickerIOSExample.js similarity index 93% rename from Examples/UIExplorer/PickerIOSExample.js rename to Examples/UIExplorer/js/PickerIOSExample.js index 5530135386a68a..5b8e1988fc60d3 100644 --- a/Examples/UIExplorer/PickerIOSExample.js +++ b/Examples/UIExplorer/js/PickerIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/PointerEventsExample.js b/Examples/UIExplorer/js/PointerEventsExample.js similarity index 95% rename from Examples/UIExplorer/PointerEventsExample.js rename to Examples/UIExplorer/js/PointerEventsExample.js index 9e40ed2fac950e..5b15ebb2714a2d 100644 --- a/Examples/UIExplorer/PointerEventsExample.js +++ b/Examples/UIExplorer/js/PointerEventsExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ProgressBarAndroidExample.android.js b/Examples/UIExplorer/js/ProgressBarAndroidExample.android.js similarity index 88% rename from Examples/UIExplorer/ProgressBarAndroidExample.android.js rename to Examples/UIExplorer/js/ProgressBarAndroidExample.android.js index 2e08ab73801072..81be4637cc85a8 100644 --- a/Examples/UIExplorer/ProgressBarAndroidExample.android.js +++ b/Examples/UIExplorer/js/ProgressBarAndroidExample.android.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ProgressViewIOSExample.js b/Examples/UIExplorer/js/ProgressViewIOSExample.js similarity index 88% rename from Examples/UIExplorer/ProgressViewIOSExample.js rename to Examples/UIExplorer/js/ProgressViewIOSExample.js index 2cc816015487e7..9967d0ff741cfe 100644 --- a/Examples/UIExplorer/ProgressViewIOSExample.js +++ b/Examples/UIExplorer/js/ProgressViewIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/PushNotificationIOSExample.js b/Examples/UIExplorer/js/PushNotificationIOSExample.js similarity index 93% rename from Examples/UIExplorer/PushNotificationIOSExample.js rename to Examples/UIExplorer/js/PushNotificationIOSExample.js index ee137d83a1a7c2..064a44580661cd 100644 --- a/Examples/UIExplorer/PushNotificationIOSExample.js +++ b/Examples/UIExplorer/js/PushNotificationIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/RCTRootViewIOSExample.js b/Examples/UIExplorer/js/RCTRootViewIOSExample.js similarity index 90% rename from Examples/UIExplorer/RCTRootViewIOSExample.js rename to Examples/UIExplorer/js/RCTRootViewIOSExample.js index 349b022d9a4fff..1228f2f299bfcb 100644 --- a/Examples/UIExplorer/RCTRootViewIOSExample.js +++ b/Examples/UIExplorer/js/RCTRootViewIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/RefreshControlExample.js b/Examples/UIExplorer/js/RefreshControlExample.js similarity index 91% rename from Examples/UIExplorer/RefreshControlExample.js rename to Examples/UIExplorer/js/RefreshControlExample.js index e3fb6e184d82b6..9d85274cb2028c 100644 --- a/Examples/UIExplorer/RefreshControlExample.js +++ b/Examples/UIExplorer/js/RefreshControlExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js b/Examples/UIExplorer/js/RootViewSizeFlexibilityExampleApp.js similarity index 86% rename from Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js rename to Examples/UIExplorer/js/RootViewSizeFlexibilityExampleApp.js index 285404a3854c4e..4fe03451b3333b 100644 --- a/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js +++ b/Examples/UIExplorer/js/RootViewSizeFlexibilityExampleApp.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ScrollViewExample.js b/Examples/UIExplorer/js/ScrollViewExample.js similarity index 94% rename from Examples/UIExplorer/ScrollViewExample.js rename to Examples/UIExplorer/js/ScrollViewExample.js index 358c8f66a4e722..de6ac3f7b0a00b 100644 --- a/Examples/UIExplorer/ScrollViewExample.js +++ b/Examples/UIExplorer/js/ScrollViewExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ScrollViewSimpleExample.js b/Examples/UIExplorer/js/ScrollViewSimpleExample.js similarity index 87% rename from Examples/UIExplorer/ScrollViewSimpleExample.js rename to Examples/UIExplorer/js/ScrollViewSimpleExample.js index a2bd4462171220..60efdad42397e4 100644 --- a/Examples/UIExplorer/ScrollViewSimpleExample.js +++ b/Examples/UIExplorer/js/ScrollViewSimpleExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/SegmentedControlIOSExample.js b/Examples/UIExplorer/js/SegmentedControlIOSExample.js similarity index 93% rename from Examples/UIExplorer/SegmentedControlIOSExample.js rename to Examples/UIExplorer/js/SegmentedControlIOSExample.js index bdac8e9e451d35..4a23037a29c857 100644 --- a/Examples/UIExplorer/SegmentedControlIOSExample.js +++ b/Examples/UIExplorer/js/SegmentedControlIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/SetPropertiesExampleApp.js b/Examples/UIExplorer/js/SetPropertiesExampleApp.js similarity index 79% rename from Examples/UIExplorer/SetPropertiesExampleApp.js rename to Examples/UIExplorer/js/SetPropertiesExampleApp.js index 6d0be80347d33f..db17baaa9276a5 100644 --- a/Examples/UIExplorer/SetPropertiesExampleApp.js +++ b/Examples/UIExplorer/js/SetPropertiesExampleApp.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/js/ShareExample.js b/Examples/UIExplorer/js/ShareExample.js new file mode 100644 index 00000000000000..4a2f2b84d3e03e --- /dev/null +++ b/Examples/UIExplorer/js/ShareExample.js @@ -0,0 +1,124 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + View, + Text, + TouchableHighlight, + Share, +} = ReactNative; + +exports.framework = 'React'; +exports.title = 'Share'; +exports.description = 'Share data with other Apps.'; +exports.examples = [{ + title: 'Share Text Content', + render() { + return <ShareMessageExample />; + } +}]; + +class ShareMessageExample extends React.Component { + _shareMessage: Function; + _shareText: Function; + _showResult: Function; + state: any; + + constructor(props) { + super(props); + + this._shareMessage = this._shareMessage.bind(this); + this._shareText = this._shareText.bind(this); + this._showResult = this._showResult.bind(this); + + this.state = { + result: '' + }; + } + + render() { + return ( + <View> + <TouchableHighlight style={styles.wrapper} + onPress={this._shareMessage}> + <View style={styles.button}> + <Text>Click to share message</Text> + </View> + </TouchableHighlight> + <TouchableHighlight style={styles.wrapper} + onPress={this._shareText}> + <View style={styles.button}> + <Text>Click to share message, URL and title</Text> + </View> + </TouchableHighlight> + <Text>{this.state.result}</Text> + </View> + ); + } + + _shareMessage() { + Share.share({ + message: 'React Native | A framework for building native apps using React' + }) + .then(this._showResult) + .catch((error) => this.setState({result: 'error: ' + error.message})); + } + + _shareText() { + Share.share({ + message: 'A framework for building native apps using React', + url: 'http://facebook.github.io/react-native/', + title: 'React Native' + }, { + dialogTitle: 'Share React Native website', + excludedActivityTypes: [ + 'com.apple.UIKit.activity.PostToTwitter' + ], + tintColor: 'green' + }) + .then(this._showResult) + .catch((error) => this.setState({result: 'error: ' + error.message})); + } + + _showResult(result) { + if (result.action === Share.sharedAction) { + if (result.activityType) { + this.setState({result: 'shared with an activityType: ' + result.activityType}); + } else { + this.setState({result: 'shared'}); + } + } else if (result.action === Share.dismissedAction) { + this.setState({result: 'dismissed'}); + } + } + +} + + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +}); diff --git a/Examples/UIExplorer/SliderExample.js b/Examples/UIExplorer/js/SliderExample.js similarity index 92% rename from Examples/UIExplorer/SliderExample.js rename to Examples/UIExplorer/js/SliderExample.js index a987671b50f53c..ff7b31de9ab10a 100644 --- a/Examples/UIExplorer/SliderExample.js +++ b/Examples/UIExplorer/js/SliderExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/SliderIOSExample.js b/Examples/UIExplorer/js/SliderIOSExample.js similarity index 90% rename from Examples/UIExplorer/SliderIOSExample.js rename to Examples/UIExplorer/js/SliderIOSExample.js index eaabfeaa86d27d..8cd69a825a71c0 100644 --- a/Examples/UIExplorer/SliderIOSExample.js +++ b/Examples/UIExplorer/js/SliderIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/SnapshotExample.js b/Examples/UIExplorer/js/SnapshotExample.js similarity index 85% rename from Examples/UIExplorer/SnapshotExample.js rename to Examples/UIExplorer/js/SnapshotExample.js index bb9ddb7ad8d1ee..1a3e5c181cd92f 100644 --- a/Examples/UIExplorer/SnapshotExample.js +++ b/Examples/UIExplorer/js/SnapshotExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/StatusBarExample.js b/Examples/UIExplorer/js/StatusBarExample.js similarity index 97% rename from Examples/UIExplorer/StatusBarExample.js rename to Examples/UIExplorer/js/StatusBarExample.js index 7026a8f29aee53..5a213a32c4ccbd 100644 --- a/Examples/UIExplorer/StatusBarExample.js +++ b/Examples/UIExplorer/js/StatusBarExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/SwitchExample.js b/Examples/UIExplorer/js/SwitchExample.js similarity index 93% rename from Examples/UIExplorer/SwitchExample.js rename to Examples/UIExplorer/js/SwitchExample.js index dc4d1e391da789..0fc64562688bdf 100644 --- a/Examples/UIExplorer/SwitchExample.js +++ b/Examples/UIExplorer/js/SwitchExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/TabBarIOSExample.js b/Examples/UIExplorer/js/TabBarIOSExample.js similarity index 93% rename from Examples/UIExplorer/TabBarIOSExample.js rename to Examples/UIExplorer/js/TabBarIOSExample.js index 435f2de21edcab..30a28c6b348537 100644 --- a/Examples/UIExplorer/TabBarIOSExample.js +++ b/Examples/UIExplorer/js/TabBarIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/TextExample.android.js b/Examples/UIExplorer/js/TextExample.android.js similarity index 97% rename from Examples/UIExplorer/TextExample.android.js rename to Examples/UIExplorer/js/TextExample.android.js index 230bd79bb170e9..8360c937567e62 100644 --- a/Examples/UIExplorer/TextExample.android.js +++ b/Examples/UIExplorer/js/TextExample.android.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -393,14 +400,14 @@ var TextExample = React.createClass({ Demo text shadow </Text> </UIExplorerBlock> - <UIExplorerBlock title="Line break mode"> + <UIExplorerBlock title="Ellipsize mode"> <Text numberOfLines={1}> This very long text should be truncated with dots in the end. </Text> - <Text lineBreakMode="middle" numberOfLines={1}> + <Text ellipsizeMode="middle" numberOfLines={1}> This very long text should be truncated with dots in the middle. </Text> - <Text lineBreakMode="head" numberOfLines={1}> + <Text ellipsizeMode="head" numberOfLines={1}> This very long text should be truncated with dots in the beginning. </Text> </UIExplorerBlock> diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/js/TextExample.ios.js similarity index 98% rename from Examples/UIExplorer/TextExample.ios.js rename to Examples/UIExplorer/js/TextExample.ios.js index 3262aeed004a2a..3c5802157d9fa5 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/js/TextExample.ios.js @@ -445,20 +445,20 @@ exports.examples = [ ); }, }, { - title: 'Line break mode', + title: 'Ellipsize mode', render: function() { return ( <View> <Text numberOfLines={1}> This very long text should be truncated with dots in the end. </Text> - <Text lineBreakMode="middle" numberOfLines={1}> + <Text ellipsizeMode="middle" numberOfLines={1}> This very long text should be truncated with dots in the middle. </Text> - <Text lineBreakMode="head" numberOfLines={1}> + <Text ellipsizeMode="head" numberOfLines={1}> This very long text should be truncated with dots in the beginning. </Text> - <Text lineBreakMode="clip" numberOfLines={1}> + <Text ellipsizeMode="clip" numberOfLines={1}> This very looooooooooooooooooooooooooooong text should be clipped. </Text> </View> diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/js/TextInputExample.android.js similarity index 94% rename from Examples/UIExplorer/TextInputExample.android.js rename to Examples/UIExplorer/js/TextInputExample.android.js index cc86ca45186df0..83731531de64b7 100644 --- a/Examples/UIExplorer/TextInputExample.android.js +++ b/Examples/UIExplorer/js/TextInputExample.android.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -76,18 +83,21 @@ var TextEventsExample = React.createClass({ class AutoExpandingTextInput extends React.Component { constructor(props) { super(props); - this.state = {text: '', height: 0}; + this.state = { + text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about ā learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.', + height: 0, + }; } render() { return ( <TextInput {...this.props} multiline={true} - onChange={(event) => { - this.setState({ - text: event.nativeEvent.text, - height: event.nativeEvent.contentSize.height, - }); + onContentSizeChange={(event) => { + this.setState({height: event.nativeEvent.contentSize.height}); + }} + onChangeText={(text) => { + this.setState({text}); }} style={[styles.default, {height: Math.max(35, this.state.height)}]} value={this.state.text} diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/js/TextInputExample.ios.js similarity index 93% rename from Examples/UIExplorer/TextInputExample.ios.js rename to Examples/UIExplorer/js/TextInputExample.ios.js index a3bae1d4629641..a65d55393a1e80 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/js/TextInputExample.ios.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -102,18 +109,21 @@ class AutoExpandingTextInput extends React.Component { constructor(props) { super(props); - this.state = {text: '', height: 0}; + this.state = { + text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about ā learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.', + height: 0, + }; } render() { return ( <TextInput {...this.props} multiline={true} - onChange={(event) => { - this.setState({ - text: event.nativeEvent.text, - height: event.nativeEvent.contentSize.height, - }); + onChangeText={(text) => { + this.setState({text}); + }} + onContentSizeChange={(event) => { + this.setState({height: event.nativeEvent.contentSize.height}); }} style={[styles.default, {height: Math.max(35, this.state.height)}]} value={this.state.text} @@ -669,6 +679,12 @@ exports.examples = [ keyboardType="url" style={[styles.multiline, styles.multilineWithFontStyles]} /> + <TextInput + placeholder="multiline text input with max length" + maxLength={5} + multiline={true} + style={styles.multiline} + /> <TextInput placeholder="uneditable multiline text input" editable={false} diff --git a/Examples/UIExplorer/Thumbnails/bandaged.png b/Examples/UIExplorer/js/Thumbnails/bandaged.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/bandaged.png rename to Examples/UIExplorer/js/Thumbnails/bandaged.png diff --git a/Examples/UIExplorer/Thumbnails/call.png b/Examples/UIExplorer/js/Thumbnails/call.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/call.png rename to Examples/UIExplorer/js/Thumbnails/call.png diff --git a/Examples/UIExplorer/Thumbnails/dislike.png b/Examples/UIExplorer/js/Thumbnails/dislike.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/dislike.png rename to Examples/UIExplorer/js/Thumbnails/dislike.png diff --git a/Examples/UIExplorer/Thumbnails/fist.png b/Examples/UIExplorer/js/Thumbnails/fist.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/fist.png rename to Examples/UIExplorer/js/Thumbnails/fist.png diff --git a/Examples/UIExplorer/Thumbnails/flowers.png b/Examples/UIExplorer/js/Thumbnails/flowers.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/flowers.png rename to Examples/UIExplorer/js/Thumbnails/flowers.png diff --git a/Examples/UIExplorer/Thumbnails/heart.png b/Examples/UIExplorer/js/Thumbnails/heart.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/heart.png rename to Examples/UIExplorer/js/Thumbnails/heart.png diff --git a/Examples/UIExplorer/Thumbnails/like.png b/Examples/UIExplorer/js/Thumbnails/like.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/like.png rename to Examples/UIExplorer/js/Thumbnails/like.png diff --git a/Examples/UIExplorer/Thumbnails/liking.png b/Examples/UIExplorer/js/Thumbnails/liking.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/liking.png rename to Examples/UIExplorer/js/Thumbnails/liking.png diff --git a/Examples/UIExplorer/Thumbnails/party.png b/Examples/UIExplorer/js/Thumbnails/party.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/party.png rename to Examples/UIExplorer/js/Thumbnails/party.png diff --git a/Examples/UIExplorer/Thumbnails/poke.png b/Examples/UIExplorer/js/Thumbnails/poke.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/poke.png rename to Examples/UIExplorer/js/Thumbnails/poke.png diff --git a/Examples/UIExplorer/Thumbnails/superlike.png b/Examples/UIExplorer/js/Thumbnails/superlike.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/superlike.png rename to Examples/UIExplorer/js/Thumbnails/superlike.png diff --git a/Examples/UIExplorer/Thumbnails/victory.png b/Examples/UIExplorer/js/Thumbnails/victory.png similarity index 100% rename from Examples/UIExplorer/Thumbnails/victory.png rename to Examples/UIExplorer/js/Thumbnails/victory.png diff --git a/Examples/UIExplorer/TimePickerAndroidExample.js b/Examples/UIExplorer/js/TimePickerAndroidExample.js similarity index 92% rename from Examples/UIExplorer/TimePickerAndroidExample.js rename to Examples/UIExplorer/js/TimePickerAndroidExample.js index 60ef949ddf4d75..db3d54acf558ce 100644 --- a/Examples/UIExplorer/TimePickerAndroidExample.js +++ b/Examples/UIExplorer/js/TimePickerAndroidExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/TimerExample.js b/Examples/UIExplorer/js/TimerExample.js similarity index 71% rename from Examples/UIExplorer/TimerExample.js rename to Examples/UIExplorer/js/TimerExample.js index 8fdc92081f045d..97d4b9a32d5fc9 100644 --- a/Examples/UIExplorer/TimerExample.js +++ b/Examples/UIExplorer/js/TimerExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -21,10 +28,87 @@ var { AlertIOS, Platform, ToastAndroid, + Text, View, } = ReactNative; var TimerMixin = require('react-timer-mixin'); var UIExplorerButton = require('./UIExplorerButton'); +var performanceNow = require('fbjs/lib/performanceNow'); + +function burnCPU(milliseconds) { + const start = performanceNow(); + while (performanceNow() < (start + milliseconds)) {} +} + +var RequestIdleCallbackTester = React.createClass({ + _idleTimer: (null: any), + _iters: 0, + + getInitialState() { + return { + message: '-', + }; + }, + + componentWillUnmount() { + cancelIdleCallback(this._idleTimer); + }, + + render() { + return ( + <View> + <UIExplorerButton onPress={this._run.bind(this, false)}> + Run requestIdleCallback + </UIExplorerButton> + + <UIExplorerButton onPress={this._run.bind(this, true)}> + Burn CPU inside of requestIdleCallback + </UIExplorerButton> + + <UIExplorerButton onPress={this._runBackground}> + Run background task + </UIExplorerButton> + + <UIExplorerButton onPress={this._stopBackground}> + Stop background task + </UIExplorerButton> + + <Text>{this.state.message}</Text> + </View> + ); + }, + + _run(shouldBurnCPU) { + cancelIdleCallback(this._idleTimer); + this._idleTimer = requestIdleCallback((deadline) => { + let message = ''; + + if (shouldBurnCPU) { + burnCPU(10); + message = 'Burned CPU for 10ms,'; + } + this.setState({message: `${message} ${deadline.timeRemaining()}ms remaining in frame`}); + }); + }, + + _runBackground() { + cancelIdleCallback(this._idleTimer); + const handler = (deadline) => { + while (deadline.timeRemaining() > 5) { + burnCPU(5); + this.setState({message: `Burned CPU for 5ms ${this._iters++} times, ${deadline.timeRemaining()}ms remaining in frame`}); + } + + this._idleTimer = requestIdleCallback(handler); + }; + this._idleTimer = requestIdleCallback(handler); + }, + + _stopBackground() { + this._iters = 0; + cancelIdleCallback(this._idleTimer); + } +}); var TimerTester = React.createClass({ mixins: [TimerMixin], @@ -134,6 +218,17 @@ exports.examples = [ ); }, }, + { + title: 'this.requestIdleCallback(fn)', + description: 'Execute function fn on the next JS frame that has idle time', + render: function() { + return ( + <View> + <RequestIdleCallbackTester /> + </View> + ); + }, + }, { title: 'this.setImmediate(fn)', description: 'Execute function fn at the end of the current JS event loop.', diff --git a/Examples/UIExplorer/ToastAndroidExample.android.js b/Examples/UIExplorer/js/ToastAndroidExample.android.js similarity index 53% rename from Examples/UIExplorer/ToastAndroidExample.android.js rename to Examples/UIExplorer/js/ToastAndroidExample.android.js index 9bb8aaa0b7aac1..86866b1c346d68 100644 --- a/Examples/UIExplorer/ToastAndroidExample.android.js +++ b/Examples/UIExplorer/js/ToastAndroidExample.android.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -53,7 +60,43 @@ var ToastExample = React.createClass({ <TouchableWithoutFeedback onPress={() => ToastAndroid.show('This is a toast with long duration', ToastAndroid.LONG)}> - <Text style={styles.text}>Click me too.</Text> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Toast with top gravity"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.showWithGravity( + 'This is a toast with top gravity', + ToastAndroid.SHORT, + ToastAndroid.TOP, + ) + }> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Toast with center gravity"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.showWithGravity( + 'This is a toast with center gravity', + ToastAndroid.SHORT, + ToastAndroid.CENTER, + ) + }> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Toast with bottom gravity"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.showWithGravity( + 'This is a toast with bottom gravity', + ToastAndroid.SHORT, + ToastAndroid.BOTTOM, + ) + }> + <Text style={styles.text}>Click me.</Text> </TouchableWithoutFeedback> </UIExplorerBlock> </UIExplorerPage> diff --git a/Examples/UIExplorer/ToolbarAndroidExample.android.js b/Examples/UIExplorer/js/ToolbarAndroidExample.android.js similarity index 93% rename from Examples/UIExplorer/ToolbarAndroidExample.android.js rename to Examples/UIExplorer/js/ToolbarAndroidExample.android.js index 95decb766816dc..b0fb8efbc529c4 100644 --- a/Examples/UIExplorer/ToolbarAndroidExample.android.js +++ b/Examples/UIExplorer/js/ToolbarAndroidExample.android.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/js/TouchableExample.js similarity index 97% rename from Examples/UIExplorer/TouchableExample.js rename to Examples/UIExplorer/js/TouchableExample.js index 12f417b2d4e2fd..69e3819c19bd3e 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/js/TouchableExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/TransformExample.js b/Examples/UIExplorer/js/TransformExample.js similarity index 95% rename from Examples/UIExplorer/TransformExample.js rename to Examples/UIExplorer/js/TransformExample.js index c85cac0fd0ec44..a13e9c34575453 100644 --- a/Examples/UIExplorer/TransformExample.js +++ b/Examples/UIExplorer/js/TransformExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/TransparentHitTestExample.js b/Examples/UIExplorer/js/TransparentHitTestExample.js similarity index 100% rename from Examples/UIExplorer/TransparentHitTestExample.js rename to Examples/UIExplorer/js/TransparentHitTestExample.js diff --git a/Examples/UIExplorer/UIExplorerActions.js b/Examples/UIExplorer/js/UIExplorerActions.js similarity index 100% rename from Examples/UIExplorer/UIExplorerActions.js rename to Examples/UIExplorer/js/UIExplorerActions.js diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/js/UIExplorerApp.android.js similarity index 100% rename from Examples/UIExplorer/UIExplorerApp.android.js rename to Examples/UIExplorer/js/UIExplorerApp.android.js diff --git a/Examples/UIExplorer/UIExplorerApp.ios.js b/Examples/UIExplorer/js/UIExplorerApp.ios.js similarity index 100% rename from Examples/UIExplorer/UIExplorerApp.ios.js rename to Examples/UIExplorer/js/UIExplorerApp.ios.js diff --git a/Examples/UIExplorer/UIExplorerBlock.js b/Examples/UIExplorer/js/UIExplorerBlock.js similarity index 88% rename from Examples/UIExplorer/UIExplorerBlock.js rename to Examples/UIExplorer/js/UIExplorerBlock.js index 9a3c7a5498c709..160fcbc312b998 100644 --- a/Examples/UIExplorer/UIExplorerBlock.js +++ b/Examples/UIExplorer/js/UIExplorerBlock.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/UIExplorerButton.js b/Examples/UIExplorer/js/UIExplorerButton.js similarity index 82% rename from Examples/UIExplorer/UIExplorerButton.js rename to Examples/UIExplorer/js/UIExplorerButton.js index ba6c213244f5bd..51d5e1a66474d2 100644 --- a/Examples/UIExplorer/UIExplorerButton.js +++ b/Examples/UIExplorer/js/UIExplorerButton.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/UIExplorerExampleList.js b/Examples/UIExplorer/js/UIExplorerExampleList.js similarity index 96% rename from Examples/UIExplorer/UIExplorerExampleList.js rename to Examples/UIExplorer/js/UIExplorerExampleList.js index ea06c05f10c6eb..b8207ee9f1f491 100644 --- a/Examples/UIExplorer/UIExplorerExampleList.js +++ b/Examples/UIExplorer/js/UIExplorerExampleList.js @@ -43,7 +43,10 @@ const ds = new ListView.DataSource({ }); class UIExplorerExampleList extends React.Component { - constuctor(props: { + + state = {filter: ''}; + + constructor(props: { disableTitleRow: ?boolean, onNavigate: Function, filter: ?string, @@ -53,7 +56,7 @@ class UIExplorerExampleList extends React.Component { }, style: ?any, }) { - + super(props); } static makeRenderable(example: any): ReactClass<any> { @@ -63,7 +66,7 @@ class UIExplorerExampleList extends React.Component { } render(): ?ReactElement<any> { - const filterText = this.props.filter || ''; + const filterText = this.state.filter || ''; const filterRegex = new RegExp(String(filterText), 'i'); const filter = (example) => filterRegex.test(example.module.title); @@ -116,12 +119,12 @@ class UIExplorerExampleList extends React.Component { autoCorrect={false} clearButtonMode="always" onChangeText={text => { - this.props.onNavigate(UIExplorerActions.ExampleListWithFilter(text)); + this.setState({filter: text}); }} placeholder="Search..." style={[styles.searchTextInput, this.props.searchTextInputStyle]} testID="explorer_search" - value={this.props.filter} + value={this.state.filter} /> </View> ); diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/js/UIExplorerList.android.js similarity index 96% rename from Examples/UIExplorer/UIExplorerList.android.js rename to Examples/UIExplorer/js/UIExplorerList.android.js index c1410d023644d7..dcd33a82c33f6c 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/js/UIExplorerList.android.js @@ -50,6 +50,10 @@ var ComponentExamples: Array<UIExplorerExample> = [ key: 'ModalExample', module: require('./ModalExample'), }, + { + key: 'NativeAnimationsExample', + module: require('./NativeAnimationsExample'), + }, { key: 'PickerExample', module: require('./PickerExample'), @@ -177,6 +181,10 @@ const APIExamples = [ key: 'PointerEventsExample', module: require('./PointerEventsExample'), }, + { + key: 'ShareExample', + module: require('./ShareExample'), + }, { key: 'TimePickerAndroidExample', module: require('./TimePickerAndroidExample'), diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/js/UIExplorerList.ios.js similarity index 98% rename from Examples/UIExplorer/UIExplorerList.ios.js rename to Examples/UIExplorer/js/UIExplorerList.ios.js index 81f26bae62a8da..8875f58e7718e0 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/js/UIExplorerList.ios.js @@ -247,6 +247,10 @@ const APIExamples: Array<UIExplorerExample> = [ key: 'RCTRootViewIOSExample', module: require('./RCTRootViewIOSExample'), }, + { + key: 'ShareExample', + module: require('./ShareExample'), + }, { key: 'SnapshotExample', module: require('./SnapshotExample'), diff --git a/Examples/UIExplorer/UIExplorerNavigationReducer.js b/Examples/UIExplorer/js/UIExplorerNavigationReducer.js similarity index 100% rename from Examples/UIExplorer/UIExplorerNavigationReducer.js rename to Examples/UIExplorer/js/UIExplorerNavigationReducer.js diff --git a/Examples/UIExplorer/UIExplorerPage.js b/Examples/UIExplorer/js/UIExplorerPage.js similarity index 87% rename from Examples/UIExplorer/UIExplorerPage.js rename to Examples/UIExplorer/js/UIExplorerPage.js index 3aaf4119cf98ef..3991fef1169eab 100644 --- a/Examples/UIExplorer/UIExplorerPage.js +++ b/Examples/UIExplorer/js/UIExplorerPage.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/UIExplorerStateTitleMap.js b/Examples/UIExplorer/js/UIExplorerStateTitleMap.js similarity index 100% rename from Examples/UIExplorer/UIExplorerStateTitleMap.js rename to Examples/UIExplorer/js/UIExplorerStateTitleMap.js diff --git a/Examples/UIExplorer/UIExplorerTitle.js b/Examples/UIExplorer/js/UIExplorerTitle.js similarity index 81% rename from Examples/UIExplorer/UIExplorerTitle.js rename to Examples/UIExplorer/js/UIExplorerTitle.js index 81a74dc279812c..33feaf705a2238 100644 --- a/Examples/UIExplorer/UIExplorerTitle.js +++ b/Examples/UIExplorer/js/UIExplorerTitle.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/URIActionMap.js b/Examples/UIExplorer/js/URIActionMap.js similarity index 85% rename from Examples/UIExplorer/URIActionMap.js rename to Examples/UIExplorer/js/URIActionMap.js index 2940e0736001b8..63051ea7573e35 100644 --- a/Examples/UIExplorer/URIActionMap.js +++ b/Examples/UIExplorer/js/URIActionMap.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/VibrationExample.js b/Examples/UIExplorer/js/VibrationExample.js similarity index 89% rename from Examples/UIExplorer/VibrationExample.js rename to Examples/UIExplorer/js/VibrationExample.js index e4948e54cb5811..554ae2f1f5514d 100644 --- a/Examples/UIExplorer/VibrationExample.js +++ b/Examples/UIExplorer/js/VibrationExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/VibrationIOSExample.js b/Examples/UIExplorer/js/VibrationIOSExample.js similarity index 82% rename from Examples/UIExplorer/VibrationIOSExample.js rename to Examples/UIExplorer/js/VibrationIOSExample.js index cce819f994ef52..59d1b3e352016e 100644 --- a/Examples/UIExplorer/VibrationIOSExample.js +++ b/Examples/UIExplorer/js/VibrationIOSExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/ViewExample.js b/Examples/UIExplorer/js/ViewExample.js similarity index 100% rename from Examples/UIExplorer/ViewExample.js rename to Examples/UIExplorer/js/ViewExample.js diff --git a/Examples/UIExplorer/ViewPagerAndroidExample.android.js b/Examples/UIExplorer/js/ViewPagerAndroidExample.android.js similarity index 100% rename from Examples/UIExplorer/ViewPagerAndroidExample.android.js rename to Examples/UIExplorer/js/ViewPagerAndroidExample.android.js diff --git a/Examples/UIExplorer/WebSocketExample.js b/Examples/UIExplorer/js/WebSocketExample.js similarity index 94% rename from Examples/UIExplorer/WebSocketExample.js rename to Examples/UIExplorer/js/WebSocketExample.js index c43079162a31ee..d3400fdbcd1150 100644 --- a/Examples/UIExplorer/WebSocketExample.js +++ b/Examples/UIExplorer/js/WebSocketExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -81,7 +88,7 @@ function showValue(value) { if (typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined' && value instanceof ArrayBuffer) { - return `ArrayBuffer {${Array.from(new Uint8Array(value))}}`; + return `ArrayBuffer {${String(Array.from(new Uint8Array(value)))}}`; } return value; } diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/js/WebViewExample.js similarity index 95% rename from Examples/UIExplorer/WebViewExample.js rename to Examples/UIExplorer/js/WebViewExample.js index 9e58ae67c5fa11..6f1066701d46c0 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/js/WebViewExample.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -171,8 +178,8 @@ var Button = React.createClass({ render: function() { return ( <TouchableWithoutFeedback onPress={this._handlePress}> - <View style={[styles.button, this.props.enabled ? {} : styles.buttonDisabled]}> - <Text style={styles.buttonText}>{this.props.text}</Text> + <View style={styles.button}> + <Text>{this.props.text}</Text> </View> </TouchableWithoutFeedback> ); diff --git a/Examples/UIExplorer/XHRExample.android.js b/Examples/UIExplorer/js/XHRExample.android.js similarity index 66% rename from Examples/UIExplorer/XHRExample.android.js rename to Examples/UIExplorer/js/XHRExample.android.js index 991f88d4528671..9cc6a7564e4730 100644 --- a/Examples/UIExplorer/XHRExample.android.js +++ b/Examples/UIExplorer/js/XHRExample.android.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -18,14 +25,15 @@ var React = require('react'); var ReactNative = require('react-native'); var { + CameraRoll, + Image, ProgressBarAndroid, StyleSheet, + Switch, Text, TextInput, TouchableHighlight, View, - Image, - CameraRoll } = ReactNative; var XHRExampleHeaders = require('./XHRExampleHeaders'); @@ -33,6 +41,13 @@ var XHRExampleCookies = require('./XHRExampleCookies'); var XHRExampleFetch = require('./XHRExampleFetch'); var XHRExampleOnTimeOut = require('./XHRExampleOnTimeOut'); +/** + * Convert number of bytes to MB and round to the nearest 0.1 MB. + */ +function roundKilo(value: number): number { + return Math.round(value / 1000); +} + // TODO t7093728 This is a simplified XHRExample.ios.js. // Once we have Camera roll, Toast, Intent (for opening URLs) // we should make this consistent with iOS. @@ -47,8 +62,18 @@ class Downloader extends React.Component { this.cancelled = false; this.state = { status: '', - contentSize: 1, - downloaded: 0, + downloading: false, + + // set by onreadystatechange + contentLength: 1, + responseLength: 0, + // set by onprogress + progressTotal: 1, + progressLoaded: 0, + + readystateHandler: false, + progressHandler: true, + arraybuffer: false, }; } @@ -56,44 +81,66 @@ class Downloader extends React.Component { this.xhr && this.xhr.abort(); var xhr = this.xhr || new XMLHttpRequest(); - xhr.onreadystatechange = () => { + const onreadystatechange = () => { if (xhr.readyState === xhr.HEADERS_RECEIVED) { - var contentSize = parseInt(xhr.getResponseHeader('Content-Length'), 10); + const contentLength = parseInt(xhr.getResponseHeader('Content-Length'), 10); this.setState({ - contentSize: contentSize, - downloaded: 0, + contentLength, + responseLength: 0, }); - } else if (xhr.readyState === xhr.LOADING) { + } else if (xhr.readyState === xhr.LOADING && xhr.response) { this.setState({ - downloaded: xhr.responseText.length, + responseLength: xhr.response.length, }); - } else if (xhr.readyState === xhr.DONE) { - if (this.cancelled) { - this.cancelled = false; - return; - } - if (xhr.status === 200) { - this.setState({ - status: 'Download complete!', - }); - } else if (xhr.status !== 0) { - this.setState({ - status: 'Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText, - }); - } else { - this.setState({ - status: 'Error: ' + xhr.responseText, - }); + } + }; + const onprogress = (event) => { + this.setState({ + progressTotal: event.total, + progressLoaded: event.loaded, + }); + }; + + if (this.state.readystateHandler) { + xhr.onreadystatechange = onreadystatechange; + } + if (this.state.progressHandler) { + xhr.onprogress = onprogress; + } + if (this.state.arraybuffer) { + xhr.responseType = 'arraybuffer'; + } + xhr.onload = () => { + this.setState({downloading: false}); + if (this.cancelled) { + this.cancelled = false; + return; + } + if (xhr.status === 200) { + let responseType = `Response is a string, ${xhr.response.length} characters long.`; + if (typeof ArrayBuffer !== 'undefined' && + xhr.response instanceof ArrayBuffer) { + responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`; } + this.setState({status: `Download complete! ${responseType}`}); + } else if (xhr.status !== 0) { + this.setState({ + status: 'Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText + }); + } else { + this.setState({status: 'Error: ' + xhr.responseText}); } }; - xhr.open('GET', 'http://www.gutenberg.org/cache/epub/100/pg100.txt'); + xhr.open('GET', 'http://aleph.gutenberg.org/cache/epub/100/pg100.txt.utf8'); // Avoid gzip so we can actually show progress xhr.setRequestHeader('Accept-Encoding', ''); xhr.send(); this.xhr = xhr; - this.setState({status: 'Downloading...'}); + this.setState({ + downloading: true, + status: 'Downloading...', + }); } componentWillUnmount() { @@ -102,7 +149,7 @@ class Downloader extends React.Component { } render() { - var button = this.state.status === 'Downloading...' ? ( + var button = this.state.downloading ? ( <View style={styles.wrapper}> <View style={styles.button}> <Text>...</Text> @@ -118,11 +165,67 @@ class Downloader extends React.Component { </TouchableHighlight> ); + let readystate = null; + let progress = null; + if (this.state.readystateHandler && !this.state.arraybuffer) { + const { responseLength, contentLength } = this.state; + readystate = ( + <View> + <Text style={styles.progressBarLabel}> + responseText:{' '} + {roundKilo(responseLength)}/{roundKilo(contentLength)}k chars + </Text> + <ProgressBarAndroid + progress={(responseLength / contentLength)} + styleAttr="Horizontal" + indeterminate={false} + /> + </View> + ); + } + if (this.state.progressHandler) { + const { progressLoaded, progressTotal } = this.state; + progress = ( + <View> + <Text style={styles.progressBarLabel}> + onprogress:{' '} + {roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB + </Text> + <ProgressBarAndroid + progress={(progressLoaded / progressTotal)} + styleAttr="Horizontal" + indeterminate={false} + /> + </View> + ); + } + return ( <View> + <View style={styles.configRow}> + <Text>onreadystatechange handler</Text> + <Switch + value={this.state.readystateHandler} + onValueChange={(readystateHandler => this.setState({readystateHandler}))} + /> + </View> + <View style={styles.configRow}> + <Text>onprogress handler</Text> + <Switch + value={this.state.progressHandler} + onValueChange={(progressHandler => this.setState({progressHandler}))} + /> + </View> + <View style={styles.configRow}> + <Text>download as arraybuffer</Text> + <Switch + value={this.state.arraybuffer} + onValueChange={(arraybuffer => this.setState({arraybuffer}))} + /> + </View> {button} - <ProgressBarAndroid progress={(this.state.downloaded / this.state.contentSize)} - styleAttr="Horizontal" indeterminate={false} /> + {readystate} + {progress} <Text>{this.state.status}</Text> </View> ); @@ -357,6 +460,16 @@ var styles = StyleSheet.create({ backgroundColor: '#eeeeee', padding: 8, }, + progressBarLabel: { + marginTop: 12, + marginBottom: 8, + }, + configRow: { + flexDirection: 'row', + paddingVertical: 8, + alignItems: 'center', + justifyContent: 'space-between', + }, paramRow: { flexDirection: 'row', paddingVertical: 8, diff --git a/Examples/UIExplorer/XHRExample.ios.js b/Examples/UIExplorer/js/XHRExample.ios.js similarity index 69% rename from Examples/UIExplorer/XHRExample.ios.js rename to Examples/UIExplorer/js/XHRExample.ios.js index 646c3f05bdab9c..053bad559171fd 100644 --- a/Examples/UIExplorer/XHRExample.ios.js +++ b/Examples/UIExplorer/js/XHRExample.ios.js @@ -31,6 +31,7 @@ var { Linking, ProgressViewIOS, StyleSheet, + Switch, Text, TextInput, TouchableHighlight, @@ -41,6 +42,13 @@ var XHRExampleHeaders = require('./XHRExampleHeaders'); var XHRExampleFetch = require('./XHRExampleFetch'); var XHRExampleOnTimeOut = require('./XHRExampleOnTimeOut'); +/** + * Convert number of bytes to MB and round to the nearest 0.1 MB. + */ +function roundKilo(value: number): number { + return Math.round(value / 1000); +} + class Downloader extends React.Component { state: any; @@ -52,8 +60,16 @@ class Downloader extends React.Component { this.cancelled = false; this.state = { downloading: false, - contentSize: 1, - downloaded: 0, + // set by onreadystatechange + contentLength: 1, + responseLength: 0, + // set by onprogress + progressTotal: 1, + progressLoaded: 0, + + readystateHandler: false, + progressHandler: true, + arraybuffer: false, }; } @@ -61,41 +77,62 @@ class Downloader extends React.Component { this.xhr && this.xhr.abort(); var xhr = this.xhr || new XMLHttpRequest(); - xhr.onreadystatechange = () => { + const onreadystatechange = () => { if (xhr.readyState === xhr.HEADERS_RECEIVED) { - var contentSize = parseInt(xhr.getResponseHeader('Content-Length'), 10); + const contentLength = parseInt(xhr.getResponseHeader('Content-Length'), 10); this.setState({ - contentSize: contentSize, - downloaded: 0, + contentLength, + responseLength: 0, }); } else if (xhr.readyState === xhr.LOADING) { this.setState({ - downloaded: xhr.responseText.length, + responseLength: xhr.responseText.length, }); - } else if (xhr.readyState === xhr.DONE) { - this.setState({ - downloading: false, - }); - if (this.cancelled) { - this.cancelled = false; - return; - } - if (xhr.status === 200) { - alert('Download complete!'); - } else if (xhr.status !== 0) { - alert('Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText); - } else { - alert('Error: ' + xhr.responseText); + } + }; + const onprogress = (event) => { + this.setState({ + progressTotal: event.total, + progressLoaded: event.loaded, + }); + }; + + if (this.state.readystateHandler) { + xhr.onreadystatechange = onreadystatechange; + } + if (this.state.progressHandler) { + xhr.onprogress = onprogress; + } + if (this.state.arraybuffer) { + xhr.responseType = 'arraybuffer'; + } + xhr.onload = () => { + this.setState({downloading: false}); + if (this.cancelled) { + this.cancelled = false; + return; + } + if (xhr.status === 200) { + let responseType = `Response is a string, ${xhr.response.length} characters long.`; + if (typeof ArrayBuffer !== 'undefined' && + xhr.response instanceof ArrayBuffer) { + responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`; } + alert(`Download complete! ${responseType}`); + } else if (xhr.status !== 0) { + alert('Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText); + } else { + alert('Error: ' + xhr.responseText); } }; - xhr.open('GET', 'http://www.gutenberg.org/cache/epub/100/pg100.txt'); + xhr.open('GET', 'http://aleph.gutenberg.org/cache/epub/100/pg100.txt.utf8'); xhr.send(); this.xhr = xhr; this.setState({downloading: true}); } + componentWillUnmount() { this.cancelled = true; this.xhr && this.xhr.abort(); @@ -113,15 +150,68 @@ class Downloader extends React.Component { style={styles.wrapper} onPress={this.download.bind(this)}> <View style={styles.button}> - <Text>Download 5MB Text File</Text> + <Text>Download 5MB Text File</Text> </View> </TouchableHighlight> ); + let readystate = null; + let progress = null; + if (this.state.readystateHandler && !this.state.arraybuffer) { + const { responseLength, contentLength } = this.state; + readystate = ( + <View> + <Text style={styles.progressBarLabel}> + responseText:{' '} + {roundKilo(responseLength)}/{roundKilo(contentLength)}k chars + </Text> + <ProgressViewIOS + progress={(responseLength / contentLength)} + /> + </View> + ); + } + if (this.state.progressHandler) { + const { progressLoaded, progressTotal } = this.state; + progress = ( + <View> + <Text style={styles.progressBarLabel}> + onprogress:{' '} + {roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB + </Text> + <ProgressViewIOS + progress={(progressLoaded / progressTotal)} + /> + </View> + ); + } + return ( <View> + <View style={styles.configRow}> + <Text>onreadystatechange handler</Text> + <Switch + value={this.state.readystateHandler} + onValueChange={(readystateHandler => this.setState({readystateHandler}))} + /> + </View> + <View style={styles.configRow}> + <Text>onprogress handler</Text> + <Switch + value={this.state.progressHandler} + onValueChange={(progressHandler => this.setState({progressHandler}))} + /> + </View> + <View style={styles.configRow}> + <Text>download as arraybuffer</Text> + <Switch + value={this.state.arraybuffer} + onValueChange={(arraybuffer => this.setState({arraybuffer}))} + /> + </View> {button} - <ProgressViewIOS progress={(this.state.downloaded / this.state.contentSize)}/> + {readystate} + {progress} </View> ); } @@ -232,7 +322,6 @@ class FormUploader extends React.Component { (param) => formdata.append(param.name, param.value) ); xhr.upload.onprogress = (event) => { - console.log('upload onprogress', event); if (event.lengthComputable) { this.setState({uploadProgress: event.loaded / event.total}); } @@ -290,7 +379,7 @@ class FormUploader extends React.Component { } return ( <View> - <View style={[styles.paramRow, styles.photoRow]}> + <View style={styles.paramRow}> <Text style={styles.photoLabel}> Random photo from your library (<Text style={styles.textButton} onPress={this._fetchRandomPhoto}> @@ -354,6 +443,16 @@ var styles = StyleSheet.create({ backgroundColor: '#eeeeee', padding: 8, }, + progressBarLabel: { + marginTop: 12, + marginBottom: 8, + }, + configRow: { + flexDirection: 'row', + paddingVertical: 8, + alignItems: 'center', + justifyContent: 'space-between', + }, paramRow: { flexDirection: 'row', paddingVertical: 8, diff --git a/Examples/UIExplorer/XHRExampleCookies.js b/Examples/UIExplorer/js/XHRExampleCookies.js similarity index 91% rename from Examples/UIExplorer/XHRExampleCookies.js rename to Examples/UIExplorer/js/XHRExampleCookies.js index d07c7af03d7bb9..d80295c7fa4023 100644 --- a/Examples/UIExplorer/XHRExampleCookies.js +++ b/Examples/UIExplorer/js/XHRExampleCookies.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * @@ -69,7 +76,7 @@ class XHRExampleCookies extends React.Component { clearCookies() { RCTNetworking.clearCookies((cleared) => { - this.setStatus('Cookies cleared, had cookies=' + cleared); + this.setStatus('Cookies cleared, had cookies=' + cleared.toString()); this.refreshWebview(); }); } diff --git a/Examples/UIExplorer/XHRExampleFetch.js b/Examples/UIExplorer/js/XHRExampleFetch.js similarity index 89% rename from Examples/UIExplorer/XHRExampleFetch.js rename to Examples/UIExplorer/js/XHRExampleFetch.js index c2d7c0076e214b..06a90dac398db2 100644 --- a/Examples/UIExplorer/XHRExampleFetch.js +++ b/Examples/UIExplorer/js/XHRExampleFetch.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/XHRExampleHeaders.js b/Examples/UIExplorer/js/XHRExampleHeaders.js similarity index 90% rename from Examples/UIExplorer/XHRExampleHeaders.js rename to Examples/UIExplorer/js/XHRExampleHeaders.js index ed75ad0ad2cf3c..20ecbd9eaf9f42 100644 --- a/Examples/UIExplorer/XHRExampleHeaders.js +++ b/Examples/UIExplorer/js/XHRExampleHeaders.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/XHRExampleOnTimeOut.js b/Examples/UIExplorer/js/XHRExampleOnTimeOut.js similarity index 89% rename from Examples/UIExplorer/XHRExampleOnTimeOut.js rename to Examples/UIExplorer/js/XHRExampleOnTimeOut.js index 4fbd2aa8205c7d..1c06430918097b 100644 --- a/Examples/UIExplorer/XHRExampleOnTimeOut.js +++ b/Examples/UIExplorer/js/XHRExampleOnTimeOut.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/bunny.png b/Examples/UIExplorer/js/bunny.png similarity index 100% rename from Examples/UIExplorer/bunny.png rename to Examples/UIExplorer/js/bunny.png diff --git a/Examples/UIExplorer/createExamplePage.js b/Examples/UIExplorer/js/createExamplePage.js similarity index 90% rename from Examples/UIExplorer/createExamplePage.js rename to Examples/UIExplorer/js/createExamplePage.js index 72a9bc685f99f1..2a037dbe04d63f 100644 --- a/Examples/UIExplorer/createExamplePage.js +++ b/Examples/UIExplorer/js/createExamplePage.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Examples/UIExplorer/flux@3x.png b/Examples/UIExplorer/js/flux@3x.png similarity index 100% rename from Examples/UIExplorer/flux@3x.png rename to Examples/UIExplorer/js/flux@3x.png diff --git a/Examples/UIExplorer/hawk.png b/Examples/UIExplorer/js/hawk.png similarity index 100% rename from Examples/UIExplorer/hawk.png rename to Examples/UIExplorer/js/hawk.png diff --git a/Examples/UIExplorer/helloworld.html b/Examples/UIExplorer/js/helloworld.html similarity index 100% rename from Examples/UIExplorer/helloworld.html rename to Examples/UIExplorer/js/helloworld.html diff --git a/Examples/UIExplorer/relay@3x.png b/Examples/UIExplorer/js/relay@3x.png similarity index 100% rename from Examples/UIExplorer/relay@3x.png rename to Examples/UIExplorer/js/relay@3x.png diff --git a/Examples/UIExplorer/slider-left.png b/Examples/UIExplorer/js/slider-left.png similarity index 100% rename from Examples/UIExplorer/slider-left.png rename to Examples/UIExplorer/js/slider-left.png diff --git a/Examples/UIExplorer/slider-left@2x.png b/Examples/UIExplorer/js/slider-left@2x.png similarity index 100% rename from Examples/UIExplorer/slider-left@2x.png rename to Examples/UIExplorer/js/slider-left@2x.png diff --git a/Examples/UIExplorer/slider-right.png b/Examples/UIExplorer/js/slider-right.png similarity index 100% rename from Examples/UIExplorer/slider-right.png rename to Examples/UIExplorer/js/slider-right.png diff --git a/Examples/UIExplorer/slider-right@2x.png b/Examples/UIExplorer/js/slider-right@2x.png similarity index 100% rename from Examples/UIExplorer/slider-right@2x.png rename to Examples/UIExplorer/js/slider-right@2x.png diff --git a/Examples/UIExplorer/slider.png b/Examples/UIExplorer/js/slider.png similarity index 100% rename from Examples/UIExplorer/slider.png rename to Examples/UIExplorer/js/slider.png diff --git a/Examples/UIExplorer/slider@2x.png b/Examples/UIExplorer/js/slider@2x.png similarity index 100% rename from Examples/UIExplorer/slider@2x.png rename to Examples/UIExplorer/js/slider@2x.png diff --git a/Examples/UIExplorer/uie_comment_highlighted@2x.png b/Examples/UIExplorer/js/uie_comment_highlighted@2x.png similarity index 100% rename from Examples/UIExplorer/uie_comment_highlighted@2x.png rename to Examples/UIExplorer/js/uie_comment_highlighted@2x.png diff --git a/Examples/UIExplorer/uie_comment_normal@2x.png b/Examples/UIExplorer/js/uie_comment_normal@2x.png similarity index 100% rename from Examples/UIExplorer/uie_comment_normal@2x.png rename to Examples/UIExplorer/js/uie_comment_normal@2x.png diff --git a/Examples/UIExplorer/uie_thumb_big.png b/Examples/UIExplorer/js/uie_thumb_big.png similarity index 100% rename from Examples/UIExplorer/uie_thumb_big.png rename to Examples/UIExplorer/js/uie_thumb_big.png diff --git a/Examples/UIExplorer/uie_thumb_normal@2x.png b/Examples/UIExplorer/js/uie_thumb_normal@2x.png similarity index 100% rename from Examples/UIExplorer/uie_thumb_normal@2x.png rename to Examples/UIExplorer/js/uie_thumb_normal@2x.png diff --git a/Examples/UIExplorer/uie_thumb_selected@2x.png b/Examples/UIExplorer/js/uie_thumb_selected@2x.png similarity index 100% rename from Examples/UIExplorer/uie_thumb_selected@2x.png rename to Examples/UIExplorer/js/uie_thumb_selected@2x.png diff --git a/Examples/UIExplorer/websocket_test_server.js b/Examples/UIExplorer/js/websocket_test_server.js similarity index 81% rename from Examples/UIExplorer/websocket_test_server.js rename to Examples/UIExplorer/js/websocket_test_server.js index 24240127843d56..42f44b126a77a4 100644 --- a/Examples/UIExplorer/websocket_test_server.js +++ b/Examples/UIExplorer/js/websocket_test_server.js @@ -1,4 +1,11 @@ /** + * Copyright (c) 2013-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. + * * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * diff --git a/Libraries/Animated/src/__tests__/AnimatedNative-test.js b/Libraries/Animated/src/__tests__/AnimatedNative-test.js deleted file mode 100644 index 34175ef835ef6f..00000000000000 --- a/Libraries/Animated/src/__tests__/AnimatedNative-test.js +++ /dev/null @@ -1,362 +0,0 @@ -/** - * 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. - */ -'use strict'; - -jest - .disableAutomock() - .setMock('Text', {}) - .setMock('View', {}) - .setMock('Image', {}) - .setMock('React', {Component: class {}}) - .setMock('NativeModules', { - NativeAnimatedModule: {}, - }); - -var Animated = require('Animated'); - -describe('Animated', () => { - - beforeEach(() => { - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - nativeAnimatedModule.createAnimatedNode = jest.fn(); - nativeAnimatedModule.connectAnimatedNodes = jest.fn(); - nativeAnimatedModule.disconnectAnimatedNodes = jest.fn(); - nativeAnimatedModule.startAnimatingNode = jest.fn(); - nativeAnimatedModule.stopAnimation = jest.fn(); - nativeAnimatedModule.setAnimatedNodeValue = jest.fn(); - nativeAnimatedModule.connectAnimatedNodeToView = jest.fn(); - nativeAnimatedModule.disconnectAnimatedNodeFromView = jest.fn(); - nativeAnimatedModule.dropAnimatedNode = jest.fn(); - - // jest environment doesn't have cancelAnimationFrame :( - if (!global.cancelAnimationFrame) { - global.cancelAnimationFrame = jest.fn(); - } - }); - - it('creates and detaches nodes', () => { - var anim = new Animated.Value(0); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - }; - c.componentWillMount(); - - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - c.componentWillUnmount(); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.createAnimatedNode.mock.calls.length).toBe(3); - expect(nativeAnimatedModule.connectAnimatedNodes.mock.calls.length).toBe(2); - - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - jasmine.any(Number), - jasmine.any(Number), - {type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), delay: jasmine.any(Number)}, - jasmine.any(Function) - ); - - expect(nativeAnimatedModule.disconnectAnimatedNodes.mock.calls.length).toBe(2); - expect(nativeAnimatedModule.dropAnimatedNode.mock.calls.length).toBe(2); - }); - - it('sends a valid description for value, style and props nodes', () => { - var anim = new Animated.Value(0); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - }; - c.componentWillMount(); - - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { type: 'value', value: 0 }); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { type: 'style', style: { opacity: jasmine.any(Number) }}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { type: 'props', props: { style: jasmine.any(Number) }}); - }); - - it('sends a valid graph description for Animated.add nodes', () => { - var first = new Animated.Value(1); - var second = new Animated.Value(2); - - var c = new Animated.View(); - c.props = { - style: { - opacity: Animated.add(first, second), - }, - }; - c.componentWillMount(); - - Animated.timing(first, {toValue: 2, duration: 1000, useNativeDriver: true}).start(); - Animated.timing(second, {toValue: 3, duration: 1000, useNativeDriver: true}).start(); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { type: 'addition', input: jasmine.any(Array) }); - var additionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( - (call) => call[1].type === 'addition' - ); - expect(additionCalls.length).toBe(1); - var additionCall = additionCalls[0]; - var additionNodeTag = additionCall[0]; - var additionConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( - (call) => call[1] === additionNodeTag - ); - expect(additionConnectionCalls.length).toBe(2); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(additionCall[1].input[0], { type: 'value', value: 1 }); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(additionCall[1].input[1], { type: 'value', value: 2 }); - }); - - it('sends a valid graph description for Animated.multiply nodes', () => { - var first = new Animated.Value(2); - var second = new Animated.Value(1); - - var c = new Animated.View(); - c.props = { - style: { - opacity: Animated.multiply(first, second), - }, - }; - c.componentWillMount(); - - Animated.timing(first, {toValue: 5, duration: 1000, useNativeDriver: true}).start(); - Animated.timing(second, {toValue: -1, duration: 1000, useNativeDriver: true}).start(); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { type: 'multiplication', input: jasmine.any(Array) }); - var multiplicationCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( - (call) => call[1].type === 'multiplication' - ); - expect(multiplicationCalls.length).toBe(1); - var multiplicationCall = multiplicationCalls[0]; - var multiplicationNodeTag = multiplicationCall[0]; - var multiplicationConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( - (call) => call[1] === multiplicationNodeTag - ); - expect(multiplicationConnectionCalls.length).toBe(2); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(multiplicationCall[1].input[0], { type: 'value', value: 2 }); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(multiplicationCall[1].input[1], { type: 'value', value: 1 }); - }); - - it('sends a valid graph description for interpolate() nodes', () => { - var node = new Animated.Value(10); - - var c = new Animated.View(); - c.props = { - style: { - opacity: node.interpolate({ - inputRange: [10, 20], - outputRange: [0, 1], - }), - }, - }; - c.componentWillMount(); - - Animated.timing(node, {toValue: 20, duration: 1000, useNativeDriver: true}).start(); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(jasmine.any(Number), { type: 'value', value: 10 }); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { - type: 'interpolation', - inputRange: [10, 20], - outputRange: [0, 1], - }); - var interpolationNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find( - (call) => call[1].type === 'interpolation' - )[0]; - var valueNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find( - (call) => call[1].type === 'value' - )[0]; - expect(nativeAnimatedModule.connectAnimatedNodes).toBeCalledWith(valueNodeTag, interpolationNodeTag); - }); - - it('sends a valid timing animation description', () => { - var anim = new Animated.Value(0); - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - jasmine.any(Number), - jasmine.any(Number), - {type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), delay: jasmine.any(Number)}, - jasmine.any(Function) - ); - }); - - it('proxies `setValue` correctly', () => { - var anim = new Animated.Value(0); - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - }; - c.componentWillMount(); - - // We expect `setValue` not to propagate down to `setNativeProps`, otherwise it may try to access `setNativeProps` - // via component refs table that we override here. - c.refs = { - node: { - setNativeProps: jest.genMockFunction(), - }, - }; - - anim.setValue(0.5); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(jasmine.any(Number), 0.5); - expect(c.refs.node.setNativeProps.mock.calls.length).toBe(0); - }); - - it('doesn\'t call into native API if useNativeDriver is set to false', () => { - var anim = new Animated.Value(0); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - }; - c.componentWillMount(); - - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: false}).start(); - - c.componentWillUnmount(); - - expect(require('NativeModules').NativeAnimatedModule.createAnimatedNode).not.toBeCalled(); - }); - - it('fails when trying to run non-native animation on native node', () => { - var anim = new Animated.Value(0); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - }; - c.componentWillMount(); - - Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true}).start(); - jest.runAllTimers(); - - Animated.timing(anim, {toValue: 4, duration: 500, useNativeDriver: false}).start(); - expect(jest.runAllTimers).toThrow(); - }); - - it('fails for unsupported prop types', () => { - var anim = new Animated.Value(0); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - randomProperty: anim, - }; - c.componentWillMount(); - - var animation = Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true}); - expect(animation.start).toThrowError(/randomProperty/); - }); - - it('fails for unsupported styles', () => { - var anim = new Animated.Value(0); - - var c = new Animated.View(); - c.props = { - style: { - left: anim, - }, - }; - c.componentWillMount(); - - var animation = Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true}); - expect(animation.start).toThrowError(/left/); - }); - - it('fails for unsupported interpolation parameters', () => { - var anim = new Animated.Value(0); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim.interpolate({ - inputRange: [0, 100], - outputRange: [0, 1], - extrapolate: 'clamp', - }), - }, - }; - c.componentWillMount(); - - var animation = Animated.timing(anim, {toValue: 100, duration: 50, useNativeDriver: true}); - expect(animation.start).toThrowError(/extrapolate/); - }); - - it('works for any `static` props and styles', () => { - // Passing "unsupported" props should work just fine as long as they are not animated - var anim = new Animated.Value(0); - - var node = new Animated.__PropsOnlyForTests({ - style: { - left: 10, - top: 20, - opacity: anim, - }, - removeClippedSubviews: true, - }); - Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true}).start(); - node.__detach(); - - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { type: 'style', style: { opacity: jasmine.any(Number) }}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(jasmine.any(Number), { type: 'props', props: { style: jasmine.any(Number) }}); - }); - - it('sends stopAnimation command to native', () => { - var value = new Animated.Value(0); - var animation = Animated.timing(value, {toValue: 10, duration: 50, useNativeDriver: true}); - var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - - animation.start(); - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - jasmine.any(Number), - jasmine.any(Number), - {type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), delay: jasmine.any(Number)}, - jasmine.any(Function) - ); - var animationId = nativeAnimatedModule.startAnimatingNode.mock.calls[0][0]; - - animation.stop(); - expect(nativeAnimatedModule.stopAnimation).toBeCalledWith(animationId); - }); - -}); diff --git a/Libraries/Animated/src/__tests__/bezier-test.js b/Libraries/Animated/src/__tests__/bezier-test.js index 4b38bae78c8a3a..ef1bb68c2d828b 100644 --- a/Libraries/Animated/src/__tests__/bezier-test.js +++ b/Libraries/Animated/src/__tests__/bezier-test.js @@ -5,13 +5,13 @@ var bezier = require('bezier'); var identity = function (x) { return x; }; -function assertClose (a, b, precision) { - expect(a).toBeCloseTo(b, 3); +function assertClose (a, b, precision = 3) { + expect(a).toBeCloseTo(b, precision); } function makeAssertCloseWithPrecision (precision) { - return function (a, b, message) { - assertClose(a, b, message, precision); + return function (a, b) { + assertClose(a, b, precision); }; } @@ -19,7 +19,7 @@ function allEquals (be1, be2, samples, assertion) { if (!assertion) assertion = assertClose; for (var i=0; i<=samples; ++i) { var x = i / samples; - assertion(be1(x), be2(x), 'comparing '+be1+' and '+be2+' for value '+x); + assertion(be1(x), be2(x)); } } @@ -64,7 +64,7 @@ describe('bezier', function(){ var easing = bezier(a, b, c, d); var projected = bezier(b, a, d, c); var composed = function (x) { return projected(easing(x)); }; - allEquals(identity, composed, 100, makeAssertCloseWithPrecision(0.05)); + allEquals(identity, composed, 100, makeAssertCloseWithPrecision(2)); }); }); }); @@ -81,7 +81,7 @@ describe('bezier', function(){ repeat(10)(function () { var a = Math.random(), b = 2*Math.random()-0.5, c = 1-a, d = 1-b; var easing = bezier(a, b, c, d); - assertClose(easing(0.5), 0.5, easing+'(0.5) should be 0.5'); + assertClose(easing(0.5), 0.5); }); }); it('should be symetrical', function () { @@ -89,7 +89,7 @@ describe('bezier', function(){ var a = Math.random(), b = 2*Math.random()-0.5, c = 1-a, d = 1-b; var easing = bezier(a, b, c, d); var sym = function (x) { return 1 - easing(1-x); }; - allEquals(easing, sym, 100); + allEquals(easing, sym, 100, makeAssertCloseWithPrecision(2)); }); }); }); diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index 1573a90129c9dd..e8babe4dd61c8d 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -83,12 +83,10 @@ class AppState extends NativeEventEmitter { memoryWarning: new Map(), }; - // TODO: getCurrentAppState callback seems to be called at a really late stage - // after app launch. Trying to get currentState when mounting App component - // will likely to have the initial value here. - // Initialize to 'active' instead of null. - this.currentState = 'active'; - + // TODO: Remove the 'active' fallback after `initialAppState` is exported by + // the Android implementation. + this.currentState = RCTAppState.initialAppState || 'active'; + // TODO: this is a terrible solution - in order to ensure `currentState` prop // is up to date, we have to register an observer that updates it whenever // the state changes, even if nobody cares. We should just deprecate the @@ -99,7 +97,7 @@ class AppState extends NativeEventEmitter { this.currentState = appStateData.app_state; } ); - + // TODO: see above - this request just populates the value of `currentState` // when the module is first initialized. Would be better to get rid of the prop // and expose `getCurrentAppState` method directly. @@ -111,7 +109,7 @@ class AppState extends NativeEventEmitter { ); } - /** + /** * Add a handler to AppState changes by listening to the `change` event type * and providing the handler * diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index dfbd9edd1a3554..f1a4da1d4c2412 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -46,8 +46,11 @@ Object.keys(RemoteModules).forEach((moduleName) => { get: () => { let module = RemoteModules[moduleName]; if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) { - const json = global.nativeRequireModuleConfig(moduleName); - const config = json && JSON.parse(json); + // The old bridge (still used by iOS) will send the config as + // a JSON string that needs parsing, so we set config according + // to the type of response we got. + const rawConfig = global.nativeRequireModuleConfig(moduleName); + const config = typeof rawConfig === 'string' ? JSON.parse(rawConfig) : rawConfig; module = config && BatchedBridge.processModuleConfig(config, module.moduleID); RemoteModules[moduleName] = module; } diff --git a/Libraries/BugReporting/dumpReactTree.js b/Libraries/BugReporting/dumpReactTree.js index 89a7d874c53939..06c734e3a62042 100644 --- a/Libraries/BugReporting/dumpReactTree.js +++ b/Libraries/BugReporting/dumpReactTree.js @@ -101,6 +101,7 @@ function convertObject(object: Object, depth: number) { if (!first) { output += ', '; } + // $FlowFixMe(>=0.28.0) output += `${key}: ${convertValue(object[key], depth + 1)}`; first = false; } diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js index 7ef5962759aed0..3d4a8b376979ca 100644 --- a/Libraries/CameraRoll/CameraRoll.js +++ b/Libraries/CameraRoll/CameraRoll.js @@ -143,6 +143,7 @@ class CameraRoll { invariant( type === 'photo' || type === 'video' || type === undefined, + // $FlowFixMe(>=0.28.0) `The second argument to saveToCameraRoll must be 'photo' or 'video'. You passed ${type}` ); diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m index 8512cf64e743d6..83519a059d2241 100644 --- a/Libraries/CameraRoll/RCTCameraRollManager.m +++ b/Libraries/CameraRoll/RCTCameraRollManager.m @@ -90,7 +90,7 @@ @implementation RCTCameraRollManager if ([type isEqualToString:@"video"]) { // It's unclear if writeVideoAtPathToSavedPhotosAlbum is thread-safe dispatch_async(dispatch_get_main_queue(), ^{ - [_bridge.assetsLibrary writeVideoAtPathToSavedPhotosAlbum:request.URL completionBlock:^(NSURL *assetURL, NSError *saveError) { + [self->_bridge.assetsLibrary writeVideoAtPathToSavedPhotosAlbum:request.URL completionBlock:^(NSURL *assetURL, NSError *saveError) { if (saveError) { reject(RCTErrorUnableToSave, nil, saveError); } else { @@ -107,7 +107,7 @@ @implementation RCTCameraRollManager } // It's unclear if writeImageToSavedPhotosAlbum is thread-safe dispatch_async(dispatch_get_main_queue(), ^{ - [_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) { + [self->_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) { if (saveError) { RCTLogWarn(@"Error saving cropped image: %@", saveError); reject(RCTErrorUnableToSave, nil, saveError); diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 80a42fbb36876c..1943ce7130dfaa 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -100,7 +100,7 @@ type Event = Object; * animations and behavior from UIKIt. * * As the name implies, it is only available on iOS. Take a look at - * [`Navigator`](/docs/navigator.html) for a similar solution for your + * [`Navigator`](/react-native/docs/navigator.html) for a similar solution for your * cross-platform needs, or check out * [react-native-navigation](https://github.com/wix/react-native-navigation), a * component that aims to provide native navigation on both iOS and Android. diff --git a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js index d9831b93771e29..cc7246daf75ee2 100644 --- a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js +++ b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js @@ -75,8 +75,32 @@ var RecyclerViewBackedScrollView = React.createClass({ this.props.onContentSizeChange(width, height); }, + /** + * A helper function to scroll to a specific point in the scrollview. + * This is currently used to help focus on child textviews, but can also + * be used to quickly scroll to any element we want to focus. Syntax: + * + * scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true}) + * + * Note: The weird argument signature is due to the fact that, for historical reasons, + * the function also accepts separate arguments as as alternative to the options object. + * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. + */ + scrollTo: function( + y?: number | { x?: number, y?: number, animated?: boolean }, + x?: number, + animated?: boolean + ) { + if (typeof y === 'number') { + console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.'); + } else { + ({x, y, animated} = y || {}); + } + this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false}); + }, + render: function() { - var props = { + var recyclerProps = { ...this.props, onTouchStart: this.scrollResponderHandleTouchStart, onTouchMove: this.scrollResponderHandleTouchMove, @@ -92,12 +116,11 @@ var RecyclerViewBackedScrollView = React.createClass({ onResponderRelease: this.scrollResponderHandleResponderRelease, onResponderReject: this.scrollResponderHandleResponderReject, onScroll: this.scrollResponderHandleScroll, - style: ([{flex: 1}, this.props.style]: ?Array<any>), ref: INNERVIEW, }; if (this.props.onContentSizeChange) { - props.onContentSizeChange = this._handleContentSizeChange; + recyclerProps.onContentSizeChange = this._handleContentSizeChange; } var wrappedChildren = React.Children.map(this.props.children, (child) => { @@ -113,8 +136,20 @@ var RecyclerViewBackedScrollView = React.createClass({ ); }); + const refreshControl = this.props.refreshControl; + if (refreshControl) { + // Wrap the NativeAndroidRecyclerView with a AndroidSwipeRefreshLayout. + return React.cloneElement( + refreshControl, + {style: [styles.base, this.props.style]}, + <NativeAndroidRecyclerView {...recyclerProps} style={styles.base}> + {wrappedChildren} + </NativeAndroidRecyclerView> + ); + } + return ( - <NativeAndroidRecyclerView {...props}> + <NativeAndroidRecyclerView {...recyclerProps} style={[styles.base, this.props.style]}> {wrappedChildren} </NativeAndroidRecyclerView> ); @@ -128,6 +163,9 @@ var styles = StyleSheet.create({ left: 0, right: 0, }, + base: { + flex: 1, + }, }); var NativeAndroidRecyclerView = requireNativeComponent( diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 4a34b7c4fb5b31..8a08381e91f72f 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -27,6 +27,7 @@ const TimerMixin = require('react-timer-mixin'); const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); const UIManager = require('UIManager'); const View = require('View'); +const warning = require('fbjs/lib/warning'); const emptyFunction = require('fbjs/lib/emptyFunction'); const invariant = require('fbjs/lib/invariant'); @@ -271,7 +272,7 @@ const TextInput = React.createClass({ * Sets the return key to the label. Use it instead of `returnKeyType`. * @platform android */ - returnKeyLabel: PropTypes.string, + returnKeyLabel: PropTypes.string, /** * Limits the maximum number of characters that can be entered. Use this * instead of implementing the logic in JS to avoid flicker. @@ -311,6 +312,14 @@ const TextInput = React.createClass({ * Changed text is passed as an argument to the callback handler. */ onChangeText: PropTypes.func, + /** + * Callback that is called when the text input's content size changes. + * This will be called with + * `{ nativeEvent: { contentSize: { width, height } } }`. + * + * Only called for multiline text inputs. + */ + onContentSizeChange: PropTypes.func, /** * Callback that is called when text input ends. */ @@ -542,9 +551,10 @@ const TextInput = React.createClass({ if (__DEV__) { for (var propKey in onlyMultiline) { if (props[propKey]) { - throw new Error( + const error = new Error( 'TextInput prop `' + propKey + '` is only supported with multiline.' ); + warning(false, '%s', error.stack); } } } @@ -581,6 +591,7 @@ const TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} + onContentSizeChange={this.props.onContentSizeChange} onSelectionChange={onSelectionChange} onTextInput={this._onTextInput} onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue} @@ -641,6 +652,7 @@ const TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} + onContentSizeChange={this.props.onContentSizeChange} onSelectionChange={onSelectionChange} onTextInput={this._onTextInput} onEndEditing={this.props.onEndEditing} diff --git a/Libraries/Components/ToastAndroid/ToastAndroid.android.js b/Libraries/Components/ToastAndroid/ToastAndroid.android.js index 1006b929387ced..ac03e62d4805e8 100644 --- a/Libraries/Components/ToastAndroid/ToastAndroid.android.js +++ b/Libraries/Components/ToastAndroid/ToastAndroid.android.js @@ -19,13 +19,22 @@ var RCTToastAndroid = require('NativeModules').ToastAndroid; * * 1. String message: A string with the text to toast * 2. int duration: The duration of the toast. May be ToastAndroid.SHORT or ToastAndroid.LONG + * + * There is also a function `showWithGravity` to specify the layout gravity. May be + * ToastAndroid.TOP, ToastAndroid.BOTTOM, ToastAndroid.CENTER */ var ToastAndroid = { + // Toast duration constants SHORT: RCTToastAndroid.SHORT, LONG: RCTToastAndroid.LONG, + // Toast gravity constants + TOP: RCTToastAndroid.TOP, + BOTTOM: RCTToastAndroid.BOTTOM, + CENTER: RCTToastAndroid.CENTER, + show: function ( message: string, duration: number @@ -33,6 +42,13 @@ var ToastAndroid = { RCTToastAndroid.show(message, duration); }, + showWithGravity: function ( + message: string, + duration: number, + gravity: number, + ): void { + RCTToastAndroid.showWithGravity(message, duration, gravity); + }, }; module.exports = ToastAndroid; diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index ca93c40a1c0f3b..7acd9a6a51fd78 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -54,7 +54,7 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; * <TouchableHighlight onPress={this._onPressButton}> * <Image * style={styles.button} - * source={require('image!myButton')} + * source={require('./myButton.png')} * /> * </TouchableHighlight> * ); diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js index 8d8a12af7594c7..180bccd6f79290 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js @@ -185,7 +185,7 @@ var TouchableNativeFeedback = React.createClass({ _handleResponderMove: function(e) { this.touchableHandleResponderMove(e); - this._dispatchHotspotUpdate(e.nativeEvent.pageX, e.nativeEvent.pageY); + this._dispatchHotspotUpdate(e.nativeEvent.locationX, e.nativeEvent.locationY); }, _dispatchHotspotUpdate: function(destX, destY) { diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index fba3a779a587c0..dba277297644fd 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -41,7 +41,7 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; * <TouchableOpacity onPress={this._onPressButton}> * <Image * style={styles.button} - * source={require('image!myButton')} + * source={require('./myButton.png')} * /> * </TouchableOpacity> * ); diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 49891d3e66099d..018f913d1c49dc 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -59,6 +59,15 @@ type ErrorEvent = { type Event = Object; +const DataDetectorTypes = [ + 'phoneNumber', + 'link', + 'address', + 'calendarEvent', + 'none', + 'all', +]; + var defaultRenderLoading = () => ( <View style={styles.loadingView}> <ActivityIndicator /> @@ -238,6 +247,28 @@ var WebView = React.createClass({ */ style: View.propTypes.style, + /** + * Determines the types of data converted to clickable URLs in the web viewās content. + * By default only phone numbers are detected. + * + * You can provide one type or an array of many types. + * + * Possible values for `dataDetectorTypes` are: + * + * - `'phoneNumber'` + * - `'link'` + * - `'address'` + * - `'calendarEvent'` + * - `'none'` + * - `'all'` + * + * @platform ios + */ + dataDetectorTypes: PropTypes.oneOfType([ + PropTypes.oneOf(DataDetectorTypes), + PropTypes.arrayOf(PropTypes.oneOf(DataDetectorTypes)), + ]), + /** * Boolean value to enable JavaScript in the `WebView`. Used on Android only * as JavaScript is enabled by default on iOS. The default value is `true`. @@ -374,6 +405,7 @@ var WebView = React.createClass({ scalesPageToFit={this.props.scalesPageToFit} allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback} mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction} + dataDetectorTypes={this.props.dataDetectorTypes} />; return ( diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js index d04e13373ff3c0..3df376bd58eff2 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js @@ -42,8 +42,6 @@ const ReactComponentWithPureRenderMixin = require('react/lib/ReactComponentWithP const StyleSheet = require('StyleSheet'); const View = require('View'); -const emptyFunction = require('fbjs/lib/emptyFunction'); - const {PropTypes} = React; const {Directions} = NavigationCardStackPanResponder; @@ -66,11 +64,11 @@ type Props = { renderScene: NavigationSceneRenderer, cardStyle?: any, style: any, + gestureResponseDistance?: ?number, }; type DefaultProps = { direction: NavigationGestureDirection, - renderOverlay: ?NavigationSceneRenderer, }; /** @@ -98,11 +96,11 @@ class NavigationCardStack extends React.Component<DefaultProps, Props, void> { renderOverlay: PropTypes.func, renderScene: PropTypes.func.isRequired, cardStyle: View.propTypes.style, + gestureResponseDistance: PropTypes.number, }; static defaultProps: DefaultProps = { direction: Directions.HORIZONTAL, - renderOverlay: emptyFunction.thatReturnsNull, }; constructor(props: Props, context: any) { @@ -134,31 +132,16 @@ class NavigationCardStack extends React.Component<DefaultProps, Props, void> { _render(props: NavigationTransitionProps): ReactElement<any> { const { - navigationState, - } = props; - - let overlay = null; - const renderOverlay = this.props.renderOverlay; + renderOverlay + } = this.props; - if (renderOverlay) { - const route = navigationState.routes[navigationState.index]; - - const activeScene = props.scenes.find( - scene => !scene.isStale && scene.route === route ? scene : undefined - ); - - overlay = renderOverlay({ - ...props, - scene: activeScene - }); - } + const overlay = renderOverlay && renderOverlay(props); const scenes = props.scenes.map( scene => this._renderScene({ ...props, scene, - }), - this + }) ); return ( @@ -183,6 +166,7 @@ class NavigationCardStack extends React.Component<DefaultProps, Props, void> { const panHandlersProps = { ...props, onNavigateBack: this.props.onNavigateBack, + gestureResponseDistance: this.props.gestureResponseDistance, }; const panHandlers = isVertical ? NavigationCardStackPanResponder.forVertical(panHandlersProps) : diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackPanResponder.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackPanResponder.js index 68330289175cb1..47e538e52c2220 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackPanResponder.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackPanResponder.js @@ -39,14 +39,6 @@ const POSITION_THRESHOLD = 1 / 3; */ const RESPOND_THRESHOLD = 15; -/** - * The distance from the edge of the navigator which gesture response can start for. - * For horizontal scroll views, a distance of 30 from the left of the screen is the - * standard maximum position to start touch responsiveness. - */ -const RESPOND_POSITION_MAX_HORIZONTAL = 30; -const RESPOND_POSITION_MAX_VERTICAL = null; - /** * The threshold (in pixels) to finish the gesture action. */ @@ -64,6 +56,10 @@ export type NavigationGestureDirection = 'horizontal' | 'vertical'; type Props = NavigationSceneRendererProps & { onNavigateBack: ?Function, + /** + * The distance from the edge of the navigator which gesture response can start for. + **/ + gestureResponseDistance: ?number, }; /** @@ -115,8 +111,12 @@ class NavigationCardStackPanResponder extends NavigationAbstractPanResponder { layout.width.__getValue(); const positionMax = isVertical ? - RESPOND_POSITION_MAX_VERTICAL : - RESPOND_POSITION_MAX_HORIZONTAL; + props.gestureResponseDistance : + /** + * For horizontal scroll views, a distance of 30 from the left of the screen is the + * standard maximum position to start touch responsiveness. + */ + props.gestureResponseDistance || 30; if (positionMax != null && currentDragPosition > positionMax) { return false; diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackStyleInterpolator.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackStyleInterpolator.js index fb5a7f4dd01dec..77fa010e5d654e 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackStyleInterpolator.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackStyleInterpolator.js @@ -90,18 +90,18 @@ function forHorizontal(props: NavigationSceneRendererProps): Object { const opacity = position.interpolate({ inputRange, - outputRange: [1, 1, 0.3], + outputRange: ([1, 1, 0.3]: Array<number>), }); const scale = position.interpolate({ inputRange, - outputRange: [1, 1, 0.95], + outputRange: ([1, 1, 0.95]: Array<number>), }); const translateY = 0; const translateX = position.interpolate({ inputRange, - outputRange: [width, 0, -10], + outputRange: ([width, 0, -10]: Array<number>), }); return { @@ -131,18 +131,18 @@ function forVertical(props: NavigationSceneRendererProps): Object { const opacity = position.interpolate({ inputRange, - outputRange: [1, 1, 0.3], + outputRange: ([1, 1, 0.3]: Array<number>), }); const scale = position.interpolate({ inputRange, - outputRange: [1, 1, 0.95], + outputRange: ([1, 1, 0.95]: Array<number>), }); const translateX = 0; const translateY = position.interpolate({ inputRange, - outputRange: [height, 0, -10], + outputRange: ([height, 0, -10]: Array<number>), }); return { diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderStyleInterpolator.js b/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderStyleInterpolator.js index f6a0dbf51662de..fcc37c7a5ab568 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderStyleInterpolator.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderStyleInterpolator.js @@ -54,7 +54,7 @@ function forLeft(props: NavigationSceneRendererProps): Object { return { opacity: position.interpolate({ inputRange: [ index - 1, index, index + 1 ], - outputRange: [ 0, 1, 0 ], + outputRange: ([ 0, 1, 0 ]: Array<number>), }), }; } @@ -65,13 +65,13 @@ function forCenter(props: NavigationSceneRendererProps): Object { return { opacity:position.interpolate({ inputRange: [ index - 1, index, index + 1 ], - outputRange: [ 0, 1, 0 ], + outputRange: ([ 0, 1, 0 ]: Array<number>), }), transform: [ { translateX: position.interpolate({ inputRange: [ index - 1, index + 1 ], - outputRange: [ 200, -200 ], + outputRange: ([ 200, -200 ]: Array<number>), }), } ], @@ -84,7 +84,7 @@ function forRight(props: NavigationSceneRendererProps): Object { return { opacity: position.interpolate({ inputRange: [ index - 1, index, index + 1 ], - outputRange: [ 0, 1, 0 ], + outputRange: ([ 0, 1, 0 ]: Array<number>), }), }; } diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationPagerPanResponder.js b/Libraries/CustomComponents/NavigationExperimental/NavigationPagerPanResponder.js index d4da7a52275004..6bd5410b120533 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationPagerPanResponder.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationPagerPanResponder.js @@ -15,6 +15,7 @@ const Animated = require('Animated'); const NavigationAbstractPanResponder = require('NavigationAbstractPanResponder'); const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder'); +const I18nManager = require('I18nManager'); const clamp = require('clamp'); @@ -37,12 +38,22 @@ type Props = NavigationSceneRendererProps & { */ const { ANIMATION_DURATION, - DISTANCE_THRESHOLD, POSITION_THRESHOLD, RESPOND_THRESHOLD, Directions, } = NavigationCardStackPanResponder; +/** + * The threshold (in pixels) to finish the gesture action. + */ +const DISTANCE_THRESHOLD = 50; + +/** + * The threshold to trigger the gesture action. This determines the rate of the + * flick when the action will be triggered + */ +const VELOCITY_THRESHOLD = 1.5; + /** * Pan responder that handles gesture for a card in the cards list. * @@ -123,6 +134,9 @@ class NavigationPagerPanResponder extends NavigationAbstractPanResponder { const distance = isVertical ? layout.height.__getValue() : layout.width.__getValue(); + const currentValue = I18nManager.isRTL && axis === 'dx' ? + this._startValue + (gesture[axis] / distance) : + this._startValue - (gesture[axis] / distance); const prevIndex = Math.max( 0, @@ -136,7 +150,7 @@ class NavigationPagerPanResponder extends NavigationAbstractPanResponder { const value = clamp( prevIndex, - this._startValue - (gesture[axis] / distance), + currentValue, nextIndex, ); @@ -159,14 +173,21 @@ class NavigationPagerPanResponder extends NavigationAbstractPanResponder { const isVertical = this._isVertical; const axis = isVertical ? 'dy' : 'dx'; + const velocityAxis = isVertical ? 'vy' : 'vx'; const index = navigationState.index; - const distance = gesture[axis]; + const distance = I18nManager.isRTL && axis === 'dx' ? + -gesture[axis] : + gesture[axis]; + const moveSpeed = I18nManager.isRTL && velocityAxis === 'vx' ? + -gesture[velocityAxis] : + gesture[velocityAxis]; position.stopAnimation((value: number) => { this._reset(); if ( distance > DISTANCE_THRESHOLD || - value <= index - POSITION_THRESHOLD + value <= index - POSITION_THRESHOLD || + moveSpeed > VELOCITY_THRESHOLD ) { onNavigateBack && onNavigateBack(); return; @@ -174,7 +195,8 @@ class NavigationPagerPanResponder extends NavigationAbstractPanResponder { if ( distance < -DISTANCE_THRESHOLD || - value >= index + POSITION_THRESHOLD + value >= index + POSITION_THRESHOLD || + moveSpeed < -VELOCITY_THRESHOLD ) { onNavigateForward && onNavigateForward(); } diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationPagerStyleInterpolator.js b/Libraries/CustomComponents/NavigationExperimental/NavigationPagerStyleInterpolator.js index 78a9987cab5f86..950aa5eb136a1c 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationPagerStyleInterpolator.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationPagerStyleInterpolator.js @@ -32,6 +32,8 @@ */ 'use strict'; +const I18nManager = require('I18nManager'); + import type { NavigationSceneRendererProps, } from 'NavigationTypeDefinition'; @@ -87,11 +89,14 @@ function forHorizontal(props: NavigationSceneRendererProps): Object { const index = scene.index; const inputRange = [index - 1, index, index + 1]; - const width = layout.initWidth; + const outputRange = I18nManager.isRTL ? + ([-width, 0, width]: Array<number>) : + ([width, 0, -width]: Array<number>); + const translateX = position.interpolate({ inputRange, - outputRange: [width, 0, -width], + outputRange, }); return { diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js index 5307e1f869c754..797097cac1b8d4 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js @@ -27,7 +27,7 @@ */ 'use strict'; -var invariant = require('fbjs/lib/invariant'); +const invariant = require('fbjs/lib/invariant'); class NavigationEventPool { _list: Array<any>; @@ -37,7 +37,7 @@ class NavigationEventPool { } get(type: string, currentTarget: Object, data: any): NavigationEvent { - var event; + let event; if (this._list.length > 0) { event = this._list.pop(); event.constructor.call(event, type, currentTarget, data); @@ -52,7 +52,7 @@ class NavigationEventPool { } } -var _navigationEventPool = new NavigationEventPool(); +const _navigationEventPool = new NavigationEventPool(); /** * The NavigationEvent interface represents any event of the navigation. @@ -87,7 +87,7 @@ class NavigationEvent { _defaultPrevented: boolean; _disposed: boolean; _propagationStopped: boolean; - _type: ?string; + _type: string; target: ?Object; @@ -111,22 +111,18 @@ class NavigationEvent { this._propagationStopped = false; } - /* $FlowFixMe - get/set properties not yet supported */ get type(): string { return this._type; } - /* $FlowFixMe - get/set properties not yet supported */ - get currentTarget(): Object { + get currentTarget(): ?Object { return this._currentTarget; } - /* $FlowFixMe - get/set properties not yet supported */ get data(): any { return this._data; } - /* $FlowFixMe - get/set properties not yet supported */ get defaultPrevented(): boolean { return this._defaultPrevented; } @@ -160,7 +156,7 @@ class NavigationEvent { // Clean up. this.target = null; this.eventPhase = NavigationEvent.NONE; - this._type = null; + this._type = ''; this._currentTarget = null; this._data = null; this._defaultPrevented = false; diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js index f73ab10f01e2af..3290ba462d2e2e 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js @@ -61,12 +61,10 @@ class RouteStack { this._index = index; } - /* $FlowFixMe - get/set properties not yet supported */ get size(): number { return this._routeNodes.size; } - /* $FlowFixMe - get/set properties not yet supported */ get index(): number { return this._index; } diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js index 9d3811df64c316..e9a54290dcd9f4 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js +++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js @@ -1,4 +1,3 @@ - /** * Copyright (c) 2015, Facebook, Inc. All rights reserved. * @@ -58,7 +57,7 @@ describe('NavigationEvent', () => { it('recycles', () => { var event1 = NavigationEvent.pool('foo', {}, 123); event1.dispose(); - expect(event1.type).toBe(null); + expect(event1.type).toBeFalsy(); expect(event1.data).toBe(null); expect(event1.target).toBe(null); @@ -67,5 +66,3 @@ describe('NavigationEvent', () => { expect(event2).toBe(event1); }); }); - - diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 63b53b03f20fac..d5fbd0674c1b31 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -501,13 +501,13 @@ var Navigator = React.createClass({ }, _transitionTo: function(destIndex, velocity, jumpSpringTo, cb) { - if ( - destIndex === this.state.presentedIndex && - this.state.transitionQueue.length === 0 - ) { + if (this.state.presentedIndex === destIndex) { + cb && cb(); return; } + if (this.state.transitionFromIndex !== null) { + // Navigation is still transitioning, put the `destIndex` into queue. this.state.transitionQueue.push({ destIndex, velocity, diff --git a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js index c26396dd6e3ea1..3578281ebe0c72 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js +++ b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js @@ -1,5 +1,10 @@ /** - * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * Copyright (c) 2013-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. * * Facebook, Inc. ("Facebook") owns all right, title and interest, including * all intellectual property and other proprietary rights, in and to the React @@ -28,9 +33,12 @@ var Dimensions = require('Dimensions'); var PixelRatio = require('PixelRatio'); +var I18nManager = require('I18nManager'); var buildStyleInterpolator = require('buildStyleInterpolator'); +var IS_RTL = I18nManager.isRTL; + var SCREEN_WIDTH = Dimensions.get('window').width; var SCREEN_HEIGHT = Dimensions.get('window').height; @@ -50,6 +58,14 @@ var ToTheLeftIOS = { }, }; +var ToTheRightIOS = { + ...ToTheLeftIOS, + transformTranslate: { + from: {x: 0, y: 0, z: 0}, + to: {x: SCREEN_WIDTH * 0.3, y: 0, z: 0}, + }, +}; + var FadeToTheLeft = { // Rotate *requires* you to break out each individual component of // rotation (x, y, z, w) @@ -126,7 +142,7 @@ var FadeToTheRight = { translateX: { from: 0, to: Math.round(SCREEN_WIDTH * 0.3), - } + }, }; var FadeIn = { @@ -179,6 +195,32 @@ var ToTheLeft = { }, }; +var ToTheRight = { + transformTranslate: { + from: {x: 0, y: 0, z: 0}, + to: {x: Dimensions.get('window').width, y: 0, z: 0}, + min: 0, + max: 1, + type: 'linear', + extrapolate: true, + round: PixelRatio.get(), + }, + opacity: { + value: 1.0, + type: 'constant', + }, + + translateX: { + from: 0, + to: Dimensions.get('window').width, + min: 0, + max: 1, + type: 'linear', + extrapolate: true, + round: PixelRatio.get(), + }, +}; + var ToTheUp = { transformTranslate: { from: {x: 0, y: 0, z: 0}, @@ -500,10 +542,40 @@ var BaseUpDownGesture = { direction: 'up-to-down', }; +// For RTL experiment, we need to swap all the Left and Right gesture and animation. +// So we create a direction mapping for both LTR and RTL, and change left/right to start/end. +let directionMapping = { + ToTheStartIOS: ToTheLeftIOS, + ToTheEndIOS: ToTheRightIOS, + FadeToTheStart: FadeToTheLeft, + FadeToTheEnd: FadeToTheRight, + ToTheStart: ToTheLeft, + ToTheEnd: ToTheRight, + FromTheStart: FromTheLeft, + FromTheEnd: FromTheRight, + BaseStartToEndGesture: BaseLeftToRightGesture, + BaseEndToStartGesture: BaseRightToLeftGesture, +}; + +if (IS_RTL) { + directionMapping = { + ToTheStartIOS: ToTheRightIOS, + ToTheEndIOS: ToTheLeftIOS, + FadeToTheStart: FadeToTheRight, + FadeToTheEnd: FadeToTheLeft, + ToTheStart: ToTheRight, + ToTheEnd: ToTheLeft, + FromTheStart: FromTheRight, + FromTheEnd: FromTheLeft, + BaseStartToEndGesture: BaseRightToLeftGesture, + BaseEndToStartGesture: BaseLeftToRightGesture, + }; +} + var BaseConfig = { // A list of all gestures that are enabled on this scene gestures: { - pop: BaseLeftToRightGesture, + pop: directionMapping.BaseStartToEndGesture, }, // Rebound spring parameters when transitioning FROM this scene @@ -515,8 +587,8 @@ var BaseConfig = { // Animation interpolators for horizontal transitioning: animationInterpolators: { - into: buildStyleInterpolator(FromTheRight), - out: buildStyleInterpolator(FadeToTheLeft), + into: buildStyleInterpolator(directionMapping.FromTheEnd), + out: buildStyleInterpolator(directionMapping.FadeToTheStart), }, }; @@ -524,8 +596,8 @@ var NavigatorSceneConfigs = { PushFromRight: { ...BaseConfig, animationInterpolators: { - into: buildStyleInterpolator(FromTheRight), - out: buildStyleInterpolator(ToTheLeftIOS), + into: buildStyleInterpolator(directionMapping.FromTheEnd), + out: buildStyleInterpolator(directionMapping.ToTheStartIOS), }, }, FloatFromRight: { @@ -535,18 +607,18 @@ var NavigatorSceneConfigs = { FloatFromLeft: { ...BaseConfig, gestures: { - pop: BaseRightToLeftGesture, + pop: directionMapping.BaseEndToStartGesture, }, animationInterpolators: { - into: buildStyleInterpolator(FromTheLeft), - out: buildStyleInterpolator(FadeToTheRight), + into: buildStyleInterpolator(directionMapping.FromTheStart), + out: buildStyleInterpolator(directionMapping.FadeToTheEnd), }, }, FloatFromBottom: { ...BaseConfig, gestures: { pop: { - ...BaseLeftToRightGesture, + ...directionMapping.BaseStartToEndGesture, edgeHitWidth: 150, direction: 'top-to-bottom', fullDistance: SCREEN_HEIGHT, @@ -579,43 +651,43 @@ var NavigatorSceneConfigs = { ...BaseConfig, gestures: { jumpBack: { - ...BaseLeftToRightGesture, + ...directionMapping.BaseStartToEndGesture, overswipe: BaseOverswipeConfig, edgeHitWidth: null, isDetachable: true, }, jumpForward: { - ...BaseRightToLeftGesture, + ...directionMapping.BaseEndToStartGesture, overswipe: BaseOverswipeConfig, edgeHitWidth: null, isDetachable: true, }, }, animationInterpolators: { - into: buildStyleInterpolator(FromTheRight), - out: buildStyleInterpolator(ToTheLeft), + into: buildStyleInterpolator(directionMapping.FromTheEnd), + out: buildStyleInterpolator(directionMapping.ToTheStart), }, }, HorizontalSwipeJumpFromRight: { ...BaseConfig, gestures: { jumpBack: { - ...BaseRightToLeftGesture, + ...directionMapping.BaseEndToStartGesture, overswipe: BaseOverswipeConfig, edgeHitWidth: null, isDetachable: true, }, jumpForward: { - ...BaseLeftToRightGesture, + ...directionMapping.BaseStartToEndGesture, overswipe: BaseOverswipeConfig, edgeHitWidth: null, isDetachable: true, }, - pop: BaseRightToLeftGesture, + pop: directionMapping.BaseEndToStartGesture, }, animationInterpolators: { - into: buildStyleInterpolator(FromTheLeft), - out: buildStyleInterpolator(FadeToTheRight), + into: buildStyleInterpolator(directionMapping.FromTheStart), + out: buildStyleInterpolator(directionMapping.FadeToTheEnd), }, }, VerticalUpSwipeJump: { diff --git a/Libraries/Devtools/setupDevtools.js b/Libraries/Devtools/setupDevtools.js index 307c268f717b7c..e81a8629907baf 100644 --- a/Libraries/Devtools/setupDevtools.js +++ b/Libraries/Devtools/setupDevtools.js @@ -11,10 +11,17 @@ */ 'use strict'; +var Platform = require('Platform'); +var NativeModules = require('NativeModules'); + function setupDevtools() { var messageListeners = []; var closeListeners = []; - var ws = new window.WebSocket('ws://localhost:8097/devtools'); + var hostname = 'localhost'; + if (Platform.OS === 'android' && NativeModules.AndroidConstants) { + hostname = NativeModules.AndroidConstants.ServerHost.split(':')[0]; + } + var ws = new window.WebSocket('ws://' + hostname + ':8097/devtools'); // this is accessed by the eval'd backend code var FOR_BACKEND = { // eslint-disable-line no-unused-vars resolveRNStyle: require('flattenStyle'), diff --git a/Libraries/Experimental/SwipeableRow/SwipeableListView.js b/Libraries/Experimental/SwipeableRow/SwipeableListView.js index f18d240b12fcc0..206f4e54d24ce2 100644 --- a/Libraries/Experimental/SwipeableRow/SwipeableListView.js +++ b/Libraries/Experimental/SwipeableRow/SwipeableListView.js @@ -52,7 +52,7 @@ const SwipeableListView = React.createClass({ statics: { getNewDataSource(): Object { return new SwipeableListViewDataSource({ - getRowData: (data, sectionID, rowID) => data[rowID], + getRowData: (data, sectionID, rowID) => data[sectionID][rowID], getSectionHeaderData: (data, sectionID) => data[sectionID], sectionHeaderHasChanged: (s1, s2) => s1 !== s2, rowHasChanged: (row1, row2) => row1 !== row2, diff --git a/Libraries/Experimental/SwipeableRow/SwipeableRow.js b/Libraries/Experimental/SwipeableRow/SwipeableRow.js index 39ec4b21902578..922d70a25eb43a 100644 --- a/Libraries/Experimental/SwipeableRow/SwipeableRow.js +++ b/Libraries/Experimental/SwipeableRow/SwipeableRow.js @@ -25,6 +25,7 @@ const Animated = require('Animated'); const PanResponder = require('PanResponder'); +const I18nManager = require('I18nManager'); const React = require('React'); const StyleSheet = require('StyleSheet'); const TimerMixin = require('react-timer-mixin'); @@ -34,6 +35,8 @@ const {PropTypes} = React; const emptyFunction = require('fbjs/lib/emptyFunction'); +const IS_RTL = I18nManager.isRTL; + // NOTE: Eventually convert these consts to an input object of configurations // Position of the left of the swipable item when closed @@ -125,10 +128,6 @@ const SwipeableRow = React.createClass({ componentWillMount(): void { this._panResponder = PanResponder.create({ - onStartShouldSetPanResponder: (event, gestureState) => true, - // Don't capture child's start events - onStartShouldSetPanResponderCapture: (event, gestureState) => false, - onMoveShouldSetPanResponder: (event, gestureState) => false, onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture, onPanResponderGrant: this._handlePanResponderGrant, onPanResponderMove: this._handlePanResponderMove, @@ -240,7 +239,8 @@ const SwipeableRow = React.createClass({ }, _isSwipingRightFromClosed(gestureState: Object): boolean { - return this._previousLeft === CLOSED_LEFT_POSITION && gestureState.dx > 0; + const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx; + return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0; }, _swipeFullSpeed(gestureState: Object): void { @@ -259,9 +259,10 @@ const SwipeableRow = React.createClass({ * swiping is available, but swiping right does not do anything * functionally. */ + const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx; return ( this._isSwipingRightFromClosed(gestureState) && - gestureState.dx > RIGHT_SWIPE_THRESHOLD + gestureStateDx > RIGHT_SWIPE_THRESHOLD ); }, @@ -290,7 +291,8 @@ const SwipeableRow = React.createClass({ }, _animateToOpenPosition(): void { - this._animateTo(-this.props.maxSwipeDistance); + const maxSwipeDistance = IS_RTL ? -this.props.maxSwipeDistance : this.props.maxSwipeDistance; + this._animateTo(-maxSwipeDistance); }, _animateToClosedPosition(duration: number = SWIPE_DURATION): void { @@ -306,8 +308,11 @@ const SwipeableRow = React.createClass({ * When swiping right, we want to bounce back past closed position on release * so users know they should swipe right to get content. */ + const swipeBounceBackDistance = IS_RTL ? + -RIGHT_SWIPE_BOUNCE_BACK_DISTANCE : + RIGHT_SWIPE_BOUNCE_BACK_DISTANCE; this._animateTo( - -RIGHT_SWIPE_BOUNCE_BACK_DISTANCE, + -swipeBounceBackDistance, duration, this._animateToClosedPositionDuringBounce, ); @@ -330,8 +335,7 @@ const SwipeableRow = React.createClass({ }, _handlePanResponderEnd(event: Object, gestureState: Object): void { - const horizontalDistance = gestureState.dx; - + const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx; if (this._isSwipingRightFromClosed(gestureState)) { this.props.onOpen(); this._animateBounceBack(RIGHT_SWIPE_BOUNCE_BACK_DURATION); diff --git a/Libraries/Experimental/WindowedListView.js b/Libraries/Experimental/WindowedListView.js index bec56ecbe4983b..2cef3471ce04c1 100644 --- a/Libraries/Experimental/WindowedListView.js +++ b/Libraries/Experimental/WindowedListView.js @@ -626,6 +626,7 @@ class CellRenderer extends React.Component { if (this.props.asyncRowPerfEventName) { this._perfUpdateID = g_perf_update_id++; this._asyncCookie = Systrace.beginAsyncEvent(this.props.asyncRowPerfEventName + this._perfUpdateID); + // $FlowFixMe(>=0.28.0) infoLog(`perf_asynctest_${this.props.asyncRowPerfEventName}_start ${this._perfUpdateID} ${Date.now()}`); } if (this.props.includeInLayout) { @@ -662,6 +663,7 @@ class CellRenderer extends React.Component { // Note this doesn't include the native render time but is more accurate than also including the JS render // time of anything that has been queued up. Systrace.endAsyncEvent(this.props.asyncRowPerfEventName + this._perfUpdateID, this._asyncCookie); + // $FlowFixMe(>=0.28.0) infoLog(`perf_asynctest_${this.props.asyncRowPerfEventName}_end ${this._perfUpdateID} ${Date.now()}`); } } diff --git a/Libraries/Fetch/fetch.js b/Libraries/Fetch/fetch.js index 7cb122a07f4cc2..e8170f874d9e54 100644 --- a/Libraries/Fetch/fetch.js +++ b/Libraries/Fetch/fetch.js @@ -6,429 +6,12 @@ * 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. * - * This is a third-party polyfill grabbed from: - * https://github.com/github/fetch - * * @providesModule fetch * @nolint - */ -/* eslint-disable */ -'use strict'; - -var self = {}; - -/** - * Copyright (c) 2014 GitHub, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - * @preserve-header */ -(function() { - 'use strict'; - - if (self.fetch) { - return - } - - function normalizeName(name) { - if (typeof name !== 'string') { - name = String(name) - } - if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { - throw new TypeError('Invalid character in header field name') - } - return name.toLowerCase() - } - - function normalizeValue(value) { - if (typeof value !== 'string') { - value = String(value) - } - return value - } - - function Headers(headers) { - this.map = {} - - if (headers instanceof Headers) { - headers.forEach(function(value, name) { - this.append(name, value) - }, this) - - } else if (headers) { - Object.getOwnPropertyNames(headers).forEach(function(name) { - this.append(name, headers[name]) - }, this) - } - } - - Headers.prototype.append = function(name, value) { - name = normalizeName(name) - value = normalizeValue(value) - var list = this.map[name] - if (!list) { - list = [] - this.map[name] = list - } - list.push(value) - } - - Headers.prototype['delete'] = function(name) { - delete this.map[normalizeName(name)] - } - - Headers.prototype.get = function(name) { - var values = this.map[normalizeName(name)] - return values ? values[0] : null - } - - Headers.prototype.getAll = function(name) { - return this.map[normalizeName(name)] || [] - } - - Headers.prototype.has = function(name) { - return this.map.hasOwnProperty(normalizeName(name)) - } - - Headers.prototype.set = function(name, value) { - this.map[normalizeName(name)] = [normalizeValue(value)] - } - - Headers.prototype.forEach = function(callback, thisArg) { - Object.getOwnPropertyNames(this.map).forEach(function(name) { - this.map[name].forEach(function(value) { - callback.call(thisArg, value, name, this) - }, this) - }, this) - } - - function consumed(body) { - if (body.bodyUsed) { - return Promise.reject(new TypeError('Already read')) - } - body.bodyUsed = true - } - - function fileReaderReady(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result) - } - reader.onerror = function() { - reject(reader.error) - } - }) - } - - function readBlobAsArrayBuffer(blob) { - var reader = new FileReader() - reader.readAsArrayBuffer(blob) - return fileReaderReady(reader) - } - - function readBlobAsText(blob) { - var reader = new FileReader() - reader.readAsText(blob) - return fileReaderReady(reader) - } - - var support = { - blob: typeof FileReader === 'function' && typeof Blob === 'function' && (function() { - try { - new Blob(); - return true - } catch(e) { - return false - } - })(), - formData: typeof FormData === 'function', - arrayBuffer: typeof ArrayBuffer === 'function' - } - - function Body() { - this.bodyUsed = false - - this._initBody = function(body) { - this._bodyInit = body - if (typeof body === 'string') { - this._bodyText = body - } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body - } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body - } else if (!body) { - this._bodyText = '' - } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) { - // Only support ArrayBuffers for POST method. - // Receiving ArrayBuffers happens via Blobs, instead. - } else { - throw new Error('unsupported BodyInit type') - } - - if (!this.headers.get('content-type')) { - if (typeof body === 'string') { - this.headers.set('content-type', 'text/plain;charset=UTF-8') - } else if (this._bodyBlob && this._bodyBlob.type) { - this.headers.set('content-type', this._bodyBlob.type) - } - } - } - - if (support.blob) { - this.blob = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob') - } else { - return Promise.resolve(new Blob([this._bodyText])) - } - } - - this.arrayBuffer = function() { - return this.blob().then(readBlobAsArrayBuffer) - } - - this.text = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return readBlobAsText(this._bodyBlob) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as text') - } else { - return Promise.resolve(this._bodyText) - } - } - } else { - this.text = function() { - var rejected = consumed(this) - return rejected ? rejected : Promise.resolve(this._bodyText) - } - } - - if (support.formData) { - this.formData = function() { - return this.text().then(decode) - } - } - - this.json = function() { - return this.text().then(JSON.parse) - } - - return this - } - - // HTTP methods whose capitalization should be normalized - var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] - - function normalizeMethod(method) { - var upcased = method.toUpperCase() - return (methods.indexOf(upcased) > -1) ? upcased : method - } - - function Request(input, options) { - options = options || {} - var body = options.body - if (Request.prototype.isPrototypeOf(input)) { - if (input.bodyUsed) { - throw new TypeError('Already read') - } - this.url = input.url - this.credentials = input.credentials - if (!options.headers) { - this.headers = new Headers(input.headers) - } - this.method = input.method - this.mode = input.mode - if (!body) { - body = input._bodyInit - input.bodyUsed = true - } - } else { - this.url = input - } - - this.credentials = options.credentials || this.credentials || 'omit' - if (options.headers || !this.headers) { - this.headers = new Headers(options.headers) - } - this.method = normalizeMethod(options.method || this.method || 'GET') - this.mode = options.mode || this.mode || null - this.referrer = null - - if ((this.method === 'GET' || this.method === 'HEAD') && body) { - throw new TypeError('Body not allowed for GET or HEAD requests') - } - this._initBody(body) - } - - Request.prototype.clone = function() { - return new Request(this) - } - - function decode(body) { - var form = new FormData() - body.trim().split('&').forEach(function(bytes) { - if (bytes) { - var split = bytes.split('=') - var name = split.shift().replace(/\+/g, ' ') - var value = split.join('=').replace(/\+/g, ' ') - form.append(decodeURIComponent(name), decodeURIComponent(value)) - } - }) - return form - } - - function headers(xhr) { - var head = new Headers() - var pairs = xhr.getAllResponseHeaders().trim().split('\n') - pairs.forEach(function(header) { - var split = header.trim().split(':') - var key = split.shift().trim() - var value = split.join(':').trim() - head.append(key, value) - }) - return head - } - - Body.call(Request.prototype) - - function Response(bodyInit, options) { - if (!options) { - options = {} - } - - this.type = 'default' - this.status = options.status - this.ok = this.status >= 200 && this.status < 300 - this.statusText = options.statusText - this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) - this.url = options.url || '' - this._initBody(bodyInit) - } - Body.call(Response.prototype) - - Response.prototype.clone = function() { - return new Response(this._bodyInit, { - status: this.status, - statusText: this.statusText, - headers: new Headers(this.headers), - url: this.url - }) - } - - Response.error = function() { - var response = new Response(null, {status: 0, statusText: ''}) - response.type = 'error' - return response - } - - var redirectStatuses = [301, 302, 303, 307, 308] - - Response.redirect = function(url, status) { - if (redirectStatuses.indexOf(status) === -1) { - throw new RangeError('Invalid status code') - } - - return new Response(null, {status: status, headers: {location: url}}) - } - - self.Headers = Headers; - self.Request = Request; - self.Response = Response; - - self.fetch = function(input, init) { - return new Promise(function(resolve, reject) { - var request - if (Request.prototype.isPrototypeOf(input) && !init) { - request = input - } else { - request = new Request(input, init) - } - - var xhr = new XMLHttpRequest() - - function responseURL() { - if ('responseURL' in xhr) { - return xhr.responseURL - } - - // Avoid security warnings on getResponseHeader when not allowed by CORS - if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { - return xhr.getResponseHeader('X-Request-URL') - } - - return; - } - - xhr.onload = function() { - var status = (xhr.status === 1223) ? 204 : xhr.status - if (status < 100 || status > 599) { - reject(new TypeError('Network request failed')) - return - } - - var options = { - status: status, - statusText: xhr.statusText, - headers: headers(xhr), - url: responseURL() - } - var body = 'response' in xhr ? xhr.response : xhr.responseText; - resolve(new Response(body, options)) - } - - xhr.onerror = function() { - reject(new TypeError('Network request failed')) - } - - xhr.open(request.method, request.url, true) - - if (request.credentials === 'include') { - xhr.withCredentials = true - } - - if ('responseType' in xhr && support.blob) { - xhr.responseType = 'blob' - } - - request.headers.forEach(function(value, name) { - xhr.setRequestHeader(name, value) - }) +'use strict'; - xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) - }) - } - self.fetch.polyfill = true -})(); +import 'whatwg-fetch'; -/** End of the third-party code */ -module.exports = self; +module.exports = {fetch, Headers, Request, Response}; diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index dfcfe7317e3c06..cafd1f22a50dec 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -34,6 +34,11 @@ var { ImageLoader, } = NativeModules; +let _requestId = 1; +function generateRequestId() { + return _requestId++; +} + /** * <Image> - A react component for displaying different types of images, * including network images, static resources, temporary local images, and @@ -148,8 +153,17 @@ var Image = React.createClass({ * Prefetches a remote image for later use by downloading it to the disk * cache */ - prefetch(url: string) { - return ImageLoader.prefetchImage(url); + prefetch(url: string, callback: ?Function) { + const requestId = generateRequestId(); + callback && callback(requestId); + return ImageLoader.prefetchImage(url, requestId); + }, + + /** + * Abort prefetch request + */ + abortPrefetch(requestId: number) { + ImageLoader.abortRequest(requestId); }, }, diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 88b03b6ee1f72a..0fc1d20c5c33bc 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -173,7 +173,7 @@ const Image = React.createClass({ * - `repeat`: Repeat the image to cover the frame of the view. The * image will keep it's size and aspect ratio. (iOS only) */ - resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat']), + resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']), /** * A unique identifier for this element to be used in UI Automation * testing scripts. diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 5e642b41fd0492..e5b29308f5d73f 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -12,9 +12,7 @@ 1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */; }; 134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; }; 139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 139A38831C4D587C00862840 /* RCTResizeMode.m */; }; - 13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */; }; - 13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F0A1BC42D4E003F47DD /* RCTVirtualImageManager.m */; }; - 13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */; }; + 13EF7F7F1BC825B1003F47DD /* RCTLocalAssetImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F7E1BC825B1003F47DD /* RCTLocalAssetImageLoader.m */; }; 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; }; 35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; }; 354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; }; @@ -44,8 +42,8 @@ 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; }; 139A38821C4D57AD00862840 /* RCTResizeMode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTResizeMode.h; sourceTree = "<group>"; }; 139A38831C4D587C00862840 /* RCTResizeMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTResizeMode.m; sourceTree = "<group>"; }; - 13EF7F7D1BC825B1003F47DD /* RCTXCAssetImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTXCAssetImageLoader.h; sourceTree = "<group>"; }; - 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTXCAssetImageLoader.m; sourceTree = "<group>"; }; + 13EF7F7D1BC825B1003F47DD /* RCTLocalAssetImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLocalAssetImageLoader.h; sourceTree = "<group>"; }; + 13EF7F7E1BC825B1003F47DD /* RCTLocalAssetImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocalAssetImageLoader.m; sourceTree = "<group>"; }; 143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; }; 143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; }; 35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; }; @@ -75,8 +73,8 @@ EEF314711C9B0DD30049118E /* RCTImageBlurUtils.m */, 139A38821C4D57AD00862840 /* RCTResizeMode.h */, 139A38831C4D587C00862840 /* RCTResizeMode.m */, - 13EF7F7D1BC825B1003F47DD /* RCTXCAssetImageLoader.h */, - 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */, + 13EF7F7D1BC825B1003F47DD /* RCTLocalAssetImageLoader.h */, + 13EF7F7E1BC825B1003F47DD /* RCTLocalAssetImageLoader.m */, 1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */, 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */, 354631661B69857700AA0B86 /* RCTImageEditingManager.h */, @@ -169,7 +167,7 @@ 139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */, 1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */, EEF314721C9B0DD30049118E /* RCTImageBlurUtils.m in Sources */, - 13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */, + 13EF7F7F1BC825B1003F47DD /* RCTLocalAssetImageLoader.m in Sources */, 134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Libraries/Image/RCTImageEditingManager.m b/Libraries/Image/RCTImageEditingManager.m index cc8dfd0faf27f6..bb65990ba75fff 100644 --- a/Libraries/Image/RCTImageEditingManager.m +++ b/Libraries/Image/RCTImageEditingManager.m @@ -67,7 +67,7 @@ @implementation RCTImageEditingManager } // Store image - [_bridge.imageStoreManager storeImage:croppedImage withBlock:^(NSString *croppedImageTag) { + [self->_bridge.imageStoreManager storeImage:croppedImage withBlock:^(NSString *croppedImageTag) { if (!croppedImageTag) { NSString *errorMessage = @"Error storing cropped image in RCTImageStoreManager"; RCTLogWarn(@"%@", errorMessage); diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index 1daafdfb88877c..4fbb1cb7222a9a 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -13,11 +13,9 @@ #import "RCTURLRequestHandler.h" #import "RCTResizeMode.h" -@class ALAssetsLibrary; - typedef void (^RCTImageLoaderProgressBlock)(int64_t progress, int64_t total); typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, UIImage *image); -typedef void (^RCTImageLoaderCancellationBlock)(void); +typedef dispatch_block_t RCTImageLoaderCancellationBlock; @interface UIImage (React) @@ -96,48 +94,6 @@ typedef void (^RCTImageLoaderCancellationBlock)(void); @end -@interface RCTImageLoader (Deprecated) - -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - callback:(RCTImageLoaderCompletionBlock)callback -__deprecated_msg("Use loadImageWithURLRequest:callback: instead"); - -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressBlock - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -__deprecated_msg("Use loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock: instead"); - -- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressBlock - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -__deprecated_msg("Use loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock: instead"); - -- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -__deprecated_msg("Use decodeImageData:size:scale:clipped:resizeMode:completionBlock: instead"); - -- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -__deprecated_msg("Use decodeImageData:size:scale:clipped:resizeMode:completionBlock: instead"); - -- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag - block:(void(^)(NSError *error, CGSize size))completionBlock -__deprecated_msg("Use getImageSizeWithURLRequest:block: instead"); - -@end - @interface RCTBridge (RCTImageLoader) /** @@ -185,6 +141,22 @@ __deprecated_msg("Use getImageSizeWithURLRequest:block: instead"); */ - (float)loaderPriority; +/** + * If the loader must be called on the serial url cache queue, and whether the completion + * block should be dispatched off the main thread. If this is NO, the loader will be + * called from the main queue. Defaults to YES. + * + * Use with care: disabling scheduling will reduce RCTImageLoader's ability to throttle + * network requests. + */ +- (BOOL)requiresScheduling; + +/** + * If images loaded by the loader should be cached in the decoded image cache. + * Defaults to YES. + */ +- (BOOL)shouldCacheLoadedImages; + @end /** @@ -204,6 +176,9 @@ __deprecated_msg("Use getImageSizeWithURLRequest:block: instead"); * Decode an image from the data object. The method should call the * completionHandler when the decoding operation has finished. The method * should also return a cancellation block, if applicable. + * + * If you provide a custom image decoder, you most implement scheduling yourself, + * to avoid decoding large amounts of images at the same time. */ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 2132837a1d3eb7..ec78c3a5fac087 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -9,10 +9,12 @@ #import "RCTImageLoader.h" -#import <libkern/OSAtomic.h> -#import <UIKit/UIKit.h> #import <ImageIO/ImageIO.h> +#import <libkern/OSAtomic.h> + +#import <objc/runtime.h> + #import "RCTConvert.h" #import "RCTDefines.h" #import "RCTImageUtils.h" @@ -20,13 +22,13 @@ #import "RCTNetworking.h" #import "RCTUtils.h" -static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 1048576; // 1MB +static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 1024 * 1024; // 1MB -static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, - CGFloat scale, RCTResizeMode resizeMode) +static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale, + RCTResizeMode resizeMode, NSString *responseDate) { - return [NSString stringWithFormat:@"%@|%g|%g|%g|%zd", - imageTag, size.width, size.height, scale, resizeMode]; + return [NSString stringWithFormat:@"%@|%g|%g|%g|%zd|%@", + imageTag, size.width, size.height, scale, resizeMode, responseDate]; } @implementation UIImage (React) @@ -48,8 +50,7 @@ @implementation RCTImageLoader NSArray<id<RCTImageURLLoader>> *_loaders; NSArray<id<RCTImageDataDecoder>> *_decoders; NSOperationQueue *_imageDecodeQueue; - dispatch_queue_t _URLCacheQueue; - NSURLCache *_URLCache; + dispatch_queue_t _URLRequestQueue; NSCache *_decodedImageCache; NSMutableArray *_pendingTasks; NSInteger _activeTasks; @@ -69,7 +70,7 @@ - (void)setUp _maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2; _maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB - _URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL); + _URLRequestQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLRequestQueue", DISPATCH_QUEUE_SERIAL); _decodedImageCache = [NSCache new]; _decodedImageCache.totalCostLimit = 5 * 1024 * 1024; // 5MB @@ -218,7 +219,7 @@ - (void)dealloc } - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest - callback:(RCTImageLoaderCompletionBlock)callback + callback:(RCTImageLoaderCompletionBlock)callback { return [self loadImageWithURLRequest:imageURLRequest size:CGSizeZero @@ -231,14 +232,13 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image - (void)dequeueTasks { - dispatch_async(_URLCacheQueue, ^{ - + dispatch_async(_URLRequestQueue, ^{ // Remove completed tasks - for (RCTNetworkTask *task in _pendingTasks.reverseObjectEnumerator) { + for (RCTNetworkTask *task in self->_pendingTasks.reverseObjectEnumerator) { switch (task.status) { case RCTNetworkTaskFinished: - [_pendingTasks removeObject:task]; - _activeTasks--; + [self->_pendingTasks removeObject:task]; + self->_activeTasks--; break; case RCTNetworkTaskPending: break; @@ -246,8 +246,8 @@ - (void)dequeueTasks // Check task isn't "stuck" if (task.requestToken == nil) { RCTLogWarn(@"Task orphaned for request %@", task.request); - [_pendingTasks removeObject:task]; - _activeTasks--; + [self->_pendingTasks removeObject:task]; + self->_activeTasks--; [task cancel]; } break; @@ -255,12 +255,12 @@ - (void)dequeueTasks } // Start queued decode - NSInteger activeDecodes = _scheduledDecodes - _pendingDecodes.count; - while (activeDecodes == 0 || (_activeBytes <= _maxConcurrentDecodingBytes && - activeDecodes <= _maxConcurrentDecodingTasks)) { - dispatch_block_t decodeBlock = _pendingDecodes.firstObject; + NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count; + while (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes && + activeDecodes <= self->_maxConcurrentDecodingTasks)) { + dispatch_block_t decodeBlock = self->_pendingDecodes.firstObject; if (decodeBlock) { - [_pendingDecodes removeObjectAtIndex:0]; + [self->_pendingDecodes removeObjectAtIndex:0]; decodeBlock(); } else { break; @@ -268,13 +268,13 @@ - (void)dequeueTasks } // Start queued tasks - for (RCTNetworkTask *task in _pendingTasks) { - if (MAX(_activeTasks, _scheduledDecodes) >= _maxConcurrentLoadingTasks) { + for (RCTNetworkTask *task in self->_pendingTasks) { + if (MAX(self->_activeTasks, self->_scheduledDecodes) >= self->_maxConcurrentLoadingTasks) { break; } if (task.status == RCTNetworkTaskPending) { [task start]; - _activeTasks++; + self->_activeTasks++; } } }); @@ -285,186 +285,202 @@ - (void)dequeueTasks * path taken. This is useful if you want to skip decoding, e.g. when preloading * the image, or retrieving metadata. */ -- (RCTImageLoaderCancellationBlock)loadImageOrDataWithURLRequest:(NSURLRequest *)imageURLRequest - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressHandler - completionBlock:(void (^)(NSError *error, id imageOrData))completionBlock +- (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest *)request + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressHandler + completionBlock:(void (^)(NSError *error, id imageOrData, NSString *cacheKey))completionBlock { __block volatile uint32_t cancelled = 0; - __block void(^cancelLoad)(void) = nil; + __block dispatch_block_t cancelLoad = nil; __weak RCTImageLoader *weakSelf = self; - void (^completionHandler)(NSError *error, id imageOrData) = ^(NSError *error, id imageOrData) { - if (RCTIsMainQueue()) { + { + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + [NSURLProtocol setProperty:@"RCTImageLoader" + forKey:@"trackingName" + inRequest:mutableRequest]; + + // Add missing png extension + if (request.URL.fileURL && request.URL.pathExtension.length == 0) { + mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]]; + } + request = mutableRequest; + } + + // Find suitable image URL loader + id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:request.URL]; + BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ? + [loadHandler requiresScheduling] : YES; + void (^completionHandler)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate) { + BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? + [loadHandler shouldCacheLoadedImages] : YES; + // If we've received an image, we should try to set it synchronously, + // if it's data, do decoding on a background thread. + NSString *cacheKey = cacheResult ? + RCTCacheKeyForImage(request.URL.absoluteString, size, scale, resizeMode, fetchDate) : nil; + + if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) { // Most loaders do not return on the main thread, so caller is probably not // expecting it, and may do expensive post-processing in the callback dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (!cancelled) { - completionBlock(error, imageOrData); + completionBlock(error, imageOrData, cacheKey); } }); } else if (!cancelled) { - completionBlock(error, imageOrData); + completionBlock(error, imageOrData, cacheKey); } }; + // If the loader doesn't require scheduling we call it directly on + // the main queue. + if (loadHandler && !requiresScheduling) { + return [loadHandler loadImageForURL:request.URL + size:size + scale:scale + resizeMode:resizeMode + progressHandler:progressHandler + completionHandler:^(NSError *error, UIImage *image){ + completionHandler(error, image, nil); + }]; + } + // All access to URL cache must be serialized - if (!_URLCacheQueue) { + if (!_URLRequestQueue) { [self setUp]; } - dispatch_async(_URLCacheQueue, ^{ - - if (!_URLCache) { - _URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB - diskCapacity:200 * 1024 * 1024 // 200MB - diskPath:@"React/RCTImageDownloader"]; - } - RCTImageLoader *strongSelf = weakSelf; + dispatch_async(_URLRequestQueue, ^{ + __typeof(self) strongSelf = weakSelf; if (cancelled || !strongSelf) { return; } - // Find suitable image URL loader - NSURLRequest *request = imageURLRequest; // Use a local variable so we can reassign it in this block - id<RCTImageURLLoader> loadHandler = [strongSelf imageURLLoaderForURL:request.URL]; if (loadHandler) { cancelLoad = [loadHandler loadImageForURL:request.URL size:size scale:scale resizeMode:resizeMode progressHandler:progressHandler - completionHandler:completionHandler] ?: ^{}; - return; + completionHandler:^(NSError *error, UIImage *image){ + completionHandler(error, image, nil); + }]; + } else { + // Use networking module to load image + cancelLoad = [strongSelf _loadURLRequest:request + progressBlock:progressHandler + completionBlock:completionHandler]; } + }); - // Check if networking module is available - if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) { - RCTLogError(@"No suitable image URL loader found for %@. You may need to " - " import the RCTNetwork library in order to load images.", - request.URL.absoluteString); - return; + return ^{ + if (cancelLoad) { + cancelLoad(); } + OSAtomicOr32Barrier(1, &cancelled); + }; +} + +- (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request + progressBlock:(RCTImageLoaderProgressBlock)progressHandler + completionBlock:(void (^)(NSError *error, id imageOrData, NSString *fetchDate))completionHandler +{ + // Check if networking module is available + if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) { + RCTLogError(@"No suitable image URL loader found for %@. You may need to " + " import the RCTNetwork library in order to load images.", + request.URL.absoluteString); + return NULL; + } + + RCTNetworking *networking = [_bridge networking]; - // Check if networking module can load image - if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) { - RCTLogError(@"No suitable image URL loader found for %@", request.URL.absoluteString); + // Check if networking module can load image + if (RCT_DEBUG && ![networking canHandleRequest:request]) { + RCTLogError(@"No suitable image URL loader found for %@", request.URL.absoluteString); + return NULL; + } + + // Use networking module to load image + RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) { + // Check for system errors + if (error) { + completionHandler(error, nil, nil); + return; + } else if (!response) { + completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, nil); + return; + } else if (!data) { + completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, nil); return; } - // Use networking module to load image - RCTURLRequestCompletionBlock processResponse = - ^(NSURLResponse *response, NSData *data, NSError *error) { - - // Check for system errors - if (error) { - completionHandler(error, nil); - return; - } else if (!data) { - completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil); + // Check for http errors + NSString *responseDate; + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + if (statusCode != 200) { + completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain + code:statusCode + userInfo:nil], nil, nil); return; } - // Check for http errors - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; - if (statusCode != 200) { - completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain - code:statusCode - userInfo:nil], nil); - return; - } - } - - // Call handler - completionHandler(nil, data); - }; - - // Add missing png extension - if (request.URL.fileURL && request.URL.pathExtension.length == 0) { - NSMutableURLRequest *mutableRequest = [request mutableCopy]; - mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]]; - request = mutableRequest; + responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"]; } - // Check for cached response before reloading - // TODO: move URL cache out of RCTImageLoader into its own module - NSCachedURLResponse *cachedResponse = [_URLCache cachedResponseForRequest:request]; - - while (cachedResponse) { - if ([cachedResponse.response isKindOfClass:[NSHTTPURLResponse class]]) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)cachedResponse.response; - if (httpResponse.statusCode == 301 || httpResponse.statusCode == 302) { - NSString *location = httpResponse.allHeaderFields[@"Location"]; - if (location == nil) { - completionHandler(RCTErrorWithMessage(@"Image redirect without location"), nil); - return; - } - - NSURL *redirectURL = [NSURL URLWithString: location relativeToURL: request.URL]; - request = [NSURLRequest requestWithURL:redirectURL]; - cachedResponse = [_URLCache cachedResponseForRequest:request]; - continue; - } - } + // Call handler + completionHandler(nil, data, responseDate); + }; - processResponse(cachedResponse.response, cachedResponse.data, nil); + // Download image + __weak __typeof(self) weakSelf = self; + RCTNetworkTask *task = [networking networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { + __typeof(self) strongSelf = weakSelf; + if (!strongSelf) { return; } - // Download image - RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { + if (error || !response || !data) { + NSError *someError = nil; if (error) { - completionHandler(error, nil); - [weakSelf dequeueTasks]; - return; + someError = error; + } else if (!response) { + someError = RCTErrorWithMessage(@"Response metadata error"); + } else { + someError = RCTErrorWithMessage(@"Unknown image download error"); } + completionHandler(someError, nil, nil); + [strongSelf dequeueTasks]; + return; + } - dispatch_async(_URLCacheQueue, ^{ + dispatch_async(strongSelf->_URLRequestQueue, ^{ + // Process image data + processResponse(response, data, nil); - // Cache the response - // TODO: move URL cache out of RCTImageLoader into its own module - BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"]; - [strongSelf->_URLCache storeCachedResponse: - [[NSCachedURLResponse alloc] initWithResponse:response - data:data - userInfo:nil - storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly] - forRequest:request]; - // Process image data - processResponse(response, data, nil); + // Prepare for next task + [strongSelf dequeueTasks]; + }); - // Prepare for next task - [weakSelf dequeueTasks]; + }]; - }); - - }]; - task.downloadProgressBlock = progressHandler; + task.downloadProgressBlock = progressHandler; + if (task) { if (!_pendingTasks) { _pendingTasks = [NSMutableArray new]; } - if (task) { - [_pendingTasks addObject:task]; - [weakSelf dequeueTasks]; - } - - cancelLoad = ^{ - [task cancel]; - [weakSelf dequeueTasks]; - }; - - }); + [_pendingTasks addObject:task]; + [self dequeueTasks]; + } return ^{ - if (cancelLoad) { - cancelLoad(); - } - OSAtomicOr32Barrier(1, &cancelled); + [task cancel]; + [weakSelf dequeueTasks]; }; } @@ -480,51 +496,52 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image __block void(^cancelLoad)(void) = nil; __weak RCTImageLoader *weakSelf = self; - // Check decoded image cache - NSString *cacheKey = RCTCacheKeyForImage(imageURLRequest.URL.absoluteString, size, scale, resizeMode); - { - UIImage *image = [_decodedImageCache objectForKey:cacheKey]; - if (image) { - // Most loaders do not return on the main thread, so caller is probably not - // expecting it, and may do expensive post-processing in the callback - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - completionBlock(nil, image); - }); - return ^{}; + void (^completionHandler)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *cacheKey) { + __typeof(self) strongSelf = weakSelf; + if (cancelled || !strongSelf) { + return; } - } - RCTImageLoaderCompletionBlock cacheResultHandler = ^(NSError *error, UIImage *image) { - if (image) { - CGFloat bytes = image.size.width * image.size.height * image.scale * image.scale * 4; - if (bytes <= RCTMaxCachableDecodedImageSizeInBytes) { - [_decodedImageCache setObject:image forKey:cacheKey cost:bytes]; - } + if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { + completionBlock(error, imageOrData); + return; } - completionBlock(error, image); - }; - void (^completionHandler)(NSError *, id) = ^(NSError *error, id imageOrData) { - if (!cancelled) { - if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { - cacheResultHandler(error, imageOrData); - } else { - cancelLoad = [weakSelf decodeImageData:imageOrData - size:size - scale:scale - clipped:clipped - resizeMode:resizeMode - completionBlock:cacheResultHandler]; + // Check decoded image cache + if (cacheKey) { + UIImage *image = [strongSelf->_decodedImageCache objectForKey:cacheKey]; + if (image) { + completionBlock(nil, image); + return; } } + + // Store decoded image in cache + RCTImageLoaderCompletionBlock cacheResultHandler = ^(NSError *error_, UIImage *image) { + if (image) { + CGFloat bytes = image.size.width * image.size.height * image.scale * image.scale * 4; + if (bytes <= RCTMaxCachableDecodedImageSizeInBytes) { + [self->_decodedImageCache setObject:image forKey:cacheKey cost:bytes]; + } + } + + completionBlock(error_, image); + }; + + cancelLoad = [weakSelf decodeImageData:imageOrData + size:size + scale:scale + clipped:clipped + resizeMode:resizeMode + completionBlock:cacheKey ? cacheResultHandler: completionBlock]; }; - cancelLoad = [self loadImageOrDataWithURLRequest:imageURLRequest - size:size - scale:scale - resizeMode:resizeMode - progressBlock:progressHandler - completionBlock:completionHandler]; + cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest + size:size + scale:scale + resizeMode:resizeMode + progressBlock:progressHandler + completionBlock:completionHandler]; return ^{ if (cancelLoad) { cancelLoad(); @@ -568,74 +585,69 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data resizeMode:resizeMode completionHandler:completionHandler] ?: ^{}; } else { + dispatch_block_t decodeBlock = ^{ + // Calculate the size, in bytes, that the decompressed image will require + NSInteger decodedImageBytes = (size.width * scale) * (size.height * scale) * 4; - if (!_URLCacheQueue) { - [self setUp]; - } - dispatch_async(_URLCacheQueue, ^{ - dispatch_block_t decodeBlock = ^{ - - // Calculate the size, in bytes, that the decompressed image will require - NSInteger decodedImageBytes = (size.width * scale) * (size.height * scale) * 4; + // Mark these bytes as in-use + self->_activeBytes += decodedImageBytes; - // Mark these bytes as in-use - _activeBytes += decodedImageBytes; - - // Do actual decompression on a concurrent background queue - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (!cancelled) { + // Do actual decompression on a concurrent background queue + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + if (!cancelled) { - // Decompress the image data (this may be CPU and memory intensive) - UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode); + // Decompress the image data (this may be CPU and memory intensive) + UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode); #if RCT_DEV - - CGSize imagePixelSize = RCTSizeInPixels(image.size, image.scale); - CGSize screenPixelSize = RCTSizeInPixels(RCTScreenSize(), RCTScreenScale()); - if (imagePixelSize.width * imagePixelSize.height > - screenPixelSize.width * screenPixelSize.height) { - RCTLogInfo(@"[PERF ASSETS] Loading image at size %@, which is larger " - "than the screen size %@", NSStringFromCGSize(imagePixelSize), - NSStringFromCGSize(screenPixelSize)); - } - + CGSize imagePixelSize = RCTSizeInPixels(image.size, image.scale); + CGSize screenPixelSize = RCTSizeInPixels(RCTScreenSize(), RCTScreenScale()); + if (imagePixelSize.width * imagePixelSize.height > + screenPixelSize.width * screenPixelSize.height) { + RCTLogInfo(@"[PERF ASSETS] Loading image at size %@, which is larger " + "than the screen size %@", NSStringFromCGSize(imagePixelSize), + NSStringFromCGSize(screenPixelSize)); + } #endif - if (image) { - completionHandler(nil, image); - } else { - NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length]; - NSError *finalError = RCTErrorWithMessage(errorMessage); - completionHandler(finalError, nil); - } + if (image) { + completionHandler(nil, image); + } else { + NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length]; + NSError *finalError = RCTErrorWithMessage(errorMessage); + completionHandler(finalError, nil); } + } - // We're no longer retaining the uncompressed data, so now we'll mark - // the decoding as complete so that the loading task queue can resume. - dispatch_async(_URLCacheQueue, ^{ - _scheduledDecodes--; - _activeBytes -= decodedImageBytes; - [self dequeueTasks]; - }); + // We're no longer retaining the uncompressed data, so now we'll mark + // the decoding as complete so that the loading task queue can resume. + dispatch_async(self->_URLRequestQueue, ^{ + self->_scheduledDecodes--; + self->_activeBytes -= decodedImageBytes; + [self dequeueTasks]; }); - }; + }); + }; + if (!_URLRequestQueue) { + [self setUp]; + } + dispatch_async(_URLRequestQueue, ^{ // The decode operation retains the compressed image data until it's // complete, so we'll mark it as having started, in order to block // further image loads from happening until we're done with the data. - _scheduledDecodes++; + self->_scheduledDecodes++; - if (!_pendingDecodes) { - _pendingDecodes = [NSMutableArray new]; + if (!self->_pendingDecodes) { + self->_pendingDecodes = [NSMutableArray new]; } - NSInteger activeDecodes = _scheduledDecodes - _pendingDecodes.count - 1; - if (activeDecodes == 0 || (_activeBytes <= _maxConcurrentDecodingBytes && - activeDecodes <= _maxConcurrentDecodingTasks)) { + NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count - 1; + if (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes && + activeDecodes <= self->_maxConcurrentDecodingTasks)) { decodeBlock(); } else { - [_pendingDecodes addObject:decodeBlock]; + [self->_pendingDecodes addObject:decodeBlock]; } - }); return ^{ @@ -645,30 +657,32 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data } - (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest - block:(void(^)(NSError *error, CGSize size))completionBlock + block:(void(^)(NSError *error, CGSize size))callback { - return [self loadImageOrDataWithURLRequest:imageURLRequest - size:CGSizeZero - scale:1 - resizeMode:RCTResizeModeStretch - progressBlock:nil - completionBlock:^(NSError *error, id imageOrData) { - CGSize size; - if ([imageOrData isKindOfClass:[NSData class]]) { - NSDictionary *meta = RCTGetImageMetadata(imageOrData); - size = (CGSize){ - [meta[(id)kCGImagePropertyPixelWidth] doubleValue], - [meta[(id)kCGImagePropertyPixelHeight] doubleValue], - }; - } else { - UIImage *image = imageOrData; - size = (CGSize){ - image.size.width * image.scale, - image.size.height * image.scale, - }; - } - completionBlock(error, size); - }]; + void (^completion)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *cacheKey) { + CGSize size; + if ([imageOrData isKindOfClass:[NSData class]]) { + NSDictionary *meta = RCTGetImageMetadata(imageOrData); + size = (CGSize){ + [meta[(id)kCGImagePropertyPixelWidth] doubleValue], + [meta[(id)kCGImagePropertyPixelHeight] doubleValue], + }; + } else { + UIImage *image = imageOrData; + size = (CGSize){ + image.size.width * image.scale, + image.size.height * image.scale, + }; + } + callback(error, size); + }; + + return [self _loadImageOrDataWithURLRequest:imageURLRequest + size:CGSizeZero + scale:1 + resizeMode:RCTResizeModeStretch + progressBlock:NULL + completionBlock:completion]; } #pragma mark - RCTURLRequestHandler @@ -729,90 +743,6 @@ - (void)cancelRequest:(id)requestToken @end -@implementation RCTImageLoader (Deprecated) - -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - callback:(RCTImageLoaderCompletionBlock)callback -{ - RCTLogWarn(@"[RCTImageLoader loadImageWithTag:callback:] is deprecated. Instead use [RCTImageLoader loadImageWithURLRequest:callback:]"); - return [self loadImageWithURLRequest:[RCTConvert NSURLRequest:imageTag] - callback:callback]; -} - -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressBlock - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -{ - RCTLogWarn(@"[RCTImageLoader loadImageWithTag:size:scale:resizeMode:progressBlock:completionBlock:] is deprecated. Instead use [RCTImageLoader loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock:]"); - return [self loadImageWithURLRequest:[RCTConvert NSURLRequest:imageTag] - size:size - scale:scale - clipped:YES - resizeMode:resizeMode - progressBlock:progressBlock - completionBlock:completionBlock]; -} - -- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressBlock - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -{ - RCTLogWarn(@"[RCTImageLoader loadImageWithoutClipping:size:scale:resizeMode:progressBlock:completionBlock:] is deprecated. Instead use [RCTImageLoader loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock:]"); - return [self loadImageWithURLRequest:[RCTConvert NSURLRequest:imageTag] - size:size - scale:scale - clipped:NO - resizeMode:resizeMode - progressBlock:progressBlock - completionBlock:completionBlock]; -} - -- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -{ - RCTLogWarn(@"[RCTImageLoader decodeImageData:size:scale:resizeMode:completionBlock:] is deprecated. Instead use [RCTImageLoader decodeImageData:size:scale:clipped:resizeMode:completionBlock:]"); - return [self decodeImageData:imageData - size:size - scale:scale - clipped:NO - resizeMode:resizeMode - completionBlock:completionBlock]; -} - -- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)imageData - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -{ - RCTLogWarn(@"[RCTImageLoader decodeImageDataWithoutClipping:size:scale:resizeMode:completionBlock:] is deprecated. Instead use [RCTImageLoader decodeImageData:size:scale:clipped:resizeMode:completionBlock:]"); - return [self decodeImageData:imageData - size:size - scale:scale - clipped:NO - resizeMode:resizeMode - completionBlock:completionBlock]; -} - -- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag - block:(void(^)(NSError *error, CGSize size))completionBlock -{ - RCTLogWarn(@"[RCTImageLoader getImageSize:block:] is deprecated. Instead use [RCTImageLoader getImageSizeForURLRequest:block:]"); - return [self getImageSizeForURLRequest:[RCTConvert NSURLRequest:imageTag] - block:completionBlock]; -} - -@end - @implementation RCTBridge (RCTImageLoader) - (RCTImageLoader *)imageLoader diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m index 32afcee20f8647..c33ee08e428d65 100644 --- a/Libraries/Image/RCTImageStoreManager.m +++ b/Libraries/Image/RCTImageStoreManager.m @@ -23,7 +23,7 @@ @implementation RCTImageStoreManager { NSMutableDictionary<NSString *, NSData *> *_store; - NSUInteger *_id; + NSUInteger _id; } @synthesize methodQueue = _methodQueue; @@ -66,7 +66,7 @@ - (void)getImageDataForTag:(NSString *)imageTag withBlock:(void (^)(NSData *imag { RCTAssertParam(block); dispatch_async(_methodQueue, ^{ - block(_store[imageTag]); + block(self->_store[imageTag]); }); } @@ -117,7 +117,7 @@ - (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))bloc dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; if (imageData) { - dispatch_async(_methodQueue, ^{ + dispatch_async(self->_methodQueue, ^{ successCallback(@[[self _storeImageData:imageData]]); }); } else { @@ -147,7 +147,7 @@ - (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate } NSString *imageTag = request.URL.absoluteString; - NSData *imageData = _store[imageTag]; + NSData *imageData = self->_store[imageTag]; if (!imageData) { NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]); [delegate URLRequest:cancellationBlock didCompleteWithError:error]; @@ -206,7 +206,7 @@ - (UIImage *)imageForTag:(NSString *)imageTag RCTLogWarn(@"RCTImageStoreManager.imageForTag() is deprecated and has poor performance. Use an alternative method instead."); __block NSData *imageData; dispatch_sync(_methodQueue, ^{ - imageData = _store[imageTag]; + imageData = self->_store[imageTag]; }); return [UIImage imageWithData:imageData]; } @@ -215,7 +215,7 @@ - (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image)) { RCTAssertParam(block); dispatch_async(_methodQueue, ^{ - NSData *imageData = _store[imageTag]; + NSData *imageData = self->_store[imageTag]; dispatch_async(dispatch_get_main_queue(), ^{ // imageWithData: is not thread-safe, so we can't do this on methodQueue block([UIImage imageWithData:imageData]); diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m index 515948a46e8e34..ce7a5530bbaf8c 100644 --- a/Libraries/Image/RCTImageUtils.m +++ b/Libraries/Image/RCTImageUtils.m @@ -53,9 +53,10 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, destSize.height = destSize.width / aspect; } - // Calculate target aspect ratio if needed (don't bother if resizeMode == stretch) + // Calculate target aspect ratio if needed CGFloat targetAspect = 0.0; - if (resizeMode != UIViewContentModeScaleToFill) { + if (resizeMode != RCTResizeModeCenter && + resizeMode != RCTResizeModeStretch) { targetAspect = destSize.width / destSize.height; if (aspect == targetAspect) { resizeMode = RCTResizeModeStretch; @@ -72,12 +73,12 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, if (targetAspect <= aspect) { // target is taller than content - sourceSize.width = destSize.width = destSize.width; + sourceSize.width = destSize.width; sourceSize.height = sourceSize.width / aspect; } else { // target is wider than content - sourceSize.height = destSize.height = destSize.height; + sourceSize.height = destSize.height; sourceSize.width = sourceSize.height * aspect; } return (CGRect){ @@ -92,7 +93,7 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, if (targetAspect <= aspect) { // target is taller than content - sourceSize.height = destSize.height = destSize.height; + sourceSize.height = destSize.height; sourceSize.width = sourceSize.height * aspect; destSize.width = destSize.height * targetAspect; return (CGRect){ @@ -102,7 +103,7 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, } else { // target is wider than content - sourceSize.width = destSize.width = destSize.width; + sourceSize.width = destSize.width; sourceSize.height = sourceSize.width / aspect; destSize.height = destSize.width / targetAspect; return (CGRect){ @@ -110,6 +111,26 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, RCTCeilSize(sourceSize, destScale) }; } + + case RCTResizeModeCenter: + + // Make sure the image is not clipped by the target. + if (sourceSize.height > destSize.height) { + sourceSize.width = destSize.width; + sourceSize.height = sourceSize.width / aspect; + } + if (sourceSize.width > destSize.width) { + sourceSize.height = destSize.height; + sourceSize.width = sourceSize.height * aspect; + } + + return (CGRect){ + { + RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale), + RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale), + }, + RCTCeilSize(sourceSize, destScale) + }; } } @@ -131,6 +152,10 @@ CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale, BOOL allowUpscaling) { switch (resizeMode) { + case RCTResizeModeCenter: + + return RCTTargetRect(sourceSize, destSize, destScale, resizeMode).size; + case RCTResizeModeStretch: if (!allowUpscaling) { @@ -207,6 +232,7 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale, } case RCTResizeModeRepeat: + case RCTResizeModeCenter: return NO; } diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 48cfca6d8e2d91..51c78d9d4de079 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -216,7 +216,7 @@ - (void)reloadImage RCTImageLoaderProgressBlock progressHandler = nil; if (_onProgress) { progressHandler = ^(int64_t loaded, int64_t total) { - _onProgress(@{ + self->_onProgress(@{ @"loaded": @((double)loaded), @"total": @((double)total), }); diff --git a/Libraries/Image/RCTXCAssetImageLoader.h b/Libraries/Image/RCTLocalAssetImageLoader.h similarity index 83% rename from Libraries/Image/RCTXCAssetImageLoader.h rename to Libraries/Image/RCTLocalAssetImageLoader.h index ca982bf03633ef..bf6e5c675a9309 100644 --- a/Libraries/Image/RCTXCAssetImageLoader.h +++ b/Libraries/Image/RCTLocalAssetImageLoader.h @@ -9,6 +9,6 @@ #import "RCTImageLoader.h" -@interface RCTXCAssetImageLoader : NSObject <RCTImageURLLoader> +@interface RCTLocalAssetImageLoader : NSObject <RCTImageURLLoader> @end diff --git a/Libraries/Image/RCTXCAssetImageLoader.m b/Libraries/Image/RCTLocalAssetImageLoader.m similarity index 75% rename from Libraries/Image/RCTXCAssetImageLoader.m rename to Libraries/Image/RCTLocalAssetImageLoader.m index 750a1f848ed1f7..5eced698722101 100644 --- a/Libraries/Image/RCTXCAssetImageLoader.m +++ b/Libraries/Image/RCTLocalAssetImageLoader.m @@ -7,19 +7,33 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTXCAssetImageLoader.h" +#import "RCTLocalAssetImageLoader.h" #import <libkern/OSAtomic.h> #import "RCTUtils.h" -@implementation RCTXCAssetImageLoader +@implementation RCTLocalAssetImageLoader RCT_EXPORT_MODULE() - (BOOL)canLoadImageURL:(NSURL *)requestURL { - return RCTIsXCAssetURL(requestURL); + return RCTIsLocalAssetURL(requestURL); +} + +- (BOOL)requiresScheduling +{ + // Don't schedule this loader on the URL queue so we can load the + // local assets synchronously to avoid flickers. + return NO; +} + +- (BOOL)shouldCacheLoadedImages +{ + // UIImage imageNamed handles the caching automatically so we don't want + // to add it to the image cache. + return NO; } - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL @@ -30,11 +44,11 @@ - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL completionHandler:(RCTImageLoaderCompletionBlock)completionHandler { __block volatile uint32_t cancelled = 0; - dispatch_async(dispatch_get_main_queue(), ^{ - + RCTExecuteOnMainQueue(^{ if (cancelled) { return; } + NSString *imageName = RCTBundlePathForURL(imageURL); UIImage *image = [UIImage imageNamed:imageName]; if (image) { diff --git a/Libraries/Image/RCTResizeMode.h b/Libraries/Image/RCTResizeMode.h index 4f623eac72f73d..2b791454243945 100644 --- a/Libraries/Image/RCTResizeMode.h +++ b/Libraries/Image/RCTResizeMode.h @@ -13,6 +13,7 @@ typedef NS_ENUM(NSInteger, RCTResizeMode) { RCTResizeModeCover = UIViewContentModeScaleAspectFill, RCTResizeModeContain = UIViewContentModeScaleAspectFit, RCTResizeModeStretch = UIViewContentModeScaleToFill, + RCTResizeModeCenter = UIViewContentModeCenter, RCTResizeModeRepeat = -1, // Use negative values to avoid conflicts with iOS enum values. }; diff --git a/Libraries/Image/RCTResizeMode.m b/Libraries/Image/RCTResizeMode.m index 85f5766d631d35..6142fc6ab1fb73 100644 --- a/Libraries/Image/RCTResizeMode.m +++ b/Libraries/Image/RCTResizeMode.m @@ -15,6 +15,7 @@ @implementation RCTConvert(RCTResizeMode) @"cover": @(RCTResizeModeCover), @"contain": @(RCTResizeModeContain), @"stretch": @(RCTResizeModeStretch), + @"center": @(RCTResizeModeCenter), @"repeat": @(RCTResizeModeRepeat), }), RCTResizeModeStretch, integerValue) diff --git a/Libraries/Inspector/ElementProperties.js b/Libraries/Inspector/ElementProperties.js index 9170a62f17e16d..a8e7cb37fc5afe 100644 --- a/Libraries/Inspector/ElementProperties.js +++ b/Libraries/Inspector/ElementProperties.js @@ -24,7 +24,7 @@ var {fetch} = require('fetch'); var flattenStyle = require('flattenStyle'); var mapWithSeparator = require('mapWithSeparator'); -var getDevServer = require('getDevServer'); +var openFileInEditor = require('openFileInEditor'); var ElementProperties = React.createClass({ propTypes: { @@ -52,7 +52,7 @@ var ElementProperties = React.createClass({ openFileButton = ( <TouchableHighlight style={styles.openButton} - onPress={this._openFile.bind(null, fileName, lineNumber)}> + onPress={openFileInEditor.bind(null, fileName, lineNumber)}> <Text style={styles.openButtonTitle} numberOfLines={1}> {fileNameShort}:{lineNumber} </Text> @@ -95,13 +95,6 @@ var ElementProperties = React.createClass({ </TouchableWithoutFeedback> ); }, - - _openFile: function(fileName: string, lineNumber: number) { - fetch(getDevServer().url + 'open-stack-frame', { - method: 'POST', - body: JSON.stringify({file: fileName, lineNumber}), - }); - }, }); function getInstanceName(instance) { diff --git a/Libraries/Interaction/PanResponder.js b/Libraries/Interaction/PanResponder.js index a5ad0af42b0bd2..0692ffbfcb66e0 100644 --- a/Libraries/Interaction/PanResponder.js +++ b/Libraries/Interaction/PanResponder.js @@ -116,7 +116,7 @@ const currentCentroidY = TouchHistoryMath.currentCentroidY; * ### Working Example * * To see it in action, try the - * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js) + * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/js/PanResponderExample.js) */ const PanResponder = { @@ -388,7 +388,12 @@ const PanResponder = { config.onPanResponderTerminationRequest(e, gestureState); } }; - return { panHandlers: panHandlers }; + return { + panHandlers, + getInteractionHandle(): ?number { + return interactionState.handle; + }, + }; } }; diff --git a/Libraries/Interaction/TaskQueue.js b/Libraries/Interaction/TaskQueue.js index 33838b15a15378..660241fb35bc5a 100644 --- a/Libraries/Interaction/TaskQueue.js +++ b/Libraries/Interaction/TaskQueue.js @@ -15,12 +15,12 @@ const infoLog = require('infoLog'); const invariant = require('fbjs/lib/invariant'); type SimpleTask = { - name: string; - run: () => void; + name: string, + run: () => void, }; type PromiseTask = { - name: string; - gen: () => Promise<any>; + name: string, + gen: () => Promise<any>, }; export type Task = Function | SimpleTask | PromiseTask; @@ -75,7 +75,7 @@ class TaskQueue { ...queue, tasks: queue.tasks.filter((task) => tasksToCancel.indexOf(task) === -1), })) - .filter((queue) => queue.tasks.length > 0); + .filter((queue, idx) => (queue.tasks.length > 0 || idx === 0)); } /** @@ -151,7 +151,10 @@ class TaskQueue { DEBUG && infoLog('exec gen task ' + task.name); task.gen() .then(() => { - DEBUG && infoLog('onThen for gen task ' + task.name, {stackIdx, queueStackSize: this._queueStack.length}); + DEBUG && infoLog( + 'onThen for gen task ' + task.name, + {stackIdx, queueStackSize: this._queueStack.length}, + ); this._queueStack[stackIdx].popable = true; this.hasTasksToProcess() && this._onMoreTasks(); }) diff --git a/Libraries/Interaction/__tests__/TaskQueue-test.js b/Libraries/Interaction/__tests__/TaskQueue-test.js index 1da2e4430f2320..e1a24009f01e57 100644 --- a/Libraries/Interaction/__tests__/TaskQueue-test.js +++ b/Libraries/Interaction/__tests__/TaskQueue-test.js @@ -142,4 +142,13 @@ describe('TaskQueue', () => { expectToBeCalledOnce(task4); expect(taskQueue.hasTasksToProcess()).toBe(false); }); + + it('should not crash when last task is cancelled', () => { + const task1 = jest.fn(); + taskQueue.enqueue(task1); + taskQueue.cancelTasks([task1]); + clearTaskQueue(taskQueue); + expect(task1).not.toBeCalled(); + expect(taskQueue.hasTasksToProcess()).toBe(false); + }); }); diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 60bbb7e795f16b..60b574709896f1 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -144,6 +144,8 @@ function setUpTimers(): void { defineLazyTimer('clearImmediate'); defineLazyTimer('requestAnimationFrame'); defineLazyTimer('cancelAnimationFrame'); + defineLazyTimer('requestIdleCallback'); + defineLazyTimer('cancelIdleCallback'); } function setUpAlert(): void { diff --git a/Libraries/JavaScriptAppEngine/Initialization/openFileInEditor.js b/Libraries/JavaScriptAppEngine/Initialization/openFileInEditor.js new file mode 100644 index 00000000000000..650cb568340c4f --- /dev/null +++ b/Libraries/JavaScriptAppEngine/Initialization/openFileInEditor.js @@ -0,0 +1,23 @@ +/** + * 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. + * + * @providesModule openFileInEditor + * @flow + */ +'use strict'; + +const getDevServer = require('getDevServer'); + +function openFileInEditor(file: string, lineNumber: number) { + fetch(getDevServer().url + 'open-stack-frame', { + method: 'POST', + body: JSON.stringify({file, lineNumber}), + }); +} + +module.exports = openFileInEditor; diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js index 0ec0a4c08c2816..75b1e23be9c70d 100644 --- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js +++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js @@ -96,6 +96,36 @@ var JSTimers = { return newID; }, + /** + * @param {function} func Callback to be invoked every frame and provided + * with time remaining in frame. + */ + requestIdleCallback: function(func) { + if (JSTimersExecution.requestIdleCallbacks.length === 0) { + RCTTiming.setSendIdleEvents(true); + } + + var newID = JSTimersExecution.GUID++; + var freeIndex = JSTimers._getFreeIndex(); + JSTimersExecution.timerIDs[freeIndex] = newID; + JSTimersExecution.callbacks[freeIndex] = func; + JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.requestIdleCallback; + JSTimersExecution.requestIdleCallbacks.push(newID); + return newID; + }, + + cancelIdleCallback: function(timerID) { + JSTimers._clearTimerID(timerID); + var index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID); + if (index !== -1) { + JSTimersExecution.requestIdleCallbacks.splice(index, 1); + } + + if (JSTimersExecution.requestIdleCallbacks.length === 0) { + RCTTiming.setSendIdleEvents(false); + } + }, + clearTimeout: function(timerID) { JSTimers._clearTimerID(timerID); }, @@ -127,7 +157,9 @@ var JSTimers = { // See corresponding comment in `callTimers` for reasoning behind this if (index !== -1) { JSTimersExecution._clearIndex(index); - if (JSTimersExecution.types[index] !== JSTimersExecution.Type.setImmediate) { + var type = JSTimersExecution.types[index]; + if (type !== JSTimersExecution.Type.setImmediate && + type !== JSTimersExecution.Type.requestIdleCallback) { RCTTiming.deleteTimer(timerID); } } diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js index df1b609723e6dd..1460187719447e 100644 --- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js +++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js @@ -10,24 +10,33 @@ */ 'use strict'; -var invariant = require('fbjs/lib/invariant'); -var keyMirror = require('fbjs/lib/keyMirror'); -var performanceNow = require('fbjs/lib/performanceNow'); -var warning = require('fbjs/lib/warning'); -var Systrace = require('Systrace'); +const Systrace = require('Systrace'); + +const invariant = require('fbjs/lib/invariant'); +const keyMirror = require('fbjs/lib/keyMirror'); +const performanceNow = require('fbjs/lib/performanceNow'); +const warning = require('fbjs/lib/warning'); + +// These timing contants should be kept in sync with the ones in native ios and +// android `RCTTiming` module. +const FRAME_DURATION = 1000 / 60; +const IDLE_CALLBACK_FRAME_DEADLINE = 1; + +let hasEmittedTimeDriftWarning = false; /** * JS implementation of timer functions. Must be completely driven by an * external clock signal, all that's stored here is timerID, timer type, and * callback. */ -var JSTimersExecution = { +const JSTimersExecution = { GUID: 1, Type: keyMirror({ setTimeout: null, setInterval: null, requestAnimationFrame: null, setImmediate: null, + requestIdleCallback: null, }), // Parallel arrays: @@ -35,15 +44,20 @@ var JSTimersExecution = { types: [], timerIDs: [], immediates: [], + requestIdleCallbacks: [], /** * Calls the callback associated with the ID. Also unregister that callback * if it was a one time timer (setTimeout), and not unregister it if it was * recurring (setInterval). */ - callTimer: function(timerID) { - warning(timerID <= JSTimersExecution.GUID, 'Tried to call timer with ID ' + timerID + ' but no such timer exists'); - var timerIndex = JSTimersExecution.timerIDs.indexOf(timerID); + callTimer(timerID, frameTime) { + warning( + timerID <= JSTimersExecution.GUID, + 'Tried to call timer with ID %s but no such timer exists.', + timerID + ); + const timerIndex = JSTimersExecution.timerIDs.indexOf(timerID); // timerIndex of -1 means that no timer with that ID exists. There are // two situations when this happens, when a garbage timer ID was given // and when a previously existing timer was deleted before this callback @@ -52,13 +66,14 @@ var JSTimersExecution = { if (timerIndex === -1) { return; } - var type = JSTimersExecution.types[timerIndex]; - var callback = JSTimersExecution.callbacks[timerIndex]; + const type = JSTimersExecution.types[timerIndex]; + const callback = JSTimersExecution.callbacks[timerIndex]; // Clear the metadata if (type === JSTimersExecution.Type.setTimeout || type === JSTimersExecution.Type.setImmediate || - type === JSTimersExecution.Type.requestAnimationFrame) { + type === JSTimersExecution.Type.requestAnimationFrame || + type === JSTimersExecution.Type.requestIdleCallback) { JSTimersExecution._clearIndex(timerIndex); } @@ -68,8 +83,17 @@ var JSTimersExecution = { type === JSTimersExecution.Type.setImmediate) { callback(); } else if (type === JSTimersExecution.Type.requestAnimationFrame) { - var currentTime = performanceNow(); + const currentTime = performanceNow(); callback(currentTime); + } else if (type === JSTimersExecution.Type.requestIdleCallback) { + callback({ + timeRemaining: function() { + // TODO: Optimisation: allow running for longer than one frame if + // there are no pending JS calls on the bridge from native. This + // would require a way to check the bridge queue synchronously. + return Math.max(0, FRAME_DURATION - (performanceNow() - frameTime)); + }, + }); } else { console.error('Tried to call a callback with invalid type: ' + type); return; @@ -85,19 +109,22 @@ var JSTimersExecution = { * This is called from the native side. We are passed an array of timerIDs, * and */ - callTimers: function(timerIDs) { - invariant(timerIDs.length !== 0, 'Probably shouldn\'t call "callTimers" with no timerIDs'); + callTimers(timerIDs) { + invariant( + timerIDs.length !== 0, + 'Cannot call `callTimers` with an empty list of IDs.' + ); JSTimersExecution.errors = null; - timerIDs.forEach(JSTimersExecution.callTimer); + timerIDs.forEach((id) => { JSTimersExecution.callTimer(id); }); - var errors = JSTimersExecution.errors; + const errors = JSTimersExecution.errors; if (errors) { - var errorCount = errors.length; + const errorCount = errors.length; if (errorCount > 1) { // Throw all the other errors in a setTimeout, which will throw each // error one at a time - for (var ii = 1; ii < errorCount; ii++) { + for (let ii = 1; ii < errorCount; ii++) { require('JSTimers').setTimeout( ((error) => { throw error; }).bind(null, errors[ii]), 0 @@ -108,22 +135,51 @@ var JSTimersExecution = { } }, + callIdleCallbacks: function(frameTime) { + const { Timing } = require('NativeModules'); + + if (FRAME_DURATION - (performanceNow() - frameTime) < IDLE_CALLBACK_FRAME_DEADLINE) { + return; + } + + JSTimersExecution.errors = null; + + if (JSTimersExecution.requestIdleCallbacks.length > 0) { + const passIdleCallbacks = JSTimersExecution.requestIdleCallbacks.slice(); + JSTimersExecution.requestIdleCallbacks = []; + + for (let i = 0; i < passIdleCallbacks.length; ++i) { + JSTimersExecution.callTimer(passIdleCallbacks[i], frameTime); + } + } + + if (JSTimersExecution.requestIdleCallbacks.length === 0) { + Timing.setSendIdleEvents(false); + } + + if (JSTimersExecution.errors) { + JSTimersExecution.errors.forEach((error) => + require('JSTimers').setTimeout(() => { throw error; }, 0) + ); + } + }, + /** * Performs a single pass over the enqueued immediates. Returns whether * more immediates are queued up (can be used as a condition a while loop). */ - callImmediatesPass: function() { + callImmediatesPass() { Systrace.beginEvent('JSTimersExecution.callImmediatesPass()'); // The main reason to extract a single pass is so that we can track // in the system trace if (JSTimersExecution.immediates.length > 0) { - var passImmediates = JSTimersExecution.immediates.slice(); + const passImmediates = JSTimersExecution.immediates.slice(); JSTimersExecution.immediates = []; // Use for loop rather than forEach as per @vjeux's advice // https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051 - for (var i = 0; i < passImmediates.length; ++i) { + for (let i = 0; i < passImmediates.length; ++i) { JSTimersExecution.callTimer(passImmediates[i]); } } @@ -137,7 +193,7 @@ var JSTimersExecution = { * This is called after we execute any command we receive from native but * before we hand control back to native. */ - callImmediates: function() { + callImmediates() { JSTimersExecution.errors = null; while (JSTimersExecution.callImmediatesPass()) {} if (JSTimersExecution.errors) { @@ -147,7 +203,18 @@ var JSTimersExecution = { } }, - _clearIndex: function(i) { + /** + * Called from native (in development) when environment times are out-of-sync. + */ + emitTimeDriftWarning(warningMessage) { + if (hasEmittedTimeDriftWarning) { + return; + } + hasEmittedTimeDriftWarning = true; + console.warn(warningMessage); + }, + + _clearIndex(i) { JSTimersExecution.timerIDs[i] = null; JSTimersExecution.callbacks[i] = null; JSTimersExecution.types[i] = null; diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 076123468e828f..b91200132b4ab3 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -11,6 +11,7 @@ */ 'use strict'; +const I18nManager = require('I18nManager'); const Platform = require('Platform'); const PropTypes = require('react/lib/ReactPropTypes'); const React = require('React'); @@ -157,13 +158,14 @@ class Modal extends React.Component { } } +const side = I18nManager.isRTL ? 'right' : 'left'; const styles = StyleSheet.create({ modal: { position: 'absolute', }, container: { position: 'absolute', - left: 0, + [side] : 0, top: 0, } }); diff --git a/Libraries/NavigationExperimental/NavigationTransitioner.js b/Libraries/NavigationExperimental/NavigationTransitioner.js index d3aabc5a35a5d8..e0eb6cdc8348ec 100644 --- a/Libraries/NavigationExperimental/NavigationTransitioner.js +++ b/Libraries/NavigationExperimental/NavigationTransitioner.js @@ -125,9 +125,6 @@ class NavigationTransitioner extends React.Component<any, Props, State> { progress, } = nextState; - // update scenes. - this.setState(nextState); - // get the transition spec. const transitionUserSpec = nextProps.configureTransition ? nextProps.configureTransition( @@ -168,12 +165,14 @@ class NavigationTransitioner extends React.Component<any, Props, State> { ); } - // play the transition. - nextProps.onTransitionStart && nextProps.onTransitionStart( - this._transitionProps, - this._prevTransitionProps, - ); - Animated.parallel(animations).start(this._onTransitionEnd); + // update scenes and play the transition + this.setState(nextState, () => { + nextProps.onTransitionStart && nextProps.onTransitionStart( + this._transitionProps, + this._prevTransitionProps, + ); + Animated.parallel(animations).start(this._onTransitionEnd); + }); } render(): ReactElement<any> { diff --git a/Libraries/NavigationExperimental/NavigationTypeDefinition.js b/Libraries/NavigationExperimental/NavigationTypeDefinition.js index 1c106a54350344..b82efe3a13fbc5 100644 --- a/Libraries/NavigationExperimental/NavigationTypeDefinition.js +++ b/Libraries/NavigationExperimental/NavigationTypeDefinition.js @@ -70,6 +70,9 @@ export type NavigationTransitionProps = { // The active scene, corresponding to the route at // `navigationState.routes[navigationState.index]`. scene: NavigationScene, + + // The gesture distance for `horizontal` and `vertical` transitions + gestureResponseDistance?: ?number, }; // Similar to `NavigationTransitionProps`, except that the prop `scene` diff --git a/Libraries/Network/RCTFileRequestHandler.m b/Libraries/Network/RCTFileRequestHandler.m index 348f46d97c89c8..214a6eb364e95c 100644 --- a/Libraries/Network/RCTFileRequestHandler.m +++ b/Libraries/Network/RCTFileRequestHandler.m @@ -30,7 +30,7 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request { return [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame - && !RCTIsXCAssetURL(request.URL); + && !RCTIsLocalAssetURL(request.URL); } - (NSOperation *)sendRequest:(NSURLRequest *)request diff --git a/Libraries/Network/RCTNetworkTask.h b/Libraries/Network/RCTNetworkTask.h index 3f2de225ff8ce2..189471def2f591 100644 --- a/Libraries/Network/RCTNetworkTask.h +++ b/Libraries/Network/RCTNetworkTask.h @@ -14,7 +14,7 @@ typedef void (^RCTURLRequestCompletionBlock)(NSURLResponse *response, NSData *data, NSError *error); typedef void (^RCTURLRequestCancellationBlock)(void); -typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data); +typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data, int64_t progress, int64_t total); typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total); typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response); diff --git a/Libraries/Network/RCTNetworkTask.m b/Libraries/Network/RCTNetworkTask.m index 8ce51e86240ffc..c8abb7ac0f668d 100644 --- a/Libraries/Network/RCTNetworkTask.m +++ b/Libraries/Network/RCTNetworkTask.m @@ -126,7 +126,7 @@ - (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data } [_data appendData:data]; if (_incrementalDataBlock) { - _incrementalDataBlock(data); + _incrementalDataBlock(data, _data.length, _response.expectedContentLength); } if (_downloadProgressBlock && _response.expectedContentLength > 0) { _downloadProgressBlock(_data.length, _response.expectedContentLength); diff --git a/Libraries/Network/RCTNetworking.android.js b/Libraries/Network/RCTNetworking.android.js index 23aa9e8bf54199..7a0a9073fa2d9a 100644 --- a/Libraries/Network/RCTNetworking.android.js +++ b/Libraries/Network/RCTNetworking.android.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule RCTNetworking + * @flow */ 'use strict'; @@ -27,7 +28,7 @@ function convertHeadersMapToArray(headers: Object): Array<Header> { } let _requestId = 1; -function generateRequestId() { +function generateRequestId(): number { return _requestId++; } @@ -41,37 +42,49 @@ class RCTNetworking extends NativeEventEmitter { super(RCTNetworkingNative); } - sendRequest(method, url, headers, data, incrementalUpdates, timeout, callback) { - if (typeof data === 'string') { - data = {string: data}; - } else if (data instanceof FormData) { - data = { - formData: data.getParts().map((part) => { - part.headers = convertHeadersMapToArray(part.headers); - return part; - }), - }; - } + sendRequest( + method: string, + trackingName: string, + url: string, + headers: Object, + data: string | FormData | {uri: string}, + responseType: 'text' | 'base64', + incrementalUpdates: boolean, + timeout: number, + callback: (requestId: number) => any + ) { + const body = + typeof data === 'string' ? {string: data} : + data instanceof FormData ? {formData: getParts(data)} : + data; const requestId = generateRequestId(); RCTNetworkingNative.sendRequest( method, url, requestId, convertHeadersMapToArray(headers), - data, + {...body, trackingName}, + responseType, incrementalUpdates, timeout ); callback(requestId); } - abortRequest(requestId) { + abortRequest(requestId: number) { RCTNetworkingNative.abortRequest(requestId); } - clearCookies(callback) { + clearCookies(callback: (result: boolean) => any) { RCTNetworkingNative.clearCookies(callback); } } +function getParts(data) { + return data.getParts().map((part) => { + part.headers = convertHeadersMapToArray(part.headers); + return part; + }); +} + module.exports = new RCTNetworking(); diff --git a/Libraries/Network/RCTNetworking.ios.js b/Libraries/Network/RCTNetworking.ios.js index 3e0e4c35284119..80418c3e8c16d6 100644 --- a/Libraries/Network/RCTNetworking.ios.js +++ b/Libraries/Network/RCTNetworking.ios.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule RCTNetworking + * @flow */ 'use strict'; @@ -20,27 +21,37 @@ class RCTNetworking extends NativeEventEmitter { super(RCTNetworkingNative); } - sendRequest(method, url, headers, data, incrementalUpdates, timeout, callback) { - if (typeof data === 'string') { - data = {string: data}; - } else if (data instanceof FormData) { - data = {formData: data.getParts()}; - } + sendRequest( + method: string, + trackingName: string, + url: string, + headers: Object, + data: string | FormData | {uri: string}, + responseType: 'text' | 'base64', + incrementalUpdates: boolean, + timeout: number, + callback: (requestId: number) => any + ) { + const body = + typeof data === 'string' ? {string: data} : + data instanceof FormData ? {formData: data.getParts()} : + data; RCTNetworkingNative.sendRequest({ method, url, - data, + data: {...body, trackingName}, headers, + responseType, incrementalUpdates, timeout }, callback); } - abortRequest(requestId) { + abortRequest(requestId: number) { RCTNetworkingNative.abortRequest(requestId); } - clearCookies(callback) { + clearCookies(callback: (result: boolean) => any) { console.warn('RCTNetworking.clearCookies is not supported on iOS'); } } diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 07c21f095d3fad..ea912e0f82c647 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -95,7 +95,7 @@ - (RCTURLRequestCancellationBlock)handleResult:(NSDictionary<NSString *, id> *)r headers[@"content-type"] = partContentType; } [headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) { - [_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue] + [self->_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue] dataUsingEncoding:NSUTF8StringEncoding]]; }]; @@ -138,6 +138,8 @@ @implementation RCTNetworking return @[@"didCompleteNetworkResponse", @"didReceiveNetworkResponse", @"didSendNetworkData", + @"didReceiveNetworkIncrementalData", + @"didReceiveNetworkDataProgress", @"didReceiveNetworkData"]; } @@ -221,6 +223,12 @@ - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)q request.allHTTPHeaderFields = [self stripNullsInRequestHeaders:[RCTConvert NSDictionary:query[@"headers"]]]; request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]]; NSDictionary<NSString *, id> *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])]; + NSString *trackingName = data[@"trackingName"]; + if (trackingName) { + [NSURLProtocol setProperty:trackingName + forKey:@"trackingName" + inRequest:request]; + } return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary<NSString *, id> *result) { if (error) { RCTLogError(@"Error processing request body: %@", error); @@ -239,7 +247,7 @@ - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)q [request setValue:(@(request.HTTPBody.length)).description forHTTPHeaderField:@"Content-Length"]; } - dispatch_async(_methodQueue, ^{ + dispatch_async(self->_methodQueue, ^{ block(request); }); @@ -287,7 +295,7 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary __block RCTURLRequestCancellationBlock cancellationBlock = nil; RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { - dispatch_async(_methodQueue, ^{ + dispatch_async(self->_methodQueue, ^{ cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil); }); }]; @@ -313,26 +321,16 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary return callback(nil, nil); } -- (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task ++ (NSString *)decodeTextData:(NSData *)data fromResponse:(NSURLResponse *)response { - RCTAssertThread(_methodQueue, @"sendData: must be called on method queue"); - - if (data.length == 0) { - return; - } - - // Get text encoding - NSURLResponse *response = task.response; NSStringEncoding encoding = NSUTF8StringEncoding; if (response.textEncodingName) { CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); } - // Attempt to decode text - NSString *responseText = [[NSString alloc] initWithData:data encoding:encoding]; - if (!responseText && data.length) { - + NSString *encodedResponse = [[NSString alloc] initWithData:data encoding:encoding]; + if (!encodedResponse && data.length) { // We don't have an encoding, or the encoding is incorrect, so now we // try to guess (unfortunately, this feature is available in iOS 8+ only) if ([NSString respondsToSelector:@selector(stringEncodingForData: @@ -341,22 +339,43 @@ - (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task usedLossyConversion:)]) { [NSString stringEncodingForData:data encodingOptions:nil - convertedString:&responseText + convertedString:&encodedResponse usedLossyConversion:NULL]; } + } + return encodedResponse; +} - // If we still can't decode it, bail out - if (!responseText) { +- (void)sendData:(NSData *)data + responseType:(NSString *)responseType + forTask:(RCTNetworkTask *)task +{ + RCTAssertThread(_methodQueue, @"sendData: must be called on method queue"); + + if (data.length == 0) { + return; + } + + NSString *responseString; + if ([responseType isEqualToString:@"text"]) { + responseString = [RCTNetworking decodeTextData:data fromResponse:task.response]; + if (!responseString) { RCTLogWarn(@"Received data was not a string, or was not a recognised encoding."); return; } + } else if ([responseType isEqualToString:@"base64"]) { + responseString = [data base64EncodedStringWithOptions:0]; + } else { + RCTLogWarn(@"Invalid responseType: %@", responseType); + return; } - NSArray<id> *responseJSON = @[task.requestID, responseText ?: @""]; + NSArray<id> *responseJSON = @[task.requestID, responseString]; [self sendEventWithName:@"didReceiveNetworkData" body:responseJSON]; } - (void)sendRequest:(NSURLRequest *)request + responseType:(NSString *)responseType incrementalUpdates:(BOOL)incrementalUpdates responseSender:(RCTResponseSenderBlock)responseSender { @@ -365,14 +384,14 @@ - (void)sendRequest:(NSURLRequest *)request __block RCTNetworkTask *task; RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) { - dispatch_async(_methodQueue, ^{ + dispatch_async(self->_methodQueue, ^{ NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)]; [self sendEventWithName:@"didSendNetworkData" body:responseJSON]; }); }; - void (^responseBlock)(NSURLResponse *) = ^(NSURLResponse *response) { - dispatch_async(_methodQueue, ^{ + RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) { + dispatch_async(self->_methodQueue, ^{ NSDictionary<NSString *, NSString *> *headers; NSInteger status; if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request @@ -389,17 +408,44 @@ - (void)sendRequest:(NSURLRequest *)request }); }; - void (^incrementalDataBlock)(NSData *) = incrementalUpdates ? ^(NSData *data) { - dispatch_async(_methodQueue, ^{ - [self sendData:data forTask:task]; - }); - } : nil; + // XHR does not allow you to peek at xhr.response before the response is + // finished. Only when xhr.responseType is set to ''/'text', consumers may + // peek at xhr.responseText. So unless the requested responseType is 'text', + // we only send progress updates and not incremental data updates to JS here. + RCTURLRequestIncrementalDataBlock incrementalDataBlock = nil; + RCTURLRequestProgressBlock downloadProgressBlock = nil; + if (incrementalUpdates) { + if ([responseType isEqualToString:@"text"]) { + incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) { + dispatch_async(self->_methodQueue, ^{ + NSString *responseString = [RCTNetworking decodeTextData:data fromResponse:task.response]; + if (!responseString) { + RCTLogWarn(@"Received data was not a string, or was not a recognised encoding."); + return; + } + NSArray<id> *responseJSON = @[task.requestID, responseString, @(progress), @(total)]; + [self sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON]; + }); + }; + } else { + downloadProgressBlock = ^(int64_t progress, int64_t total) { + dispatch_async(self->_methodQueue, ^{ + NSArray<id> *responseJSON = @[task.requestID, @(progress), @(total)]; + [self sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON]; + }); + }; + } + } RCTURLRequestCompletionBlock completionBlock = ^(NSURLResponse *response, NSData *data, NSError *error) { - dispatch_async(_methodQueue, ^{ - if (!incrementalUpdates) { - [self sendData:data forTask:task]; + dispatch_async(self->_methodQueue, ^{ + // Unless we were sending incremental (text) chunks to JS, all along, now + // is the time to send the request body to JS. + if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) { + [self sendData:data + responseType:responseType + forTask:task]; } NSArray *responseJSON = @[task.requestID, RCTNullIfNil(error.localizedDescription), @@ -407,11 +453,12 @@ - (void)sendRequest:(NSURLRequest *)request ]; [self sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON]; - [_tasksByRequestID removeObjectForKey:task.requestID]; + [self->_tasksByRequestID removeObjectForKey:task.requestID]; }); }; task = [self networkTaskWithRequest:request completionBlock:completionBlock]; + task.downloadProgressBlock = downloadProgressBlock; task.incrementalDataBlock = incrementalDataBlock; task.responseBlock = responseBlock; task.uploadProgressBlock = uploadProgressBlock; @@ -453,8 +500,10 @@ - (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request // loading a large file to build the request body [self buildRequest:query completionBlock:^(NSURLRequest *request) { + NSString *responseType = [RCTConvert NSString:query[@"responseType"]]; BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]]; [self sendRequest:request + responseType:responseType incrementalUpdates:incrementalUpdates responseSender:responseSender]; }]; diff --git a/Libraries/Network/XMLHttpRequest.js b/Libraries/Network/XMLHttpRequest.js index ed39d9fe450070..793a9c13cef068 100644 --- a/Libraries/Network/XMLHttpRequest.js +++ b/Libraries/Network/XMLHttpRequest.js @@ -14,8 +14,8 @@ const RCTNetworking = require('RCTNetworking'); const EventTarget = require('event-target-shim'); +const base64 = require('base64-js'); const invariant = require('fbjs/lib/invariant'); -const utf8 = require('utf8'); const warning = require('fbjs/lib/warning'); type ResponseType = '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text'; @@ -102,10 +102,11 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { _method: ?string = null; _response: string | ?Object; _responseType: ResponseType; - _responseText: string = ''; + _response: string = ''; _sent: boolean; _url: ?string = null; _timedOut: boolean = false; + _trackingName: string = 'unknown'; _incrementalEvents: boolean = false; constructor() { @@ -124,7 +125,7 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { this._cachedResponse = undefined; this._hasError = false; this._headers = {}; - this._responseText = ''; + this._response = ''; this._responseType = ''; this._sent = false; this._lowerCaseResponseHeaders = {}; @@ -133,17 +134,15 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { this._timedOut = false; } - // $FlowIssue #10784535 get responseType(): ResponseType { return this._responseType; } - // $FlowIssue #10784535 set responseType(responseType: ResponseType): void { - if (this.readyState > HEADERS_RECEIVED) { + if (this._sent) { throw new Error( - "Failed to set the 'responseType' property on 'XMLHttpRequest': The " + - "response type cannot be set if the object's state is LOADING or DONE" + 'Failed to set the \'responseType\' property on \'XMLHttpRequest\': The ' + + 'response type cannot be set after the request has been sent.' ); } if (!SUPPORTED_RESPONSE_TYPES.hasOwnProperty(responseType)) { @@ -162,27 +161,25 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { this._responseType = responseType; } - // $FlowIssue #10784535 get responseText(): string { if (this._responseType !== '' && this._responseType !== 'text') { throw new Error( - `The 'responseText' property is only available if 'responseType' ` + + "The 'responseText' property is only available if 'responseType' " + `is set to '' or 'text', but it is '${this._responseType}'.` ); } if (this.readyState < LOADING) { return ''; } - return this._responseText; + return this._response; } - // $FlowIssue #10784535 get response(): Response { const {responseType} = this; if (responseType === '' || responseType === 'text') { return this.readyState < LOADING || this._hasError ? '' - : this._responseText; + : this._response; } if (this.readyState !== DONE) { @@ -193,26 +190,25 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { return this._cachedResponse; } - switch (this._responseType) { + switch (responseType) { case 'document': this._cachedResponse = null; break; case 'arraybuffer': - this._cachedResponse = toArrayBuffer( - this._responseText, this.getResponseHeader('content-type') || ''); + this._cachedResponse = base64.toByteArray(this._response).buffer; break; case 'blob': this._cachedResponse = new global.Blob( - [this._responseText], + [base64.toByteArray(this._response).buffer], {type: this.getResponseHeader('content-type') || ''} ); break; case 'json': try { - this._cachedResponse = JSON.parse(this._responseText); + this._cachedResponse = JSON.parse(this._response); } catch (_) { this._cachedResponse = null; } @@ -231,7 +227,11 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { } // exposed for testing - __didUploadProgress(requestId: number, progress: number, total: number): void { + __didUploadProgress( + requestId: number, + progress: number, + total: number + ): void { if (requestId === this._requestId) { this.upload.dispatchEvent({ type: 'progress', @@ -260,16 +260,47 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { } } - __didReceiveData(requestId: number, responseText: string): void { - if (requestId === this._requestId) { - if (!this._responseText) { - this._responseText = responseText; - } else { - this._responseText += responseText; - } - this._cachedResponse = undefined; // force lazy recomputation - this.setReadyState(this.LOADING); + __didReceiveData(requestId: number, response: string): void { + if (requestId !== this._requestId) { + return; + } + this._response = response; + this._cachedResponse = undefined; // force lazy recomputation + this.setReadyState(this.LOADING); + } + + __didReceiveIncrementalData( + requestId: number, + responseText: string, + progress: number, + total: number + ) { + if (requestId !== this._requestId) { + return; } + if (!this._response) { + this._response = responseText; + } else { + this._response += responseText; + } + this.setReadyState(this.LOADING); + this.__didReceiveDataProgress(requestId, progress, total); + } + + __didReceiveDataProgress( + requestId: number, + loaded: number, + total: number + ): void { + if (requestId !== this._requestId) { + return; + } + this.dispatchEvent({ + type: 'progress', + lengthComputable: total >= 0, + loaded, + total, + }); } // exposed for testing @@ -280,7 +311,9 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { ): void { if (requestId === this._requestId) { if (error) { - this._responseText = error; + if (this._responseType === '' || this._responseType === 'text') { + this._response = error; + } this._hasError = true; if (timeOutError) { this._timedOut = true; @@ -322,6 +355,14 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { this._headers[header.toLowerCase()] = value; } + /** + * Custom extension for tracking origins of request. + */ + setTrackingName(trackingName: string): XMLHttpRequest { + this._trackingName = trackingName; + return this; + } + open(method: string, url: string, async: ?boolean): void { /* Other optional arguments are not supported yet */ if (this.readyState !== this.UNSENT) { @@ -334,21 +375,24 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { if (!url) { throw new Error('Cannot load an empty url'); } - this._reset(); this._method = method.toUpperCase(); this._url = url; this._aborted = false; this.setReadyState(this.OPENED); } - sendImpl( - method: ?string, - url: ?string, - headers: Object, - data: any, - useIncrementalUpdates: boolean, - timeout: number, - ): void { + send(data: any): void { + if (this.readyState !== this.OPENED) { + throw new Error('Request has not been opened'); + } + if (this._sent) { + throw new Error('Request has already been sent'); + } + this._sent = true; + const incrementalEvents = this._incrementalEvents || + !!this.onreadystatechange || + !!this.onprogress; + this._subscriptions.push(RCTNetworking.addListener( 'didSendNetworkData', (args) => this.__didUploadProgress(...args) @@ -359,39 +403,38 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { )); this._subscriptions.push(RCTNetworking.addListener( 'didReceiveNetworkData', - (args) => this.__didReceiveData(...args) + (args) => this.__didReceiveData(...args) + )); + this._subscriptions.push(RCTNetworking.addListener( + 'didReceiveNetworkIncrementalData', + (args) => this.__didReceiveIncrementalData(...args) + )); + this._subscriptions.push(RCTNetworking.addListener( + 'didReceiveNetworkDataProgress', + (args) => this.__didReceiveDataProgress(...args) )); this._subscriptions.push(RCTNetworking.addListener( 'didCompleteNetworkResponse', (args) => this.__didCompleteResponse(...args) )); - RCTNetworking.sendRequest( - method, - url, - headers, - data, - useIncrementalUpdates, - timeout, - this.__didCreateRequest.bind(this), - ); - } - send(data: any): void { - if (this.readyState !== this.OPENED) { - throw new Error('Request has not been opened'); + let nativeResponseType = 'text'; + if (this._responseType === 'arraybuffer' || this._responseType === 'blob') { + nativeResponseType = 'base64'; } - if (this._sent) { - throw new Error('Request has already been sent'); - } - this._sent = true; - const incrementalEvents = this._incrementalEvents || !!this.onreadystatechange; - this.sendImpl( + + invariant(this._method, 'Request method needs to be defined.'); + invariant(this._url, 'Request URL needs to be defined.'); + RCTNetworking.sendRequest( this._method, + this._trackingName, this._url, this._headers, data, + nativeResponseType, incrementalEvents, - this.timeout + this.timeout, + this.__didCreateRequest.bind(this), ); } @@ -444,32 +487,11 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { // have to send repeated LOADING events with incremental updates // to responseText, which will avoid a bunch of native -> JS // bridge traffic. - if (type === 'readystatechange') { + if (type === 'readystatechange' || type === 'progress') { this._incrementalEvents = true; } super.addEventListener(type, listener); } } - -function toArrayBuffer(text: string, contentType: string): ArrayBuffer { - const {length} = text; - if (length === 0) { - return new ArrayBuffer(0); - } - - const charsetMatch = contentType.match(/;\s*charset=([^;]*)/i); - const charset = charsetMatch ? charsetMatch[1].trim() : 'utf-8'; - - if (/^utf-?8$/i.test(charset)) { - return utf8.encode(text); - } else { //TODO: utf16 / ucs2 / utf32 - const array = new Uint8Array(length); - for (let i = 0; i < length; i++) { - array[i] = text.charCodeAt(i); // Uint8Array automatically masks with 0xff - } - return array.buffer; - } -} - module.exports = XMLHttpRequest; diff --git a/Libraries/Network/__tests__/XMLHttpRequest-test.js b/Libraries/Network/__tests__/XMLHttpRequest-test.js index afbae4780b080f..a369f237d8f49c 100644 --- a/Libraries/Network/__tests__/XMLHttpRequest-test.js +++ b/Libraries/Network/__tests__/XMLHttpRequest-test.js @@ -10,160 +10,174 @@ 'use strict'; jest - .disableAutomock() - .dontMock('event-target-shim') - .setMock('NativeModules', { + .disableAutomock() + .dontMock('event-target-shim') + .setMock('NativeModules', { Networking: { - addListener: function(){}, - removeListeners: function(){}, + addListener: function() {}, + removeListeners: function() {}, + sendRequest: (options, callback) => { + callback(1); + }, + abortRequest: function() {}, } }); const XMLHttpRequest = require('XMLHttpRequest'); -describe('XMLHttpRequest', function(){ - var xhr; - var handleTimeout; - var handleError; - var handleLoad; - var handleReadyStateChange; - - beforeEach(() => { - xhr = new XMLHttpRequest(); - - xhr.ontimeout = jest.fn(); - xhr.onerror = jest.fn(); - xhr.onload = jest.fn(); - xhr.onreadystatechange = jest.fn(); - - handleTimeout = jest.fn(); - handleError = jest.fn(); - handleLoad = jest.fn(); - handleReadyStateChange = jest.fn(); - - xhr.addEventListener('timeout', handleTimeout); - xhr.addEventListener('error', handleError); - xhr.addEventListener('load', handleLoad); - xhr.addEventListener('readystatechange', handleReadyStateChange); - - xhr.__didCreateRequest(1); - }); +describe('XMLHttpRequest', function() { + var xhr; + var handleTimeout; + var handleError; + var handleLoad; + var handleReadyStateChange; + + beforeEach(() => { + xhr = new XMLHttpRequest(); + + xhr.ontimeout = jest.fn(); + xhr.onerror = jest.fn(); + xhr.onload = jest.fn(); + xhr.onreadystatechange = jest.fn(); + + handleTimeout = jest.fn(); + handleError = jest.fn(); + handleLoad = jest.fn(); + handleReadyStateChange = jest.fn(); + + xhr.addEventListener('timeout', handleTimeout); + xhr.addEventListener('error', handleError); + xhr.addEventListener('load', handleLoad); + xhr.addEventListener('readystatechange', handleReadyStateChange); + }); - afterEach(() => { - xhr = null; - handleTimeout = null; - handleError = null; - handleLoad = null; - }); + afterEach(() => { + xhr = null; + handleTimeout = null; + handleError = null; + handleLoad = null; + }); - it('should transition readyState correctly', function() { + it('should transition readyState correctly', function() { + expect(xhr.readyState).toBe(xhr.UNSENT); - expect(xhr.readyState).toBe(xhr.UNSENT); + xhr.open('GET', 'blabla'); - xhr.open('GET', 'blabla'); + expect(xhr.onreadystatechange.mock.calls.length).toBe(1); + expect(handleReadyStateChange.mock.calls.length).toBe(1); + expect(xhr.readyState).toBe(xhr.OPENED); + }); - expect(xhr.onreadystatechange.mock.calls.length).toBe(1); - expect(handleReadyStateChange.mock.calls.length).toBe(1); - expect(xhr.readyState).toBe(xhr.OPENED); - }); + it('should expose responseType correctly', function() { + expect(xhr.responseType).toBe(''); - it('should expose responseType correctly', function() { - expect(xhr.responseType).toBe(''); + // Setting responseType to an unsupported value has no effect. + xhr.responseType = 'arrayblobbuffertextfile'; + expect(xhr.responseType).toBe(''); - // Setting responseType to an unsupported value has no effect. - xhr.responseType = 'arrayblobbuffertextfile'; - expect(xhr.responseType).toBe(''); + xhr.responseType = 'arraybuffer'; + expect(xhr.responseType).toBe('arraybuffer'); - xhr.responseType = 'arraybuffer'; - expect(xhr.responseType).toBe('arraybuffer'); + // Can't change responseType after first data has been received. + xhr.open('GET', 'blabla'); + xhr.send(); + expect(() => { xhr.responseType = 'text'; }).toThrow(); + }); - // Can't change responseType after first data has been received. - xhr.__didReceiveData(1, 'Some data'); - expect(() => { xhr.responseType = 'text'; }).toThrow(); - }); + it('should expose responseText correctly', function() { + xhr.responseType = ''; + expect(xhr.responseText).toBe(''); + expect(xhr.response).toBe(''); - it('should expose responseText correctly', function() { - xhr.responseType = ''; - expect(xhr.responseText).toBe(''); - expect(xhr.response).toBe(''); + xhr.responseType = 'arraybuffer'; + expect(() => xhr.responseText).toThrow(); + expect(xhr.response).toBe(null); - xhr.responseType = 'arraybuffer'; - expect(() => xhr.responseText).toThrow(); - expect(xhr.response).toBe(null); + xhr.responseType = 'text'; + expect(xhr.responseText).toBe(''); + expect(xhr.response).toBe(''); - xhr.responseType = 'text'; - expect(xhr.responseText).toBe(''); - expect(xhr.response).toBe(''); + // responseText is read-only. + expect(() => { xhr.responseText = 'hi'; }).toThrow(); + expect(xhr.responseText).toBe(''); + expect(xhr.response).toBe(''); - // responseText is read-only. - expect(() => { xhr.responseText = 'hi'; }).toThrow(); - expect(xhr.responseText).toBe(''); - expect(xhr.response).toBe(''); + xhr.open('GET', 'blabla'); + xhr.send(); + xhr.__didReceiveData(1, 'Some data'); + expect(xhr.responseText).toBe('Some data'); + }); - xhr.__didReceiveData(1, 'Some data'); - expect(xhr.responseText).toBe('Some data'); - }); + it('should call ontimeout function when the request times out', function() { + xhr.open('GET', 'blabla'); + xhr.send(); + xhr.__didCompleteResponse(1, 'Timeout', true); + xhr.__didCompleteResponse(1, 'Timeout', true); - it('should call ontimeout function when the request times out', function(){ - xhr.__didCompleteResponse(1, 'Timeout', true); + expect(xhr.readyState).toBe(xhr.DONE); - expect(xhr.readyState).toBe(xhr.DONE); + expect(xhr.ontimeout.mock.calls.length).toBe(1); + expect(xhr.onerror).not.toBeCalled(); + expect(xhr.onload).not.toBeCalled(); - expect(xhr.ontimeout.mock.calls.length).toBe(1); - expect(xhr.onerror).not.toBeCalled(); - expect(xhr.onload).not.toBeCalled(); + expect(handleTimeout.mock.calls.length).toBe(1); + expect(handleError).not.toBeCalled(); + expect(handleLoad).not.toBeCalled(); + }); - expect(handleTimeout.mock.calls.length).toBe(1); - expect(handleError).not.toBeCalled(); - expect(handleLoad).not.toBeCalled(); - }); + it('should call onerror function when the request times out', function() { + xhr.open('GET', 'blabla'); + xhr.send(); + xhr.__didCompleteResponse(1, 'Generic error'); - it('should call onerror function when the request times out', function(){ - xhr.__didCompleteResponse(1, 'Generic error'); + expect(xhr.readyState).toBe(xhr.DONE); - expect(xhr.readyState).toBe(xhr.DONE); + expect(xhr.onreadystatechange.mock.calls.length).toBe(2); + expect(xhr.onerror.mock.calls.length).toBe(1); + expect(xhr.ontimeout).not.toBeCalled(); + expect(xhr.onload).not.toBeCalled(); - expect(xhr.onreadystatechange.mock.calls.length).toBe(1); - expect(xhr.onerror.mock.calls.length).toBe(1); - expect(xhr.ontimeout).not.toBeCalled(); - expect(xhr.onload).not.toBeCalled(); + expect(handleReadyStateChange.mock.calls.length).toBe(2); + expect(handleError.mock.calls.length).toBe(1); + expect(handleTimeout).not.toBeCalled(); + expect(handleLoad).not.toBeCalled(); + }); - expect(handleReadyStateChange.mock.calls.length).toBe(1); - expect(handleError.mock.calls.length).toBe(1); - expect(handleTimeout).not.toBeCalled(); - expect(handleLoad).not.toBeCalled(); - }); + it('should call onload function when there is no error', function() { + xhr.open('GET', 'blabla'); + xhr.send(); + xhr.__didCompleteResponse(1, null); - it('should call onload function when there is no error', function(){ - xhr.__didCompleteResponse(1, null); + expect(xhr.readyState).toBe(xhr.DONE); - expect(xhr.readyState).toBe(xhr.DONE); + expect(xhr.onreadystatechange.mock.calls.length).toBe(2); + expect(xhr.onload.mock.calls.length).toBe(1); + expect(xhr.onerror).not.toBeCalled(); + expect(xhr.ontimeout).not.toBeCalled(); - expect(xhr.onreadystatechange.mock.calls.length).toBe(1); - expect(xhr.onload.mock.calls.length).toBe(1); - expect(xhr.onerror).not.toBeCalled(); - expect(xhr.ontimeout).not.toBeCalled(); + expect(handleReadyStateChange.mock.calls.length).toBe(2); + expect(handleLoad.mock.calls.length).toBe(1); + expect(handleError).not.toBeCalled(); + expect(handleTimeout).not.toBeCalled(); + }); - expect(handleReadyStateChange.mock.calls.length).toBe(1); - expect(handleLoad.mock.calls.length).toBe(1); - expect(handleError).not.toBeCalled(); - expect(handleTimeout).not.toBeCalled(); - }); + it('should call onload function when there is no error', function() { + xhr.open('GET', 'blabla'); + xhr.send(); - it('should call onload function when there is no error', function() { - xhr.upload.onprogress = jest.fn(); - var handleProgress = jest.fn(); - xhr.upload.addEventListener('progress', handleProgress); + xhr.upload.onprogress = jest.fn(); + var handleProgress = jest.fn(); + xhr.upload.addEventListener('progress', handleProgress); - xhr.__didUploadProgress(1, 42, 100); + xhr.__didUploadProgress(1, 42, 100); - expect(xhr.upload.onprogress.mock.calls.length).toBe(1); - expect(handleProgress.mock.calls.length).toBe(1); + expect(xhr.upload.onprogress.mock.calls.length).toBe(1); + expect(handleProgress.mock.calls.length).toBe(1); - expect(xhr.upload.onprogress.mock.calls[0][0].loaded).toBe(42); - expect(xhr.upload.onprogress.mock.calls[0][0].total).toBe(100); - expect(handleProgress.mock.calls[0][0].loaded).toBe(42); - expect(handleProgress.mock.calls[0][0].total).toBe(100); - }); + expect(xhr.upload.onprogress.mock.calls[0][0].loaded).toBe(42); + expect(xhr.upload.onprogress.mock.calls[0][0].total).toBe(100); + expect(handleProgress.mock.calls[0][0].loaded).toBe(42); + expect(handleProgress.mock.calls[0][0].total).toBe(100); + }); }); diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index 5089e7ee601b4b..83d8928ec4e20b 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -19,10 +19,6 @@ const PushNotificationEmitter = new NativeEventEmitter(RCTPushNotificationManage const _notifHandlers = new Map(); -//TODO: remove this once all call sites for popInitialNotification() have been removed -let _initialNotification = RCTPushNotificationManager && - RCTPushNotificationManager.initialNotification; - const DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; const NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; const DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived'; @@ -92,7 +88,7 @@ class PushNotificationIOS { * - `soundName` : The sound played when the notification is fired (optional). * - `category` : The category of this notification, required for actionable notifications (optional). * - `userInfo` : An optional object containing additional notification data. - * - `applicationIconBadgeNumber` (optional) : The number to display as the appās icon badge. The default value of this property is 0, which means that no badge is displayed. + * - `applicationIconBadgeNumber` (optional) : The number to display as the app's icon badge. The default value of this property is 0, which means that no badge is displayed. */ static presentLocalNotification(details: Object) { RCTPushNotificationManager.presentLocalNotification(details); @@ -109,7 +105,7 @@ class PushNotificationIOS { * - `soundName` : The sound played when the notification is fired (optional). * - `category` : The category of this notification, required for actionable notifications (optional). * - `userInfo` : An optional object containing additional notification data. - * - `applicationIconBadgeNumber` (optional) : The number to display as the appās icon badge. Setting the number to 0 removes the icon badge. + * - `applicationIconBadgeNumber` (optional) : The number to display as the app's icon badge. Setting the number to 0 removes the icon badge. */ static scheduleLocalNotification(details: Object) { RCTPushNotificationManager.scheduleLocalNotification(details); @@ -177,7 +173,6 @@ class PushNotificationIOS { listener = PushNotificationEmitter.addListener( DEVICE_NOTIF_EVENT, (notifData) => { - notifData.remote = true; handler(new PushNotificationIOS(notifData)); } ); @@ -185,7 +180,6 @@ class PushNotificationIOS { listener = PushNotificationEmitter.addListener( DEVICE_LOCAL_NOTIF_EVENT, (notifData) => { - notifData.remote = false; handler(new PushNotificationIOS(notifData)); } ); @@ -289,21 +283,6 @@ class PushNotificationIOS { RCTPushNotificationManager.checkPermissions(callback); } - /** - * DEPRECATED: An initial notification will be available if the app was - * cold-launched from a notification. - * - * The first caller of `popInitialNotification` will get the initial - * notification object, or `null`. Subsequent invocations will return null. - */ - static popInitialNotification() { - console.warn('PushNotificationIOS.popInitialNotification() is deprecated. Use getInitialNotification() instead.'); - var initialNotification = _initialNotification && - new PushNotificationIOS(_initialNotification); - _initialNotification = null; - return initialNotification; - } - /** * If the app launch was triggered by a push notification, * it will give the notification object, otherwise it will give `null` @@ -317,7 +296,7 @@ class PushNotificationIOS { /** * You will never need to instantiate `PushNotificationIOS` yourself. * Listening to the `notification` event and invoking - * `popInitialNotification` is sufficient + * `getInitialNotification` is sufficient */ constructor(nativeNotif: Object) { this._data = {}; diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index a84f4d959753ff..806eccc5eb53d2 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -71,6 +71,7 @@ @implementation RCTPushNotificationManager formattedLocalNotification[@"category"] = RCTNullIfNil(notification.category); formattedLocalNotification[@"soundName"] = RCTNullIfNil(notification.soundName); formattedLocalNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(notification.userInfo)); + formattedLocalNotification[@"remote"] = @NO; return formattedLocalNotification; } @@ -113,25 +114,6 @@ - (void)stopObserving @"remoteNotificationsRegistered"]; } -// TODO: Once all JS call sites for popInitialNotification have -// been removed we can get rid of this -- (NSDictionary<NSString *, id> *)constantsToExport -{ - NSDictionary<NSString *, id> *initialNotification = - self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; - - UILocalNotification *initialLocalNotification = - self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; - - if (initialNotification) { - return @{@"initialNotification": [initialNotification copy]}; - } else if (initialLocalNotification) { - return @{@"initialNotification": RCTFormatLocalNotification(initialLocalNotification)}; - } else { - return @{@"initialNotification": (id)kCFNull}; - } -} - + (void)didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings { if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)]) { @@ -176,7 +158,9 @@ - (void)handleLocalNotificationReceived:(NSNotification *)notification - (void)handleRemoteNotificationReceived:(NSNotification *)notification { - [self sendEventWithName:@"remoteNotificationReceived" body:notification.userInfo]; + NSMutableDictionary *userInfo = [notification.userInfo mutableCopy]; + userInfo[@"remote"] = @YES; + [self sendEventWithName:@"remoteNotificationReceived" body:userInfo]; } - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification @@ -329,14 +313,15 @@ - (void)handleRegisterUserNotificationSettings:(NSNotification *)notification RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject) { - NSDictionary<NSString *, id> *initialNotification = - self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; + NSMutableDictionary<NSString *, id> *initialNotification = + [self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] mutableCopy]; UILocalNotification *initialLocalNotification = self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; if (initialNotification) { - resolve([initialNotification copy]); + initialNotification[@"remote"] = @YES; + resolve(initialNotification); } else if (initialLocalNotification) { resolve(RCTFormatLocalNotification(initialLocalNotification)); } else { diff --git a/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js b/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js index fa6b257dd3e64d..42f75f688bd231 100644 --- a/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js +++ b/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js @@ -21,12 +21,6 @@ var fixOpts = function(opts) { }; var QuickPerformanceLogger = { - - // These two empty containers will cause all calls to ActionId.SOMETHING or MarkerId.OTHER - // to equal 'undefined', unless they are given a concrete value elsewhere. - ActionId: {}, - MarkerId: {}, - markerStart(markerId, opts) { if (typeof markerId !== 'number') { return; diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 2d86a1c6ba2115..e9ba29132c6680 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -35,16 +35,16 @@ - (dispatch_queue_t)methodQueue [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) { - NSString *testName = NSStringFromSelector(_testSelector); - if (!_snapshotCounter) { - _snapshotCounter = [NSMutableDictionary new]; + NSString *testName = NSStringFromSelector(self->_testSelector); + if (!self->_snapshotCounter) { + self->_snapshotCounter = [NSMutableDictionary new]; } - _snapshotCounter[testName] = (@([_snapshotCounter[testName] integerValue] + 1)).stringValue; + self->_snapshotCounter[testName] = (@([self->_snapshotCounter[testName] integerValue] + 1)).stringValue; NSError *error = nil; - BOOL success = [_controller compareSnapshotOfView:_view - selector:_testSelector - identifier:_snapshotCounter[testName] + BOOL success = [self->_controller compareSnapshotOfView:self->_view + selector:self->_testSelector + identifier:self->_snapshotCounter[testName] error:&error]; callback(@[@(success)]); }]; @@ -76,7 +76,7 @@ - (dispatch_queue_t)methodQueue RCT_EXPORT_METHOD(markTestPassed:(BOOL)success) { [_bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) { - _status = success ? RCTTestStatusPassed : RCTTestStatusFailed; + self->_status = success ? RCTTestStatusPassed : RCTTestStatusFailed; }]; } diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index d6d8f59e4d7b79..c9d84a183605ac 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -47,11 +47,10 @@ - (instancetype)initWithApp:(NSString *)app if (getenv("CI_USE_PACKAGER")) { _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]]; - RCTAssert(_scriptURL != nil, @"No scriptURL set"); } else { _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; - RCTAssert(_scriptURL != nil, @"Could not locate main.jsBundle"); } + RCTAssert(_scriptURL != nil, @"No scriptURL set"); } return self; } diff --git a/Libraries/ReactIOS/YellowBox.js b/Libraries/ReactIOS/YellowBox.js index 82507b56f639ae..f9fc498cb1f758 100644 --- a/Libraries/ReactIOS/YellowBox.js +++ b/Libraries/ReactIOS/YellowBox.js @@ -19,6 +19,7 @@ const StyleSheet = require('StyleSheet'); const infoLog = require('infoLog'); const parseErrorStack = require('parseErrorStack'); const symbolicateStackTrace = require('symbolicateStackTrace'); +const openFileInEditor = require('openFileInEditor'); import type EmitterSubscription from 'EmitterSubscription'; import type {StackFrame} from 'parseErrorStack'; @@ -172,12 +173,21 @@ const WarningRow = ({count, warning, onPress}) => { type StackRowProps = { frame: StackFrame }; const StackRow = ({frame}: StackRowProps) => { const Text = require('Text'); - const fileParts = frame.file.split('/'); + const TouchableHighlight = require('TouchableHighlight'); + const {file, lineNumber} = frame; + const fileParts = file.split('/'); const fileName = fileParts[fileParts.length - 1]; + return ( - <Text style={styles.inspectorCountText}> - {`${fileName}:${frame.lineNumber}`} - </Text> + <TouchableHighlight + activeOpacity={0.5} + style={styles.openInEditorButton} + underlayColor="transparent" + onPress={openFileInEditor.bind(null, file, lineNumber)}> + <Text style={styles.inspectorCountText}> + {fileName}:{lineNumber} + </Text> + </TouchableHighlight> ); }; @@ -220,7 +230,7 @@ const WarningInspector = ({ <TouchableHighlight activeOpacity={0.5} onPress={toggleStacktrace} - style={styles.stacktraceButton} + style={styles.toggleStacktraceButton} underlayColor="transparent"> <Text style={styles.inspectorButtonText}> {stacktraceVisible ? 'Hide' : 'Show'} Stacktrace @@ -394,7 +404,7 @@ var styles = StyleSheet.create({ padding: 22, backgroundColor: backgroundColor(1), }, - stacktraceButton: { + toggleStacktraceButton: { flex: 1, padding: 5, }, @@ -411,6 +421,10 @@ var styles = StyleSheet.create({ flex: 1, paddingTop: 5, }, + openInEditorButton: { + paddingTop: 5, + paddingBottom: 5, + }, inspectorCount: { padding: 15, paddingBottom: 0, diff --git a/Libraries/Settings/RCTSettingsManager.m b/Libraries/Settings/RCTSettingsManager.m index 859506e671e12a..eac4e3e98f952a 100644 --- a/Libraries/Settings/RCTSettingsManager.m +++ b/Libraries/Settings/RCTSettingsManager.m @@ -80,9 +80,9 @@ - (void)userDefaultsDidChange:(NSNotification *)note [values enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, BOOL *stop) { id plist = [RCTConvert NSPropertyList:json]; if (plist) { - [_defaults setObject:plist forKey:key]; + [self->_defaults setObject:plist forKey:key]; } else { - [_defaults removeObjectForKey:key]; + [self->_defaults removeObjectForKey:key]; } }]; diff --git a/Libraries/Share/Share.js b/Libraries/Share/Share.js new file mode 100644 index 00000000000000..22839a07967c41 --- /dev/null +++ b/Libraries/Share/Share.js @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2016-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. + * + * @providesModule Share + * @flow + */ +'use strict'; + +const Platform = require('Platform'); +const { + ActionSheetManager, + ShareModule +} = require('NativeModules'); +const invariant = require('fbjs/lib/invariant'); +const processColor = require('processColor'); + +type Content = { title?: string, message: string } | { title?: string, url: string }; +type Options = { dialogTitle?: string, excludeActivityTypes?: Array<string>, tintColor?: string }; + +class Share { + + /** + * Open a dialog to share text content. + * + * In iOS, Returns a Promise which will be invoked an object containing `action`, `activityType`. + * If the user dismissed the dialog, the Promise will still be resolved with action being `Share.dismissedAction` + * and all the other keys being undefined. + * + * In Android, Returns a Promise which always be resolved with action being `Share.sharedAction`. + * + * ### Content + * + * - `message` - a message to share + * - `title` - title of the message + * + * #### iOS + * + * - `url` - an URL to share + * + * At least one of URL and message is required. + * + * ### Options + * + * #### iOS + * + * - `excludedActivityTypes` + * - `tintColor` + * + * #### Android + * + * - `dialogTitle` + * + */ + static share(content: Content, options: Options = {}): Promise<Object> { + invariant( + typeof content === 'object' && content !== null, + 'Content must a valid object' + ); + invariant( + typeof content.url === 'string' || typeof content.message === 'string', + 'At least one of URL and message is required' + ); + invariant( + typeof options === 'object' && options !== null, + 'Options must be a valid object' + ); + + if (Platform.OS === 'android') { + invariant( + !content.title || typeof content.title === 'string', + 'Invalid title: title should be a string.' + ); + return ShareModule.share(content, options.dialogTitle); + } else if (Platform.OS === 'ios') { + return new Promise((resolve, reject) => { + ActionSheetManager.showShareActionSheetWithOptions( + {...content, ...options, tintColor: processColor(options.tintColor)}, + (error) => reject(error), + (success, activityType) => { + if (success) { + resolve({ + 'action': 'sharedAction', + 'activityType': activityType + }); + } else { + resolve({ + 'action': 'dismissedAction' + }); + } + } + ); + }); + } else { + return Promise.reject(new Error('Unsupported platform')); + } + } + + /** + * The content was successfully shared. + */ + static get sharedAction() { return 'sharedAction'; } + + /** + * The dialog has been dismissed. + * @platform ios + */ + static get dismissedAction() { return 'dismissedAction'; } + +} + +module.exports = Share; diff --git a/Libraries/Storage/AsyncStorage.js b/Libraries/Storage/AsyncStorage.js index 471c5304c9f9af..9006476d8bec5e 100644 --- a/Libraries/Storage/AsyncStorage.js +++ b/Libraries/Storage/AsyncStorage.js @@ -24,7 +24,7 @@ var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncSQLiteStorage || RCTAsyn /** * @class * @description - * `AsyncStorage` is a simple, asynchronous, persistent, key-value storage + * `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value storage * system that is global to the app. It should be used instead of LocalStorage. * * It is recommended that you use an abstraction on top of `AsyncStorage` diff --git a/Libraries/StyleSheet/LayoutPropTypes.js b/Libraries/StyleSheet/LayoutPropTypes.js index 2e21f43f4305ff..eba04870b64903 100644 --- a/Libraries/StyleSheet/LayoutPropTypes.js +++ b/Libraries/StyleSheet/LayoutPropTypes.js @@ -31,7 +31,7 @@ var LayoutPropTypes = { * * It works similarly to `width` in CSS, but in React Native you * must use logical pixel units, rather than percents, ems, or any of that. - * See http://www.w3schools.com/cssref/pr_dim_width.asp for more details. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/width for more details. */ width: ReactPropTypes.number, @@ -39,7 +39,7 @@ var LayoutPropTypes = { * * It works similarly to `height` in CSS, but in React Native you * must use logical pixel units, rather than percents, ems, or any of that. - * See http://www.w3schools.com/cssref/pr_dim_width.asp for more details. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/height for more details. */ height: ReactPropTypes.number, @@ -92,7 +92,7 @@ var LayoutPropTypes = { * It works similarly to `min-width` in CSS, but in React Native you * must use logical pixel units, rather than percents, ems, or any of that. * - * See http://www.w3schools.com/cssref/pr_dim_min-width.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/min-width * for more details. */ minWidth: ReactPropTypes.number, @@ -102,7 +102,7 @@ var LayoutPropTypes = { * It works similarly to `max-width` in CSS, but in React Native you * must use logical pixel units, rather than percents, ems, or any of that. * - * See http://www.w3schools.com/cssref/pr_dim_max-width.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/max-width * for more details. */ maxWidth: ReactPropTypes.number, @@ -112,7 +112,7 @@ var LayoutPropTypes = { * It works similarly to `min-height` in CSS, but in React Native you * must use logical pixel units, rather than percents, ems, or any of that. * - * See http://www.w3schools.com/cssref/pr_dim_min-height.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/min-height * for more details. */ minHeight: ReactPropTypes.number, @@ -122,13 +122,15 @@ var LayoutPropTypes = { * It works similarly to `max-height` in CSS, but in React Native you * must use logical pixel units, rather than percents, ems, or any of that. * - * See http://www.w3schools.com/cssref/pr_dim_max-height.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/max-height * for more details. */ maxHeight: ReactPropTypes.number, /** Setting `margin` has the same effect as setting each of * `marginTop`, `marginLeft`, `marginBottom`, and `marginRight`. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin + * for more details. */ margin: ReactPropTypes.number, @@ -143,33 +145,32 @@ var LayoutPropTypes = { marginHorizontal: ReactPropTypes.number, /** `marginTop` works like `margin-top` in CSS. - * See http://www.w3schools.com/cssref/pr_margin-top.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top * for more details. */ marginTop: ReactPropTypes.number, /** `marginBottom` works like `margin-bottom` in CSS. - * See http://www.w3schools.com/cssref/pr_margin-bottom.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom * for more details. */ marginBottom: ReactPropTypes.number, /** `marginLeft` works like `margin-left` in CSS. - * See http://www.w3schools.com/cssref/pr_margin-left.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left * for more details. */ marginLeft: ReactPropTypes.number, /** `marginRight` works like `margin-right` in CSS. - * See http://www.w3schools.com/cssref/pr_margin-right.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right * for more details. */ marginRight: ReactPropTypes.number, - /** `padding` works like `padding` in CSS. - * It's like setting each of `paddingTop`, `paddingBottom`, - * `paddingLeft`, and `paddingRight` to the same thing. - * See http://www.w3schools.com/css/css_padding.asp + /** Setting `padding` has the same effect as setting each of + * `paddingTop`, `paddingBottom`, `paddingLeft`, and `paddingRight`. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding * for more details. */ padding: ReactPropTypes.number, @@ -185,55 +186,55 @@ var LayoutPropTypes = { paddingHorizontal: ReactPropTypes.number, /** `paddingTop` works like `padding-top` in CSS. - * See http://www.w3schools.com/cssref/pr_padding-top.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top * for more details. */ paddingTop: ReactPropTypes.number, /** `paddingBottom` works like `padding-bottom` in CSS. - * See http://www.w3schools.com/cssref/pr_padding-bottom.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom * for more details. */ paddingBottom: ReactPropTypes.number, /** `paddingLeft` works like `padding-left` in CSS. - * See http://www.w3schools.com/cssref/pr_padding-left.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left * for more details. */ paddingLeft: ReactPropTypes.number, /** `paddingRight` works like `padding-right` in CSS. - * See http://www.w3schools.com/cssref/pr_padding-right.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-right * for more details. */ paddingRight: ReactPropTypes.number, /** `borderWidth` works like `border-width` in CSS. - * See http://www.w3schools.com/cssref/pr_border-width.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/border-width * for more details. */ borderWidth: ReactPropTypes.number, /** `borderTopWidth` works like `border-top-width` in CSS. - * See http://www.w3schools.com/cssref/pr_border-top_width.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-width * for more details. */ borderTopWidth: ReactPropTypes.number, /** `borderRightWidth` works like `border-right-width` in CSS. - * See http://www.w3schools.com/cssref/pr_border-right_width.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-width * for more details. */ borderRightWidth: ReactPropTypes.number, /** `borderBottomWidth` works like `border-bottom-width` in CSS. - * See http://www.w3schools.com/cssref/pr_border-bottom_width.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-width * for more details. */ borderBottomWidth: ReactPropTypes.number, /** `borderLeftWidth` works like `border-left-width` in CSS. - * See http://www.w3schools.com/cssref/pr_border-bottom_width.asp + * See https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-width * for more details. */ borderLeftWidth: ReactPropTypes.number, @@ -262,9 +263,9 @@ var LayoutPropTypes = { /** `flexDirection` controls which directions children of a container go. * `row` goes left to right, `column` goes top to bottom, and you may * be able to guess what the other two do. It works like `flex-direction` - * in CSS, except the default is `column`. See - * https://css-tricks.com/almanac/properties/f/flex-direction/ - * for more detail. + * in CSS, except the default is `column`. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction + * for more details. */ flexDirection: ReactPropTypes.oneOf([ 'row', @@ -275,9 +276,9 @@ var LayoutPropTypes = { /** `flexWrap` controls whether children can wrap around after they * hit the end of a flex container. - * It works like `flex-wrap` in CSS. See - * https://css-tricks.com/almanac/properties/f/flex-wrap/ - * for more detail. + * It works like `flex-wrap` in CSS. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap + * for more details. */ flexWrap: ReactPropTypes.oneOf([ 'wrap', @@ -287,9 +288,9 @@ var LayoutPropTypes = { /** `justifyContent` aligns children in the main direction. * For example, if children are flowing vertically, `justifyContent` * controls how they align vertically. - * It works like `justify-content` in CSS. See - * https://css-tricks.com/almanac/properties/j/justify-content/ - * for more detail. + * It works like `justify-content` in CSS. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content + * for more details. */ justifyContent: ReactPropTypes.oneOf([ 'flex-start', @@ -303,9 +304,9 @@ var LayoutPropTypes = { * For example, if children are flowing vertically, `alignItems` * controls how they align horizontally. * It works like `align-items` in CSS, except the default value - * is `stretch` instead of `flex-start`. See - * https://css-tricks.com/almanac/properties/a/align-items/ - * for more detail. + * is `stretch` instead of `flex-start`. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/align-items + * for more details. */ alignItems: ReactPropTypes.oneOf([ 'flex-start', @@ -316,9 +317,9 @@ var LayoutPropTypes = { /** `alignSelf` controls how a child aligns in the cross direction, * overriding the `alignItems` of the parent. It works like `align-self` - * in CSS. See - * https://css-tricks.com/almanac/properties/a/align-self/ - * for more detail. + * in CSS. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/align-self + * for more details. */ alignSelf: ReactPropTypes.oneOf([ 'auto', @@ -331,7 +332,7 @@ var LayoutPropTypes = { /** In React Native `flex` does not work the same way that it does in CSS. * `flex` is a number rather than a string, and it works * according to the `css-layout` library - * at https://github.com/facebook/css-layout . + * at https://github.com/facebook/css-layout. * * When `flex` is a positive number, it makes the component flexible * and it will be sized proportional to its flex value. So a @@ -355,9 +356,9 @@ var LayoutPropTypes = { * * It works like the CSS `z-index` property - components with a larger * `zIndex` will render on top. Think of the z-direction like it's - * pointing from the phone into your eyeball. See - * https://developer.mozilla.org/en-US/docs/Web/CSS/z-index for - * more detail. + * pointing from the phone into your eyeball. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/z-index for + * more details. */ zIndex: ReactPropTypes.number, }; diff --git a/Libraries/StyleSheet/StyleSheet.js b/Libraries/StyleSheet/StyleSheet.js index 28ee3fae54c5c8..c9eab42c4bbf21 100644 --- a/Libraries/StyleSheet/StyleSheet.js +++ b/Libraries/StyleSheet/StyleSheet.js @@ -160,8 +160,8 @@ module.exports = { /** * Creates a StyleSheet style reference from the given object. */ - create(obj: {[key: string]: any}): {[key: string]: number} { - var result = {}; + create<T: Object, U>(obj: T): {[key:$Keys<T>]: number} { + var result: T = (({}: any): T); for (var key in obj) { StyleSheetValidation.validateStyle(key, obj); result[key] = ReactNativePropRegistry.register(obj[key]); diff --git a/Libraries/StyleSheet/StyleSheetTypes.js b/Libraries/StyleSheet/StyleSheetTypes.js index 779e64772307ba..515d3e6145ea77 100644 --- a/Libraries/StyleSheet/StyleSheetTypes.js +++ b/Libraries/StyleSheet/StyleSheetTypes.js @@ -12,4 +12,4 @@ 'use strict'; type Atom = number | bool | Object | Array<?Atom>; -export type StyleObj = Atom | Array<?StyleObj>; +export type StyleObj = Atom; diff --git a/Libraries/StyleSheet/StyleSheetValidation.js b/Libraries/StyleSheet/StyleSheetValidation.js index 99cc380df925a6..259dd558b3d567 100644 --- a/Libraries/StyleSheet/StyleSheetValidation.js +++ b/Libraries/StyleSheet/StyleSheetValidation.js @@ -13,6 +13,7 @@ var ImageStylePropTypes = require('ImageStylePropTypes'); var ReactPropTypeLocations = require('react/lib/ReactPropTypeLocations'); +var ReactPropTypesSecret = require('react/lib/ReactPropTypesSecret'); var TextStylePropTypes = require('TextStylePropTypes'); var ViewStylePropTypes = require('ViewStylePropTypes'); @@ -33,7 +34,9 @@ class StyleSheetValidation { style, prop, caller, - ReactPropTypeLocations.prop + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret ); if (error) { styleError(error.message, style, caller); diff --git a/Libraries/Text/RCTShadowRawText.m b/Libraries/Text/RCTShadowRawText.m index 6d1dd6d2538174..12db153826dabf 100644 --- a/Libraries/Text/RCTShadowRawText.m +++ b/Libraries/Text/RCTShadowRawText.m @@ -31,7 +31,6 @@ - (void)dealloc - (void)contentSizeMultiplierDidChange:(NSNotification *)note { - [self dirtyLayout]; [self dirtyText]; } @@ -39,7 +38,6 @@ - (void)setText:(NSString *)text { if (_text != text && ![_text isEqualToString:text]) { _text = [text copy]; - [self dirtyLayout]; [self dirtyText]; } } diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index ed0cc5d6a1a5b5..851970aa94d667 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -24,7 +24,7 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, assign) CGFloat letterSpacing; @property (nonatomic, assign) CGFloat lineHeight; @property (nonatomic, assign) NSUInteger numberOfLines; -@property (nonatomic, assign) NSLineBreakMode lineBreakMode; +@property (nonatomic, assign) NSLineBreakMode ellipsizeMode; @property (nonatomic, assign) CGSize shadowOffset; @property (nonatomic, assign) NSTextAlignment textAlign; @property (nonatomic, assign) NSWritingDirection writingDirection; diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index c111dbbd9bfc19..bd51f851739e0b 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -33,7 +33,7 @@ @implementation RCTShadowText CGFloat _effectiveLetterSpacing; } -static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t widthMode, float height, css_measure_mode_t heightMode) +static CSSSize RCTMeasure(void *context, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode) { RCTShadowText *shadowText = (__bridge RCTShadowText *)context; NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width widthMode:widthMode]; @@ -41,12 +41,12 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width NSTextContainer *textContainer = layoutManager.textContainers.firstObject; CGSize computedSize = [layoutManager usedRectForTextContainer:textContainer].size; - css_dim_t result; - result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width); + CSSSize result; + result.width = RCTCeilPixelValue(computedSize.width); if (shadowText->_effectiveLetterSpacing < 0) { - result.dimensions[CSS_WIDTH] -= shadowText->_effectiveLetterSpacing; + result.width -= shadowText->_effectiveLetterSpacing; } - result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height); + result.height = RCTCeilPixelValue(computedSize.height); return result; } @@ -61,6 +61,7 @@ - (instancetype)init _cachedTextStorageWidth = -1; _cachedTextStorageWidthMode = -1; _fontSizeMultiplier = 1.0; + CSSNodeSetMeasureFunc(self.cssNode, RCTMeasure); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentSizeMultiplierDidChange:) name:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification @@ -80,9 +81,14 @@ - (NSString *)description return [[superDescription substringToIndex:superDescription.length - 1] stringByAppendingFormat:@"; text: %@>", [self attributedString].string]; } +- (BOOL)isCSSLeafNode +{ + return YES; +} + - (void)contentSizeMultiplierDidChange:(NSNotification *)note { - [self dirtyLayout]; + CSSNodeMarkDirty(self.cssNode); [self dirtyText]; } @@ -100,7 +106,7 @@ - (void)contentSizeMultiplierDidChange:(NSNotification *)note CGFloat width = self.frame.size.width - (padding.left + padding.right); NSNumber *parentTag = [[self reactSuperview] reactTag]; - NSTextStorage *textStorage = [self buildTextStorageForWidth:width widthMode:CSS_MEASURE_MODE_EXACTLY]; + NSTextStorage *textStorage = [self buildTextStorageForWidth:width widthMode:CSSMeasureModeExactly]; [applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) { RCTText *view = (RCTText *)viewRegistry[self.reactTag]; view.textStorage = textStorage; @@ -122,7 +128,7 @@ - (void)contentSizeMultiplierDidChange:(NSNotification *)note return parentProperties; } -- (void)applyLayoutNode:(css_node_t *)node +- (void)applyLayoutNode:(CSSNodeRef)node viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition { @@ -130,21 +136,21 @@ - (void)applyLayoutNode:(css_node_t *)node [self dirtyPropagation]; } -- (void)applyLayoutToChildren:(css_node_t *)node +- (void)applyLayoutToChildren:(CSSNodeRef)node viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition { // Run layout on subviews. - NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width widthMode:CSS_MEASURE_MODE_EXACTLY]; + NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width widthMode:CSSMeasureModeExactly]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; [layoutManager.textStorage enumerateAttribute:RCTShadowViewAttributeName inRange:characterRange options:0 usingBlock:^(RCTShadowView *child, NSRange range, BOOL *_) { if (child) { - css_node_t *childNode = child.cssNode; - float width = childNode->style.dimensions[CSS_WIDTH]; - float height = childNode->style.dimensions[CSS_HEIGHT]; + CSSNodeRef childNode = child.cssNode; + float width = CSSNodeStyleGetWidth(childNode); + float height = CSSNodeStyleGetHeight(childNode); if (isUndefined(width) || isUndefined(height)) { RCTLogError(@"Views nested within a <Text> must have a width and height"); } @@ -169,7 +175,7 @@ - (void)applyLayoutToChildren:(css_node_t *)node }]; } -- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(css_measure_mode_t)widthMode +- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(CSSMeasureMode)widthMode { if (_cachedTextStorage && width == _cachedTextStorageWidth && widthMode == _cachedTextStorageWidthMode) { return _cachedTextStorage; @@ -184,13 +190,13 @@ - (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(css_measur textContainer.lineFragmentPadding = 0.0; if (_numberOfLines > 0) { - textContainer.lineBreakMode = _lineBreakMode; + textContainer.lineBreakMode = _ellipsizeMode; } else { textContainer.lineBreakMode = NSLineBreakByClipping; } textContainer.maximumNumberOfLines = _numberOfLines; - textContainer.size = (CGSize){widthMode == CSS_MEASURE_MODE_UNDEFINED ? CGFLOAT_MAX : width, CGFLOAT_MAX}; + textContainer.size = (CGSize){widthMode == CSSMeasureModeUndefined ? CGFLOAT_MAX : width, CGFLOAT_MAX}; [layoutManager addTextContainer:textContainer]; [layoutManager ensureLayoutForTextContainer:textContainer]; @@ -285,8 +291,8 @@ - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:shadowRawText.text ?: @""]]; [child setTextComputed]; } else { - float width = child.cssNode->style.dimensions[CSS_WIDTH]; - float height = child.cssNode->style.dimensions[CSS_HEIGHT]; + float width = CSSNodeStyleGetWidth(child.cssNode); + float height = CSSNodeStyleGetHeight(child.cssNode); if (isUndefined(width) || isUndefined(height)) { RCTLogError(@"Views nested within a <Text> must have a width and height"); } @@ -324,7 +330,7 @@ - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily // create a non-mutable attributedString for use by the Text system which avoids copies down the line _cachedAttributedString = [[NSAttributedString alloc] initWithAttributedString:attributedString]; - [self dirtyLayout]; + CSSNodeMarkDirty(self.cssNode); return _cachedAttributedString; } @@ -372,6 +378,24 @@ - (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attrib } NSTextAlignment newTextAlign = _textAlign ?: NSTextAlignmentNatural; + + // The part below is to address textAlign for RTL language before setting paragraph style + // Since we can't get layout directly because this logic is currently run just before layout is calculatede + // We will climb up to the first node which style has been setted as non-inherit + if (newTextAlign == NSTextAlignmentRight || newTextAlign == NSTextAlignmentLeft) { + RCTShadowView *view = self; + while (view != nil && CSSNodeStyleGetDirection(view.cssNode) == CSSDirectionInherit) { + view = [view reactSuperview]; + } + if (view != nil && CSSNodeStyleGetDirection(view.cssNode) == CSSDirectionRTL) { + if (newTextAlign == NSTextAlignmentRight) { + newTextAlign = NSTextAlignmentLeft; + } else if (newTextAlign == NSTextAlignmentLeft) { + newTextAlign = NSTextAlignmentRight; + } + } + } + if (self.textAlign != newTextAlign) { self.textAlign = newTextAlign; } @@ -424,25 +448,6 @@ - (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attrib } } -- (void)fillCSSNode:(css_node_t *)node -{ - [super fillCSSNode:node]; - node->measure = RCTMeasure; - node->children_count = 0; -} - -- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:subview atIndex:atIndex]; - self.cssNode->children_count = 0; -} - -- (void)removeReactSubview:(RCTShadowView *)subview -{ - [super removeReactSubview:subview]; - self.cssNode->children_count = 0; -} - - (void)setBackgroundColor:(UIColor *)backgroundColor { super.backgroundColor = backgroundColor; @@ -465,7 +470,7 @@ - (void)set##setProp:(type)value; \ RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat) RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat) RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger) -RCT_TEXT_PROPERTY(LineBreakMode, _lineBreakMode, NSLineBreakMode) +RCT_TEXT_PROPERTY(EllipsizeMode, _ellipsizeMode, NSLineBreakMode) RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment) RCT_TEXT_PROPERTY(TextDecorationColor, _textDecorationColor, UIColor *); RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLineType); diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index 11a4984939100e..6fc8d0ae7962cf 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -9,7 +9,7 @@ #import "RCTTextManager.h" -#import "Layout.h" +#import <CSSLayout/CSSLayout.h> #import "RCTAccessibilityManager.h" #import "RCTAssert.h" #import "RCTConvert.h" @@ -34,7 +34,7 @@ static void collectDirtyNonTextDescendants(RCTShadowText *shadowView, NSMutableA @interface RCTShadowText (Private) -- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(css_measure_mode_t)widthMode; +- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(CSSMeasureMode)widthMode; @end @@ -64,7 +64,7 @@ - (RCTShadowView *)shadowView RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger) -RCT_EXPORT_SHADOW_PROPERTY(lineBreakMode, NSLineBreakMode) +RCT_EXPORT_SHADOW_PROPERTY(ellipsizeMode, NSLineBreakMode) RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment) RCT_EXPORT_SHADOW_PROPERTY(textDecorationStyle, NSUnderlineStyle) RCT_EXPORT_SHADOW_PROPERTY(textDecorationColor, UIColor) diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index 8268e7a53ddea9..49e499fedccdf5 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -29,6 +29,7 @@ @property (nonatomic, strong) NSNumber *maxLength; @property (nonatomic, copy) RCTDirectEventBlock onChange; +@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange; @property (nonatomic, copy) RCTDirectEventBlock onSelectionChange; @property (nonatomic, copy) RCTDirectEventBlock onTextInput; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 94e99af4cc6001..3275a9e6f442d2 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -77,6 +77,9 @@ @implementation RCTTextView BOOL _blockTextShouldChange; BOOL _nativeUpdatesInFlight; NSInteger _nativeEventCount; + + CGSize _previousContentSize; + BOOL _viewDidCompleteInitialLayout; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -261,6 +264,17 @@ - (void)updateContentSize size.height = [_textView sizeThatFits:size].height; _scrollView.contentSize = size; _textView.frame = (CGRect){CGPointZero, size}; + + if (_viewDidCompleteInitialLayout && _onContentSizeChange && !CGSizeEqualToSize(_previousContentSize, size)) { + _previousContentSize = size; + _onContentSizeChange(@{ + @"contentSize": @{ + @"height": @(size.height), + @"width": @(size.width), + }, + @"target": self.reactTag, + }); + } } - (void)updatePlaceholder @@ -363,16 +377,20 @@ - (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)rang if (_maxLength) { NSUInteger allowedLength = _maxLength.integerValue - textView.text.length + range.length; if (text.length > allowedLength) { + // If we typed/pasted more than one character, limit the text inputted if (text.length > 1) { // Truncate the input string so the result is exactly maxLength NSString *limitedString = [text substringToIndex:allowedLength]; NSMutableString *newString = textView.text.mutableCopy; [newString replaceCharactersInRange:range withString:limitedString]; textView.text = newString; + _predictedText = newString; + // Collapse selection at end of insert to match normal paste behavior UITextPosition *insertEnd = [textView positionFromPosition:textView.beginningOfDocument offset:(range.location + allowedLength)]; textView.selectedTextRange = [textView textRangeFromPosition:insertEnd toPosition:insertEnd]; + [self textViewDidChange:textView]; } return NO; @@ -633,6 +651,11 @@ - (BOOL)resignFirstResponder - (void)layoutSubviews { [super layoutSubviews]; + + // Start sending content size updates only after the view has been laid out + // otherwise we send multiple events with bad dimensions on initial render. + _viewDidCompleteInitialLayout = YES; + [self updateFrames]; } diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index 97fbe5e351c5d4..cbbd60a38fa1eb 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -35,6 +35,7 @@ - (UIView *)view RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, textView.keyboardAppearance, UIKeyboardAppearance) RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index bf28e3bef875b5..76213298430a11 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -29,7 +29,7 @@ const viewConfig = { validAttributes: merge(ReactNativeViewAttributes.UIView, { isHighlighted: true, numberOfLines: true, - lineBreakMode: true, + ellipsizeMode: true, allowFontScaling: true, selectable: true, }), @@ -90,7 +90,7 @@ const viewConfig = { const Text = React.createClass({ propTypes: { /** - * Line Break mode. This can be one of the following values: + * This can be one of the following values: * * - `head` - The line is displayed so that the end fits in the container and the missing text * at the beginning of the line is indicated by an ellipsis glyph. e.g., "...wxyz" @@ -106,13 +106,13 @@ const Text = React.createClass({ * * > `clip` is working only for iOS */ - lineBreakMode: React.PropTypes.oneOf(['head', 'middle', 'tail', 'clip']), + ellipsizeMode: React.PropTypes.oneOf(['head', 'middle', 'tail', 'clip']), /** * Used to truncate the text with an ellipsis after computing the text * layout, including line wrapping, such that the total number of lines * does not exceed this number. * - * This prop is commonly used with `lineBreakMode`. + * This prop is commonly used with `ellipsizeMode`. */ numberOfLines: React.PropTypes.number, /** @@ -172,7 +172,7 @@ const Text = React.createClass({ return { accessible: true, allowFontScaling: true, - lineBreakMode: 'tail', + ellipsizeMode: 'tail', }; }, getInitialState: function(): Object { diff --git a/Libraries/Utilities/Alert.js b/Libraries/Utilities/Alert.js index ee733d1ece63fa..21b18adfa83447 100644 --- a/Libraries/Utilities/Alert.js +++ b/Libraries/Utilities/Alert.js @@ -113,7 +113,7 @@ class AlertAndroid { } DialogModuleAndroid.showAlert( config, - (errorMessage) => console.warn(message), + (errorMessage) => console.warn(errorMessage), (action, buttonKey) => { if (action !== DialogModuleAndroid.buttonClicked) { return; diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js index 50a5accc42670d..19df75f3cde308 100644 --- a/Libraries/Utilities/AlertIOS.js +++ b/Libraries/Utilities/AlertIOS.js @@ -163,7 +163,7 @@ class AlertIOS { * example). `style` should be one of 'default', 'cancel' or 'destructive'. * @param type This configures the text input. One of 'plain-text', * 'secure-text' or 'login-password'. - * @param defaultValue The dialog's title. + * @param defaultValue The default text in text input. * * @example <caption>Example with custom buttons</caption> * diff --git a/Libraries/Utilities/BackAndroid.android.js b/Libraries/Utilities/BackAndroid.android.js index 795380448d2f1d..b459e0b5cad317 100644 --- a/Libraries/Utilities/BackAndroid.android.js +++ b/Libraries/Utilities/BackAndroid.android.js @@ -25,11 +25,14 @@ var _backPressSubscriptions = new Set(); RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() { var backPressSubscriptions = new Set(_backPressSubscriptions); var invokeDefault = true; - backPressSubscriptions.forEach((subscription) => { - if (subscription()) { + var subscriptions = [...backPressSubscriptions].reverse(); + for (var i = 0; i < subscriptions.length; ++i) { + if (subscriptions[i]()) { invokeDefault = false; - } - }); + break; + }; + } + if (invokeDefault) { BackAndroid.exitApp(); } diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.h b/Libraries/Utilities/I18nManager.js similarity index 51% rename from Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.h rename to Libraries/Utilities/I18nManager.js index 9a4887829fca01..eae4e5935d4c8c 100644 --- a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.h +++ b/Libraries/Utilities/I18nManager.js @@ -5,16 +5,20 @@ * 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. + * + * @providesModule I18nManager + * @flow */ +'use strict'; -#import <Foundation/Foundation.h> - -@interface FBPortForwardingClient : NSObject - -- (instancetype)init; +type I18nManagerStatus = { + isRTL: boolean, + allowRTL: (allowRTL: boolean) => {}, +}; -- (void)forwardConnectionsToPort:(NSUInteger)port; -- (void)connectToMultiplexingChannelOnPort:(NSUInteger)port; -- (void)close; +const I18nManager : I18nManagerStatus = require('NativeModules').I18nManager || { + isRTL: false, + allowRTL: () => {}, +}; -@end +module.exports = I18nManager; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 7f8c7067f55863..6555631a4e7ad0 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -68,6 +68,7 @@ class MessageQueue { [ 'invokeCallbackAndReturnFlushedQueue', 'callFunctionReturnFlushedQueue', + 'callFunction', 'flushedQueue', ].forEach((fn) => (this[fn] = this[fn].bind(this))); @@ -98,6 +99,16 @@ class MessageQueue { return this.flushedQueue(); } + callFunction(module, method, args) { + let result; + guard(() => { + result = this.__callFunction(module, method, args); + this.__callImmediates(); + }); + + return result; + } + invokeCallbackAndReturnFlushedQueue(cbID, args) { guard(() => { this.__invokeCallback(cbID, args); @@ -190,8 +201,9 @@ class MessageQueue { 'Module %s is not a registered callable module.', module ); - moduleMethods[method].apply(moduleMethods, args); + const result = moduleMethods[method].apply(moduleMethods, args); Systrace.endEvent(); + return result; } __invokeCallback(cbID, args) { diff --git a/Libraries/Utilities/__tests__/utf8-test.js b/Libraries/Utilities/__tests__/utf8-test.js index 51dd50aaae1622..ec91f020588d73 100644 --- a/Libraries/Utilities/__tests__/utf8-test.js +++ b/Libraries/Utilities/__tests__/utf8-test.js @@ -50,13 +50,13 @@ }); it('allows for stray high surrogates', () => { - const arrayBuffer = encode('a\ud8c6b'); + const arrayBuffer = encode(String.fromCharCode(0x61, 0xd8c6, 0x62)); expect(new Uint8Array(arrayBuffer)).toEqual( new Uint8Array([0x61, 0xed, 0xa3, 0x86, 0x62])); }); it('allows for stray low surrogates', () => { - const arrayBuffer = encode('a\ude19b'); + const arrayBuffer = encode(String.fromCharCode(0x61, 0xde19, 0x62)); expect(new Uint8Array(arrayBuffer)).toEqual( new Uint8Array([0x61, 0xed, 0xb8, 0x99, 0x62])); }); diff --git a/Libraries/Utilities/createStrictShapeTypeChecker.js b/Libraries/Utilities/createStrictShapeTypeChecker.js index 710de6a5477ff8..d86ce5942e5a65 100644 --- a/Libraries/Utilities/createStrictShapeTypeChecker.js +++ b/Libraries/Utilities/createStrictShapeTypeChecker.js @@ -12,6 +12,7 @@ 'use strict'; var ReactPropTypeLocationNames = require('react/lib/ReactPropTypeLocationNames'); +var ReactPropTypesSecret = require('react/lib/ReactPropTypesSecret'); var invariant = require('fbjs/lib/invariant'); var merge = require('merge'); @@ -54,7 +55,7 @@ function createStrictShapeTypeChecker( `\nValid keys: ` + JSON.stringify(Object.keys(shapeTypes), null, ' ') ); } - var error = checker(propValue, key, componentName, location); + var error = checker(propValue, key, componentName, location, null, ReactPropTypesSecret); if (error) { invariant( false, diff --git a/Libraries/Utilities/deprecatedPropType.js b/Libraries/Utilities/deprecatedPropType.js index 63bc61020b4a0c..8bdcae77d570b3 100644 --- a/Libraries/Utilities/deprecatedPropType.js +++ b/Libraries/Utilities/deprecatedPropType.js @@ -12,6 +12,8 @@ 'use strict'; const UIManager = require('UIManager'); +const ReactPropTypesSecret = require('react/lib/ReactPropTypesSecret'); +const ReactPropTypeLocations = require('react/lib/ReactPropTypeLocations'); /** * Adds a deprecation warning when the prop is used. @@ -26,7 +28,14 @@ function deprecatedPropType( console.warn(`\`${propName}\` supplied to \`${componentName}\` has been deprecated. ${explanation}`); } - return propType(props, propName, componentName); + return propType( + props, + propName, + componentName, + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret + ); }; } diff --git a/Libraries/WebSocket/RCTSRWebSocket.m b/Libraries/WebSocket/RCTSRWebSocket.m index e5ed0adb99ef25..26bd54bcf93b73 100644 --- a/Libraries/WebSocket/RCTSRWebSocket.m +++ b/Libraries/WebSocket/RCTSRWebSocket.m @@ -471,9 +471,9 @@ - (void)_readHTTPHeader; } [self _readUntilHeaderCompleteWithCallback:^(RCTSRWebSocket *socket, NSData *data) { - CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + CFHTTPMessageAppendBytes(self->_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); - if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { + if (CFHTTPMessageIsHeaderComplete(self->_receivedHTTPHeaders)) { RCTSRLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); [socket _HTTPHeadersDidFinish]; } else { @@ -643,7 +643,7 @@ - (void)_closeWithProtocolError:(NSString *)message; // Need to shunt this on the _callbackQueue first to see if they received any messages [self _performDelegateBlock:^{ [self closeWithCode:RCTSRStatusCodeProtocolError reason:message]; - dispatch_async(_workQueue, ^{ + dispatch_async(self->_workQueue, ^{ [self _disconnect]; }); }]; @@ -653,7 +653,7 @@ - (void)_failWithError:(NSError *)error; { dispatch_async(_workQueue, ^{ if (self.readyState != RCTSR_CLOSED) { - _failed = YES; + self->_failed = YES; [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { [self.delegate webSocket:self didFailWithError:error]; @@ -661,7 +661,7 @@ - (void)_failWithError:(NSError *)error; }]; self.readyState = RCTSR_CLOSED; - _selfRetain = nil; + self->_selfRetain = nil; RCTSRLog(@"Failing with error %@", error.localizedDescription); @@ -713,7 +713,7 @@ - (void)handlePing:(NSData *)pingData; { // Need to pingpong this off _callbackQueue first to make sure messages happen in order [self _performDelegateBlock:^{ - dispatch_async(_workQueue, ^{ + dispatch_async(self->_workQueue, ^{ [self _sendFrameWithOpcode:RCTSROpCodePong data:pingData]; }); }]; @@ -990,7 +990,7 @@ - (void)_readFrameContinue; [socket _closeWithProtocolError:@"Client must receive unmasked data"]; } - size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; + size_t extra_bytes_needed = header.masked ? sizeof(self->_currentReadMaskKey) : 0; if (header.payload_length == 126) { extra_bytes_needed += sizeof(uint16_t); @@ -1020,7 +1020,7 @@ - (void)_readFrameContinue; } if (header.masked) { - assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); + assert(mapped_size >= sizeof(self->_currentReadMaskOffset) + offset); memcpy(_socket->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(_socket->_currentReadMaskKey)); } @@ -1033,12 +1033,12 @@ - (void)_readFrameContinue; - (void)_readFrameNew; { dispatch_async(_workQueue, ^{ - _currentFrameData.length = 0; + self->_currentFrameData.length = 0; - _currentFrameOpcode = 0; - _currentFrameCount = 0; - _readOpCount = 0; - _currentStringScanPosition = 0; + self->_currentFrameOpcode = 0; + self->_currentFrameCount = 0; + self->_readOpCount = 0; + self->_currentStringScanPosition = 0; [self _readFrameContinue]; }); @@ -1081,7 +1081,7 @@ - (void)_pumpWriting; if (!_failed) { [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { - [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; + [self.delegate webSocket:self didCloseWithCode:self->_closeCode reason:self->_closeReason wasClean:YES]; } }]; } @@ -1388,9 +1388,9 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; if (self.readyState >= RCTSR_CLOSING) { return; } - assert(_readBuffer); + assert(self->_readBuffer); - if (self.readyState == RCTSR_CONNECTING && aStream == _inputStream) { + if (self.readyState == RCTSR_CONNECTING && aStream == self->_inputStream) { [self didConnect]; } [self _pumpWriting]; @@ -1402,8 +1402,8 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; RCTSRLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [aStream.streamError copy]); // TODO: specify error better! [self _failWithError:aStream.streamError]; - _readBufferOffset = 0; - _readBuffer.length = 0; + self->_readBufferOffset = 0; + self->_readBuffer.length = 0; break; } @@ -1414,14 +1414,14 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; if (aStream.streamError) { [self _failWithError:aStream.streamError]; } else { - dispatch_async(_workQueue, ^{ + dispatch_async(self->_workQueue, ^{ if (self.readyState != RCTSR_CLOSED) { self.readyState = RCTSR_CLOSED; - _selfRetain = nil; + self->_selfRetain = nil; } - if (!_sentClose && !_failed) { - _sentClose = YES; + if (!self->_sentClose && !self->_failed) { + self->_sentClose = YES; // If we get closed in this state it's probably not clean because we should be sending this when we send messages [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { @@ -1440,13 +1440,13 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; const int bufferSize = 2048; uint8_t buffer[bufferSize]; - while (_inputStream.hasBytesAvailable) { - NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize]; + while (self->_inputStream.hasBytesAvailable) { + NSInteger bytes_read = [self->_inputStream read:buffer maxLength:bufferSize]; if (bytes_read > 0) { - [_readBuffer appendBytes:buffer length:bytes_read]; + [self->_readBuffer appendBytes:buffer length:bytes_read]; } else if (bytes_read < 0) { - [self _failWithError:_inputStream.streamError]; + [self _failWithError:self->_inputStream.streamError]; } if (bytes_read != bufferSize) { diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index c9696c1bb5e62c..ccf7e6db3a36ea 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -154,10 +154,10 @@ - (void)sendMessage:(NSDictionary<NSString *, id> *)message waitForReply:(RCTWSM } NSNumber *expectedID = @(lastID++); - _callbacks[expectedID] = [callback copy]; + self->_callbacks[expectedID] = [callback copy]; NSMutableDictionary<NSString *, id> *messageWithID = [message mutableCopy]; messageWithID[@"id"] = expectedID; - [_socket send:RCTJSONStringify(messageWithID, NULL)]; + [self->_socket send:RCTJSONStringify(messageWithID, NULL)]; }); } @@ -215,7 +215,7 @@ - (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callbac - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { dispatch_async(_jsQueue, ^{ - _injectedObjects[objectName] = script; + self->_injectedObjects[objectName] = script; onComplete(nil); }); } diff --git a/Libraries/WebSocket/RCTWebSocketModule.m b/Libraries/WebSocket/RCTWebSocketModule.m index 6a914a6f482d20..cc5dc4dc6387d2 100644 --- a/Libraries/WebSocket/RCTWebSocketModule.m +++ b/Libraries/WebSocket/RCTWebSocketModule.m @@ -9,6 +9,8 @@ #import "RCTWebSocketModule.h" +#import <objc/runtime.h> + #import "RCTConvert.h" #import "RCTUtils.h" diff --git a/Libraries/react-native/React.js b/Libraries/react-native/React.js index 05c1f2eafdc015..4dcb46b769ea1e 100644 --- a/Libraries/react-native/React.js +++ b/Libraries/react-native/React.js @@ -8,5 +8,7 @@ * * @providesModule React */ - 'use strict'; - module.exports = require('react/lib/React'); + +'use strict'; + +module.exports = require('react/lib/React'); diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 361234ace4b371..4f1b5d1b939e17 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -54,6 +54,7 @@ const ReactNative = { get RecyclerViewBackedScrollView() { return require('RecyclerViewBackedScrollView'); }, get RefreshControl() { return require('RefreshControl'); }, get StatusBar() { return require('StatusBar'); }, + get SwipeableListView() { return require('SwipeableListView'); }, get SwitchAndroid() { return require('SwitchAndroid'); }, get SwitchIOS() { return require('SwitchIOS'); }, get TabBarIOS() { return require('TabBarIOS'); }, @@ -100,6 +101,7 @@ const ReactNative = { get PixelRatio() { return require('PixelRatio'); }, get PushNotificationIOS() { return require('PushNotificationIOS'); }, get Settings() { return require('Settings'); }, + get Share() { return require('Share'); }, get StatusBarIOS() { return require('StatusBarIOS'); }, get StyleSheet() { return require('StyleSheet'); }, get Systrace() { return require('Systrace'); }, diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow index 05eff164c749de..721eb98e378a86 100644 --- a/Libraries/react-native/react-native.js.flow +++ b/Libraries/react-native/react-native.js.flow @@ -63,6 +63,7 @@ var ReactNative = { Slider: require('Slider'), SnapshotViewIOS: require('SnapshotViewIOS'), StatusBar: require('StatusBar'), + SwipeableListView: require('SwipeableListView'), Switch: require('Switch'), RecyclerViewBackedScrollView: require('RecyclerViewBackedScrollView'), RefreshControl: require('RefreshControl'), @@ -112,6 +113,7 @@ var ReactNative = { PixelRatio: require('PixelRatio'), PushNotificationIOS: require('PushNotificationIOS'), Settings: require('Settings'), + Share: require('Share'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), Systrace: require('Systrace'), diff --git a/README.md b/README.md index 207893c79b6d81..505795f13558bc 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Please use these community resources for getting help. We use the GitHub issues ## Documentation [The websiteās documentation](https://facebook.github.io/react-native/docs/) is divided into multiple sections. -- There are **Guides** that discuss topics like [debugging](https://facebook.github.io/react-native/docs/debugging.html), [integrating with existing apps](https://facebook.github.io/react-native/docs/embedded-app-ios.html), and [the gesture responder system](https://facebook.github.io/react-native/docs/gesture-responder-system.html). +- There are **Guides** that discuss topics like [debugging](https://facebook.github.io/react-native/docs/debugging.html), [integrating with existing apps](https://facebook.github.io/react-native/docs/integration-with-existing-apps.html), and [the gesture responder system](https://facebook.github.io/react-native/docs/gesture-responder-system.html). - The **Components** section covers React components such as [`View`](https://facebook.github.io/react-native/docs/view.html) and [`Navigator`](https://facebook.github.io/react-native/docs/navigator.html). - The **APIs** section covers other libraries like [Animated](https://facebook.github.io/react-native/docs/animated.html) and [StyleSheet](https://facebook.github.io/react-native/docs/stylesheet.html) that arenāt React components themselves. - Finally, React Native provides a small number of **Polyfills** that offer web-like APIs. diff --git a/React.podspec b/React.podspec index 24c366dc99e158..77b01c46a22bce 100644 --- a/React.podspec +++ b/React.podspec @@ -57,7 +57,6 @@ Pod::Spec.new do |s| s.subspec 'RCTAnimation' do |ss| ss.dependency 'React/Core' ss.source_files = "Libraries/NativeAnimation/{Nodes/*,*}.{h,m}" - ss.preserve_paths = "Libraries/NativeAnimation/*.js" end s.subspec 'RCTCameraRoll' do |ss| diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 98d0615db4cb76..e3d9ea0d3ea41b 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -26,7 +26,7 @@ #import "RCTRedBox.h" #define RCTAssertJSThread() \ - RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTJSCExecutor"] || \ + RCTAssert(![NSStringFromClass([self->_javaScriptExecutor class]) isEqualToString:@"RCTJSCExecutor"] || \ [[[NSThread currentThread] name] isEqualToString:RCTJSCThreadName], \ @"This method must be called on JS thread") @@ -57,17 +57,22 @@ @implementation RCTBatchedBridge @synthesize flowIDMapLock = _flowIDMapLock; @synthesize loading = _loading; @synthesize valid = _valid; +@synthesize performanceLogger = _performanceLogger; - (instancetype)initWithParentBridge:(RCTBridge *)bridge { RCTAssertParam(bridge); - if ((self = [super initWithBundleURL:bridge.bundleURL - moduleProvider:bridge.moduleProvider - launchOptions:bridge.launchOptions])) { - + if (self = [super initWithDelegate:bridge.delegate + bundleURL:bridge.bundleURL + moduleProvider:bridge.moduleProvider + launchOptions:bridge.launchOptions]) { _parentBridge = bridge; + _performanceLogger = [RCTPerformanceLogger new]; + [_performanceLogger markStartForTag:RCTPLBridgeStartup]; + [_performanceLogger markStartForTag:RCTPLTTI]; + /** * Set Initial State */ @@ -87,6 +92,11 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge return self; } +RCT_NOT_IMPLEMENTED(- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate + bundleURL:(NSURL *)bundleURL + moduleProvider:(RCTBridgeModuleProviderBlock)block + launchOptions:(NSDictionary *)launchOptions) + - (void)start { dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT); @@ -97,7 +107,7 @@ - (void)start dispatch_group_enter(initModulesAndLoadSource); __weak RCTBatchedBridge *weakSelf = self; __block NSData *sourceCode; - [self loadSource:^(NSError *error, NSData *source) { + [self loadSource:^(NSError *error, NSData *source, __unused int64_t sourceLength) { if (error) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf stopLoadingWithError:error]; @@ -111,6 +121,7 @@ - (void)start // Synchronously initialize all native modules that cannot be loaded lazily [self initModulesWithDispatchGroup:initModulesAndLoadSource]; + RCTPerformanceLogger *performanceLogger = self->_performanceLogger; __block NSString *config; dispatch_group_enter(initModulesAndLoadSource); dispatch_async(bridgeQueue, ^{ @@ -118,17 +129,17 @@ - (void)start // Asynchronously initialize the JS executor dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ - RCTPerformanceLoggerStart(RCTPLJSCExecutorSetup); + [performanceLogger markStartForTag:RCTPLJSCExecutorSetup]; [weakSelf setUpExecutor]; - RCTPerformanceLoggerEnd(RCTPLJSCExecutorSetup); + [performanceLogger markStopForTag:RCTPLJSCExecutorSetup]; }); // Asynchronously gather the module config dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ if (weakSelf.valid) { - RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig); + [performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig]; config = [weakSelf moduleConfig]; - RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig); + [performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig]; } }); @@ -136,9 +147,9 @@ - (void)start // We're not waiting for this to complete to leave dispatch group, since // injectJSONConfiguration and executeSourceCode will schedule operations // on the same queue anyway. - RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig); + [performanceLogger markStartForTag:RCTPLNativeModuleInjectConfig]; [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) { - RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig); + [performanceLogger markStopForTag:RCTPLNativeModuleInjectConfig]; if (error) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf stopLoadingWithError:error]; @@ -159,20 +170,23 @@ - (void)start - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad { - RCTPerformanceLoggerStart(RCTPLScriptDownload); + [_performanceLogger markStartForTag:RCTPLScriptDownload]; - RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSData *source) { - RCTPerformanceLoggerEnd(RCTPLScriptDownload); - - _onSourceLoad(error, source); + RCTPerformanceLogger *performanceLogger = _performanceLogger; + RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSData *source, int64_t sourceLength) { + [performanceLogger markStopForTag:RCTPLScriptDownload]; + [performanceLogger setValue:sourceLength forTag:RCTPLBundleSize]; + _onSourceLoad(error, source, sourceLength); }; if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) { [self.delegate loadSourceForBridge:_parentBridge withBlock:onSourceLoad]; - } else if (self.bundleURL) { - [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onComplete:^(NSError *error, NSData *source) { + } else { + RCTAssert(self.bundleURL, @"bundleURL must be non-nil when not implementing loadSourceForBridge"); + + [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onComplete:^(NSError *error, NSData *source, int64_t sourceLength) { if (error && [self.delegate respondsToSelector:@selector(fallbackSourceURLForBridge:)]) { - NSURL *fallbackURL = [self.delegate fallbackSourceURLForBridge:_parentBridge]; + NSURL *fallbackURL = [self.delegate fallbackSourceURLForBridge:self->_parentBridge]; if (fallbackURL && ![fallbackURL isEqual:self.bundleURL]) { RCTLogError(@"Failed to load bundle(%@) with error:(%@)", self.bundleURL, error.localizedDescription); self.bundleURL = fallbackURL; @@ -180,17 +194,8 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad return; } } - onSourceLoad(error, source); + onSourceLoad(error, source, sourceLength); }]; - } else { - // Allow testing without a script - dispatch_async(dispatch_get_main_queue(), ^{ - [self didFinishLoading]; - [[NSNotificationCenter defaultCenter] - postNotificationName:RCTJavaScriptDidLoadNotification - object:_parentBridge userInfo:@{@"bridge": self}]; - }); - onSourceLoad(nil, nil); } } @@ -235,7 +240,7 @@ - (NSArray *)configForModuleName:(NSString *)moduleName - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup { - RCTPerformanceLoggerStart(RCTPLNativeModuleInit); + [_performanceLogger markStartForTag:RCTPLNativeModuleInit]; NSArray<id<RCTBridgeModule>> *extraModules = nil; if (self.delegate) { @@ -382,7 +387,7 @@ - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup _moduleSetupComplete = YES; // Set up modules that require main thread init or constants export - RCTPerformanceLoggerSet(RCTPLNativeModuleMainThread, 0); + [_performanceLogger setValue:0 forTag:RCTPLNativeModuleMainThread]; NSUInteger modulesOnMainQueueCount = 0; for (RCTModuleData *moduleData in _moduleDataByID) { __weak RCTBatchedBridge *weakSelf = self; @@ -393,19 +398,22 @@ - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup // modules on the main thread in parallel with loading the JS code, so // they will already be available before they are ever required. dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{ - if (weakSelf.valid) { - RCTPerformanceLoggerAppendStart(RCTPLNativeModuleMainThread); - (void)[moduleData instance]; - [moduleData gatherConstants]; - RCTPerformanceLoggerAppendEnd(RCTPLNativeModuleMainThread); + RCTBatchedBridge *strongSelf = weakSelf; + if (!strongSelf.valid) { + return; } + + [strongSelf->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread]; + (void)[moduleData instance]; + [moduleData gatherConstants]; + [strongSelf->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread]; }); modulesOnMainQueueCount++; } } - RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); - RCTPerformanceLoggerSet(RCTPLNativeModuleMainThreadUsesCount, modulesOnMainQueueCount); + [_performanceLogger markStopForTag:RCTPLNativeModuleInit]; + [_performanceLogger setValue:modulesOnMainQueueCount forTag:RCTPLNativeModuleMainThreadUsesCount]; } - (void)setUpExecutor @@ -457,7 +465,7 @@ - (void)executeSourceCode:(NSData *)sourceCode sourceCodeModule.scriptURL = self.bundleURL; [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) { - if (!_valid) { + if (!self->_valid) { return; } @@ -469,41 +477,43 @@ - (void)executeSourceCode:(NSData *)sourceCode } // Register the display link to start sending js calls after everything is setup - NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop]; - [_displayLink addToRunLoop:targetRunLoop]; + NSRunLoop *targetRunLoop = [self->_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop]; + [self->_displayLink addToRunLoop:targetRunLoop]; - // Perform the state update and notification on the main thread, so we can't run into + // Perform the notification on the main thread, so we can't run into // timing issues with RCTRootView dispatch_async(dispatch_get_main_queue(), ^{ - [self didFinishLoading]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification - object:_parentBridge userInfo:@{@"bridge": self}]; + object:self->_parentBridge userInfo:@{@"bridge": self}]; }); + + [self _flushPendingCalls]; }]; #if RCT_DEV - if ([RCTGetURLQueryParam(self.bundleURL, @"hot") boolValue]) { NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash NSString *host = self.bundleURL.host; NSNumber *port = self.bundleURL.port; [self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path, host, RCTNullIfNil(port)]]; } - #endif - } -- (void)didFinishLoading +- (void)_flushPendingCalls { - RCTPerformanceLoggerEnd(RCTPLBridgeStartup); + RCTAssertJSThread(); + [_performanceLogger markStopForTag:RCTPLBridgeStartup]; + + RCT_PROFILE_BEGIN_EVENT(0, @"Processing pendingCalls", @{ @"count": @(_pendingCalls.count) }); _loading = NO; - [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - for (dispatch_block_t call in _pendingCalls) { - call(); - } - }]; + NSArray *pendingCalls = _pendingCalls; + _pendingCalls = nil; + for (dispatch_block_t call in pendingCalls) { + call(); + } + RCT_PROFILE_END_EVENT(0, @"", nil); } - (void)stopLoadingWithError:(NSError *)error @@ -584,7 +594,24 @@ - (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue { if (queue == RCTJSThread) { - [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; + __weak __typeof(self) weakSelf = self; + RCTProfileBeginFlowEvent(); + RCTAssert(_javaScriptExecutor != nil, @"Need JS executor to schedule JS work"); + + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); + + RCTBatchedBridge *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (strongSelf.loading) { + [strongSelf->_pendingCalls addObject:block]; + } else { + block(); + } + }]; } else if (queue) { dispatch_async(queue, block); } @@ -632,24 +659,24 @@ - (void)invalidate } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ - [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - [_displayLink invalidate]; - _displayLink = nil; + [self->_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + [self->_displayLink invalidate]; + self->_displayLink = nil; - [_javaScriptExecutor invalidate]; - _javaScriptExecutor = nil; + [self->_javaScriptExecutor invalidate]; + self->_javaScriptExecutor = nil; if (RCTProfileIsProfiling()) { RCTProfileUnhookModules(self); } - _moduleDataByName = nil; - _moduleDataByID = nil; - _moduleClassesByID = nil; - _pendingCalls = nil; + self->_moduleDataByName = nil; + self->_moduleDataByID = nil; + self->_moduleClassesByID = nil; + self->_pendingCalls = nil; - if (_flowIDMap != NULL) { - CFRelease(_flowIDMap); + if (self->_flowIDMap != NULL) { + CFRelease(self->_flowIDMap); } }]; }); @@ -668,39 +695,23 @@ - (void)logMessage:(NSString *)message level:(NSString *)level /** * Public. Can be invoked from any thread. */ -- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args +- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion { /** * AnyThread */ - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTBatchedBridge enqueueJSCall:]", nil); + if (!_valid) { + return; + } - NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."]; - - NSString *module = ids[0]; - NSString *method = ids[1]; - - RCTProfileBeginFlowEvent(); - - __weak RCTBatchedBridge *weakSelf = self; - [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - RCTProfileEndFlowEvent(); - - RCTBatchedBridge *strongSelf = weakSelf; - if (!strongSelf || !strongSelf.valid) { - return; - } - - if (strongSelf.loading) { - dispatch_block_t pendingCall = ^{ - [weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]]; - }; - [strongSelf->_pendingCalls addObject:pendingCall]; - } else { - [strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]]; + __weak __typeof(self) weakSelf = self; + [self dispatchBlock:^{ + [weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]]; + if (completion) { + completion(); } - }]; + } queue:RCTJSThread]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"", nil); } @@ -713,27 +724,14 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args /** * AnyThread */ + if (!_valid) { + return; + } - RCTProfileBeginFlowEvent(); - - __weak RCTBatchedBridge *weakSelf = self; - [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - RCTProfileEndFlowEvent(); - - RCTBatchedBridge *strongSelf = weakSelf; - if (!strongSelf || !strongSelf.valid) { - return; - } - - if (strongSelf.loading) { - dispatch_block_t pendingCall = ^{ - [weakSelf _actuallyInvokeCallback:cbID arguments:args ?: @[]]; - }; - [strongSelf->_pendingCalls addObject:pendingCall]; - } else { - [strongSelf _actuallyInvokeCallback:cbID arguments:args]; - } - }]; + __weak __typeof(self) weakSelf = self; + [self dispatchBlock:^{ + [weakSelf _actuallyInvokeCallback:cbID arguments:args]; + } queue:RCTJSThread]; } /** @@ -742,18 +740,11 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args - (void)_immediatelyCallTimer:(NSNumber *)timer { RCTAssertJSThread(); - - dispatch_block_t block = ^{ + [_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:^{ [self _actuallyInvokeAndProcessModule:@"JSTimersExecution" method:@"callTimers" arguments:@[@[timer]]]; - }; - - if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) { - [_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block]; - } else { - [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; - } + }]; } - (void)enqueueApplicationScript:(NSData *)script @@ -773,7 +764,7 @@ - (void)enqueueApplicationScript:(NSData *)script } RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"FetchApplicationScriptCallbacks", nil); - [_javaScriptExecutor flushedQueue:^(id json, NSError *error) + [self->_javaScriptExecutor flushedQueue:^(id json, NSError *error) { RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,init", @{ @"json": RCTNullIfNil(json), @@ -884,7 +875,7 @@ - (void)handleBuffer:(NSArray *)buffer capacity:_moduleDataByName.count]; [moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) { - RCTModuleData *moduleData = _moduleDataByID[moduleID.integerValue]; + RCTModuleData *moduleData = self->_moduleDataByID[moduleID.integerValue]; dispatch_queue_t queue = moduleData.methodQueue; NSMutableOrderedSet<NSNumber *> *set = [buckets objectForKey:queue]; if (!set) { @@ -905,11 +896,11 @@ - (void)handleBuffer:(NSArray *)buffer @autoreleasepool { for (NSNumber *indexObj in calls) { NSUInteger index = indexObj.unsignedIntegerValue; - if (RCT_DEV && callID != -1 && _flowIDMap != NULL && RCTProfileIsProfiling()) { + if (RCT_DEV && callID != -1 && self->_flowIDMap != NULL && RCTProfileIsProfiling()) { [self.flowIDMapLock lock]; - int64_t newFlowID = (int64_t)CFDictionaryGetValue(_flowIDMap, (const void *)(_flowID + index)); + int64_t newFlowID = (int64_t)CFDictionaryGetValue(self->_flowIDMap, (const void *)(self->_flowID + index)); _RCTProfileEndFlowEvent(@(newFlowID)); - CFDictionaryRemoveValue(_flowIDMap, (const void *)(_flowID + index)); + CFDictionaryRemoveValue(self->_flowIDMap, (const void *)(self->_flowID + index)); [self.flowIDMapLock unlock]; } [self _handleRequestNumber:index @@ -924,11 +915,7 @@ - (void)handleBuffer:(NSArray *)buffer }); }; - if (queue == RCTJSThread) { - [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; - } else if (queue) { - dispatch_async(queue, block); - } + [self dispatchBlock:block queue:queue]; } _flowID = callID; diff --git a/React/Base/RCTBridge+Private.h b/React/Base/RCTBridge+Private.h index fd0dd9a38ffd5b..f69e2beec57461 100644 --- a/React/Base/RCTBridge+Private.h +++ b/React/Base/RCTBridge+Private.h @@ -10,9 +10,16 @@ #import "RCTBridge.h" @class RCTModuleData; +@protocol RCTJavaScriptExecutor; @interface RCTBridge () +// Private designated initializer +- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate + bundleURL:(NSURL *)bundleURL + moduleProvider:(RCTBridgeModuleProviderBlock)block + launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER; + // Used for the profiler flow events between JS and native @property (nonatomic, assign) int64_t flowID; @property (nonatomic, assign) CFMutableDictionaryRef flowIDMap; @@ -56,7 +63,9 @@ @interface RCTBridge (RCTBatchedBridge) /** - * Used for unit testing, to detect when executor has been invalidated. + * Access the underlying JavaScript executor. You can use this in unit tests to detect + * when the executor has been invalidated, or when you want to schedule calls on the + * JS VM outside of React Native. Use with care! */ @property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor; @@ -120,9 +129,9 @@ @interface RCTBatchedBridge : RCTBridge <RCTInvalidating> -@property (nonatomic, weak) RCTBridge *parentBridge; -@property (nonatomic, weak) id<RCTJavaScriptExecutor> javaScriptExecutor; -@property (nonatomic, assign) BOOL moduleSetupComplete; +@property (nonatomic, weak, readonly) RCTBridge *parentBridge; +@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor; +@property (nonatomic, assign, readonly) BOOL moduleSetupComplete; - (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 18cf73876ee54e..a3e2b500d22403 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -14,10 +14,10 @@ #import "RCTDefines.h" #import "RCTFrameUpdate.h" #import "RCTInvalidating.h" -#import "RCTJavaScriptExecutor.h" @class RCTBridge; @class RCTEventDispatcher; +@class RCTPerformanceLogger; /** * This notification triggers a reload of all bridges currently running. @@ -78,7 +78,7 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); * or configuration. */ - (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate - launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER; + launchOptions:(NSDictionary *)launchOptions; /** * DEPRECATED: Use initWithDelegate:launchOptions: instead @@ -92,7 +92,7 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleProvider:(RCTBridgeModuleProviderBlock)block - launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER; + launchOptions:(NSDictionary *)launchOptions; /** * This method is used to call functions in the JavaScript application context. @@ -100,6 +100,8 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); * with the JavaScript code. Safe to call from any thread. */ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args; +- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion; + /** * Retrieve a bridge module instance by name or class. Note that modules are @@ -160,6 +162,11 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @property (nonatomic, readonly, getter=isValid) BOOL valid; +/** + * Link to the Performance Logger that logs React Native perf events. + */ +@property (nonatomic, readonly, strong) RCTPerformanceLogger *performanceLogger; + /** * Reload the bundle and reset executor & modules. Safe to call from any thread. */ diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 5721bc0f069c33..f515a87315555e 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -110,30 +110,35 @@ + (void)setCurrentBridge:(RCTBridge *)currentBridge - (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions { - if ((self = [super init])) { - RCTPerformanceLoggerStart(RCTPLBridgeStartup); - RCTPerformanceLoggerStart(RCTPLTTI); - - _delegate = delegate; - _launchOptions = [launchOptions copy]; - [self setUp]; - RCTExecuteOnMainQueue(^{ [self bindKeys]; }); - } - return self; + return [self initWithDelegate:delegate + bundleURL:nil + moduleProvider:nil + launchOptions:launchOptions]; } - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleProvider:(RCTBridgeModuleProviderBlock)block launchOptions:(NSDictionary *)launchOptions { - if ((self = [super init])) { - RCTPerformanceLoggerStart(RCTPLBridgeStartup); - RCTPerformanceLoggerStart(RCTPLTTI); + return [self initWithDelegate:nil + bundleURL:bundleURL + moduleProvider:block + launchOptions:launchOptions]; +} +- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate + bundleURL:(NSURL *)bundleURL + moduleProvider:(RCTBridgeModuleProviderBlock)block + launchOptions:(NSDictionary *)launchOptions +{ + if (self = [super init]) { + _delegate = delegate; _bundleURL = bundleURL; _moduleProvider = block; _launchOptions = [launchOptions copy]; + [self setUp]; + RCTExecuteOnMainQueue(^{ [self bindKeys]; }); } return self; @@ -175,6 +180,11 @@ - (void)bindKeys #endif } +- (RCTPerformanceLogger *)performanceLogger +{ + return self.batchedBridge.performanceLogger; +} + - (NSArray<Class> *)moduleClasses { return self.batchedBridge.moduleClasses; @@ -269,7 +279,15 @@ - (void)invalidate - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args { - [self.batchedBridge enqueueJSCall:moduleDotMethod args:args]; + NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."]; + NSString *module = ids[0]; + NSString *method = ids[1]; + [self enqueueJSCall:module method:method args:args completion:NULL]; +} + +- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion +{ + [self.batchedBridge enqueueJSCall:module method:method args:args completion:completion]; } - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args diff --git a/React/Base/RCTBridgeDelegate.h b/React/Base/RCTBridgeDelegate.h index 8d85f78b7a862b..02da39bdaeafb8 100644 --- a/React/Base/RCTBridgeDelegate.h +++ b/React/Base/RCTBridgeDelegate.h @@ -7,10 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source); - @class RCTBridge; +#import "RCTJavaScriptLoader.h" + @protocol RCTBridgeDelegate <NSObject> /** diff --git a/React/Base/RCTBundleURLProvider.h b/React/Base/RCTBundleURLProvider.h index b1cf8e29b65b71..820e8ad6c97b91 100644 --- a/React/Base/RCTBundleURLProvider.h +++ b/React/Base/RCTBundleURLProvider.h @@ -9,10 +9,12 @@ #import <Foundation/Foundation.h> -@interface RCTBundleURLProvider : NSObject - extern NSString *const RCTBundleURLProviderUpdatedNotification; +extern const NSUInteger kRCTBundleURLProviderDefaultPort; + +@interface RCTBundleURLProvider : NSObject + /** * Set default settings on NSUserDefaults. */ @@ -45,4 +47,15 @@ extern NSString *const RCTBundleURLProviderUpdatedNotification; @property (nonatomic, assign) BOOL enableDev; + (instancetype)sharedSettings; + +/** + Given a hostname for the packager and a bundle root, returns the URL to the js bundle. Generally you should use the + instance method -jsBundleURLForBundleRoot:fallbackResource: which includes logic to guess if the packager is running + and fall back to a pre-packaged bundle if it is not. + */ ++ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerHost:(NSString *)packagerHost + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification; + @end diff --git a/React/Base/RCTBundleURLProvider.m b/React/Base/RCTBundleURLProvider.m index 105f6a1497d1d1..99bf8f2c844163 100644 --- a/React/Base/RCTBundleURLProvider.m +++ b/React/Base/RCTBundleURLProvider.m @@ -13,25 +13,15 @@ NSString *const RCTBundleURLProviderUpdatedNotification = @"RCTBundleURLProviderUpdatedNotification"; +const NSUInteger kRCTBundleURLProviderDefaultPort = 8081; + static NSString *const kRCTJsLocationKey = @"RCT_jsLocation"; static NSString *const kRCTEnableLiveReloadKey = @"RCT_enableLiveReload"; static NSString *const kRCTEnableDevKey = @"RCT_enableDev"; static NSString *const kRCTEnableMinificationKey = @"RCT_enableMinification"; -static NSString *const kDefaultPort = @"8081"; -static NSString *ipGuess; - @implementation RCTBundleURLProvider -#if RCT_DEV -+ (void)initialize -{ - NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"]; - NSString *ip = [NSString stringWithContentsOfFile:ipPath encoding:NSUTF8StringEncoding error:nil]; - ipGuess = [ip stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; -} -#endif - - (instancetype)init { self = [super init]; @@ -58,7 +48,6 @@ - (void)settingsUpdated - (void)setDefaults { [[NSUserDefaults standardUserDefaults] registerDefaults:[self defaults]]; - [self settingsUpdated]; } - (void)resetToDefaults @@ -67,74 +56,96 @@ - (void)resetToDefaults [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; } [self setDefaults]; + [self settingsUpdated]; } -- (BOOL)isPackagerRunning:(NSString *)host +static NSURL *serverRootWithHost(NSString *host) { - if (RCT_DEV) { - NSURL *url = [[NSURL URLWithString:serverRootWithHost(host)] URLByAppendingPathComponent:@"status"]; - NSURLRequest *request = [NSURLRequest requestWithURL:url]; - NSURLResponse *response; - NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL]; - NSString *status = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - return [status isEqualToString:@"packager-status:running"]; - } - return NO; + return [NSURL URLWithString: + [NSString stringWithFormat:@"http://%@:%lu/", + host, (unsigned long)kRCTBundleURLProviderDefaultPort]]; } -static NSString *serverRootWithHost(NSString *host) +#if RCT_DEV +- (BOOL)isPackagerRunning:(NSString *)host { - return [NSString stringWithFormat:@"http://%@:%@/", host, kDefaultPort]; + NSURL *url = [serverRootWithHost(host) URLByAppendingPathComponent:@"status"]; + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + NSURLResponse *response; + NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL]; + NSString *status = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return [status isEqualToString:@"packager-status:running"]; } - (NSString *)guessPackagerHost { + static NSString *ipGuess; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"]; + ipGuess = [[NSString stringWithContentsOfFile:ipPath encoding:NSUTF8StringEncoding error:nil] + stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + }); + NSString *host = ipGuess ?: @"localhost"; if ([self isPackagerRunning:host]) { return host; } return nil; } +#endif -- (NSString *)packagerServerRoot +- (NSString *)packagerServerHost { NSString *location = [self jsLocation]; if (location != nil) { - return serverRootWithHost(location); - } else { - NSString *host = [self guessPackagerHost]; - if (!host) { - return nil; - } else { - return serverRootWithHost(host); - } + return location; } +#if RCT_DEV + NSString *host = [self guessPackagerHost]; + if (host) { + return host; + } +#endif + return nil; } - (NSURL *)packagerServerURL { - NSString *root = [self packagerServerRoot]; - return root ? [NSURL URLWithString:root] : nil; + NSString *const host = [self packagerServerHost]; + return host ? serverRootWithHost(host) : nil; } - (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackResource:(NSString *)resourceName { resourceName = resourceName ?: @"main"; - NSString *serverRoot = [self packagerServerRoot]; - if (!serverRoot) { + NSString *packagerServerHost = [self packagerServerHost]; + if (!packagerServerHost) { return [[NSBundle mainBundle] URLForResource:resourceName withExtension:@"jsbundle"]; } else { - NSString *fullBundlePath = [serverRoot stringByAppendingFormat:@"%@.bundle", bundleRoot]; - if ([fullBundlePath hasPrefix:@"http"]) { - NSString *dev = [self enableDev] ? @"true" : @"false"; - NSString *min = [self enableMinification] ? @"true": @"false"; - fullBundlePath = [fullBundlePath stringByAppendingFormat:@"?platform=ios&dev=%@&minify=%@", dev, min]; - } - return [NSURL URLWithString:fullBundlePath]; + + return [[self class] jsBundleURLForBundleRoot:bundleRoot + packagerHost:packagerServerHost + enableDev:[self enableDev] + enableMinification:[self enableMinification]]; } } -- (void)updateDefaults:(id)object forKey:(NSString *)key ++ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerHost:(NSString *)packagerHost + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification +{ + NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHost(packagerHost) resolvingAgainstBaseURL:NO]; + components.path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot]; + // When we support only iOS 8 and above, use queryItems for a better API. + components.query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@", + enableDev ? @"true" : @"false", + enableMinification ? @"true": @"false"]; + return components.URL; +} + +- (void)updateValue:(id)object forKey:(NSString *)key { [[NSUserDefaults standardUserDefaults] setObject:object forKey:key]; [[NSUserDefaults standardUserDefaults] synchronize]; @@ -163,22 +174,22 @@ - (NSString *)jsLocation - (void)setEnableDev:(BOOL)enableDev { - [self updateDefaults:@(enableDev) forKey:kRCTEnableDevKey]; + [self updateValue:@(enableDev) forKey:kRCTEnableDevKey]; } - (void)setEnableLiveReload:(BOOL)enableLiveReload { - [self updateDefaults:@(enableLiveReload) forKey:kRCTEnableLiveReloadKey]; + [self updateValue:@(enableLiveReload) forKey:kRCTEnableLiveReloadKey]; } - (void)setJsLocation:(NSString *)jsLocation { - [self updateDefaults:jsLocation forKey:kRCTJsLocationKey]; + [self updateValue:jsLocation forKey:kRCTJsLocationKey]; } - (void)setEnableMinification:(BOOL)enableMinification { - [self updateDefaults:@(enableMinification) forKey:kRCTEnableMinificationKey]; + [self updateValue:@(enableMinification) forKey:kRCTEnableMinificationKey]; } + (instancetype)sharedSettings @@ -190,4 +201,5 @@ + (instancetype)sharedSettings }); return sharedInstance; } + @end diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 231f888c48211f..8aeda7e28064a3 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -10,7 +10,7 @@ #import <QuartzCore/QuartzCore.h> #import <UIKit/UIKit.h> -#import "Layout.h" +#import <CSSLayout/CSSLayout.h> #import "RCTAnimationType.h" #import "RCTBorderStyle.h" #import "RCTTextDecorationLineType.h" @@ -66,6 +66,7 @@ typedef NSURL RCTFileURL; + (UIKeyboardType)UIKeyboardType:(id)json; + (UIKeyboardAppearance)UIKeyboardAppearance:(id)json; + (UIReturnKeyType)UIReturnKeyType:(id)json; ++ (UIDataDetectorTypes)UIDataDetectorTypes:(id)json; + (UIViewContentMode)UIViewContentMode:(id)json; + (UIBarStyle)UIBarStyle:(id)json; @@ -115,11 +116,11 @@ typedef id NSPropertyList; typedef BOOL css_clip_t, css_backface_visibility_t; + (css_clip_t)css_clip_t:(id)json; + (css_backface_visibility_t)css_backface_visibility_t:(id)json; -+ (css_flex_direction_t)css_flex_direction_t:(id)json; -+ (css_justify_t)css_justify_t:(id)json; -+ (css_align_t)css_align_t:(id)json; -+ (css_position_type_t)css_position_type_t:(id)json; -+ (css_wrap_type_t)css_wrap_type_t:(id)json; ++ (CSSFlexDirection)CSSFlexDirection:(id)json; ++ (CSSJustify)CSSJustify:(id)json; ++ (CSSAlign)CSSAlign:(id)json; ++ (CSSPositionType)CSSPositionType:(id)json; ++ (CSSWrapType)CSSWrapType:(id)json; + (RCTPointerEvents)RCTPointerEvents:(id)json; + (RCTAnimationType)RCTAnimationType:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index d2d3b457877b77..24683744cd39f9 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -319,6 +319,15 @@ + (NSDate *)NSDate:(id)json @"numeric": @(UIKeyboardTypeDecimalPad), }), UIKeyboardTypeDefault, integerValue) +RCT_MULTI_ENUM_CONVERTER(UIDataDetectorTypes, (@{ + @"phoneNumber": @(UIDataDetectorTypePhoneNumber), + @"link": @(UIDataDetectorTypeLink), + @"address": @(UIDataDetectorTypeAddress), + @"calendarEvent": @(UIDataDetectorTypeCalendarEvent), + @"none": @(UIDataDetectorTypeNone), + @"all": @(UIDataDetectorTypeAll), +}), UIDataDetectorTypePhoneNumber, unsignedLongLongValue) + RCT_ENUM_CONVERTER(UIKeyboardAppearance, (@{ @"default": @(UIKeyboardAppearanceDefault), @"light": @(UIKeyboardAppearanceLight), @@ -813,38 +822,38 @@ + (NSPropertyList)NSPropertyList:(id)json @"visible": @NO }), NO, boolValue) -RCT_ENUM_CONVERTER(css_flex_direction_t, (@{ - @"row": @(CSS_FLEX_DIRECTION_ROW), - @"row-reverse": @(CSS_FLEX_DIRECTION_ROW_REVERSE), - @"column": @(CSS_FLEX_DIRECTION_COLUMN), - @"column-reverse": @(CSS_FLEX_DIRECTION_COLUMN_REVERSE) -}), CSS_FLEX_DIRECTION_COLUMN, intValue) - -RCT_ENUM_CONVERTER(css_justify_t, (@{ - @"flex-start": @(CSS_JUSTIFY_FLEX_START), - @"flex-end": @(CSS_JUSTIFY_FLEX_END), - @"center": @(CSS_JUSTIFY_CENTER), - @"space-between": @(CSS_JUSTIFY_SPACE_BETWEEN), - @"space-around": @(CSS_JUSTIFY_SPACE_AROUND) -}), CSS_JUSTIFY_FLEX_START, intValue) - -RCT_ENUM_CONVERTER(css_align_t, (@{ - @"flex-start": @(CSS_ALIGN_FLEX_START), - @"flex-end": @(CSS_ALIGN_FLEX_END), - @"center": @(CSS_ALIGN_CENTER), - @"auto": @(CSS_ALIGN_AUTO), - @"stretch": @(CSS_ALIGN_STRETCH) -}), CSS_ALIGN_FLEX_START, intValue) - -RCT_ENUM_CONVERTER(css_position_type_t, (@{ - @"absolute": @(CSS_POSITION_ABSOLUTE), - @"relative": @(CSS_POSITION_RELATIVE) -}), CSS_POSITION_RELATIVE, intValue) - -RCT_ENUM_CONVERTER(css_wrap_type_t, (@{ - @"wrap": @(CSS_WRAP), - @"nowrap": @(CSS_NOWRAP) -}), CSS_NOWRAP, intValue) +RCT_ENUM_CONVERTER(CSSFlexDirection, (@{ + @"row": @(CSSFlexDirectionRow), + @"row-reverse": @(CSSFlexDirectionRowReverse), + @"column": @(CSSFlexDirectionColumn), + @"column-reverse": @(CSSFlexDirectionColumnReverse) +}), CSSFlexDirectionColumn, intValue) + +RCT_ENUM_CONVERTER(CSSJustify, (@{ + @"flex-start": @(CSSJustifyFlexStart), + @"flex-end": @(CSSJustifyFlexEnd), + @"center": @(CSSJustifyCenter), + @"space-between": @(CSSJustifySpaceBetween), + @"space-around": @(CSSJustifySpaceAround) +}), CSSJustifyFlexStart, intValue) + +RCT_ENUM_CONVERTER(CSSAlign, (@{ + @"flex-start": @(CSSAlignFlexStart), + @"flex-end": @(CSSAlignFlexEnd), + @"center": @(CSSAlignCenter), + @"auto": @(CSSAlignAuto), + @"stretch": @(CSSAlignStretch) +}), CSSAlignFlexStart, intValue) + +RCT_ENUM_CONVERTER(CSSPositionType, (@{ + @"absolute": @(CSSPositionTypeAbsolute), + @"relative": @(CSSPositionTypeRelative) +}), CSSPositionTypeRelative, intValue) + +RCT_ENUM_CONVERTER(CSSWrapType, (@{ + @"wrap": @(CSSWrapTypeWrap), + @"nowrap": @(CSSWrapTypeNoWrap) +}), CSSWrapTypeNoWrap, intValue) RCT_ENUM_CONVERTER(RCTPointerEvents, (@{ @"none": @(RCTPointerEventsNone), diff --git a/React/Base/RCTDisplayLink.m b/React/Base/RCTDisplayLink.m index 7ad102daec1cfa..7042dc0b6929df 100644 --- a/React/Base/RCTDisplayLink.m +++ b/React/Base/RCTDisplayLink.m @@ -18,6 +18,10 @@ #import "RCTModuleData.h" #import "RCTProfile.h" +#define RCTAssertRunLoop() \ + RCTAssert(_runLoop == [NSRunLoop currentRunLoop], \ + @"This method must be called on the CADisplayLink run loop") + @implementation RCTDisplayLink { CADisplayLink *_jsDisplayLink; @@ -38,12 +42,13 @@ - (instancetype)init - (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module withModuleData:(RCTModuleData *)moduleData { - if ([_frameUpdateObservers containsObject:moduleData] || - ![moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { + if (![moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)] || + [_frameUpdateObservers containsObject:moduleData]) { return; } [_frameUpdateObservers addObject:moduleData]; + // Don't access the module instance via moduleData, as this will cause deadlock id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)module; __weak typeof(self) weakSelf = self; @@ -54,16 +59,28 @@ - (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module } CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop]; - - if (!_runLoop) { + if (!cfRunLoop) { return; } - CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{ - [weakSelf updateJSDisplayLinkState]; - }); - CFRunLoopWakeUp(cfRunLoop); + if ([NSRunLoop currentRunLoop] == strongSelf->_runLoop) { + [weakSelf updateJSDisplayLinkState]; + } else { + CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{ + [weakSelf updateJSDisplayLinkState]; + }); + CFRunLoopWakeUp(cfRunLoop); + } }; + + // Assuming we're paused right now, we only need to update the display link's state + // when the new observer is not paused. If it not paused, the observer will immediately + // start receiving updates anyway. + if (![observer isPaused] && _runLoop) { + CFRunLoopPerformBlock([_runLoop getCFRunLoop], kCFRunLoopDefaultMode, ^{ + [self updateJSDisplayLinkState]; + }); + } } - (void)addToRunLoop:(NSRunLoop *)runLoop @@ -77,12 +94,6 @@ - (void)invalidate [_jsDisplayLink invalidate]; } -- (void)assertOnRunLoop -{ - RCTAssert(_runLoop == [NSRunLoop currentRunLoop], - @"This method must be called on the CADisplayLink run loop"); -} - - (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue { @@ -95,7 +106,7 @@ - (void)dispatchBlock:(dispatch_block_t)block - (void)_jsThreadUpdate:(CADisplayLink *)displayLink { - [self assertOnRunLoop]; + RCTAssertRunLoop(); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTDisplayLink _jsThreadUpdate:]", nil); @@ -121,7 +132,7 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink - (void)updateJSDisplayLinkState { - [self assertOnRunLoop]; + RCTAssertRunLoop(); BOOL pauseDisplayLink = YES; for (RCTModuleData *moduleData in _frameUpdateObservers) { @@ -131,6 +142,7 @@ - (void)updateJSDisplayLinkState break; } } + _jsDisplayLink.paused = pauseDisplayLink; } diff --git a/React/Base/RCTErrorCustomizer.h b/React/Base/RCTErrorCustomizer.h new file mode 100644 index 00000000000000..23c8b24d36ebf9 --- /dev/null +++ b/React/Base/RCTErrorCustomizer.h @@ -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. + */ + +#import <Foundation/Foundation.h> + +@class RCTErrorInfo; + +/** + * Provides an interface to customize React Native error messages and stack + * traces from exceptions. + */ +@protocol RCTErrorCustomizer <NSObject> + +/** + * Customizes the given error, returning the passed info argument if no + * customization is required. + */ +- (nonnull RCTErrorInfo *)customizeErrorInfo:(nonnull RCTErrorInfo *)info; +@end diff --git a/React/Base/RCTErrorInfo.h b/React/Base/RCTErrorInfo.h new file mode 100644 index 00000000000000..aaa42221410411 --- /dev/null +++ b/React/Base/RCTErrorInfo.h @@ -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. + */ + +#import <Foundation/Foundation.h> + +@class RCTJSStackFrame; + +/** + * An ObjC wrapper for React Native errors. + */ +@interface RCTErrorInfo : NSObject +@property (nonatomic, copy, readonly) NSString *errorMessage; +@property (nonatomic, copy, readonly) NSArray<RCTJSStackFrame *> *stack; + + +- (instancetype)initWithErrorMessage:(NSString *)errorMessage + stack:(NSArray<RCTJSStackFrame *> *)stack; + +@end diff --git a/React/Base/RCTErrorInfo.m b/React/Base/RCTErrorInfo.m new file mode 100644 index 00000000000000..37e055fd56496c --- /dev/null +++ b/React/Base/RCTErrorInfo.m @@ -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. + */ + +#import "RCTErrorInfo.h" + +#import "RCTJSStackFrame.h" + +@implementation RCTErrorInfo + +- (instancetype)initWithErrorMessage:(NSString *)errorMessage + stack:(NSArray<RCTJSStackFrame *> *)stack { + self = [super init]; + if (self) { + _errorMessage = [errorMessage copy]; + _stack = [stack copy]; + } + return self; +} + +@end diff --git a/React/Base/RCTJSStackFrame.h b/React/Base/RCTJSStackFrame.h new file mode 100644 index 00000000000000..34c0fbe93b248e --- /dev/null +++ b/React/Base/RCTJSStackFrame.h @@ -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. + */ + +#import <Foundation/Foundation.h> + +@interface RCTJSStackFrame : NSObject + +@property (nonatomic, copy, readonly) NSString *methodName; +@property (nonatomic, copy, readonly) NSString *file; +@property (nonatomic, readonly) NSInteger lineNumber; +@property (nonatomic, readonly) NSInteger column; + +- (instancetype)initWithMethodName:(NSString *)methodName file:(NSString *)file lineNumber:(NSInteger)lineNumber column:(NSInteger)column; +- (NSDictionary *)toDictionary; + ++ (instancetype)stackFrameWithLine:(NSString *)line; ++ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict; ++ (NSArray<RCTJSStackFrame *> *)stackFramesWithLines:(NSString *)lines; ++ (NSArray<RCTJSStackFrame *> *)stackFramesWithDictionaries:(NSArray<NSDictionary *> *)dicts; + +@end diff --git a/React/Base/RCTJSStackFrame.m b/React/Base/RCTJSStackFrame.m new file mode 100644 index 00000000000000..fd2a615fe5cdb0 --- /dev/null +++ b/React/Base/RCTJSStackFrame.m @@ -0,0 +1,101 @@ +/** + * 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. + */ + +#import "RCTJSStackFrame.h" +#import "RCTLog.h" + + +static NSRegularExpression *RCTJSStackFrameRegex() +{ + static dispatch_once_t onceToken; + static NSRegularExpression *_regex; + dispatch_once(&onceToken, ^{ + NSError *regexError; + _regex = [NSRegularExpression regularExpressionWithPattern:@"^([^@]+)@(.*):(\\d+):(\\d+)$" options:0 error:®exError]; + if (regexError) { + RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]); + } + }); + return _regex; +} + +@implementation RCTJSStackFrame + +- (instancetype)initWithMethodName:(NSString *)methodName file:(NSString *)file lineNumber:(NSInteger)lineNumber column:(NSInteger)column +{ + if (self = [super init]) { + _methodName = methodName; + _file = file; + _lineNumber = lineNumber; + _column = column; + } + return self; +} + +- (NSDictionary *)toDictionary +{ + return @{ + @"methodName": self.methodName, + @"file": self.file, + @"lineNumber": @(self.lineNumber), + @"column": @(self.column) + }; +} + ++ (instancetype)stackFrameWithLine:(NSString *)line +{ + NSTextCheckingResult *match = [RCTJSStackFrameRegex() firstMatchInString:line options:0 range:NSMakeRange(0, line.length)]; + if (!match) { + return nil; + } + + NSString *methodName = [line substringWithRange:[match rangeAtIndex:1]]; + NSString *file = [line substringWithRange:[match rangeAtIndex:2]]; + NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]]; + NSString *column = [line substringWithRange:[match rangeAtIndex:4]]; + + return [[self alloc] initWithMethodName:methodName + file:file + lineNumber:[lineNumber integerValue] + column:[column integerValue]]; +} + ++ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict +{ + return [[self alloc] initWithMethodName:dict[@"methodName"] + file:dict[@"file"] + lineNumber:[dict[@"lineNumber"] integerValue] + column:[dict[@"column"] integerValue]]; +} + ++ (NSArray<RCTJSStackFrame *> *)stackFramesWithLines:(NSString *)lines +{ + NSMutableArray *stack = [NSMutableArray new]; + for (NSString *line in [lines componentsSeparatedByString:@"\n"]) { + RCTJSStackFrame *frame = [self stackFrameWithLine:line]; + if (frame) { + [stack addObject:frame]; + } + } + return stack; +} + ++ (NSArray<RCTJSStackFrame *> *)stackFramesWithDictionaries:(NSArray<NSDictionary *> *)dicts +{ + NSMutableArray *stack = [NSMutableArray new]; + for (NSDictionary *dict in dicts) { + RCTJSStackFrame *frame = [self stackFrameWithDictionary:dict]; + if (frame) { + [stack addObject:frame]; + } + } + return stack; +} + +@end diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 2e0b80860d001e..55dc458ee8e1e8 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -15,7 +15,7 @@ #import "RCTInvalidating.h" typedef void (^RCTJavaScriptCompleteBlock)(NSError *error); -typedef void (^RCTJavaScriptCallback)(id json, NSError *error); +typedef void (^RCTJavaScriptCallback)(id result, NSError *error); /** * Abstracts away a JavaScript execution context - we may be running code in a @@ -76,8 +76,6 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); */ - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block; -@optional - /** * Special case for Timers + ContextExecutor - instead of the default * if jsthread then call else dispatch call on jsthread @@ -85,10 +83,4 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); */ - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block; -/** - * For executors that support it, this method can be used to add a synchronous - * callback function for communicating with the javascript context. - */ -- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block; - @end diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h index 1d8a19ee57e330..141649556afb1a 100755 --- a/React/Base/RCTJavaScriptLoader.h +++ b/React/Base/RCTJavaScriptLoader.h @@ -9,19 +9,36 @@ #import <UIKit/UIKit.h> -#import "RCTBridgeDelegate.h" - extern uint32_t const RCTRAMBundleMagicNumber; -@class RCTBridge; +extern NSString *const RCTJavaScriptLoaderErrorDomain; + +NS_ENUM(NSInteger) { + RCTJavaScriptLoaderErrorNoScriptURL = 1, + RCTJavaScriptLoaderErrorFailedOpeningFile = 2, + RCTJavaScriptLoaderErrorFailedReadingFile = 3, + RCTJavaScriptLoaderErrorFailedStatingFile = 3, + RCTJavaScriptLoaderErrorURLLoadFailed = 3, + + RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously = 1000, +}; + +typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source, int64_t sourceLength); -/** - * Class that allows easy embedding, loading, life-cycle management of a - * JavaScript application inside of a native application. - * TODO: Incremental module loading. (low pri). - */ @interface RCTJavaScriptLoader : NSObject -+ (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(RCTSourceLoadBlock)onComplete; ++ (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComplete; + +/** + * @experimental + * Attempts to synchronously load the script at the given URL. The following two conditions must be met: + * 1. It must be a file URL. + * 2. It must point to a RAM bundle. + * If the URL does not meet those conditions, this method will return nil and supply an error with the domain + * RCTJavaScriptLoaderErrorDomain and the code RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously. + */ ++ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL + sourceLength:(int64_t *)sourceLength + error:(NSError **)error; @end diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index f677db64e23517..f07b9adaead5a8 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -19,138 +19,210 @@ uint32_t const RCTRAMBundleMagicNumber = 0xFB0BD1E5; +NSString *const RCTJavaScriptLoaderErrorDomain = @"RCTJavaScriptLoaderErrorDomain"; + @implementation RCTJavaScriptLoader RCT_NOT_IMPLEMENTED(- (instancetype)init) + (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComplete +{ + int64_t sourceLength; + NSError *error; + NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL + sourceLength:&sourceLength + error:&error]; + if (data) { + onComplete(nil, data, sourceLength); + return; + } + + const BOOL isCannotLoadSyncError = + [error.domain isEqualToString:RCTJavaScriptLoaderErrorDomain] + && error.code == RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously; + + if (isCannotLoadSyncError) { + attemptAsynchronousLoadOfBundleAtURL(scriptURL, onComplete); + } else { + onComplete(error, nil, 0); + } +} + ++ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL + sourceLength:(int64_t *)sourceLength + error:(NSError **)error { NSString *unsanitizedScriptURLString = scriptURL.absoluteString; // Sanitize the script URL - scriptURL = [RCTConvert NSURL:unsanitizedScriptURLString]; + scriptURL = sanitizeURL(scriptURL); if (!scriptURL) { - NSString *errorDescription = [NSString stringWithFormat:@"No script URL provided." - @"unsanitizedScriptURLString:(%@)", unsanitizedScriptURLString]; - NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{ - NSLocalizedDescriptionKey: errorDescription - }]; - onComplete(error, nil); - return; + if (error) { + *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain + code:RCTJavaScriptLoaderErrorNoScriptURL + userInfo:@{NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"No script URL provided. " + @"unsanitizedScriptURLString:(%@)", unsanitizedScriptURLString]}]; + } + return nil; } // Load local script file - if (scriptURL.fileURL) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSError *error = nil; - NSData *source = nil; - - // Load the first 4 bytes to check if the bundle is regular or RAM ("Random Access Modules" bundle). - // The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`. - // The benefit of RAM bundle over a regular bundle is that we can lazily inject - // modules into JSC as they're required. - FILE *bundle = fopen(scriptURL.path.UTF8String, "r"); - if (!bundle) { - onComplete(RCTErrorWithMessage([NSString stringWithFormat:@"Error opening bundle %@", scriptURL.path]), source); - return; - } - - uint32_t magicNumber; - if (fread(&magicNumber, sizeof(magicNumber), 1, bundle) != 1) { - fclose(bundle); - onComplete(RCTErrorWithMessage(@"Error reading bundle"), source); - return; - } - - magicNumber = NSSwapLittleIntToHost(magicNumber); - - int64_t sourceLength = 0; - if (magicNumber == RCTRAMBundleMagicNumber) { - source = [NSData dataWithBytes:&magicNumber length:sizeof(magicNumber)]; - - struct stat statInfo; - if (stat(scriptURL.path.UTF8String, &statInfo) != 0) { - error = RCTErrorWithMessage(@"Error reading bundle"); - } else { - sourceLength = statInfo.st_size; - } - } else { - source = [NSData dataWithContentsOfFile:scriptURL.path - options:NSDataReadingMappedIfSafe - error:&error]; - sourceLength = source.length; - } - - RCTPerformanceLoggerSet(RCTPLBundleSize, sourceLength); - fclose(bundle); - onComplete(error, source); - }); - return; + if (!scriptURL.fileURL) { + if (error) { + *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain + code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously + userInfo:@{NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"Cannot load %@ URLs synchronously", + scriptURL.scheme]}]; + } + return nil; } - // Load remote script file - NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler: - ^(NSData *data, NSURLResponse *response, NSError *error) { + // Load the first 4 bytes to check if the bundle is regular or RAM ("Random Access Modules" bundle). + // The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`. + // The benefit of RAM bundle over a regular bundle is that we can lazily inject + // modules into JSC as they're required. + FILE *bundle = fopen(scriptURL.path.UTF8String, "r"); + if (!bundle) { + if (error) { + *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain + code:RCTJavaScriptLoaderErrorFailedOpeningFile + userInfo:@{NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"Error opening bundle %@", scriptURL.path]}]; + } + return nil; + } - // Handle general request errors + uint32_t magicNumber; + size_t readResult = fread(&magicNumber, sizeof(magicNumber), 1, bundle); + fclose(bundle); + if (readResult != 1) { if (error) { - if ([error.domain isEqualToString:NSURLErrorDomain]) { - NSString *desc = [@"Could not connect to development server.\n\nEnsure the following:\n- Node server is running and available on the same network - run 'npm start' from react-native root\n- Node server URL is correctly set in AppDelegate\n\nURL: " stringByAppendingString:scriptURL.absoluteString]; - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: desc, - NSLocalizedFailureReasonErrorKey: error.localizedDescription, - NSUnderlyingErrorKey: error, - }; - error = [NSError errorWithDomain:@"JSServer" - code:error.code - userInfo:userInfo]; - } - onComplete(error, nil); - return; + *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain + code:RCTJavaScriptLoaderErrorFailedReadingFile + userInfo:@{NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"Error reading bundle %@", scriptURL.path]}]; } + return nil; + } - // Parse response as text - NSStringEncoding encoding = NSUTF8StringEncoding; - if (response.textEncodingName != nil) { - CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); - if (cfEncoding != kCFStringEncodingInvalidId) { - encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); - } + magicNumber = NSSwapLittleIntToHost(magicNumber); + if (magicNumber != RCTRAMBundleMagicNumber) { + if (error) { + *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain + code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously + userInfo:@{NSLocalizedDescriptionKey: + @"Cannot load non-RAM bundled files synchronously"}]; } - // Handle HTTP errors - if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) { - NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding]; - NSDictionary *userInfo; - NSDictionary *errorDetails = RCTJSONParse(rawText, nil); - if ([errorDetails isKindOfClass:[NSDictionary class]] && - [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { - NSMutableArray<NSDictionary *> *fakeStack = [NSMutableArray new]; - for (NSDictionary *err in errorDetails[@"errors"]) { - [fakeStack addObject: @{ - @"methodName": err[@"description"] ?: @"", - @"file": err[@"filename"] ?: @"", - @"lineNumber": err[@"lineNumber"] ?: @0 - }]; - } - userInfo = @{ - NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", - @"stack": fakeStack, - }; - } else { - userInfo = @{NSLocalizedDescriptionKey: rawText}; - } - error = [NSError errorWithDomain:@"JSServer" - code:((NSHTTPURLResponse *)response).statusCode - userInfo:userInfo]; - - onComplete(error, nil); - return; + return nil; + } + + struct stat statInfo; + if (stat(scriptURL.path.UTF8String, &statInfo) != 0) { + if (error) { + *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain + code:RCTJavaScriptLoaderErrorFailedStatingFile + userInfo:@{NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"Error stating bundle %@", scriptURL.path]}]; } - RCTPerformanceLoggerSet(RCTPLBundleSize, data.length); - onComplete(nil, data); - }]; + return nil; + } + if (sourceLength) { + *sourceLength = statInfo.st_size; + } + return [NSData dataWithBytes:&magicNumber length:sizeof(magicNumber)]; +} +static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoadBlock onComplete) +{ + scriptURL = sanitizeURL(scriptURL); + + if (scriptURL.fileURL) { + // Reading in a large bundle can be slow. Dispatch to the background queue to do it. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSError *error = nil; + NSData *source = [NSData dataWithContentsOfFile:scriptURL.path + options:NSDataReadingMappedIfSafe + error:&error]; + onComplete(error, source, source.length); + }); + return; + } + + // Load remote script file + NSURLSessionDataTask *task = + [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler: + ^(NSData *data, NSURLResponse *response, NSError *error) { + + // Handle general request errors + if (error) { + if ([error.domain isEqualToString:NSURLErrorDomain]) { + error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain + code:RCTJavaScriptLoaderErrorURLLoadFailed + userInfo: + @{ + NSLocalizedDescriptionKey: + [@"Could not connect to development server.\n\n" + "Ensure the following:\n" + "- Node server is running and available on the same network - run 'npm start' from react-native root\n" + "- Node server URL is correctly set in AppDelegate\n\n" + "URL: " stringByAppendingString:scriptURL.absoluteString], + NSLocalizedFailureReasonErrorKey: error.localizedDescription, + NSUnderlyingErrorKey: error, + }]; + } + onComplete(error, nil, 0); + return; + } + + // Parse response as text + NSStringEncoding encoding = NSUTF8StringEncoding; + if (response.textEncodingName != nil) { + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); + if (cfEncoding != kCFStringEncodingInvalidId) { + encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + } + // Handle HTTP errors + if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) { + error = [NSError errorWithDomain:@"JSServer" + code:((NSHTTPURLResponse *)response).statusCode + userInfo:userInfoForRawResponse([[NSString alloc] initWithData:data encoding:encoding])]; + onComplete(error, nil, 0); + return; + } + onComplete(nil, data, data.length); + }]; [task resume]; } +static NSURL *sanitizeURL(NSURL *url) +{ + // Why we do this is lost to time. We probably shouldn't; passing a valid URL is the caller's responsibility not ours. + return [RCTConvert NSURL:url.absoluteString]; +} + +static NSDictionary *userInfoForRawResponse(NSString *rawText) +{ + NSDictionary *parsedResponse = RCTJSONParse(rawText, nil); + if (![parsedResponse isKindOfClass:[NSDictionary class]]) { + return @{NSLocalizedDescriptionKey: rawText}; + } + NSArray *errors = parsedResponse[@"errors"]; + if (![errors isKindOfClass:[NSArray class]]) { + return @{NSLocalizedDescriptionKey: rawText}; + } + NSMutableArray<NSDictionary *> *fakeStack = [NSMutableArray new]; + for (NSDictionary *err in errors) { + [fakeStack addObject: + @{ + @"methodName": err[@"description"] ?: @"", + @"file": err[@"filename"] ?: @"", + @"lineNumber": err[@"lineNumber"] ?: @0 + }]; + } + return @{NSLocalizedDescriptionKey: parsedResponse[@"message"] ?: @"No message provided", @"stack": [fakeStack copy]}; +} + @end diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index 49465c4c62c86c..93c21d08f20281 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -144,7 +144,7 @@ - (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key static NSTimeInterval lastCommand = 0; static NSTimeInterval lastDoubleCommand = 0; static NSString *lastInput = nil; - static UIKeyModifierFlags lastModifierFlags = nil; + static UIKeyModifierFlags lastModifierFlags = 0; if (firstPress) { for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index 4d6caaf5df01db..30e5141a15359c 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -9,6 +9,8 @@ #import "RCTModuleData.h" +#import <objc/runtime.h> + #import "RCTBridge.h" #import "RCTBridge+Private.h" #import "RCTModuleMethod.h" @@ -277,7 +279,7 @@ - (void)gatherConstants RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass], nil); (void)[self instance]; RCTExecuteOnMainThread(^{ - _constantsToExport = [_instance constantsToExport] ?: @{}; + self->_constantsToExport = [self->_instance constantsToExport] ?: @{}; }, YES); } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"", nil); diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index 2d4360d89488d4..38c18c8081b7d5 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -426,7 +426,7 @@ - (SEL)selector - (NSDictionary *)profileArgs { - if (_profileArgs) { + if (!_profileArgs) { // This sets _selector [self processMethodSignature]; _profileArgs = @{ diff --git a/React/Base/RCTPerformanceLogger.h b/React/Base/RCTPerformanceLogger.h index 3cfd62b0f68947..ab37e1d54e2e81 100644 --- a/React/Base/RCTPerformanceLogger.h +++ b/React/Base/RCTPerformanceLogger.h @@ -9,8 +9,6 @@ #import <Foundation/Foundation.h> -#import "RCTDefines.h" - typedef NS_ENUM(NSUInteger, RCTPLTag) { RCTPLScriptDownload = 0, RCTPLScriptExecution, @@ -25,7 +23,6 @@ typedef NS_ENUM(NSUInteger, RCTPLTag) { RCTPLNativeModuleInjectConfig, RCTPLNativeModuleMainThreadUsesCount, RCTPLJSCWrapperOpenLibrary, - RCTPLJSCWrapperLoadFunctions, RCTPLJSCExecutorSetup, RCTPLBridgeStartup, RCTPLTTI, @@ -33,44 +30,74 @@ typedef NS_ENUM(NSUInteger, RCTPLTag) { RCTPLSize }; +@interface RCTPerformanceLogger : NSObject + /** * Starts measuring a metric with the given tag. * Overrides previous value if the measurement has been already started. * If RCTProfile is enabled it also begins appropriate async event. + * All work is scheduled on the background queue so this doesn't block current thread. */ -RCT_EXTERN void RCTPerformanceLoggerStart(RCTPLTag tag); +- (void)markStartForTag:(RCTPLTag)tag; /** * Stops measuring a metric with given tag. * Checks if RCTPerformanceLoggerStart() has been called before * and doesn't do anything and log a message if it hasn't. * If RCTProfile is enabled it also ends appropriate async event. + * All work is scheduled on the background queue so this doesn't block current thread. */ -RCT_EXTERN void RCTPerformanceLoggerEnd(RCTPLTag tag); +- (void)markStopForTag:(RCTPLTag)tag; /** * Sets given value for a metric with given tag. + * All work is scheduled on the background queue so this doesn't block current thread. */ -RCT_EXTERN void RCTPerformanceLoggerSet(RCTPLTag tag, int64_t value); +- (void)setValue:(int64_t)value forTag:(RCTPLTag)tag; /** * Adds given value to the current value for a metric with given tag. + * All work is scheduled on the background queue so this doesn't block current thread. */ -RCT_EXTERN void RCTPerformanceLoggerAdd(RCTPLTag tag, int64_t value); +- (void)addValue:(int64_t)value forTag:(RCTPLTag)tag; /** * Starts an additional measurement for a metric with given tag. * It doesn't override previous measurement, instead it'll append a new value * to the old one. + * All work is scheduled on the background queue so this doesn't block current thread. */ -RCT_EXTERN void RCTPerformanceLoggerAppendStart(RCTPLTag tag); +- (void)appendStartForTag:(RCTPLTag)tag; /** * Stops measurement and appends the result to the metric with given tag. * Checks if RCTPerformanceLoggerAppendStart() has been called before * and doesn't do anything and log a message if it hasn't. + * All work is scheduled on the background queue so this doesn't block current thread. + */ +- (void)appendStopForTag:(RCTPLTag)tag; + +/** + * Returns an array with values for all tags. + * Use RCTPLTag to go over the array, there's a pair of values + * for each tag: start and stop (with indexes 2 * tag and 2 * tag + 1). + */ +- (NSArray<NSNumber *> *)valuesForTags; + +/** + * Returns a duration in ms (stop_time - start_time) for given RCTPLTag. + */ +- (int64_t)durationForTag:(RCTPLTag)tag; + +/** + * Returns a value for given RCTPLTag. + */ +- (int64_t)valueForTag:(RCTPLTag)tag; + +/** + * Returns an array with values for all tags. + * Use RCTPLTag to go over the array. */ -RCT_EXTERN void RCTPerformanceLoggerAppendEnd(RCTPLTag tag); +- (NSArray<NSString *> *)labelsForTags; -RCT_EXTERN NSArray<NSNumber *> *RCTPerformanceLoggerOutput(void); -RCT_EXTERN NSArray *RCTPerformanceLoggerLabels(void); +@end diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m index 68e38f434fbab1..c1f990f41f5e23 100644 --- a/React/Base/RCTPerformanceLogger.m +++ b/React/Base/RCTPerformanceLogger.m @@ -14,77 +14,22 @@ #import "RCTLog.h" #import "RCTProfile.h" -static int64_t RCTPLData[RCTPLSize][2] = {}; -static NSUInteger RCTPLCookies[RCTPLSize] = {}; - -void RCTPerformanceLoggerStart(RCTPLTag tag) -{ - if (RCTProfileIsProfiling()) { - NSString *label = RCTPerformanceLoggerLabels()[tag]; - RCTPLCookies[tag] = RCTProfileBeginAsyncEvent(RCTProfileTagAlways, label, nil); - } - - RCTPLData[tag][0] = CACurrentMediaTime() * 1000; - RCTPLData[tag][1] = 0; -} - -void RCTPerformanceLoggerEnd(RCTPLTag tag) -{ - if (RCTPLData[tag][0] != 0 && RCTPLData[tag][1] == 0) { - RCTPLData[tag][1] = CACurrentMediaTime() * 1000; - - if (RCTProfileIsProfiling()) { - NSString *label = RCTPerformanceLoggerLabels()[tag]; - RCTProfileEndAsyncEvent(RCTProfileTagAlways, @"native", RCTPLCookies[tag], label, @"RCTPerformanceLogger", nil); - } - } else { - RCTLogInfo(@"Unbalanced calls start/end for tag %li", (unsigned long)tag); - } -} - -void RCTPerformanceLoggerSet(RCTPLTag tag, int64_t value) +@interface RCTPerformanceLogger () { - RCTPLData[tag][0] = 0; - RCTPLData[tag][1] = value; + int64_t _data[RCTPLSize][2]; + NSUInteger _cookies[RCTPLSize]; } -void RCTPerformanceLoggerAdd(RCTPLTag tag, int64_t value) -{ - RCTPLData[tag][0] = 0; - RCTPLData[tag][1] += value; -} +@property (nonatomic, copy) NSArray<NSString *> *labelsForTags; -void RCTPerformanceLoggerAppendStart(RCTPLTag tag) -{ - RCTPLData[tag][0] = CACurrentMediaTime() * 1000; -} - -void RCTPerformanceLoggerAppendEnd(RCTPLTag tag) -{ - if (RCTPLData[tag][0] != 0) { - RCTPLData[tag][1] += CACurrentMediaTime() * 1000 - RCTPLData[tag][0]; - RCTPLData[tag][0] = 0; - } else { - RCTLogInfo(@"Unbalanced calls start/end for tag %li", (unsigned long)tag); - } -} +@end -NSArray<NSNumber *> *RCTPerformanceLoggerOutput(void) -{ - NSMutableArray *result = [NSMutableArray array]; - for (NSUInteger index = 0; index < RCTPLSize; index++) { - [result addObject:@(RCTPLData[index][0])]; - [result addObject:@(RCTPLData[index][1])]; - } - return result; -} +@implementation RCTPerformanceLogger -NSArray *RCTPerformanceLoggerLabels(void) +- (instancetype)init { - static NSArray *labels; - static dispatch_once_t token; - dispatch_once(&token, ^{ - labels = @[ + if (self = [super init]) { + _labelsForTags = @[ @"ScriptDownload", @"ScriptExecution", @"RAMBundleLoad", @@ -98,56 +43,84 @@ void RCTPerformanceLoggerAppendEnd(RCTPLTag tag) @"NativeModuleInjectConfig", @"NativeModuleMainThreadUsesCount", @"JSCWrapperOpenLibrary", - @"JSCWrapperLoadFunctions", @"JSCExecutorSetup", @"BridgeStartup", @"RootViewTTI", @"BundleSize", ]; - }); - return labels; + } + return self; } -@interface RCTPerformanceLogger : NSObject <RCTBridgeModule> - -@end +- (void)markStartForTag:(RCTPLTag)tag +{ + if (RCTProfileIsProfiling()) { + NSString *label = _labelsForTags[tag]; + _cookies[tag] = RCTProfileBeginAsyncEvent(RCTProfileTagAlways, label, nil); + } + _data[tag][0] = CACurrentMediaTime() * 1000; + _data[tag][1] = 0; +} -@implementation RCTPerformanceLogger -RCT_EXPORT_MODULE() +- (void)markStopForTag:(RCTPLTag)tag +{ + if (RCTProfileIsProfiling()) { + NSString *label =_labelsForTags[tag]; + RCTProfileEndAsyncEvent(RCTProfileTagAlways, @"native", _cookies[tag], label, @"RCTPerformanceLogger", nil); + } + if (_data[tag][0] != 0 && _data[tag][1] == 0) { + _data[tag][1] = CACurrentMediaTime() * 1000; + } else { + RCTLogInfo(@"Unbalanced calls start/end for tag %li", (unsigned long)tag); + } +} -@synthesize bridge = _bridge; +- (void)setValue:(int64_t)value forTag:(RCTPLTag)tag +{ + _data[tag][0] = 0; + _data[tag][1] = value; +} -- (instancetype)init +- (void)addValue:(int64_t)value forTag:(RCTPLTag)tag { - // We're only overriding this to ensure the module gets created at startup - // TODO (t11106126): Remove once we have more declarative control over module setup. - return [super init]; + _data[tag][0] = 0; + _data[tag][1] += value; } -- (void)setBridge:(RCTBridge *)bridge +- (void)appendStartForTag:(RCTPLTag)tag { - _bridge = bridge; + _data[tag][0] = CACurrentMediaTime() * 1000; +} - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(sendTimespans) - name:RCTContentDidAppearNotification - object:nil]; +- (void)appendStopForTag:(RCTPLTag)tag +{ + if (_data[tag][0] != 0) { + _data[tag][1] += CACurrentMediaTime() * 1000 - _data[tag][0]; + _data[tag][0] = 0; + } else { + RCTLogInfo(@"Unbalanced calls start/end for tag %li", (unsigned long)tag); + } } -- (void)dealloc +- (NSArray<NSNumber *> *)valuesForTags { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + NSMutableArray *result = [NSMutableArray array]; + for (NSUInteger index = 0; index < RCTPLSize; index++) { + [result addObject:@(_data[index][0])]; + [result addObject:@(_data[index][1])]; + } + return result; } -- (void)sendTimespans +- (int64_t)durationForTag:(RCTPLTag)tag { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + return _data[tag][1] - _data[tag][0]; +} - [_bridge enqueueJSCall:@"PerformanceLogger.addTimespans" args:@[ - RCTPerformanceLoggerOutput(), - RCTPerformanceLoggerLabels(), - ]]; +- (int64_t)valueForTag:(RCTPLTag)tag +{ + return _data[tag][1]; } @end diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 78896f5fe482e1..fe0b3ed2de4e5f 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -14,7 +14,7 @@ #import <objc/runtime.h> #import "RCTAssert.h" -#import "RCTBridge+Private.h" +#import "RCTBridge.h" #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" @@ -92,7 +92,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge object:self]; if (!_bridge.loading) { - [self bundleFinishedLoading:_bridge.batchedBridge]; + [self bundleFinishedLoading:_bridge]; } [self showLoadingView]; @@ -158,12 +158,12 @@ - (void)hideLoadingView dispatch_get_main_queue(), ^{ [UIView transitionWithView:self - duration:_loadingViewFadeDuration + duration:self->_loadingViewFadeDuration options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ - _loadingView.hidden = YES; + self->_loadingView.hidden = YES; } completion:^(__unused BOOL finished) { - [_loadingView removeFromSuperview]; + [self->_loadingView removeFromSuperview]; }]; }); } else { @@ -259,7 +259,7 @@ - (void)setAppProperties:(NSDictionary *)appProperties _appProperties = [appProperties copy]; if (_contentView && _bridge.valid && !_bridge.loading) { - [self runApplication:_bridge.batchedBridge]; + [self runApplication:_bridge]; } } @@ -340,10 +340,10 @@ - (instancetype)initWithFrame:(CGRect)frame - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex { [super insertReactSubview:subview atIndex:atIndex]; - RCTPerformanceLoggerEnd(RCTPLTTI); + [_bridge.performanceLogger markStopForTag:RCTPLTTI]; dispatch_async(dispatch_get_main_queue(), ^{ - if (!_contentHasAppeared) { - _contentHasAppeared = YES; + if (!self->_contentHasAppeared) { + self->_contentHasAppeared = YES; [[NSNotificationCenter defaultCenter] postNotificationName:RCTContentDidAppearNotification object:self.superview]; } diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 04705d383287b8..af1aa8e05e316f 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -120,8 +120,8 @@ RCT_EXTERN NSData *__nullable RCTGzipData(NSData *__nullable data, float level); // (or nil, if the URL does not specify a path within the main bundle) RCT_EXTERN NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL); -// Determines if a given image URL actually refers to an XCAsset -RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *__nullable imageURL); +// Determines if a given image URL refers to a local image +RCT_EXTERN BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL); // Creates a new, unique temporary file path with the specified extension RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *__nullable extension, NSError **error); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 398f137b0b09af..b2d9877295f1e0 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -612,24 +612,15 @@ BOOL RCTIsGzippedData(NSData *__nullable data) return path; } -BOOL RCTIsXCAssetURL(NSURL *__nullable imageURL) +BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL) { NSString *name = RCTBundlePathForURL(imageURL); - if (name.pathComponents.count != 1) { - // URL is invalid, or is a file path, not an XCAsset identifier + if (!name) { return NO; } + NSString *extension = [name pathExtension]; - if (extension.length && ![extension isEqualToString:@"png"]) { - // Not a png - return NO; - } - extension = extension.length ? nil : @"png"; - if ([[NSBundle mainBundle] pathForResource:name ofType:extension]) { - // File actually exists in bundle, so is not an XCAsset - return NO; - } - return YES; + return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"]; } RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *extension, NSError **error) diff --git a/React/CSSLayout/CSSLayout-internal.h b/React/CSSLayout/CSSLayout-internal.h new file mode 100644 index 00000000000000..2c65026cdb334f --- /dev/null +++ b/React/CSSLayout/CSSLayout-internal.h @@ -0,0 +1,105 @@ +/** + * 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. + */ + +#ifndef __CSS_LAYOUT_INTERNAL_H +#define __CSS_LAYOUT_INTERNAL_H + +#include <stdio.h> +#include <stdlib.h> + +#include "CSSLayout.h" +#include "CSSNodeList.h" + +CSS_EXTERN_C_BEGIN + +typedef struct CSSCachedMeasurement { + float availableWidth; + float availableHeight; + CSSMeasureMode widthMeasureMode; + CSSMeasureMode heightMeasureMode; + + float computedWidth; + float computedHeight; +} CSSCachedMeasurement; + +// This value was chosen based on empiracle data. Even the most complicated +// layouts should not require more than 16 entries to fit within the cache. +enum { + CSS_MAX_CACHED_RESULT_COUNT = 16 +}; + +typedef struct CSSLayout { + float position[4]; + float dimensions[2]; + CSSDirection direction; + + float flexBasis; + + // Instead of recomputing the entire layout every single time, we + // cache some information to break early when nothing changed + int generationCount; + CSSDirection lastParentDirection; + + int nextCachedMeasurementsIndex; + CSSCachedMeasurement cachedMeasurements[CSS_MAX_CACHED_RESULT_COUNT]; + float measuredDimensions[2]; + + CSSCachedMeasurement cached_layout; +} CSSLayout; + +typedef struct CSSStyle { + CSSDirection direction; + CSSFlexDirection flexDirection; + CSSJustify justifyContent; + CSSAlign alignContent; + CSSAlign alignItems; + CSSAlign alignSelf; + CSSPositionType positionType; + CSSWrapType flexWrap; + CSSOverflow overflow; + float flex; + float margin[6]; + float position[4]; + /** + * You should skip all the rules that contain negative values for the + * following attributes. For example: + * {padding: 10, paddingLeft: -5} + * should output: + * {left: 10 ...} + * the following two are incorrect: + * {left: -5 ...} + * {left: 0 ...} + */ + float padding[6]; + float border[6]; + float dimensions[2]; + float minDimensions[2]; + float maxDimensions[2]; +} CSSStyle; + +typedef struct CSSNode { + CSSStyle style; + CSSLayout layout; + int lineIndex; + bool shouldUpdate; + bool isTextNode; + CSSNodeRef parent; + CSSNodeListRef children; + bool isDirty; + + struct CSSNode* nextChild; + + CSSSize (*measure)(void *context, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode); + void (*print)(void *context); + void *context; +} CSSNode; + +CSS_EXTERN_C_END + +#endif diff --git a/React/Layout/Layout.c b/React/CSSLayout/CSSLayout.c similarity index 51% rename from React/Layout/Layout.c rename to React/CSSLayout/CSSLayout.c index da1e2559f43704..49136970f227bb 100644 --- a/React/Layout/Layout.c +++ b/React/CSSLayout/CSSLayout.c @@ -1,25 +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<<0c8bd7e17fc12884003809cf282b0988>> - - #include <assert.h> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -// in concatenated header, don't include Layout.h it's already at the top -#ifndef CSS_LAYOUT_IMPLEMENTATION -#include "Layout.h" -#endif +#include "CSSLayout-internal.h" #ifdef _MSC_VER #include <float.h> @@ -35,75 +29,200 @@ __forceinline const float fmaxf(const float a, const float b) { #define POSITIVE_FLEX_IS_AUTO 0 -int gCurrentGenerationCount = 0; - -bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableHeight, css_direction_t parentDirection, - css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, bool performLayout, char* reason); +CSSNodeRef CSSNodeNew() { + CSSNodeRef node = calloc(1, sizeof(CSSNode)); + assert(node != NULL); -bool isUndefined(float value) { - return isnan(value); + CSSNodeInit(node); + return node; } -static bool eq(float a, float b) { - if (isUndefined(a)) { - return isUndefined(b); - } - return fabs(a - b) < 0.0001; +void CSSNodeFree(CSSNodeRef node) { + CSSNodeListFree(node->children); + free(node); } -void init_css_node(css_node_t* node) { - node->style.align_items = CSS_ALIGN_STRETCH; - node->style.align_content = CSS_ALIGN_FLEX_START; +void CSSNodeInit(CSSNodeRef node) { + node->parent = NULL; + node->children = CSSNodeListNew(4); + node->shouldUpdate = true; + node->isDirty = false; + + node->style.alignItems = CSSAlignStretch; + node->style.alignContent = CSSAlignFlexStart; - node->style.direction = CSS_DIRECTION_INHERIT; - node->style.flex_direction = CSS_FLEX_DIRECTION_COLUMN; + node->style.direction = CSSDirectionInherit; + node->style.flexDirection = CSSFlexDirectionColumn; - node->style.overflow = CSS_OVERFLOW_VISIBLE; + node->style.overflow = CSSOverflowVisible; // Some of the fields default to undefined and not 0 - node->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + node->style.dimensions[CSSDimensionWidth] = CSSUndefined; + node->style.dimensions[CSSDimensionHeight] = CSSUndefined; - node->style.minDimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->style.minDimensions[CSS_HEIGHT] = CSS_UNDEFINED; + node->style.minDimensions[CSSDimensionWidth] = CSSUndefined; + node->style.minDimensions[CSSDimensionHeight] = CSSUndefined; - node->style.maxDimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->style.maxDimensions[CSS_HEIGHT] = CSS_UNDEFINED; + node->style.maxDimensions[CSSDimensionWidth] = CSSUndefined; + node->style.maxDimensions[CSSDimensionHeight] = CSSUndefined; - node->style.position[CSS_LEFT] = CSS_UNDEFINED; - node->style.position[CSS_TOP] = CSS_UNDEFINED; - node->style.position[CSS_RIGHT] = CSS_UNDEFINED; - node->style.position[CSS_BOTTOM] = CSS_UNDEFINED; + node->style.position[CSSPositionLeft] = CSSUndefined; + node->style.position[CSSPositionTop] = CSSUndefined; + node->style.position[CSSPositionRight] = CSSUndefined; + node->style.position[CSSPositionBottom] = CSSUndefined; - node->style.margin[CSS_START] = CSS_UNDEFINED; - node->style.margin[CSS_END] = CSS_UNDEFINED; - node->style.padding[CSS_START] = CSS_UNDEFINED; - node->style.padding[CSS_END] = CSS_UNDEFINED; - node->style.border[CSS_START] = CSS_UNDEFINED; - node->style.border[CSS_END] = CSS_UNDEFINED; + node->style.margin[CSSPositionStart] = CSSUndefined; + node->style.margin[CSSPositionEnd] = CSSUndefined; + node->style.padding[CSSPositionStart] = CSSUndefined; + node->style.padding[CSSPositionEnd] = CSSUndefined; + node->style.border[CSSPositionStart] = CSSUndefined; + node->style.border[CSSPositionEnd] = CSSUndefined; - node->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + node->layout.dimensions[CSSDimensionWidth] = CSSUndefined; + node->layout.dimensions[CSSDimensionHeight] = CSSUndefined; // Such that the comparison is always going to be false - node->layout.last_parent_direction = (css_direction_t)-1; - node->layout.should_update = true; - node->layout.next_cached_measurements_index = 0; - - node->layout.measured_dimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->layout.measured_dimensions[CSS_HEIGHT] = CSS_UNDEFINED; - node->layout.cached_layout.width_measure_mode = (css_measure_mode_t)-1; - node->layout.cached_layout.height_measure_mode = (css_measure_mode_t)-1; + node->layout.lastParentDirection = (CSSDirection)-1; + node->layout.nextCachedMeasurementsIndex = 0; + + node->layout.measuredDimensions[CSSDimensionWidth] = CSSUndefined; + node->layout.measuredDimensions[CSSDimensionHeight] = CSSUndefined; + node->layout.cached_layout.widthMeasureMode = (CSSMeasureMode)-1; + node->layout.cached_layout.heightMeasureMode = (CSSMeasureMode)-1; } -css_node_t* new_css_node() { - css_node_t* node = (css_node_t*)calloc(1, sizeof(*node)); - init_css_node(node); - return node; +void _CSSNodeMarkDirty(CSSNodeRef node) { + if (!node->isDirty) { + node->isDirty = true; + if (node->parent) { + _CSSNodeMarkDirty(node->parent); + } + } } -void free_css_node(css_node_t* node) { - free(node); +void CSSNodeInsertChild(CSSNodeRef node, CSSNodeRef child, unsigned int index) { + CSSNodeListInsert(node->children, child, index); + child->parent = node; + _CSSNodeMarkDirty(node); +} + +void CSSNodeRemoveChild(CSSNodeRef node, CSSNodeRef child) { + CSSNodeListDelete(node->children, child); + child->parent = NULL; + _CSSNodeMarkDirty(node); +} + +CSSNodeRef CSSNodeGetChild(CSSNodeRef node, unsigned int index) { + return CSSNodeListGet(node->children, index); +} + +unsigned int CSSNodeChildCount(CSSNodeRef node) { + return CSSNodeListCount(node->children); +} + +void CSSNodeMarkDirty(CSSNodeRef node) { + // Nodes without custom measure functions should not manually mark themselves as dirty + assert(node->measure != NULL); + _CSSNodeMarkDirty(node); +} + +#define CSS_NODE_PROPERTY_IMPL(type, name, paramName, instanceName) \ +void CSSNodeSet##name(CSSNodeRef node, type paramName) { \ + node->instanceName = paramName; \ +} \ +\ +type CSSNodeGet##name(CSSNodeRef node) { \ + return node->instanceName; \ +} \ + +#define CSS_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) \ +void CSSNodeStyleSet##name(CSSNodeRef node, type paramName) { \ + if (node->style.instanceName != paramName) { \ + node->style.instanceName = paramName; \ + _CSSNodeMarkDirty(node); \ + } \ +} \ +\ +type CSSNodeStyleGet##name(CSSNodeRef node) { \ + return node->style.instanceName; \ +} \ + +#define CSS_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) \ +type CSSNodeLayoutGet##name(CSSNodeRef node) { \ + return node->layout.instanceName; \ +} \ + +CSS_NODE_PROPERTY_IMPL(void*, Context, context, context); +CSS_NODE_PROPERTY_IMPL(CSSMeasureFunc, MeasureFunc, measureFunc, measure); +CSS_NODE_PROPERTY_IMPL(CSSPrintFunc, PrintFunc, printFunc, print); +CSS_NODE_PROPERTY_IMPL(bool, IsTextnode, isTextNode, isTextNode); +CSS_NODE_PROPERTY_IMPL(bool, ShouldUpdate, shouldUpdate, shouldUpdate); + +CSS_NODE_STYLE_PROPERTY_IMPL(CSSDirection, Direction, direction, direction); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSFlexDirection, FlexDirection, flexDirection, flexDirection); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSJustify, JustifyContent, justifyContent, justifyContent); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignContent, alignContent, alignContent); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignItems, alignItems, alignItems); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignSelf, alignSelf, alignSelf); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSPositionType, PositionType, positionType, positionType); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSWrapType, FlexWrap, flexWrap, flexWrap); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSOverflow, Overflow, overflow, overflow); +CSS_NODE_STYLE_PROPERTY_IMPL(float, Flex, flex, flex); + +CSS_NODE_STYLE_PROPERTY_IMPL(float, PositionLeft, positionLeft, position[CSSPositionLeft]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PositionTop, positionTop, position[CSSPositionTop]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PositionRight, positionRight, position[CSSPositionRight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PositionBottom, positionBottom, position[CSSPositionBottom]); + +CSS_NODE_STYLE_PROPERTY_IMPL(float, MarginLeft, marginLeft, margin[CSSPositionLeft]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MarginTop, marginTop, margin[CSSPositionTop]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MarginRight, marginRight, margin[CSSPositionRight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MarginBottom, marginBottom, margin[CSSPositionBottom]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MarginStart, marginStart, margin[CSSPositionStart]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MarginEnd, marginEnd, margin[CSSPositionEnd]); + +CSS_NODE_STYLE_PROPERTY_IMPL(float, PaddingLeft, paddingLeft, padding[CSSPositionLeft]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PaddingTop, paddingTop, padding[CSSPositionTop]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PaddingRight, paddingRight, padding[CSSPositionRight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PaddingBottom, paddingBottom, padding[CSSPositionBottom]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PaddingStart, paddingStart, padding[CSSPositionStart]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, PaddingEnd, paddingEnd, padding[CSSPositionEnd]); + +CSS_NODE_STYLE_PROPERTY_IMPL(float, BorderLeft, borderLeft, border[CSSPositionLeft]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, BorderTop, borderTop, border[CSSPositionTop]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, BorderRight, borderRight, border[CSSPositionRight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, BorderBottom, borderBottom, border[CSSPositionBottom]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, BorderStart, borderStart, border[CSSPositionStart]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, BorderEnd, BorderEnd, border[CSSPositionEnd]); + +CSS_NODE_STYLE_PROPERTY_IMPL(float, Width, width, dimensions[CSSDimensionWidth]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, Height, height, dimensions[CSSDimensionHeight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MinWidth, minWidth, minDimensions[CSSDimensionWidth]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MinHeight, minHeight, minDimensions[CSSDimensionHeight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MaxWidth, maxWidth, maxDimensions[CSSDimensionWidth]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MaxHeight, maxHeight, maxDimensions[CSSDimensionHeight]); + +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Left, position[CSSPositionLeft]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Top, position[CSSPositionTop]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Right, position[CSSPositionRight]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Bottom, position[CSSPositionBottom]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Width, dimensions[CSSDimensionWidth]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Height, dimensions[CSSDimensionHeight]); + +int gCurrentGenerationCount = 0; + +bool layoutNodeInternal(CSSNode* node, float availableWidth, float availableHeight, CSSDirection parentDirection, + CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, bool performLayout, char* reason); + +bool isUndefined(float value) { + return isnan(value); +} + +static bool eq(float a, float b) { + if (isUndefined(a)) { + return isUndefined(b); + } + return fabs(a - b) < 0.0001; } static void indent(int n) { @@ -133,8 +252,8 @@ static bool four_equal(float four[4]) { static void print_css_node_rec( - css_node_t* node, - css_print_options_t options, + CSSNode* node, + CSSPrintOptions options, int level ) { indent(level); @@ -144,124 +263,125 @@ static void print_css_node_rec( node->print(node->context); } - if (options & CSS_PRINT_LAYOUT) { + if (options & CSSPrintOptionsLayout) { printf("layout: {"); - printf("width: %g, ", node->layout.dimensions[CSS_WIDTH]); - printf("height: %g, ", node->layout.dimensions[CSS_HEIGHT]); - printf("top: %g, ", node->layout.position[CSS_TOP]); - printf("left: %g", node->layout.position[CSS_LEFT]); + printf("width: %g, ", node->layout.dimensions[CSSDimensionWidth]); + printf("height: %g, ", node->layout.dimensions[CSSDimensionHeight]); + printf("top: %g, ", node->layout.position[CSSPositionTop]); + printf("left: %g", node->layout.position[CSSPositionLeft]); printf("}, "); } - if (options & CSS_PRINT_STYLE) { - if (node->style.flex_direction == CSS_FLEX_DIRECTION_COLUMN) { + if (options & CSSPrintOptionsStyle) { + if (node->style.flexDirection == CSSFlexDirectionColumn) { printf("flexDirection: 'column', "); - } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { + } else if (node->style.flexDirection == CSSFlexDirectionColumnReverse) { printf("flexDirection: 'column-reverse', "); - } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW) { + } else if (node->style.flexDirection == CSSFlexDirectionRow) { printf("flexDirection: 'row', "); - } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW_REVERSE) { + } else if (node->style.flexDirection == CSSFlexDirectionRowReverse) { printf("flexDirection: 'row-reverse', "); } - if (node->style.justify_content == CSS_JUSTIFY_CENTER) { + if (node->style.justifyContent == CSSJustifyCenter) { printf("justifyContent: 'center', "); - } else if (node->style.justify_content == CSS_JUSTIFY_FLEX_END) { + } else if (node->style.justifyContent == CSSJustifyFlexEnd) { printf("justifyContent: 'flex-end', "); - } else if (node->style.justify_content == CSS_JUSTIFY_SPACE_AROUND) { + } else if (node->style.justifyContent == CSSJustifySpaceAround) { printf("justifyContent: 'space-around', "); - } else if (node->style.justify_content == CSS_JUSTIFY_SPACE_BETWEEN) { + } else if (node->style.justifyContent == CSSJustifySpaceBetween) { printf("justifyContent: 'space-between', "); } - if (node->style.align_items == CSS_ALIGN_CENTER) { + if (node->style.alignItems == CSSAlignCenter) { printf("alignItems: 'center', "); - } else if (node->style.align_items == CSS_ALIGN_FLEX_END) { + } else if (node->style.alignItems == CSSAlignFlexEnd) { printf("alignItems: 'flex-end', "); - } else if (node->style.align_items == CSS_ALIGN_STRETCH) { + } else if (node->style.alignItems == CSSAlignStretch) { printf("alignItems: 'stretch', "); } - if (node->style.align_content == CSS_ALIGN_CENTER) { + if (node->style.alignContent == CSSAlignCenter) { printf("alignContent: 'center', "); - } else if (node->style.align_content == CSS_ALIGN_FLEX_END) { + } else if (node->style.alignContent == CSSAlignFlexEnd) { printf("alignContent: 'flex-end', "); - } else if (node->style.align_content == CSS_ALIGN_STRETCH) { + } else if (node->style.alignContent == CSSAlignStretch) { printf("alignContent: 'stretch', "); } - if (node->style.align_self == CSS_ALIGN_FLEX_START) { + if (node->style.alignSelf == CSSAlignFlexStart) { printf("alignSelf: 'flex-start', "); - } else if (node->style.align_self == CSS_ALIGN_CENTER) { + } else if (node->style.alignSelf == CSSAlignCenter) { printf("alignSelf: 'center', "); - } else if (node->style.align_self == CSS_ALIGN_FLEX_END) { + } else if (node->style.alignSelf == CSSAlignFlexEnd) { printf("alignSelf: 'flex-end', "); - } else if (node->style.align_self == CSS_ALIGN_STRETCH) { + } else if (node->style.alignSelf == CSSAlignStretch) { printf("alignSelf: 'stretch', "); } print_number_nan("flex", node->style.flex); - if (node->style.overflow == CSS_OVERFLOW_HIDDEN) { + if (node->style.overflow == CSSOverflowHidden) { printf("overflow: 'hidden', "); - } else if (node->style.overflow == CSS_OVERFLOW_VISIBLE) { + } else if (node->style.overflow == CSSOverflowVisible) { printf("overflow: 'visible', "); } if (four_equal(node->style.margin)) { - print_number_0("margin", node->style.margin[CSS_LEFT]); + print_number_0("margin", node->style.margin[CSSPositionLeft]); } else { - print_number_0("marginLeft", node->style.margin[CSS_LEFT]); - print_number_0("marginRight", node->style.margin[CSS_RIGHT]); - print_number_0("marginTop", node->style.margin[CSS_TOP]); - print_number_0("marginBottom", node->style.margin[CSS_BOTTOM]); - print_number_0("marginStart", node->style.margin[CSS_START]); - print_number_0("marginEnd", node->style.margin[CSS_END]); + print_number_0("marginLeft", node->style.margin[CSSPositionLeft]); + print_number_0("marginRight", node->style.margin[CSSPositionRight]); + print_number_0("marginTop", node->style.margin[CSSPositionTop]); + print_number_0("marginBottom", node->style.margin[CSSPositionBottom]); + print_number_0("marginStart", node->style.margin[CSSPositionStart]); + print_number_0("marginEnd", node->style.margin[CSSPositionEnd]); } if (four_equal(node->style.padding)) { - print_number_0("padding", node->style.padding[CSS_LEFT]); + print_number_0("padding", node->style.padding[CSSPositionLeft]); } else { - print_number_0("paddingLeft", node->style.padding[CSS_LEFT]); - print_number_0("paddingRight", node->style.padding[CSS_RIGHT]); - print_number_0("paddingTop", node->style.padding[CSS_TOP]); - print_number_0("paddingBottom", node->style.padding[CSS_BOTTOM]); - print_number_0("paddingStart", node->style.padding[CSS_START]); - print_number_0("paddingEnd", node->style.padding[CSS_END]); + print_number_0("paddingLeft", node->style.padding[CSSPositionLeft]); + print_number_0("paddingRight", node->style.padding[CSSPositionRight]); + print_number_0("paddingTop", node->style.padding[CSSPositionTop]); + print_number_0("paddingBottom", node->style.padding[CSSPositionBottom]); + print_number_0("paddingStart", node->style.padding[CSSPositionStart]); + print_number_0("paddingEnd", node->style.padding[CSSPositionEnd]); } if (four_equal(node->style.border)) { - print_number_0("borderWidth", node->style.border[CSS_LEFT]); + print_number_0("borderWidth", node->style.border[CSSPositionLeft]); } else { - print_number_0("borderLeftWidth", node->style.border[CSS_LEFT]); - print_number_0("borderRightWidth", node->style.border[CSS_RIGHT]); - print_number_0("borderTopWidth", node->style.border[CSS_TOP]); - print_number_0("borderBottomWidth", node->style.border[CSS_BOTTOM]); - print_number_0("borderStartWidth", node->style.border[CSS_START]); - print_number_0("borderEndWidth", node->style.border[CSS_END]); + print_number_0("borderLeftWidth", node->style.border[CSSPositionLeft]); + print_number_0("borderRightWidth", node->style.border[CSSPositionRight]); + print_number_0("borderTopWidth", node->style.border[CSSPositionTop]); + print_number_0("borderBottomWidth", node->style.border[CSSPositionBottom]); + print_number_0("borderStartWidth", node->style.border[CSSPositionStart]); + print_number_0("borderEndWidth", node->style.border[CSSPositionEnd]); } - print_number_nan("width", node->style.dimensions[CSS_WIDTH]); - print_number_nan("height", node->style.dimensions[CSS_HEIGHT]); - print_number_nan("maxWidth", node->style.maxDimensions[CSS_WIDTH]); - print_number_nan("maxHeight", node->style.maxDimensions[CSS_HEIGHT]); - print_number_nan("minWidth", node->style.minDimensions[CSS_WIDTH]); - print_number_nan("minHeight", node->style.minDimensions[CSS_HEIGHT]); + print_number_nan("width", node->style.dimensions[CSSDimensionWidth]); + print_number_nan("height", node->style.dimensions[CSSDimensionHeight]); + print_number_nan("maxWidth", node->style.maxDimensions[CSSDimensionWidth]); + print_number_nan("maxHeight", node->style.maxDimensions[CSSDimensionHeight]); + print_number_nan("minWidth", node->style.minDimensions[CSSDimensionWidth]); + print_number_nan("minHeight", node->style.minDimensions[CSSDimensionHeight]); - if (node->style.position_type == CSS_POSITION_ABSOLUTE) { + if (node->style.positionType == CSSPositionTypeAbsolute) { printf("position: 'absolute', "); } - print_number_nan("left", node->style.position[CSS_LEFT]); - print_number_nan("right", node->style.position[CSS_RIGHT]); - print_number_nan("top", node->style.position[CSS_TOP]); - print_number_nan("bottom", node->style.position[CSS_BOTTOM]); + print_number_nan("left", node->style.position[CSSPositionLeft]); + print_number_nan("right", node->style.position[CSSPositionRight]); + print_number_nan("top", node->style.position[CSSPositionTop]); + print_number_nan("bottom", node->style.position[CSSPositionBottom]); } - if (options & CSS_PRINT_CHILDREN && node->children_count > 0) { + unsigned int childCount = CSSNodeListCount(node->children); + if (options & CSSPrintOptionsChildren && childCount > 0) { printf("children: [\n"); - for (int i = 0; i < node->children_count; ++i) { - print_css_node_rec(node->get_child(node->context, i), options, level + 1); + for (unsigned int i = 0; i < childCount; ++i) { + print_css_node_rec(CSSNodeGetChild(node, i), options, level + 1); } indent(level); printf("]},\n"); @@ -270,46 +390,46 @@ static void print_css_node_rec( } } -void print_css_node(css_node_t* node, css_print_options_t options) { +void CSSNodePrint(CSSNode* node, CSSPrintOptions options) { print_css_node_rec(node, options, 0); } -static css_position_t leading[4] = { - /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_TOP, - /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_BOTTOM, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT, - /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_RIGHT +static CSSPosition leading[4] = { + /* CSSFlexDirectionColumn = */ CSSPositionTop, + /* CSSFlexDirectionColumnReverse = */ CSSPositionBottom, + /* CSSFlexDirectionRow = */ CSSPositionLeft, + /* CSSFlexDirectionRowReverse = */ CSSPositionRight }; -static css_position_t trailing[4] = { - /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_BOTTOM, - /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_TOP, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_RIGHT, - /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_LEFT +static CSSPosition trailing[4] = { + /* CSSFlexDirectionColumn = */ CSSPositionBottom, + /* CSSFlexDirectionColumnReverse = */ CSSPositionTop, + /* CSSFlexDirectionRow = */ CSSPositionRight, + /* CSSFlexDirectionRowReverse = */ CSSPositionLeft }; -static css_position_t pos[4] = { - /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_TOP, - /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_BOTTOM, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT, - /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_RIGHT +static CSSPosition pos[4] = { + /* CSSFlexDirectionColumn = */ CSSPositionTop, + /* CSSFlexDirectionColumnReverse = */ CSSPositionBottom, + /* CSSFlexDirectionRow = */ CSSPositionLeft, + /* CSSFlexDirectionRowReverse = */ CSSPositionRight }; -static css_dimension_t dim[4] = { - /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_HEIGHT, - /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_HEIGHT, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_WIDTH, - /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_WIDTH +static CSSDimension dim[4] = { + /* CSSFlexDirectionColumn = */ CSSDimensionHeight, + /* CSSFlexDirectionColumnReverse = */ CSSDimensionHeight, + /* CSSFlexDirectionRow = */ CSSDimensionWidth, + /* CSSFlexDirectionRowReverse = */ CSSDimensionWidth }; -static bool isRowDirection(css_flex_direction_t flex_direction) { - return flex_direction == CSS_FLEX_DIRECTION_ROW || - flex_direction == CSS_FLEX_DIRECTION_ROW_REVERSE; +static bool isRowDirection(CSSFlexDirection flexDirection) { + return flexDirection == CSSFlexDirectionRow || + flexDirection == CSSFlexDirectionRowReverse; } -static bool isColumnDirection(css_flex_direction_t flex_direction) { - return flex_direction == CSS_FLEX_DIRECTION_COLUMN || - flex_direction == CSS_FLEX_DIRECTION_COLUMN_REVERSE; +static bool isColumnDirection(CSSFlexDirection flexDirection) { + return flexDirection == CSSFlexDirectionColumn || + flexDirection == CSSFlexDirectionColumnReverse; } -static bool isFlexBasisAuto(css_node_t* node) { +static bool isFlexBasisAuto(CSSNode* node) { #if POSITIVE_FLEX_IS_AUTO // All flex values are auto. (void) node; @@ -320,7 +440,7 @@ static bool isFlexBasisAuto(css_node_t* node) { #endif } -static float getFlexGrowFactor(css_node_t* node) { +static float getFlexGrowFactor(CSSNode* node) { // Flex grow is implied by positive values for flex. if (node->style.flex > 0) { return node->style.flex; @@ -328,7 +448,7 @@ static float getFlexGrowFactor(css_node_t* node) { return 0; } -static float getFlexShrinkFactor(css_node_t* node) { +static float getFlexShrinkFactor(CSSNode* node) { #if POSITIVE_FLEX_IS_AUTO // A flex shrink factor of 1 is implied by non-zero values for flex. if (node->style.flex != 0) { @@ -343,27 +463,27 @@ static float getFlexShrinkFactor(css_node_t* node) { return 0; } -static float getLeadingMargin(css_node_t* node, css_flex_direction_t axis) { - if (isRowDirection(axis) && !isUndefined(node->style.margin[CSS_START])) { - return node->style.margin[CSS_START]; +static float getLeadingMargin(CSSNode* node, CSSFlexDirection axis) { + if (isRowDirection(axis) && !isUndefined(node->style.margin[CSSPositionStart])) { + return node->style.margin[CSSPositionStart]; } return node->style.margin[leading[axis]]; } -static float getTrailingMargin(css_node_t* node, css_flex_direction_t axis) { - if (isRowDirection(axis) && !isUndefined(node->style.margin[CSS_END])) { - return node->style.margin[CSS_END]; +static float getTrailingMargin(CSSNode* node, CSSFlexDirection axis) { + if (isRowDirection(axis) && !isUndefined(node->style.margin[CSSPositionEnd])) { + return node->style.margin[CSSPositionEnd]; } return node->style.margin[trailing[axis]]; } -static float getLeadingPadding(css_node_t* node, css_flex_direction_t axis) { +static float getLeadingPadding(CSSNode* node, CSSFlexDirection axis) { if (isRowDirection(axis) && - !isUndefined(node->style.padding[CSS_START]) && - node->style.padding[CSS_START] >= 0) { - return node->style.padding[CSS_START]; + !isUndefined(node->style.padding[CSSPositionStart]) && + node->style.padding[CSSPositionStart] >= 0) { + return node->style.padding[CSSPositionStart]; } if (node->style.padding[leading[axis]] >= 0) { @@ -373,11 +493,11 @@ static float getLeadingPadding(css_node_t* node, css_flex_direction_t axis) { return 0; } -static float getTrailingPadding(css_node_t* node, css_flex_direction_t axis) { +static float getTrailingPadding(CSSNode* node, CSSFlexDirection axis) { if (isRowDirection(axis) && - !isUndefined(node->style.padding[CSS_END]) && - node->style.padding[CSS_END] >= 0) { - return node->style.padding[CSS_END]; + !isUndefined(node->style.padding[CSSPositionEnd]) && + node->style.padding[CSSPositionEnd] >= 0) { + return node->style.padding[CSSPositionEnd]; } if (node->style.padding[trailing[axis]] >= 0) { @@ -387,11 +507,11 @@ static float getTrailingPadding(css_node_t* node, css_flex_direction_t axis) { return 0; } -static float getLeadingBorder(css_node_t* node, css_flex_direction_t axis) { +static float getLeadingBorder(CSSNode* node, CSSFlexDirection axis) { if (isRowDirection(axis) && - !isUndefined(node->style.border[CSS_START]) && - node->style.border[CSS_START] >= 0) { - return node->style.border[CSS_START]; + !isUndefined(node->style.border[CSSPositionStart]) && + node->style.border[CSSPositionStart] >= 0) { + return node->style.border[CSSPositionStart]; } if (node->style.border[leading[axis]] >= 0) { @@ -401,11 +521,11 @@ static float getLeadingBorder(css_node_t* node, css_flex_direction_t axis) { return 0; } -static float getTrailingBorder(css_node_t* node, css_flex_direction_t axis) { +static float getTrailingBorder(CSSNode* node, CSSFlexDirection axis) { if (isRowDirection(axis) && - !isUndefined(node->style.border[CSS_END]) && - node->style.border[CSS_END] >= 0) { - return node->style.border[CSS_END]; + !isUndefined(node->style.border[CSSPositionEnd]) && + node->style.border[CSSPositionEnd] >= 0) { + return node->style.border[CSSPositionEnd]; } if (node->style.border[trailing[axis]] >= 0) { @@ -415,103 +535,103 @@ static float getTrailingBorder(css_node_t* node, css_flex_direction_t axis) { return 0; } -static float getLeadingPaddingAndBorder(css_node_t* node, css_flex_direction_t axis) { +static float getLeadingPaddingAndBorder(CSSNode* node, CSSFlexDirection axis) { return getLeadingPadding(node, axis) + getLeadingBorder(node, axis); } -static float getTrailingPaddingAndBorder(css_node_t* node, css_flex_direction_t axis) { +static float getTrailingPaddingAndBorder(CSSNode* node, CSSFlexDirection axis) { return getTrailingPadding(node, axis) + getTrailingBorder(node, axis); } -static float getMarginAxis(css_node_t* node, css_flex_direction_t axis) { +static float getMarginAxis(CSSNode* node, CSSFlexDirection axis) { return getLeadingMargin(node, axis) + getTrailingMargin(node, axis); } -static float getPaddingAndBorderAxis(css_node_t* node, css_flex_direction_t axis) { +static float getPaddingAndBorderAxis(CSSNode* node, CSSFlexDirection axis) { return getLeadingPaddingAndBorder(node, axis) + getTrailingPaddingAndBorder(node, axis); } -static css_align_t getAlignItem(css_node_t* node, css_node_t* child) { - if (child->style.align_self != CSS_ALIGN_AUTO) { - return child->style.align_self; +static CSSAlign getAlignItem(CSSNode* node, CSSNode* child) { + if (child->style.alignSelf != CSSAlignAuto) { + return child->style.alignSelf; } - return node->style.align_items; + return node->style.alignItems; } -static css_direction_t resolveDirection(css_node_t* node, css_direction_t parentDirection) { - css_direction_t direction = node->style.direction; +static CSSDirection resolveDirection(CSSNode* node, CSSDirection parentDirection) { + CSSDirection direction = node->style.direction; - if (direction == CSS_DIRECTION_INHERIT) { - direction = parentDirection > CSS_DIRECTION_INHERIT ? parentDirection : CSS_DIRECTION_LTR; + if (direction == CSSDirectionInherit) { + direction = parentDirection > CSSDirectionInherit ? parentDirection : CSSDirectionLTR; } return direction; } -static css_flex_direction_t getFlexDirection(css_node_t* node) { - return node->style.flex_direction; +static CSSFlexDirection getFlexDirection(CSSNode* node) { + return node->style.flexDirection; } -static css_flex_direction_t resolveAxis(css_flex_direction_t flex_direction, css_direction_t direction) { - if (direction == CSS_DIRECTION_RTL) { - if (flex_direction == CSS_FLEX_DIRECTION_ROW) { - return CSS_FLEX_DIRECTION_ROW_REVERSE; - } else if (flex_direction == CSS_FLEX_DIRECTION_ROW_REVERSE) { - return CSS_FLEX_DIRECTION_ROW; +static CSSFlexDirection resolveAxis(CSSFlexDirection flexDirection, CSSDirection direction) { + if (direction == CSSDirectionRTL) { + if (flexDirection == CSSFlexDirectionRow) { + return CSSFlexDirectionRowReverse; + } else if (flexDirection == CSSFlexDirectionRowReverse) { + return CSSFlexDirectionRow; } } - return flex_direction; + return flexDirection; } -static css_flex_direction_t getCrossFlexDirection(css_flex_direction_t flex_direction, css_direction_t direction) { - if (isColumnDirection(flex_direction)) { - return resolveAxis(CSS_FLEX_DIRECTION_ROW, direction); +static CSSFlexDirection getCrossFlexDirection(CSSFlexDirection flexDirection, CSSDirection direction) { + if (isColumnDirection(flexDirection)) { + return resolveAxis(CSSFlexDirectionRow, direction); } else { - return CSS_FLEX_DIRECTION_COLUMN; + return CSSFlexDirectionColumn; } } -static float getFlex(css_node_t* node) { +static float getFlex(CSSNode* node) { return node->style.flex; } -static bool isFlex(css_node_t* node) { +static bool isFlex(CSSNode* node) { return ( - node->style.position_type == CSS_POSITION_RELATIVE && + node->style.positionType == CSSPositionTypeRelative && getFlex(node) != 0 ); } -static bool isFlexWrap(css_node_t* node) { - return node->style.flex_wrap == CSS_WRAP; +static bool isFlexWrap(CSSNode* node) { + return node->style.flexWrap == CSSWrapTypeWrap; } -static float getDimWithMargin(css_node_t* node, css_flex_direction_t axis) { - return node->layout.measured_dimensions[dim[axis]] + +static float getDimWithMargin(CSSNode* node, CSSFlexDirection axis) { + return node->layout.measuredDimensions[dim[axis]] + getLeadingMargin(node, axis) + getTrailingMargin(node, axis); } -static bool isStyleDimDefined(css_node_t* node, css_flex_direction_t axis) { +static bool isStyleDimDefined(CSSNode* node, CSSFlexDirection axis) { float value = node->style.dimensions[dim[axis]]; return !isUndefined(value) && value >= 0.0; } -static bool isLayoutDimDefined(css_node_t* node, css_flex_direction_t axis) { - float value = node->layout.measured_dimensions[dim[axis]]; +static bool isLayoutDimDefined(CSSNode* node, CSSFlexDirection axis) { + float value = node->layout.measuredDimensions[dim[axis]]; return !isUndefined(value) && value >= 0.0; } -static bool isPosDefined(css_node_t* node, css_position_t position) { +static bool isPosDefined(CSSNode* node, CSSPosition position) { return !isUndefined(node->style.position[position]); } -static bool isMeasureDefined(css_node_t* node) { +static bool isMeasureDefined(CSSNode* node) { return node->measure; } -static float getPosition(css_node_t* node, css_position_t position) { +static float getPosition(CSSNode* node, CSSPosition position) { float result = node->style.position[position]; if (!isUndefined(result)) { return result; @@ -519,16 +639,16 @@ static float getPosition(css_node_t* node, css_position_t position) { return 0; } -static float boundAxisWithinMinAndMax(css_node_t* node, css_flex_direction_t axis, float value) { - float min = CSS_UNDEFINED; - float max = CSS_UNDEFINED; +static float boundAxisWithinMinAndMax(CSSNode* node, CSSFlexDirection axis, float value) { + float min = CSSUndefined; + float max = CSSUndefined; if (isColumnDirection(axis)) { - min = node->style.minDimensions[CSS_HEIGHT]; - max = node->style.maxDimensions[CSS_HEIGHT]; + min = node->style.minDimensions[CSSDimensionHeight]; + max = node->style.maxDimensions[CSSDimensionHeight]; } else if (isRowDirection(axis)) { - min = node->style.minDimensions[CSS_WIDTH]; - max = node->style.maxDimensions[CSS_WIDTH]; + min = node->style.minDimensions[CSSDimensionWidth]; + max = node->style.maxDimensions[CSSDimensionWidth]; } float boundValue = value; @@ -545,20 +665,20 @@ static float boundAxisWithinMinAndMax(css_node_t* node, css_flex_direction_t axi // Like boundAxisWithinMinAndMax but also ensures that the value doesn't go below the // padding and border amount. -static float boundAxis(css_node_t* node, css_flex_direction_t axis, float value) { +static float boundAxis(CSSNode* node, CSSFlexDirection axis, float value) { return fmaxf(boundAxisWithinMinAndMax(node, axis, value), getPaddingAndBorderAxis(node, axis)); } -static void setTrailingPosition(css_node_t* node, css_node_t* child, css_flex_direction_t axis) { - float size = child->style.position_type == CSS_POSITION_ABSOLUTE ? +static void setTrailingPosition(CSSNode* node, CSSNode* child, CSSFlexDirection axis) { + float size = child->style.positionType == CSSPositionTypeAbsolute ? 0 : - child->layout.measured_dimensions[dim[axis]]; - child->layout.position[trailing[axis]] = node->layout.measured_dimensions[dim[axis]] - size - child->layout.position[pos[axis]]; + child->layout.measuredDimensions[dim[axis]]; + child->layout.position[trailing[axis]] = node->layout.measuredDimensions[dim[axis]] - size - child->layout.position[pos[axis]]; } // If both left and right are defined, then use left. Otherwise return // +left or -right depending on which is defined. -static float getRelativePosition(css_node_t* node, css_flex_direction_t axis) { +static float getRelativePosition(CSSNode* node, CSSFlexDirection axis) { float lead = node->style.position[leading[axis]]; if (!isUndefined(lead)) { return lead; @@ -566,9 +686,9 @@ static float getRelativePosition(css_node_t* node, css_flex_direction_t axis) { return -getPosition(node, trailing[axis]); } -static void setPosition(css_node_t* node, css_direction_t direction) { - css_flex_direction_t mainAxis = resolveAxis(getFlexDirection(node), direction); - css_flex_direction_t crossAxis = getCrossFlexDirection(mainAxis, direction); +static void setPosition(CSSNode* node, CSSDirection direction) { + CSSFlexDirection mainAxis = resolveAxis(getFlexDirection(node), direction); + CSSFlexDirection crossAxis = getCrossFlexDirection(mainAxis, direction); node->layout.position[leading[mainAxis]] = getLeadingMargin(node, mainAxis) + getRelativePosition(node, mainAxis); @@ -626,7 +746,7 @@ static void setPosition(css_node_t* node, css_direction_t direction) { // Input parameters: // - node: current node to be sized and layed out // - availableWidth & availableHeight: available size to be used for sizing the node -// or CSS_UNDEFINED if the size is not available; interpretation depends on layout +// or CSSUndefined if the size is not available; interpretation depends on layout // flags // - parentDirection: the inline (text) direction within the parent (left-to-right or // right-to-left) @@ -639,36 +759,35 @@ static void setPosition(css_node_t* node, css_direction_t direction) { // Details: // This routine is called recursively to lay out subtrees of flexbox elements. It uses the // information in node.style, which is treated as a read-only input. It is responsible for -// setting the layout.direction and layout.measured_dimensions fields for the input node as well -// as the layout.position and layout.line_index fields for its child nodes. The -// layout.measured_dimensions field includes any border or padding for the node but does +// setting the layout.direction and layout.measuredDimensions fields for the input node as well +// as the layout.position and layout.lineIndex fields for its child nodes. The +// layout.measuredDimensions field includes any border or padding for the node but does // not include margins. // // The spec describes four different layout modes: "fill available", "max content", "min content", // and "fit content". Of these, we don't use "min content" because we don't support default // minimum main sizes (see above for details). Each of our measure modes maps to a layout mode // from the spec (https://www.w3.org/TR/css3-sizing/#terms): -// - CSS_MEASURE_MODE_UNDEFINED: max content -// - CSS_MEASURE_MODE_EXACTLY: fill available -// - CSS_MEASURE_MODE_AT_MOST: fit content +// - CSSMeasureModeUndefined: max content +// - CSSMeasureModeExactly: fill available +// - CSSMeasureModeAtMost: fit content // // When calling layoutNodeImpl and layoutNodeInternal, if the caller passes an available size of -// undefined then it must also pass a measure mode of CSS_MEASURE_MODE_UNDEFINED in that dimension. +// undefined then it must also pass a measure mode of CSSMeasureModeUndefined in that dimension. // -static void layoutNodeImpl(css_node_t* node, float availableWidth, float availableHeight, - css_direction_t parentDirection, css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, bool performLayout) { - /** START_GENERATED **/ +static void layoutNodeImpl(CSSNode* node, float availableWidth, float availableHeight, + CSSDirection parentDirection, CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, bool performLayout) { - assert(isUndefined(availableWidth) ? widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED : true); // availableWidth is indefinite so widthMeasureMode must be CSS_MEASURE_MODE_UNDEFINED - assert(isUndefined(availableHeight) ? heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED : true); // availableHeight is indefinite so heightMeasureMode must be CSS_MEASURE_MODE_UNDEFINED + assert(isUndefined(availableWidth) ? widthMeasureMode == CSSMeasureModeUndefined : true); // availableWidth is indefinite so widthMeasureMode must be CSSMeasureModeUndefined + assert(isUndefined(availableHeight) ? heightMeasureMode == CSSMeasureModeUndefined : true); // availableHeight is indefinite so heightMeasureMode must be CSSMeasureModeUndefined - float paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - float paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); - float marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); - float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + float paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSSFlexDirectionRow); + float paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSSFlexDirectionColumn); + float marginAxisRow = getMarginAxis(node, CSSFlexDirectionRow); + float marginAxisColumn = getMarginAxis(node, CSSFlexDirectionColumn); // Set the resolved resolution in the node's layout. - css_direction_t direction = resolveDirection(node, parentDirection); + CSSDirection direction = resolveDirection(node, parentDirection); node->layout.direction = direction; // For content (text) nodes, determine the dimensions based on the text contents. @@ -676,35 +795,35 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab float innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; float innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; - if (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && heightMeasureMode == CSS_MEASURE_MODE_EXACTLY) { + if (widthMeasureMode == CSSMeasureModeExactly && heightMeasureMode == CSSMeasureModeExactly) { // Don't bother sizing the text if both dimensions are already defined. - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn); } else if (innerWidth <= 0 || innerHeight <= 0) { // Don't bother sizing the text if there's no horizontal or vertical space. - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0); + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, 0); } else { // Measure the text under the current constraints. - css_dim_t measureDim = node->measure( + CSSSize measuredSize = node->measure( node->context, - + innerWidth, widthMeasureMode, innerHeight, heightMeasureMode ); - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, - (widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? - measureDim.dimensions[CSS_WIDTH] + paddingAndBorderAxisRow : + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, + (widthMeasureMode == CSSMeasureModeUndefined || widthMeasureMode == CSSMeasureModeAtMost) ? + measuredSize.width + paddingAndBorderAxisRow : availableWidth - marginAxisRow); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, - (heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? - measureDim.dimensions[CSS_HEIGHT] + paddingAndBorderAxisColumn : + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, + (heightMeasureMode == CSSMeasureModeUndefined || heightMeasureMode == CSSMeasureModeAtMost) ? + measuredSize.height + paddingAndBorderAxisColumn : availableHeight - marginAxisColumn); } @@ -713,14 +832,14 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // For nodes with no children, use the available values if they were provided, or // the minimum size as indicated by the padding and border sizes. - int childCount = node->children_count; + unsigned int childCount = CSSNodeListCount(node->children); if (childCount == 0) { - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, - (widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, + (widthMeasureMode == CSSMeasureModeUndefined || widthMeasureMode == CSSMeasureModeAtMost) ? paddingAndBorderAxisRow : availableWidth - marginAxisRow); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, - (heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, + (heightMeasureMode == CSSMeasureModeUndefined || heightMeasureMode == CSSMeasureModeAtMost) ? paddingAndBorderAxisColumn : availableHeight - marginAxisColumn); return; @@ -731,42 +850,42 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab if (!performLayout) { // If we're being asked to size the content with an at most constraint but there is no available width, // the measurement will always be zero. - if (widthMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0 && - heightMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) { - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + if (widthMeasureMode == CSSMeasureModeAtMost && availableWidth <= 0 && + heightMeasureMode == CSSMeasureModeAtMost && availableHeight <= 0) { + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0); + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, 0); return; } - if (widthMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0) { - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, isUndefined(availableHeight) ? 0 : (availableHeight - marginAxisColumn)); + if (widthMeasureMode == CSSMeasureModeAtMost && availableWidth <= 0) { + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0); + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, isUndefined(availableHeight) ? 0 : (availableHeight - marginAxisColumn)); return; } - if (heightMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) { - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, isUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow)); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + if (heightMeasureMode == CSSMeasureModeAtMost && availableHeight <= 0) { + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, isUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow)); + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, 0); return; } // If we're being asked to use an exact width/height, there's no need to measure the children. - if (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && heightMeasureMode == CSS_MEASURE_MODE_EXACTLY) { - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); + if (widthMeasureMode == CSSMeasureModeExactly && heightMeasureMode == CSSMeasureModeExactly) { + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn); return; } } // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM - css_flex_direction_t mainAxis = resolveAxis(getFlexDirection(node), direction); - css_flex_direction_t crossAxis = getCrossFlexDirection(mainAxis, direction); + CSSFlexDirection mainAxis = resolveAxis(getFlexDirection(node), direction); + CSSFlexDirection crossAxis = getCrossFlexDirection(mainAxis, direction); bool isMainAxisRow = isRowDirection(mainAxis); - css_justify_t justifyContent = node->style.justify_content; + CSSJustify justifyContent = node->style.justifyContent; bool isNodeFlexWrap = isFlexWrap(node); - css_node_t* firstAbsoluteChild = NULL; - css_node_t* currentAbsoluteChild = NULL; + CSSNode* firstAbsoluteChild = NULL; + CSSNode* currentAbsoluteChild = NULL; float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); float trailingPaddingAndBorderMain = getTrailingPaddingAndBorder(node, mainAxis); @@ -774,8 +893,8 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); float paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis); - css_measure_mode_t measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; - css_measure_mode_t measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; + CSSMeasureMode measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + CSSMeasureMode measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; @@ -784,24 +903,24 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM - css_node_t* child; - int i; + CSSNode* child; + unsigned int i; float childWidth; float childHeight; - css_measure_mode_t childWidthMeasureMode; - css_measure_mode_t childHeightMeasureMode; + CSSMeasureMode childWidthMeasureMode; + CSSMeasureMode childHeightMeasureMode; for (i = 0; i < childCount; i++) { - child = node->get_child(node->context, i); + child = CSSNodeListGet(node->children, i); if (performLayout) { // Set the initial position (relative to the parent). - css_direction_t childDirection = resolveDirection(child, direction); + CSSDirection childDirection = resolveDirection(child, direction); setPosition(child, childDirection); } // Absolute-positioned children don't participate in flex layout. Add them // to a list that we can process later. - if (child->style.position_type == CSS_POSITION_ABSOLUTE) { + if (child->style.positionType == CSSPositionTypeAbsolute) { // Store a private linked list of absolutely positioned children // so that we can efficiently traverse them later. @@ -809,39 +928,39 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab firstAbsoluteChild = child; } if (currentAbsoluteChild != NULL) { - currentAbsoluteChild->next_child = child; + currentAbsoluteChild->nextChild = child; } currentAbsoluteChild = child; - child->next_child = NULL; + child->nextChild = NULL; } else { - if (isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) { + if (isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionRow)) { // The width is definite, so use that as the flex basis. - child->layout.flex_basis = fmaxf(child->style.dimensions[CSS_WIDTH], getPaddingAndBorderAxis(child, CSS_FLEX_DIRECTION_ROW)); - } else if (!isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) { + child->layout.flexBasis = fmaxf(child->style.dimensions[CSSDimensionWidth], getPaddingAndBorderAxis(child, CSSFlexDirectionRow)); + } else if (!isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionColumn)) { // The height is definite, so use that as the flex basis. - child->layout.flex_basis = fmaxf(child->style.dimensions[CSS_HEIGHT], getPaddingAndBorderAxis(child, CSS_FLEX_DIRECTION_COLUMN)); + child->layout.flexBasis = fmaxf(child->style.dimensions[CSSDimensionHeight], getPaddingAndBorderAxis(child, CSSFlexDirectionColumn)); } else if (!isFlexBasisAuto(child) && !isUndefined(availableInnerMainDim)) { // If the basis isn't 'auto', it is assumed to be zero. - child->layout.flex_basis = fmaxf(0, getPaddingAndBorderAxis(child, mainAxis)); + child->layout.flexBasis = fmaxf(0, getPaddingAndBorderAxis(child, mainAxis)); } else { // Compute the flex basis and hypothetical main size (i.e. the clamped flex basis). - childWidth = CSS_UNDEFINED; - childHeight = CSS_UNDEFINED; - childWidthMeasureMode = CSS_MEASURE_MODE_UNDEFINED; - childHeightMeasureMode = CSS_MEASURE_MODE_UNDEFINED; - - if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) { - childWidth = child->style.dimensions[CSS_WIDTH] + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); - childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + childWidth = CSSUndefined; + childHeight = CSSUndefined; + childWidthMeasureMode = CSSMeasureModeUndefined; + childHeightMeasureMode = CSSMeasureModeUndefined; + + if (isStyleDimDefined(child, CSSFlexDirectionRow)) { + childWidth = child->style.dimensions[CSSDimensionWidth] + getMarginAxis(child, CSSFlexDirectionRow); + childWidthMeasureMode = CSSMeasureModeExactly; } - if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) { - childHeight = child->style.dimensions[CSS_HEIGHT] + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN); - childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + if (isStyleDimDefined(child, CSSFlexDirectionColumn)) { + childHeight = child->style.dimensions[CSSDimensionHeight] + getMarginAxis(child, CSSFlexDirectionColumn); + childHeightMeasureMode = CSSMeasureModeExactly; } // According to the spec, if the main size is not definite and the @@ -850,15 +969,15 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // the main size. Otherwise use "AT_MOST" in the cross axis. if (!isMainAxisRow && isUndefined(childWidth) && !isUndefined(availableInnerWidth)) { childWidth = availableInnerWidth; - childWidthMeasureMode = CSS_MEASURE_MODE_AT_MOST; + childWidthMeasureMode = CSSMeasureModeAtMost; } // The W3C spec doesn't say anything about the 'overflow' property, // but all major browsers appear to implement the following logic. - if (node->style.overflow == CSS_OVERFLOW_HIDDEN) { + if (node->style.overflow == CSSOverflowHidden) { if (isMainAxisRow && isUndefined(childHeight) && !isUndefined(availableInnerHeight)) { childHeight = availableInnerHeight; - childHeightMeasureMode = CSS_MEASURE_MODE_AT_MOST; + childHeightMeasureMode = CSSMeasureModeAtMost; } } @@ -866,25 +985,25 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // axis to be measured exactly with the available inner width if (!isMainAxisRow && !isUndefined(availableInnerWidth) && - !isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW) && - widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && - getAlignItem(node, child) == CSS_ALIGN_STRETCH) { + !isStyleDimDefined(child, CSSFlexDirectionRow) && + widthMeasureMode == CSSMeasureModeExactly && + getAlignItem(node, child) == CSSAlignStretch) { childWidth = availableInnerWidth; - childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + childWidthMeasureMode = CSSMeasureModeExactly; } if (isMainAxisRow && !isUndefined(availableInnerHeight) && - !isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN) && - heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && - getAlignItem(node, child) == CSS_ALIGN_STRETCH) { + !isStyleDimDefined(child, CSSFlexDirectionColumn) && + heightMeasureMode == CSSMeasureModeExactly && + getAlignItem(node, child) == CSSAlignStretch) { childHeight = availableInnerHeight; - childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + childHeightMeasureMode = CSSMeasureModeExactly; } // Measure the child layoutNodeInternal(child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "measure"); - child->layout.flex_basis = fmaxf(isMainAxisRow ? child->layout.measured_dimensions[CSS_WIDTH] : child->layout.measured_dimensions[CSS_HEIGHT], getPaddingAndBorderAxis(child, mainAxis)); + child->layout.flexBasis = fmaxf(isMainAxisRow ? child->layout.measuredDimensions[CSSDimensionWidth] : child->layout.measuredDimensions[CSSDimensionHeight], getPaddingAndBorderAxis(child, mainAxis)); } } } @@ -922,16 +1041,16 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab i = startOfLineIndex; // Maintain a linked list of the child nodes that can shrink and/or grow. - css_node_t* firstRelativeChild = NULL; - css_node_t* currentRelativeChild = NULL; + CSSNode* firstRelativeChild = NULL; + CSSNode* currentRelativeChild = NULL; // Add items to the current line until it's full or we run out of items. while (i < childCount) { - child = node->get_child(node->context, i); - child->line_index = lineCount; + child = CSSNodeListGet(node->children, i); + child->lineIndex = lineCount; - if (child->style.position_type != CSS_POSITION_ABSOLUTE) { - float outerFlexBasis = child->layout.flex_basis + getMarginAxis(child, mainAxis); + if (child->style.positionType != CSSPositionTypeAbsolute) { + float outerFlexBasis = child->layout.flexBasis + getMarginAxis(child, mainAxis); // If this is a multi-line flow and this item pushes us over the available size, we've // hit the end of the current line. Break out of the loop and lay out the current line. @@ -947,7 +1066,7 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // Unlike the grow factor, the shrink factor is scaled relative to the child // dimension. - totalFlexShrinkScaledFactors += getFlexShrinkFactor(child) * child->layout.flex_basis; + totalFlexShrinkScaledFactors += getFlexShrinkFactor(child) * child->layout.flexBasis; } // Store a private linked list of children that need to be layed out. @@ -955,10 +1074,10 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab firstRelativeChild = child; } if (currentRelativeChild != NULL) { - currentRelativeChild->next_child = child; + currentRelativeChild->nextChild = child; } currentRelativeChild = child; - child->next_child = NULL; + child->nextChild = NULL; } i++; @@ -966,7 +1085,7 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab } // If we don't need to measure the cross axis, we can skip the entire flex step. - bool canSkipFlex = !performLayout && measureModeCrossDim == CSS_MEASURE_MODE_EXACTLY; + bool canSkipFlex = !performLayout && measureModeCrossDim == CSSMeasureModeExactly; // In order to position the elements in the main axis, we have two // controls. The space between the beginning and the first element @@ -1016,7 +1135,7 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab float deltaFlexGrowFactors = 0; currentRelativeChild = firstRelativeChild; while (currentRelativeChild != NULL) { - childFlexBasis = currentRelativeChild->layout.flex_basis; + childFlexBasis = currentRelativeChild->layout.flexBasis; if (remainingFreeSpace < 0) { flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; @@ -1052,7 +1171,7 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab } } - currentRelativeChild = currentRelativeChild->next_child; + currentRelativeChild = currentRelativeChild->nextChild; } totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; @@ -1063,7 +1182,7 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab deltaFreeSpace = 0; currentRelativeChild = firstRelativeChild; while (currentRelativeChild != NULL) { - childFlexBasis = currentRelativeChild->layout.flex_basis; + childFlexBasis = currentRelativeChild->layout.flexBasis; float updatedMainSize = childFlexBasis; if (remainingFreeSpace < 0) { @@ -1087,48 +1206,48 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab deltaFreeSpace -= updatedMainSize - childFlexBasis; if (isMainAxisRow) { - childWidth = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW); - childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + childWidth = updatedMainSize + getMarginAxis(currentRelativeChild, CSSFlexDirectionRow); + childWidthMeasureMode = CSSMeasureModeExactly; if (!isUndefined(availableInnerCrossDim) && - !isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN) && - heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && - getAlignItem(node, currentRelativeChild) == CSS_ALIGN_STRETCH) { + !isStyleDimDefined(currentRelativeChild, CSSFlexDirectionColumn) && + heightMeasureMode == CSSMeasureModeExactly && + getAlignItem(node, currentRelativeChild) == CSSAlignStretch) { childHeight = availableInnerCrossDim; - childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; - } else if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN)) { + childHeightMeasureMode = CSSMeasureModeExactly; + } else if (!isStyleDimDefined(currentRelativeChild, CSSFlexDirectionColumn)) { childHeight = availableInnerCrossDim; - childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST; + childHeightMeasureMode = isUndefined(childHeight) ? CSSMeasureModeUndefined : CSSMeasureModeAtMost; } else { - childHeight = currentRelativeChild->style.dimensions[CSS_HEIGHT] + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN); - childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + childHeight = currentRelativeChild->style.dimensions[CSSDimensionHeight] + getMarginAxis(currentRelativeChild, CSSFlexDirectionColumn); + childHeightMeasureMode = CSSMeasureModeExactly; } } else { - childHeight = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN); - childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + childHeight = updatedMainSize + getMarginAxis(currentRelativeChild, CSSFlexDirectionColumn); + childHeightMeasureMode = CSSMeasureModeExactly; if (!isUndefined(availableInnerCrossDim) && - !isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_ROW) && - widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && - getAlignItem(node, currentRelativeChild) == CSS_ALIGN_STRETCH) { + !isStyleDimDefined(currentRelativeChild, CSSFlexDirectionRow) && + widthMeasureMode == CSSMeasureModeExactly && + getAlignItem(node, currentRelativeChild) == CSSAlignStretch) { childWidth = availableInnerCrossDim; - childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; - } else if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_ROW)) { + childWidthMeasureMode = CSSMeasureModeExactly; + } else if (!isStyleDimDefined(currentRelativeChild, CSSFlexDirectionRow)) { childWidth = availableInnerCrossDim; - childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST; + childWidthMeasureMode = isUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeAtMost; } else { - childWidth = currentRelativeChild->style.dimensions[CSS_WIDTH] + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW); - childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + childWidth = currentRelativeChild->style.dimensions[CSSDimensionWidth] + getMarginAxis(currentRelativeChild, CSSFlexDirectionRow); + childWidthMeasureMode = CSSMeasureModeExactly; } } bool requiresStretchLayout = !isStyleDimDefined(currentRelativeChild, crossAxis) && - getAlignItem(node, currentRelativeChild) == CSS_ALIGN_STRETCH; + getAlignItem(node, currentRelativeChild) == CSSAlignStretch; // Recursively call the layout algorithm for this child with the updated main size. layoutNodeInternal(currentRelativeChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, performLayout && !requiresStretchLayout, "flex"); - currentRelativeChild = currentRelativeChild->next_child; + currentRelativeChild = currentRelativeChild->nextChild; } } @@ -1143,25 +1262,25 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // If we are using "at most" rules in the main axis, we won't distribute // any remaining space at this point. - if (measureModeMainDim == CSS_MEASURE_MODE_AT_MOST) { + if (measureModeMainDim == CSSMeasureModeAtMost) { remainingFreeSpace = 0; } // Use justifyContent to figure out how to allocate the remaining space // available in the main axis. - if (justifyContent != CSS_JUSTIFY_FLEX_START) { - if (justifyContent == CSS_JUSTIFY_CENTER) { + if (justifyContent != CSSJustifyFlexStart) { + if (justifyContent == CSSJustifyCenter) { leadingMainDim = remainingFreeSpace / 2; - } else if (justifyContent == CSS_JUSTIFY_FLEX_END) { + } else if (justifyContent == CSSJustifyFlexEnd) { leadingMainDim = remainingFreeSpace; - } else if (justifyContent == CSS_JUSTIFY_SPACE_BETWEEN) { + } else if (justifyContent == CSSJustifySpaceBetween) { remainingFreeSpace = fmaxf(remainingFreeSpace, 0); if (itemsOnLine > 1) { betweenMainDim = remainingFreeSpace / (itemsOnLine - 1); } else { betweenMainDim = 0; } - } else if (justifyContent == CSS_JUSTIFY_SPACE_AROUND) { + } else if (justifyContent == CSSJustifySpaceAround) { // Space on the edges is half of the space between elements betweenMainDim = remainingFreeSpace / itemsOnLine; leadingMainDim = betweenMainDim / 2; @@ -1172,9 +1291,9 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab float crossDim = 0; for (i = startOfLineIndex; i < endOfLineIndex; ++i) { - child = node->get_child(node->context, i); + child = CSSNodeListGet(node->children, i); - if (child->style.position_type == CSS_POSITION_ABSOLUTE && + if (child->style.positionType == CSSPositionTypeAbsolute && isPosDefined(child, leading[mainAxis])) { if (performLayout) { // In case the child is position absolute and has left/top being @@ -1194,11 +1313,11 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // Now that we placed the element, we need to update the variables. // We need to do that only for relative elements. Absolute elements // do not take part in that phase. - if (child->style.position_type == CSS_POSITION_RELATIVE) { + if (child->style.positionType == CSSPositionTypeRelative) { if (canSkipFlex) { // If we skipped the flex step, then we can't rely on the measuredDims because // they weren't computed. This means we can't call getDimWithMargin. - mainDim += betweenMainDim + getMarginAxis(child, mainAxis) + child->layout.flex_basis; + mainDim += betweenMainDim + getMarginAxis(child, mainAxis) + child->layout.flexBasis; crossDim = availableInnerCrossDim; } else { // The main dimension is the sum of all the elements dimension plus @@ -1216,17 +1335,17 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab mainDim += trailingPaddingAndBorderMain; float containerCrossAxis = availableInnerCrossDim; - if (measureModeCrossDim == CSS_MEASURE_MODE_UNDEFINED || measureModeCrossDim == CSS_MEASURE_MODE_AT_MOST) { + if (measureModeCrossDim == CSSMeasureModeUndefined || measureModeCrossDim == CSSMeasureModeAtMost) { // Compute the cross axis from the max cross dimension of the children. containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; - if (measureModeCrossDim == CSS_MEASURE_MODE_AT_MOST) { + if (measureModeCrossDim == CSSMeasureModeAtMost) { containerCrossAxis = fminf(containerCrossAxis, availableInnerCrossDim); } } // If there's no flex wrap, the cross dimension is defined by the container. - if (!isNodeFlexWrap && measureModeCrossDim == CSS_MEASURE_MODE_EXACTLY) { + if (!isNodeFlexWrap && measureModeCrossDim == CSSMeasureModeExactly) { crossDim = availableInnerCrossDim; } @@ -1237,9 +1356,9 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // We can skip child alignment if we're just measuring the container. if (performLayout) { for (i = startOfLineIndex; i < endOfLineIndex; ++i) { - child = node->get_child(node->context, i); + child = CSSNodeListGet(node->children, i); - if (child->style.position_type == CSS_POSITION_ABSOLUTE) { + if (child->style.positionType == CSSPositionTypeAbsolute) { // If the child is absolutely positioned and has a top/left/bottom/right // set, override all the previously computed positions to set it correctly. if (isPosDefined(child, leading[crossAxis])) { @@ -1255,35 +1374,35 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // For a relative children, we're either using alignItems (parent) or // alignSelf (child) in order to determine the position in the cross axis - css_align_t alignItem = getAlignItem(node, child); + CSSAlign alignItem = getAlignItem(node, child); // If the child uses align stretch, we need to lay it out one more time, this time // forcing the cross-axis size to be the computed cross size for the current line. - if (alignItem == CSS_ALIGN_STRETCH) { - childWidth = child->layout.measured_dimensions[CSS_WIDTH] + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); - childHeight = child->layout.measured_dimensions[CSS_HEIGHT] + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN); + if (alignItem == CSSAlignStretch) { + childWidth = child->layout.measuredDimensions[CSSDimensionWidth] + getMarginAxis(child, CSSFlexDirectionRow); + childHeight = child->layout.measuredDimensions[CSSDimensionHeight] + getMarginAxis(child, CSSFlexDirectionColumn); bool isCrossSizeDefinite = false; if (isMainAxisRow) { - isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN); + isCrossSizeDefinite = isStyleDimDefined(child, CSSFlexDirectionColumn); childHeight = crossDim; } else { - isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW); + isCrossSizeDefinite = isStyleDimDefined(child, CSSFlexDirectionRow); childWidth = crossDim; } // If the child defines a definite size for its cross axis, there's no need to stretch. if (!isCrossSizeDefinite) { - childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; - childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + childWidthMeasureMode = isUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeExactly; + childHeightMeasureMode = isUndefined(childHeight) ? CSSMeasureModeUndefined : CSSMeasureModeExactly; layoutNodeInternal(child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, true, "stretch"); } - } else if (alignItem != CSS_ALIGN_FLEX_START) { + } else if (alignItem != CSSAlignFlexStart) { float remainingCrossDim = containerCrossAxis - getDimWithMargin(child, crossAxis); - if (alignItem == CSS_ALIGN_CENTER) { + if (alignItem == CSSAlignCenter) { leadingCrossDim += remainingCrossDim / 2; - } else { // CSS_ALIGN_FLEX_END + } else { // CSSAlignFlexEnd leadingCrossDim += remainingCrossDim; } } @@ -1310,12 +1429,12 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab float crossDimLead = 0; float currentLead = leadingPaddingAndBorderCross; - css_align_t alignContent = node->style.align_content; - if (alignContent == CSS_ALIGN_FLEX_END) { + CSSAlign alignContent = node->style.alignContent; + if (alignContent == CSSAlignFlexEnd) { currentLead += remainingAlignContentDim; - } else if (alignContent == CSS_ALIGN_CENTER) { + } else if (alignContent == CSSAlignCenter) { currentLead += remainingAlignContentDim / 2; - } else if (alignContent == CSS_ALIGN_STRETCH) { + } else if (alignContent == CSSAlignStretch) { if (availableInnerCrossDim > totalLineCrossDim) { crossDimLead = (remainingAlignContentDim / lineCount); } @@ -1329,16 +1448,16 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // compute the line's height and find the endIndex float lineHeight = 0; for (j = startIndex; j < childCount; ++j) { - child = node->get_child(node->context, j); - if (child->style.position_type != CSS_POSITION_RELATIVE) { + child = CSSNodeListGet(node->children, j); + if (child->style.positionType != CSSPositionTypeRelative) { continue; } - if (child->line_index != i) { + if (child->lineIndex != i) { break; } if (isLayoutDimDefined(child, crossAxis)) { lineHeight = fmaxf(lineHeight, - child->layout.measured_dimensions[dim[crossAxis]] + getMarginAxis(child, crossAxis)); + child->layout.measuredDimensions[dim[crossAxis]] + getMarginAxis(child, crossAxis)); } } endIndex = j; @@ -1346,20 +1465,20 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab if (performLayout) { for (j = startIndex; j < endIndex; ++j) { - child = node->get_child(node->context, j); - if (child->style.position_type != CSS_POSITION_RELATIVE) { + child = CSSNodeListGet(node->children, j); + if (child->style.positionType != CSSPositionTypeRelative) { continue; } - css_align_t alignContentAlignItem = getAlignItem(node, child); - if (alignContentAlignItem == CSS_ALIGN_FLEX_START) { + CSSAlign alignContentAlignItem = getAlignItem(node, child); + if (alignContentAlignItem == CSSAlignFlexStart) { child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); - } else if (alignContentAlignItem == CSS_ALIGN_FLEX_END) { - child->layout.position[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child->layout.measured_dimensions[dim[crossAxis]]; - } else if (alignContentAlignItem == CSS_ALIGN_CENTER) { - childHeight = child->layout.measured_dimensions[dim[crossAxis]]; + } else if (alignContentAlignItem == CSSAlignFlexEnd) { + child->layout.position[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child->layout.measuredDimensions[dim[crossAxis]]; + } else if (alignContentAlignItem == CSSAlignCenter) { + childHeight = child->layout.measuredDimensions[dim[crossAxis]]; child->layout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; - } else if (alignContentAlignItem == CSS_ALIGN_STRETCH) { + } else if (alignContentAlignItem == CSSAlignStretch) { child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); // TODO(prenaux): Correctly set the height of items with indefinite // (auto) crossAxis dimension. @@ -1372,28 +1491,28 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab } // STEP 9: COMPUTING FINAL DIMENSIONS - node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); - node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn); // If the user didn't specify a width or height for the node, set the // dimensions based on the children. - if (measureModeMainDim == CSS_MEASURE_MODE_UNDEFINED) { + if (measureModeMainDim == CSSMeasureModeUndefined) { // Clamp the size to the min/max size, if specified, and make sure it // doesn't go below the padding and border amount. - node->layout.measured_dimensions[dim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim); - } else if (measureModeMainDim == CSS_MEASURE_MODE_AT_MOST) { - node->layout.measured_dimensions[dim[mainAxis]] = fmaxf( + node->layout.measuredDimensions[dim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim); + } else if (measureModeMainDim == CSSMeasureModeAtMost) { + node->layout.measuredDimensions[dim[mainAxis]] = fmaxf( fminf(availableInnerMainDim + paddingAndBorderAxisMain, boundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim)), paddingAndBorderAxisMain); } - if (measureModeCrossDim == CSS_MEASURE_MODE_UNDEFINED) { + if (measureModeCrossDim == CSSMeasureModeUndefined) { // Clamp the size to the min/max size, if specified, and make sure it // doesn't go below the padding and border amount. - node->layout.measured_dimensions[dim[crossAxis]] = boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross); - } else if (measureModeCrossDim == CSS_MEASURE_MODE_AT_MOST) { - node->layout.measured_dimensions[dim[crossAxis]] = fmaxf( + node->layout.measuredDimensions[dim[crossAxis]] = boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross); + } else if (measureModeCrossDim == CSSMeasureModeAtMost) { + node->layout.measuredDimensions[dim[crossAxis]] = fmaxf( fminf(availableInnerCrossDim + paddingAndBorderAxisCross, boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)), paddingAndBorderAxisCross); @@ -1404,20 +1523,20 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab bool needsMainTrailingPos = false; bool needsCrossTrailingPos = false; - if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || - mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { + if (mainAxis == CSSFlexDirectionRowReverse || + mainAxis == CSSFlexDirectionColumnReverse) { needsMainTrailingPos = true; } - if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || - crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { + if (crossAxis == CSSFlexDirectionRowReverse || + crossAxis == CSSFlexDirectionColumnReverse) { needsCrossTrailingPos = true; } // Set trailing position if necessary. if (needsMainTrailingPos || needsCrossTrailingPos) { for (i = 0; i < childCount; ++i) { - child = node->get_child(node->context, i); + child = CSSNodeListGet(node->children, i); if (needsMainTrailingPos) { setTrailingPosition(node, child, mainAxis); @@ -1437,37 +1556,37 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // absolutely-positioned children. if (performLayout) { - childWidth = CSS_UNDEFINED; - childHeight = CSS_UNDEFINED; + childWidth = CSSUndefined; + childHeight = CSSUndefined; - if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW)) { - childWidth = currentAbsoluteChild->style.dimensions[CSS_WIDTH] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW); + if (isStyleDimDefined(currentAbsoluteChild, CSSFlexDirectionRow)) { + childWidth = currentAbsoluteChild->style.dimensions[CSSDimensionWidth] + getMarginAxis(currentAbsoluteChild, CSSFlexDirectionRow); } else { // If the child doesn't have a specified width, compute the width based on the left/right offsets if they're defined. - if (isPosDefined(currentAbsoluteChild, CSS_LEFT) && isPosDefined(currentAbsoluteChild, CSS_RIGHT)) { - childWidth = node->layout.measured_dimensions[CSS_WIDTH] - - (getLeadingBorder(node, CSS_FLEX_DIRECTION_ROW) + getTrailingBorder(node, CSS_FLEX_DIRECTION_ROW)) - - (currentAbsoluteChild->style.position[CSS_LEFT] + currentAbsoluteChild->style.position[CSS_RIGHT]); - childWidth = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW, childWidth); + if (isPosDefined(currentAbsoluteChild, CSSPositionLeft) && isPosDefined(currentAbsoluteChild, CSSPositionRight)) { + childWidth = node->layout.measuredDimensions[CSSDimensionWidth] - + (getLeadingBorder(node, CSSFlexDirectionRow) + getTrailingBorder(node, CSSFlexDirectionRow)) - + (currentAbsoluteChild->style.position[CSSPositionLeft] + currentAbsoluteChild->style.position[CSSPositionRight]); + childWidth = boundAxis(currentAbsoluteChild, CSSFlexDirectionRow, childWidth); } } - if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN)) { - childHeight = currentAbsoluteChild->style.dimensions[CSS_HEIGHT] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN); + if (isStyleDimDefined(currentAbsoluteChild, CSSFlexDirectionColumn)) { + childHeight = currentAbsoluteChild->style.dimensions[CSSDimensionHeight] + getMarginAxis(currentAbsoluteChild, CSSFlexDirectionColumn); } else { // If the child doesn't have a specified height, compute the height based on the top/bottom offsets if they're defined. - if (isPosDefined(currentAbsoluteChild, CSS_TOP) && isPosDefined(currentAbsoluteChild, CSS_BOTTOM)) { - childHeight = node->layout.measured_dimensions[CSS_HEIGHT] - - (getLeadingBorder(node, CSS_FLEX_DIRECTION_COLUMN) + getTrailingBorder(node, CSS_FLEX_DIRECTION_COLUMN)) - - (currentAbsoluteChild->style.position[CSS_TOP] + currentAbsoluteChild->style.position[CSS_BOTTOM]); - childHeight = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN, childHeight); + if (isPosDefined(currentAbsoluteChild, CSSPositionTop) && isPosDefined(currentAbsoluteChild, CSSPositionBottom)) { + childHeight = node->layout.measuredDimensions[CSSDimensionHeight] - + (getLeadingBorder(node, CSSFlexDirectionColumn) + getTrailingBorder(node, CSSFlexDirectionColumn)) - + (currentAbsoluteChild->style.position[CSSPositionTop] + currentAbsoluteChild->style.position[CSSPositionBottom]); + childHeight = boundAxis(currentAbsoluteChild, CSSFlexDirectionColumn, childHeight); } } // If we're still missing one or the other dimension, measure the content. if (isUndefined(childWidth) || isUndefined(childHeight)) { - childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; - childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + childWidthMeasureMode = isUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeExactly; + childHeightMeasureMode = isUndefined(childHeight) ? CSSMeasureModeUndefined : CSSMeasureModeExactly; // According to the spec, if the main size is not definite and the // child's inline axis is parallel to the main axis (i.e. it's @@ -1475,45 +1594,44 @@ static void layoutNodeImpl(css_node_t* node, float availableWidth, float availab // the main size. Otherwise use "AT_MOST" in the cross axis. if (!isMainAxisRow && isUndefined(childWidth) && !isUndefined(availableInnerWidth)) { childWidth = availableInnerWidth; - childWidthMeasureMode = CSS_MEASURE_MODE_AT_MOST; + childWidthMeasureMode = CSSMeasureModeAtMost; } // The W3C spec doesn't say anything about the 'overflow' property, // but all major browsers appear to implement the following logic. - if (node->style.overflow == CSS_OVERFLOW_HIDDEN) { + if (node->style.overflow == CSSOverflowHidden) { if (isMainAxisRow && isUndefined(childHeight) && !isUndefined(availableInnerHeight)) { childHeight = availableInnerHeight; - childHeightMeasureMode = CSS_MEASURE_MODE_AT_MOST; + childHeightMeasureMode = CSSMeasureModeAtMost; } } layoutNodeInternal(currentAbsoluteChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "abs-measure"); - childWidth = currentAbsoluteChild->layout.measured_dimensions[CSS_WIDTH] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW); - childHeight = currentAbsoluteChild->layout.measured_dimensions[CSS_HEIGHT] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN); + childWidth = currentAbsoluteChild->layout.measuredDimensions[CSSDimensionWidth] + getMarginAxis(currentAbsoluteChild, CSSFlexDirectionRow); + childHeight = currentAbsoluteChild->layout.measuredDimensions[CSSDimensionHeight] + getMarginAxis(currentAbsoluteChild, CSSFlexDirectionColumn); } - layoutNodeInternal(currentAbsoluteChild, childWidth, childHeight, direction, CSS_MEASURE_MODE_EXACTLY, CSS_MEASURE_MODE_EXACTLY, true, "abs-layout"); + layoutNodeInternal(currentAbsoluteChild, childWidth, childHeight, direction, CSSMeasureModeExactly, CSSMeasureModeExactly, true, "abs-layout"); - if (isPosDefined(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_ROW]) && - !isPosDefined(currentAbsoluteChild, leading[CSS_FLEX_DIRECTION_ROW])) { - currentAbsoluteChild->layout.position[leading[CSS_FLEX_DIRECTION_ROW]] = - node->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - - currentAbsoluteChild->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - - getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_ROW]); + if (isPosDefined(currentAbsoluteChild, trailing[CSSFlexDirectionRow]) && + !isPosDefined(currentAbsoluteChild, leading[CSSFlexDirectionRow])) { + currentAbsoluteChild->layout.position[leading[CSSFlexDirectionRow]] = + node->layout.measuredDimensions[dim[CSSFlexDirectionRow]] - + currentAbsoluteChild->layout.measuredDimensions[dim[CSSFlexDirectionRow]] - + getPosition(currentAbsoluteChild, trailing[CSSFlexDirectionRow]); } - if (isPosDefined(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]) && - !isPosDefined(currentAbsoluteChild, leading[CSS_FLEX_DIRECTION_COLUMN])) { - currentAbsoluteChild->layout.position[leading[CSS_FLEX_DIRECTION_COLUMN]] = - node->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - - currentAbsoluteChild->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - - getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]); + if (isPosDefined(currentAbsoluteChild, trailing[CSSFlexDirectionColumn]) && + !isPosDefined(currentAbsoluteChild, leading[CSSFlexDirectionColumn])) { + currentAbsoluteChild->layout.position[leading[CSSFlexDirectionColumn]] = + node->layout.measuredDimensions[dim[CSSFlexDirectionColumn]] - + currentAbsoluteChild->layout.measuredDimensions[dim[CSSFlexDirectionColumn]] - + getPosition(currentAbsoluteChild, trailing[CSSFlexDirectionColumn]); } } - currentAbsoluteChild = currentAbsoluteChild->next_child; + currentAbsoluteChild = currentAbsoluteChild->nextChild; } - /** END_GENERATED **/ } int gDepth = 0; @@ -1531,19 +1649,19 @@ static const char* getSpacer(unsigned long level) { return &spacer[spacerLen - level]; } -static const char* getModeName(css_measure_mode_t mode, bool performLayout) { - const char* kMeasureModeNames[CSS_MEASURE_MODE_COUNT] = { +static const char* getModeName(CSSMeasureMode mode, bool performLayout) { + const char* kMeasureModeNames[CSSMeasureModeCount] = { "UNDEFINED", "EXACTLY", "AT_MOST" }; - const char* kLayoutModeNames[CSS_MEASURE_MODE_COUNT] = { + const char* kLayoutModeNames[CSSMeasureModeCount] = { "LAY_UNDEFINED", "LAY_EXACTLY", "LAY_AT_MOST" }; - if (mode >= CSS_MEASURE_MODE_COUNT) { + if (mode >= CSSMeasureModeCount) { return ""; } @@ -1551,38 +1669,38 @@ static const char* getModeName(css_measure_mode_t mode, bool performLayout) { } static bool canUseCachedMeasurement( - bool is_text_node, - float available_width, - float available_height, + bool isTextNode, + float availableWidth, + float availableHeight, float margin_row, float margin_column, - css_measure_mode_t width_measure_mode, - css_measure_mode_t height_measure_mode, - css_cached_measurement_t cached_layout) { + CSSMeasureMode widthMeasureMode, + CSSMeasureMode heightMeasureMode, + CSSCachedMeasurement cached_layout) { bool is_height_same = - (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) || - (cached_layout.height_measure_mode == height_measure_mode && eq(cached_layout.available_height, available_height)); + (cached_layout.heightMeasureMode == CSSMeasureModeUndefined && heightMeasureMode == CSSMeasureModeUndefined) || + (cached_layout.heightMeasureMode == heightMeasureMode && eq(cached_layout.availableHeight, availableHeight)); bool is_width_same = - (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) || - (cached_layout.width_measure_mode == width_measure_mode && eq(cached_layout.available_width, available_width)); + (cached_layout.widthMeasureMode == CSSMeasureModeUndefined && widthMeasureMode == CSSMeasureModeUndefined) || + (cached_layout.widthMeasureMode == widthMeasureMode && eq(cached_layout.availableWidth, availableWidth)); if (is_height_same && is_width_same) { return true; } bool is_height_valid = - (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_height <= (available_height - margin_column)) || - (height_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_height, available_height - margin_column)); + (cached_layout.heightMeasureMode == CSSMeasureModeUndefined && heightMeasureMode == CSSMeasureModeAtMost && cached_layout.computedHeight <= (availableHeight - margin_column)) || + (heightMeasureMode == CSSMeasureModeExactly && eq(cached_layout.computedHeight, availableHeight - margin_column)); if (is_width_same && is_height_valid) { return true; } bool is_width_valid = - (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_width <= (available_width - margin_row)) || - (width_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_width, available_width - margin_row)); + (cached_layout.widthMeasureMode == CSSMeasureModeUndefined && widthMeasureMode == CSSMeasureModeAtMost && cached_layout.computedWidth <= (availableWidth - margin_row)) || + (widthMeasureMode == CSSMeasureModeExactly && eq(cached_layout.computedWidth, availableWidth - margin_row)); if (is_height_same && is_width_valid) { return true; @@ -1593,29 +1711,29 @@ static bool canUseCachedMeasurement( } // We know this to be text so we can apply some more specialized heuristics. - if (is_text_node) { + if (isTextNode) { if (is_width_same) { - if (height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) { + if (heightMeasureMode == CSSMeasureModeUndefined) { // Width is the same and height is not restricted. Re-use cahced value. return true; } - if (height_measure_mode == CSS_MEASURE_MODE_AT_MOST && - cached_layout.computed_height < (available_height - margin_column)) { + if (heightMeasureMode == CSSMeasureModeAtMost && + cached_layout.computedHeight < (availableHeight - margin_column)) { // Width is the same and height restriction is greater than the cached height. Re-use cached value. return true; } // Width is the same but height restriction imposes smaller height than previously measured. // Update the cached value to respect the new height restriction. - cached_layout.computed_height = available_height - margin_column; + cached_layout.computedHeight = availableHeight - margin_column; return true; } - if (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) { - if (width_measure_mode == CSS_MEASURE_MODE_UNDEFINED || - (width_measure_mode == CSS_MEASURE_MODE_AT_MOST && - cached_layout.computed_width <= (available_width - margin_row))) { + if (cached_layout.widthMeasureMode == CSSMeasureModeUndefined) { + if (widthMeasureMode == CSSMeasureModeUndefined || + (widthMeasureMode == CSSMeasureModeAtMost && + cached_layout.computedWidth <= (availableWidth - margin_row))) { // Previsouly this text was measured with no width restriction, if width is now restricted // but to a larger value than the previsouly measured width we can re-use the measurement // as we know it will fit. @@ -1635,23 +1753,23 @@ static bool canUseCachedMeasurement( // Input parameters are the same as layoutNodeImpl (see above) // Return parameter is true if layout was performed, false if skipped // -bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableHeight, - css_direction_t parentDirection, css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, bool performLayout, char* reason) { - css_layout_t* layout = &node->layout; +bool layoutNodeInternal(CSSNode* node, float availableWidth, float availableHeight, + CSSDirection parentDirection, CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, bool performLayout, char* reason) { + CSSLayout* layout = &node->layout; gDepth++; - bool needToVisitNode = (node->is_dirty(node->context) && layout->generation_count != gCurrentGenerationCount) || - layout->last_parent_direction != parentDirection; + bool needToVisitNode = (node->isDirty && layout->generationCount != gCurrentGenerationCount) || + layout->lastParentDirection != parentDirection; if (needToVisitNode) { // Invalidate the cached results. - layout->next_cached_measurements_index = 0; - layout->cached_layout.width_measure_mode = (css_measure_mode_t)-1; - layout->cached_layout.height_measure_mode = (css_measure_mode_t)-1; + layout->nextCachedMeasurementsIndex = 0; + layout->cached_layout.widthMeasureMode = (CSSMeasureMode)-1; + layout->cached_layout.heightMeasureMode = (CSSMeasureMode)-1; } - css_cached_measurement_t* cachedResults = NULL; + CSSCachedMeasurement* cachedResults = NULL; // Determine whether the results are already cached. We maintain a separate // cache for layouts and measurements. A layout operation modifies the positions @@ -1661,47 +1779,47 @@ bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableH // We handle nodes with measure functions specially here because they are the most // expensive to measure, so it's worth avoiding redundant measurements if at all possible. if (isMeasureDefined(node)) { - float marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); - float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + float marginAxisRow = getMarginAxis(node, CSSFlexDirectionRow); + float marginAxisColumn = getMarginAxis(node, CSSFlexDirectionColumn); // First, try to use the layout cache. - if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + if (canUseCachedMeasurement(node->isTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, layout->cached_layout)) { cachedResults = &layout->cached_layout; } else { // Try to use the measurement cache. - for (int i = 0; i < layout->next_cached_measurements_index; i++) { - if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn, - widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) { - cachedResults = &layout->cached_measurements[i]; + for (int i = 0; i < layout->nextCachedMeasurementsIndex; i++) { + if (canUseCachedMeasurement(node->isTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + widthMeasureMode, heightMeasureMode, layout->cachedMeasurements[i])) { + cachedResults = &layout->cachedMeasurements[i]; break; } } } } else if (performLayout) { - if (eq(layout->cached_layout.available_width, availableWidth) && - eq(layout->cached_layout.available_height, availableHeight) && - layout->cached_layout.width_measure_mode == widthMeasureMode && - layout->cached_layout.height_measure_mode == heightMeasureMode) { + if (eq(layout->cached_layout.availableWidth, availableWidth) && + eq(layout->cached_layout.availableHeight, availableHeight) && + layout->cached_layout.widthMeasureMode == widthMeasureMode && + layout->cached_layout.heightMeasureMode == heightMeasureMode) { cachedResults = &layout->cached_layout; } } else { - for (int i = 0; i < layout->next_cached_measurements_index; i++) { - if (eq(layout->cached_measurements[i].available_width, availableWidth) && - eq(layout->cached_measurements[i].available_height, availableHeight) && - layout->cached_measurements[i].width_measure_mode == widthMeasureMode && - layout->cached_measurements[i].height_measure_mode == heightMeasureMode) { + for (int i = 0; i < layout->nextCachedMeasurementsIndex; i++) { + if (eq(layout->cachedMeasurements[i].availableWidth, availableWidth) && + eq(layout->cachedMeasurements[i].availableHeight, availableHeight) && + layout->cachedMeasurements[i].widthMeasureMode == widthMeasureMode && + layout->cachedMeasurements[i].heightMeasureMode == heightMeasureMode) { - cachedResults = &layout->cached_measurements[i]; + cachedResults = &layout->cachedMeasurements[i]; break; } } } if (!needToVisitNode && cachedResults != NULL) { - layout->measured_dimensions[CSS_WIDTH] = cachedResults->computed_width; - layout->measured_dimensions[CSS_HEIGHT] = cachedResults->computed_height; + layout->measuredDimensions[CSSDimensionWidth] = cachedResults->computedWidth; + layout->measuredDimensions[CSSDimensionHeight] = cachedResults->computedHeight; if (gPrintChanges && gPrintSkips) { printf("%s%d.{[skipped] ", getSpacer(gDepth), gDepth); @@ -1712,7 +1830,7 @@ bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableH getModeName(widthMeasureMode, performLayout), getModeName(heightMeasureMode, performLayout), availableWidth, availableHeight, - cachedResults->computed_width, cachedResults->computed_height, reason); + cachedResults->computedWidth, cachedResults->computedHeight, reason); } } else { @@ -1737,73 +1855,85 @@ bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableH printf("wm: %s, hm: %s, d: (%f, %f) %s\n", getModeName(widthMeasureMode, performLayout), getModeName(heightMeasureMode, performLayout), - layout->measured_dimensions[CSS_WIDTH], layout->measured_dimensions[CSS_HEIGHT], reason); + layout->measuredDimensions[CSSDimensionWidth], layout->measuredDimensions[CSSDimensionHeight], reason); } - layout->last_parent_direction = parentDirection; + layout->lastParentDirection = parentDirection; if (cachedResults == NULL) { - if (layout->next_cached_measurements_index == CSS_MAX_CACHED_RESULT_COUNT) { + if (layout->nextCachedMeasurementsIndex == CSS_MAX_CACHED_RESULT_COUNT) { if (gPrintChanges) { printf("Out of cache entries!\n"); } - layout->next_cached_measurements_index = 0; + layout->nextCachedMeasurementsIndex = 0; } - css_cached_measurement_t* newCacheEntry; + CSSCachedMeasurement* newCacheEntry; if (performLayout) { // Use the single layout cache entry. newCacheEntry = &layout->cached_layout; } else { // Allocate a new measurement cache entry. - newCacheEntry = &layout->cached_measurements[layout->next_cached_measurements_index]; - layout->next_cached_measurements_index++; + newCacheEntry = &layout->cachedMeasurements[layout->nextCachedMeasurementsIndex]; + layout->nextCachedMeasurementsIndex++; } - newCacheEntry->available_width = availableWidth; - newCacheEntry->available_height = availableHeight; - newCacheEntry->width_measure_mode = widthMeasureMode; - newCacheEntry->height_measure_mode = heightMeasureMode; - newCacheEntry->computed_width = layout->measured_dimensions[CSS_WIDTH]; - newCacheEntry->computed_height = layout->measured_dimensions[CSS_HEIGHT]; + newCacheEntry->availableWidth = availableWidth; + newCacheEntry->availableHeight = availableHeight; + newCacheEntry->widthMeasureMode = widthMeasureMode; + newCacheEntry->heightMeasureMode = heightMeasureMode; + newCacheEntry->computedWidth = layout->measuredDimensions[CSSDimensionWidth]; + newCacheEntry->computedHeight = layout->measuredDimensions[CSSDimensionHeight]; } } if (performLayout) { - node->layout.dimensions[CSS_WIDTH] = node->layout.measured_dimensions[CSS_WIDTH]; - node->layout.dimensions[CSS_HEIGHT] = node->layout.measured_dimensions[CSS_HEIGHT]; - layout->should_update = true; + node->layout.dimensions[CSSDimensionWidth] = node->layout.measuredDimensions[CSSDimensionWidth]; + node->layout.dimensions[CSSDimensionHeight] = node->layout.measuredDimensions[CSSDimensionHeight]; + node->shouldUpdate = true; + node->isDirty = false; } gDepth--; - layout->generation_count = gCurrentGenerationCount; + layout->generationCount = gCurrentGenerationCount; return (needToVisitNode || cachedResults == NULL); } -void layoutNode(css_node_t* node, float availableWidth, float availableHeight, css_direction_t parentDirection) { +void CSSNodeCalculateLayout(CSSNode* node, float availableWidth, float availableHeight, CSSDirection parentDirection) { // Increment the generation count. This will force the recursive routine to visit // all dirty nodes at least once. Subsequent visits will be skipped if the input // parameters don't change. gCurrentGenerationCount++; - // If the caller didn't specify a height/width, use the dimensions - // specified in the style. - if (isUndefined(availableWidth) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { - availableWidth = node->style.dimensions[CSS_WIDTH] + getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); - } - if (isUndefined(availableHeight) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - availableHeight = node->style.dimensions[CSS_HEIGHT] + getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + CSSMeasureMode widthMeasureMode = CSSMeasureModeUndefined; + CSSMeasureMode heightMeasureMode = CSSMeasureModeUndefined; + + if (!isUndefined(availableWidth)) { + widthMeasureMode = CSSMeasureModeExactly; + } else if (isStyleDimDefined(node, CSSFlexDirectionRow)) { + availableWidth = node->style.dimensions[dim[CSSFlexDirectionRow]] + getMarginAxis(node, CSSFlexDirectionRow); + widthMeasureMode = CSSMeasureModeExactly; + } else if (node->style.maxDimensions[CSSDimensionWidth] >= 0.0) { + availableWidth = node->style.maxDimensions[CSSDimensionWidth]; + widthMeasureMode = CSSMeasureModeAtMost; } - css_measure_mode_t widthMeasureMode = isUndefined(availableWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; - css_measure_mode_t heightMeasureMode = isUndefined(availableHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + if (!isUndefined(availableHeight)) { + heightMeasureMode = CSSMeasureModeExactly; + } else if (isStyleDimDefined(node, CSSFlexDirectionColumn)) { + availableHeight = node->style.dimensions[dim[CSSFlexDirectionColumn]] + getMarginAxis(node, CSSFlexDirectionColumn); + heightMeasureMode = CSSMeasureModeExactly; + } else if (node->style.maxDimensions[CSSDimensionHeight] >= 0.0) { + availableHeight = node->style.maxDimensions[CSSDimensionHeight]; + heightMeasureMode = CSSMeasureModeAtMost; + } if (layoutNodeInternal(node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, true, "initial")) { setPosition(node, node->layout.direction); if (gPrintTree) { - print_css_node(node, CSS_PRINT_LAYOUT | CSS_PRINT_CHILDREN | CSS_PRINT_STYLE); + CSSNodePrint(node, CSSPrintOptionsLayout | CSSPrintOptionsChildren | CSSPrintOptionsStyle); } } } diff --git a/React/CSSLayout/CSSLayout.h b/React/CSSLayout/CSSLayout.h new file mode 100644 index 00000000000000..23d38372d89fd7 --- /dev/null +++ b/React/CSSLayout/CSSLayout.h @@ -0,0 +1,210 @@ +/** + * 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. + */ + +#ifndef __CSS_LAYOUT_H +#define __CSS_LAYOUT_H + +#include <math.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +// Not defined in MSVC++ +#ifndef NAN +static const unsigned long __nan[2] = {0xffffffff, 0x7fffffff}; +#define NAN (*(const float *)__nan) +#endif + +#define CSSUndefined NAN + +#include <CSSLayout/CSSMacros.h> + +CSS_EXTERN_C_BEGIN + +typedef enum CSSDirection { + CSSDirectionInherit, + CSSDirectionLTR, + CSSDirectionRTL, +} CSSDirection; + +typedef enum CSSFlexDirection { + CSSFlexDirectionColumn, + CSSFlexDirectionColumnReverse, + CSSFlexDirectionRow, + CSSFlexDirectionRowReverse, +} CSSFlexDirection; + +typedef enum CSSJustify { + CSSJustifyFlexStart, + CSSJustifyCenter, + CSSJustifyFlexEnd, + CSSJustifySpaceBetween, + CSSJustifySpaceAround, +} CSSJustify; + +typedef enum CSSOverflow { + CSSOverflowVisible, + CSSOverflowHidden, +} CSSOverflow; + +// Note: auto is only a valid value for alignSelf. It is NOT a valid value for +// alignItems. +typedef enum CSSAlign { + CSSAlignAuto, + CSSAlignFlexStart, + CSSAlignCenter, + CSSAlignFlexEnd, + CSSAlignStretch, +} CSSAlign; + +typedef enum CSSPositionType { + CSSPositionTypeRelative, + CSSPositionTypeAbsolute, +} CSSPositionType; + +typedef enum CSSWrapType { + CSSWrapTypeNoWrap, + CSSWrapTypeWrap, +} CSSWrapType; + +// Note: left and top are shared between position[2] and position[4], so +// they have to be before right and bottom. +typedef enum CSSPosition { + CSSPositionLeft, + CSSPositionTop, + CSSPositionRight, + CSSPositionBottom, + CSSPositionStart, + CSSPositionEnd, + CSSPositionCount, +} CSSPosition; + +typedef enum CSSMeasureMode { + CSSMeasureModeUndefined, + CSSMeasureModeExactly, + CSSMeasureModeAtMost, + CSSMeasureModeCount, +} CSSMeasureMode; + +typedef enum CSSDimension { + CSSDimensionWidth, + CSSDimensionHeight, +} CSSDimension; + +typedef enum CSSPrintOptions { + CSSPrintOptionsLayout = 1, + CSSPrintOptionsStyle = 2, + CSSPrintOptionsChildren = 4, +} CSSPrintOptions; + +typedef struct CSSSize { + float width; + float height; +} CSSSize; + +typedef struct CSSNode * CSSNodeRef; +typedef CSSSize (*CSSMeasureFunc)(void *context, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode); +typedef void (*CSSPrintFunc)(void *context); + +// CSSNode +CSSNodeRef CSSNodeNew(); +void CSSNodeInit(CSSNodeRef node); +void CSSNodeFree(CSSNodeRef node); + +void CSSNodeInsertChild(CSSNodeRef node, CSSNodeRef child, unsigned int index); +void CSSNodeRemoveChild(CSSNodeRef node, CSSNodeRef child); +CSSNodeRef CSSNodeGetChild(CSSNodeRef node, unsigned int index); +unsigned int CSSNodeChildCount(CSSNodeRef node); + +void CSSNodeCalculateLayout( + CSSNodeRef node, + float availableWidth, + float availableHeight, + CSSDirection parentDirection); + +// Mark a node as dirty. Only valid for nodes with a custom measure function set. +// CSSLayout knows when to mark all other nodes as dirty but because nodes with measure functions +// depends on information not known to CSSLayout they must perform this dirty marking manually. +void CSSNodeMarkDirty(CSSNodeRef node); + +void CSSNodePrint(CSSNodeRef node, CSSPrintOptions options); + +bool isUndefined(float value); + +#define CSS_NODE_PROPERTY(type, name, paramName) \ +void CSSNodeSet##name(CSSNodeRef node, type paramName); \ +type CSSNodeGet##name(CSSNodeRef node); + +#define CSS_NODE_STYLE_PROPERTY(type, name, paramName) \ +void CSSNodeStyleSet##name(CSSNodeRef node, type paramName); \ +type CSSNodeStyleGet##name(CSSNodeRef node); + +#define CSS_NODE_LAYOUT_PROPERTY(type, name) \ +type CSSNodeLayoutGet##name(CSSNodeRef node); + +CSS_NODE_PROPERTY(void*, Context, context); +CSS_NODE_PROPERTY(CSSMeasureFunc, MeasureFunc, measureFunc); +CSS_NODE_PROPERTY(CSSPrintFunc, PrintFunc, printFunc); +CSS_NODE_PROPERTY(bool, IsTextnode, isTextNode); +CSS_NODE_PROPERTY(bool, ShouldUpdate, shouldUpdate); + +CSS_NODE_STYLE_PROPERTY(CSSDirection, Direction, direction); +CSS_NODE_STYLE_PROPERTY(CSSFlexDirection, FlexDirection, flexDirection); +CSS_NODE_STYLE_PROPERTY(CSSJustify, JustifyContent, justifyContent); +CSS_NODE_STYLE_PROPERTY(CSSAlign, AlignContent, alignContent); +CSS_NODE_STYLE_PROPERTY(CSSAlign, AlignItems, alignItems); +CSS_NODE_STYLE_PROPERTY(CSSAlign, AlignSelf, alignSelf); +CSS_NODE_STYLE_PROPERTY(CSSPositionType, PositionType, positionType); +CSS_NODE_STYLE_PROPERTY(CSSWrapType, FlexWrap, flexWrap); +CSS_NODE_STYLE_PROPERTY(CSSOverflow, Overflow, overflow); +CSS_NODE_STYLE_PROPERTY(float, Flex, flex); + +CSS_NODE_STYLE_PROPERTY(float, PositionLeft, positionLeft); +CSS_NODE_STYLE_PROPERTY(float, PositionTop, positionTop); +CSS_NODE_STYLE_PROPERTY(float, PositionRight, positionRight); +CSS_NODE_STYLE_PROPERTY(float, PositionBottom, positionBottom); + +CSS_NODE_STYLE_PROPERTY(float, MarginLeft, marginLeft); +CSS_NODE_STYLE_PROPERTY(float, MarginTop, marginTop); +CSS_NODE_STYLE_PROPERTY(float, MarginRight, marginRight); +CSS_NODE_STYLE_PROPERTY(float, MarginBottom, marginBottom); +CSS_NODE_STYLE_PROPERTY(float, MarginStart, marginStart); +CSS_NODE_STYLE_PROPERTY(float, MarginEnd, marginEnd); + +CSS_NODE_STYLE_PROPERTY(float, PaddingLeft, paddingLeft); +CSS_NODE_STYLE_PROPERTY(float, PaddingTop, paddingTop); +CSS_NODE_STYLE_PROPERTY(float, PaddingRight, paddingRight); +CSS_NODE_STYLE_PROPERTY(float, PaddingBottom, paddingBottom); +CSS_NODE_STYLE_PROPERTY(float, PaddingStart, paddingStart); +CSS_NODE_STYLE_PROPERTY(float, PaddingEnd, paddingEnd); + +CSS_NODE_STYLE_PROPERTY(float, BorderLeft, borderLeft); +CSS_NODE_STYLE_PROPERTY(float, BorderTop, borderTop); +CSS_NODE_STYLE_PROPERTY(float, BorderRight, borderRight); +CSS_NODE_STYLE_PROPERTY(float, BorderBottom, borderBottom); +CSS_NODE_STYLE_PROPERTY(float, BorderStart, borderStart); +CSS_NODE_STYLE_PROPERTY(float, BorderEnd, borderEnd); + +CSS_NODE_STYLE_PROPERTY(float, Width, width); +CSS_NODE_STYLE_PROPERTY(float, Height, height); +CSS_NODE_STYLE_PROPERTY(float, MinWidth, minWidth); +CSS_NODE_STYLE_PROPERTY(float, MinHeight, minHeight); +CSS_NODE_STYLE_PROPERTY(float, MaxWidth, maxWidth); +CSS_NODE_STYLE_PROPERTY(float, MaxHeight, maxHeight); + +CSS_NODE_LAYOUT_PROPERTY(float, Left); +CSS_NODE_LAYOUT_PROPERTY(float, Top); +CSS_NODE_LAYOUT_PROPERTY(float, Right); +CSS_NODE_LAYOUT_PROPERTY(float, Bottom); +CSS_NODE_LAYOUT_PROPERTY(float, Width); +CSS_NODE_LAYOUT_PROPERTY(float, Height); + +CSS_EXTERN_C_END + +#endif diff --git a/React/CSSLayout/CSSMacros.h b/React/CSSLayout/CSSMacros.h new file mode 100644 index 00000000000000..a2fc8e378ca5df --- /dev/null +++ b/React/CSSLayout/CSSMacros.h @@ -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. + */ + +#ifndef __CSS_MACROS_H +#define __CSS_MACROS_H + +#ifdef __cplusplus +# define CSS_EXTERN_C_BEGIN extern "C" { +# define CSS_EXTERN_C_END } +#else +# define CSS_EXTERN_C_BEGIN +# define CSS_EXTERN_C_END +#endif + +#endif diff --git a/React/CSSLayout/CSSNodeList.c b/React/CSSLayout/CSSNodeList.c new file mode 100644 index 00000000000000..4baf95ee3b42ae --- /dev/null +++ b/React/CSSLayout/CSSNodeList.c @@ -0,0 +1,86 @@ +/** + * 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. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#include "CSSNodeList.h" + +struct CSSNodeList { + int capacity; + int count; + void **items; +}; + +CSSNodeListRef CSSNodeListNew(unsigned int initialCapacity) { + CSSNodeListRef list = malloc(sizeof(struct CSSNodeList)); + assert(list != NULL); + + list->capacity = initialCapacity; + list->count = 0; + list->items = malloc(sizeof(void*) * list->capacity); + assert(list->items != NULL); + + return list; +} + +void CSSNodeListFree(CSSNodeListRef list) { + free(list); +} + +unsigned int CSSNodeListCount(CSSNodeListRef list) { + return list->count; +} + +void CSSNodeListAdd(CSSNodeListRef list, CSSNodeRef node) { + CSSNodeListInsert(list, node, list->count); +} + +void CSSNodeListInsert(CSSNodeListRef list, CSSNodeRef node, unsigned int index) { + if (list->count == list->capacity) { + list->capacity *= 2; + list->items = realloc(list->items, sizeof(void*) * list->capacity); + assert(list->items != NULL); + } + + for (unsigned int i = list->count; i > index; i--) { + list->items[i] = list->items[i - 1]; + } + + list->count++; + list->items[index] = node; +} + +CSSNodeRef CSSNodeListRemove(CSSNodeListRef list, unsigned int index) { + CSSNodeRef removed = list->items[index]; + list->items[index] = NULL; + + for (unsigned int i = index; i < list->count - 1; i++) { + list->items[i] = list->items[i + 1]; + list->items[i + 1] = NULL; + } + + list->count--; + return removed; +} + +CSSNodeRef CSSNodeListDelete(CSSNodeListRef list, CSSNodeRef node) { + for (unsigned int i = 0; i < list->count; i++) { + if (list->items[i] == node) { + return CSSNodeListRemove(list, i); + } + } + + return NULL; +} + +CSSNodeRef CSSNodeListGet(CSSNodeListRef list, unsigned int index) { + return list->items[index]; +} diff --git a/React/CSSLayout/CSSNodeList.h b/React/CSSLayout/CSSNodeList.h new file mode 100644 index 00000000000000..e59a30f5377df3 --- /dev/null +++ b/React/CSSLayout/CSSNodeList.h @@ -0,0 +1,30 @@ +/** + * 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. + */ + +#ifndef __CSS_NODE_LIST_H +#define __CSS_NODE_LIST_H + +#include <CSSLayout/CSSLayout.h> + +CSS_EXTERN_C_BEGIN + +typedef struct CSSNodeList * CSSNodeListRef; + +CSSNodeListRef CSSNodeListNew(unsigned int initialCapacity); +void CSSNodeListFree(CSSNodeListRef list); +unsigned int CSSNodeListCount(CSSNodeListRef list); +void CSSNodeListAdd(CSSNodeListRef list, CSSNodeRef node); +void CSSNodeListInsert(CSSNodeListRef list, CSSNodeRef node, unsigned int index); +CSSNodeRef CSSNodeListRemove(CSSNodeListRef list, unsigned int index); +CSSNodeRef CSSNodeListDelete(CSSNodeListRef list, CSSNodeRef node); +CSSNodeRef CSSNodeListGet(CSSNodeListRef list, unsigned int index); + +CSS_EXTERN_C_END + +#endif diff --git a/React/Executors/RCTJSCExecutor.h b/React/Executors/RCTJSCExecutor.h index db690042e4d94c..66d61a16bd4e9a 100644 --- a/React/Executors/RCTJSCExecutor.h +++ b/React/Executors/RCTJSCExecutor.h @@ -11,6 +11,8 @@ #import "RCTJavaScriptExecutor.h" +typedef void (^RCTJavaScriptValueCallback)(JSValue *result, NSError *error); + /** * Default name for the JS thread */ @@ -25,6 +27,17 @@ RCT_EXTERN NSString *const RCTJSCThreadName; */ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification; +/** + * @experimental + * May be used to pre-create the JSContext to make RCTJSCExecutor creation less costly. + * Avoid using this; it's experimental and is not likely to be supported long-term. + */ +@interface RCTJSContextProvider : NSObject + +- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary; + +@end + /** * Uses a JavaScriptCore context as the execution engine. */ @@ -49,6 +62,30 @@ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification; * If available, the error's userInfo property will contain the JS stacktrace under * the RCTJSStackTraceKey key. */ -- (NSError *)convertJSErrorToNSError:(JSValueRef)jsError context:(JSContextRef)context; +- (NSError *)errorForJSError:(JSValue *)jsError; + +/** + * @experimental + * Pass a RCTJSContextProvider object to use an NSThread/JSContext pair that have already been created. + * The returned executor has already executed the supplied application script synchronously. + * The underlying JSContext will be returned in the JSContext pointer if it is non-NULL and there was no error. + * If an error occurs, this method will return nil and specify the error in the error pointer if it is non-NULL. + */ ++ (instancetype)initializedExecutorWithContextProvider:(RCTJSContextProvider *)JSContextProvider + applicationScript:(NSData *)applicationScript + sourceURL:(NSURL *)sourceURL + JSContext:(JSContext **)JSContext + error:(NSError **)error; + +/** + * Invokes the given module/method directly. The completion block will be called with the + * JSValue returned by the JS context. + * + * Currently this does not flush the JS-to-native message queue. + */ +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + jsValueCallback:(RCTJavaScriptValueCallback)onComplete; @end diff --git a/React/Executors/RCTJSCExecutor.mm b/React/Executors/RCTJSCExecutor.mm index 4da25b0303e499..4d8f15acdc7c20 100644 --- a/React/Executors/RCTJSCExecutor.mm +++ b/React/Executors/RCTJSCExecutor.mm @@ -13,6 +13,7 @@ #import <memory> #import <pthread.h> #import <string> +#import <unordered_map> #import <UIKit/UIDevice.h> @@ -63,10 +64,31 @@ bool isEmpty() { } }; +#if RCT_PROFILE +@interface RCTCookieMap : NSObject +{ + @package + std::unordered_map<NSUInteger, NSUInteger> _cookieMap; +} +@end +@implementation RCTCookieMap @end +#endif + +struct RCTJSContextData { + BOOL useCustomJSCLibrary; + NSThread *javaScriptThread; + JSContext *context; + RCTJSCWrapper *jscWrapper; +}; + +@interface RCTJSContextProvider () +/** May only be called once, or deadlock will result. */ +- (RCTJSContextData)data; +@end + @interface RCTJavaScriptContext : NSObject <RCTInvalidating> @property (nonatomic, strong, readonly) JSContext *context; -@property (nonatomic, assign, readonly) JSGlobalContextRef ctx; - (instancetype)initWithJSContext:(JSContext *)context onThread:(NSThread *)javaScriptThread NS_DESIGNATED_INITIALIZER; @@ -100,11 +122,6 @@ - (instancetype)initWithJSContext:(JSContext *)context RCT_NOT_IMPLEMENTED(-(instancetype)init) -- (JSGlobalContextRef)ctx -{ - return _context.JSGlobalContextRef; -} - - (BOOL)isValid { return _context != nil; @@ -127,15 +144,18 @@ - (void)invalidate @implementation RCTJSCExecutor { - RCTJavaScriptContext *_context; + // Set at init time: + BOOL _useCustomJSCLibrary; NSThread *_javaScriptThread; - CFMutableDictionaryRef _cookieMap; + // Set at setUp time: + RCTPerformanceLogger *_performanceLogger; + RCTJSCWrapper *_jscWrapper; + RCTJavaScriptContext *_context; + + // Set as needed: RandomAccessBundleData _randomAccessBundle; JSValueRef _batchedBridgeRef; - - RCTJSCWrapper *_jscWrapper; - BOOL _useCustomJSCLibrary; } @synthesize valid = _valid; @@ -218,9 +238,9 @@ @implementation RCTJSCExecutor return [NSError errorWithDomain:RCTErrorDomain code:1 userInfo:errorInfo]; } -- (NSError *)convertJSErrorToNSError:(JSValueRef)jsError context:(JSContextRef)context +- (NSError *)errorForJSError:(JSValue *)jsError { - return RCTNSErrorFromJSError(_jscWrapper, context, jsError); + return RCTNSErrorFromJSError(_jscWrapper, jsError.context.JSGlobalContextRef, jsError.JSValueRef); } #if RCT_DEV @@ -263,6 +283,27 @@ + (void)runRunLoopThread } } +static NSThread *newJavaScriptThread(void) +{ + NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[RCTJSCExecutor class] + selector:@selector(runRunLoopThread) + object:nil]; + javaScriptThread.name = RCTJSCThreadName; + if ([javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) { + [javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive]; + } else { + javaScriptThread.threadPriority = [NSThread mainThread].threadPriority; + } + [javaScriptThread start]; + return javaScriptThread; +} + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + _performanceLogger = [bridge performanceLogger]; +} + - (instancetype)init { return [self initWithUseCustomJSCLibrary:NO]; @@ -270,220 +311,247 @@ - (instancetype)init - (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary { + RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTJSCExecutor init]", nil); + if (self = [super init]) { _useCustomJSCLibrary = useCustomJSCLibrary; _valid = YES; + _javaScriptThread = newJavaScriptThread(); + } - _javaScriptThread = [[NSThread alloc] initWithTarget:[self class] - selector:@selector(runRunLoopThread) - object:nil]; - _javaScriptThread.name = RCTJSCThreadName; - - if ([_javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) { - [_javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive]; - } else { - _javaScriptThread.threadPriority = [NSThread mainThread].threadPriority; - } + RCT_PROFILE_END_EVENT(0, @"", nil); + return self; +} - [_javaScriptThread start]; ++ (instancetype)initializedExecutorWithContextProvider:(RCTJSContextProvider *)JSContextProvider + applicationScript:(NSData *)applicationScript + sourceURL:(NSURL *)sourceURL + JSContext:(JSContext **)JSContext + error:(NSError **)error +{ + const RCTJSContextData data = JSContextProvider.data; + if (JSContext) { + *JSContext = data.context; + } + RCTJSCExecutor *executor = [[RCTJSCExecutor alloc] initWithJSContextData:data]; + if (![executor _synchronouslyExecuteApplicationScript:applicationScript sourceURL:sourceURL JSContext:data.context error:error]) { + return nil; // error has been set by _synchronouslyExecuteApplicationScript: } + return executor; +} +- (instancetype)initWithJSContextData:(const RCTJSContextData &)data +{ + if (self = [super init]) { + _useCustomJSCLibrary = data.useCustomJSCLibrary; + _valid = YES; + _javaScriptThread = data.javaScriptThread; + _jscWrapper = data.jscWrapper; + _context = [[RCTJavaScriptContext alloc] initWithJSContext:data.context onThread:_javaScriptThread]; + } return self; } +- (BOOL)_synchronouslyExecuteApplicationScript:(NSData *)script + sourceURL:(NSURL *)sourceURL + JSContext:(JSContext *)context + error:(NSError **)error +{ + BOOL isRAMBundle = NO; + script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, error); + if (!script) { + return NO; + } + if (isRAMBundle) { + registerNativeRequire(context, self); + } + NSError *returnedError = executeApplicationScript(script, sourceURL, _jscWrapper, _performanceLogger, _context.context.JSGlobalContextRef); + if (returnedError) { + if (error) { + *error = returnedError; + } + return NO; + } else { + return YES; + } +} + - (RCTJavaScriptContext *)context { RCTAssertThread(_javaScriptThread, @"Must be called on JS thread."); - if (!self.isValid) { return nil; } - - if (!_context) { - JSContext *context = [_jscWrapper->JSContext new]; - _context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:_javaScriptThread]; - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification - object:context]; - } - + RCTAssert(_context != nil, @"Fetching context while valid, but before it is created"); return _context; } -- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block -{ - __weak RCTJSCExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:^{ - weakSelf.context.context[name] = block; - }]; -} - - (void)setUp { - __weak RCTJSCExecutor *weakSelf = self; +#if RCT_PROFILE +#ifndef __clang_analyzer__ + _bridge.flowIDMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); +#endif + _bridge.flowIDMapLock = [NSLock new]; + + for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(toggleProfilingFlag:) + name:event + object:nil]; + } +#endif [self executeBlockOnJavaScriptQueue:^{ - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf.valid) { + if (!self.valid) { return; } - strongSelf->_jscWrapper = RCTJSCWrapperCreate(strongSelf->_useCustomJSCLibrary); - }]; + JSContext *context = nil; + if (self->_jscWrapper) { + RCTAssert(self->_context != nil, @"If wrapper was pre-initialized, context should be too"); + context = self->_context.context; + } else { + [self->_performanceLogger markStartForTag:RCTPLJSCWrapperOpenLibrary]; + self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary); + [self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary]; + + RCTAssert(self->_context == nil, @"Didn't expect to set up twice"); + context = [self->_jscWrapper->JSContext new]; + self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification + object:context]; + + configureCacheOnContext(context, self->_jscWrapper); + installBasicSynchronousHooksOnContext(context); + } + __weak RCTJSCExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:^{ - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf.valid) { - return; - } + context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) { + RCTJSCExecutor *strongSelf = weakSelf; + if (!strongSelf.valid) { + return nil; + } - if (strongSelf->_jscWrapper->configureJSContextForIOS == NULL) { - return; - } + RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", nil); + NSArray *config = [strongSelf->_bridge configForModuleName:moduleName]; + NSString *result = config ? RCTJSONStringify(config, NULL) : nil; + RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config", @{ @"moduleName": moduleName }); + return result; + }; + + context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){ + RCTJSCExecutor *strongSelf = weakSelf; + if (!strongSelf.valid || !calls) { + return; + } + + RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil); + [strongSelf->_bridge handleBuffer:calls batchEnded:NO]; + RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil); + }; + +#if RCT_PROFILE + __weak RCTBridge *weakBridge = self->_bridge; + context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) { + if (RCTProfileIsProfiling()) { + [weakBridge.flowIDMapLock lock]; + int64_t newCookie = [_RCTProfileBeginFlowEvent() longLongValue]; + CFDictionarySetValue(weakBridge.flowIDMap, (const void *)cookie, (const void *)newCookie); + [weakBridge.flowIDMapLock unlock]; + } + }; + + context[@"nativeTraceEndAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) { + if (RCTProfileIsProfiling()) { + [weakBridge.flowIDMapLock lock]; + int64_t newCookie = (int64_t)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie); + _RCTProfileEndFlowEvent(@(newCookie)); + CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie); + [weakBridge.flowIDMapLock unlock]; + } + }; +#endif + +#if RCT_DEV + RCTInstallJSCProfiler(self->_bridge, context.JSGlobalContextRef); + // Inject handler used by HMR + context[@"nativeInjectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) { + RCTJSCExecutor *strongSelf = weakSelf; + if (!strongSelf.valid) { + return; + } + + RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper; + JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString(sourceCode.UTF8String); + JSStringRef jsURL = jscWrapper->JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String); + jscWrapper->JSEvaluateScript(strongSelf->_context.context.JSGlobalContextRef, execJSString, NULL, jsURL, 0, NULL); + jscWrapper->JSStringRelease(jsURL); + jscWrapper->JSStringRelease(execJSString); + }; +#endif + }]; +} + +/** If configureJSContextForIOS is available on jscWrapper, calls it with the correct parameters. */ +static void configureCacheOnContext(JSContext *context, RCTJSCWrapper *jscWrapper) +{ + if (jscWrapper->configureJSContextForIOS != NULL) { NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; RCTAssert(cachesPath != nil, @"cachesPath should not be nil"); if (cachesPath) { - strongSelf->_jscWrapper->configureJSContextForIOS(strongSelf.context.ctx, [cachesPath UTF8String]); + jscWrapper->configureJSContextForIOS(context.JSGlobalContextRef, [cachesPath UTF8String]); } - }]; - - [self addSynchronousHookWithName:@"noop" usingBlock:^{}]; + } +} - [self addSynchronousHookWithName:@"nativeLoggingHook" usingBlock:^(NSString *message, NSNumber *logLevel) { +/** Installs synchronous hooks that don't require a weak reference back to the RCTJSCExecutor. */ +static void installBasicSynchronousHooksOnContext(JSContext *context) +{ + context[@"noop"] = ^{}; + context[@"nativeLoggingHook"] = ^(NSString *message, NSNumber *logLevel) { RCTLogLevel level = RCTLogLevelInfo; if (logLevel) { level = MAX(level, (RCTLogLevel)logLevel.integerValue); } _RCTLogJavaScriptInternal(level, message); - }]; - - [self addSynchronousHookWithName:@"nativeRequireModuleConfig" usingBlock:^NSString *(NSString *moduleName) { - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf.valid) { - return nil; - } - - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", nil); - NSArray *config = [strongSelf->_bridge configForModuleName:moduleName]; - NSString *result = config ? RCTJSONStringify(config, NULL) : nil; - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config", @{ @"moduleName": moduleName }); - return result; - }]; - - [self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){ - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf.valid || !calls) { - return; - } - - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil); - [strongSelf->_bridge handleBuffer:calls batchEnded:NO]; - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil); - }]; - - [self addSynchronousHookWithName:@"nativePerformanceNow" usingBlock:^{ + }; + context[@"nativePerformanceNow"] = ^{ return @(CACurrentMediaTime() * 1000); - }]; - + }; #if RCT_PROFILE if (RCTProfileIsProfiling()) { // Cheating, since it's not a "hook", but meh - [self addSynchronousHookWithName:@"__RCTProfileIsProfiling" usingBlock:@YES]; + context[@"__RCTProfileIsProfiling"] = @YES; } - - _cookieMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); - [self addSynchronousHookWithName:@"nativeTraceBeginAsyncSection" usingBlock:^(uint64_t tag, NSString *name, NSUInteger cookie) { - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil); - CFDictionarySetValue(strongSelf->_cookieMap, (const void *)cookie, (const void *)newCookie); - }]; - - [self addSynchronousHookWithName:@"nativeTraceEndAsyncSection" usingBlock:^(uint64_t tag, NSString *name, NSUInteger cookie) { - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(strongSelf->_cookieMap, (const void *)cookie); - RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, @"JS async", nil); - CFDictionaryRemoveValue(strongSelf->_cookieMap, (const void *)cookie); - }]; - - [self addSynchronousHookWithName:@"nativeTraceBeginSection" usingBlock:^(NSNumber *tag, NSString *profileName, NSDictionary *args) { + context[@"nativeTraceBeginSection"] = ^(NSNumber *tag, NSString *profileName, NSDictionary *args) { static int profileCounter = 1; if (!profileName) { profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++]; } RCT_PROFILE_BEGIN_EVENT(tag.longLongValue, profileName, args); - }]; - - [self addSynchronousHookWithName:@"nativeTraceEndSection" usingBlock:^(NSNumber *tag) { + }; + context[@"nativeTraceEndSection"] = ^(NSNumber *tag) { RCT_PROFILE_END_EVENT(tag.longLongValue, @"console", nil); - }]; - - __weak RCTBridge *weakBridge = _bridge; -#ifndef __clang_analyzer__ - _bridge.flowIDMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); -#endif - _bridge.flowIDMapLock = [NSLock new]; - [self addSynchronousHookWithName:@"nativeTraceBeginAsyncFlow" usingBlock:^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) { - if (RCTProfileIsProfiling()) { - [weakBridge.flowIDMapLock lock]; - int64_t newCookie = [_RCTProfileBeginFlowEvent() longLongValue]; - CFDictionarySetValue(weakBridge.flowIDMap, (const void *)cookie, (const void *)newCookie); - [weakBridge.flowIDMapLock unlock]; - } - }]; - - [self addSynchronousHookWithName:@"nativeTraceEndAsyncFlow" usingBlock:^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) { - if (RCTProfileIsProfiling()) { - [weakBridge.flowIDMapLock lock]; - int64_t newCookie = (int64_t)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie); - _RCTProfileEndFlowEvent(@(newCookie)); - CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie); - [weakBridge.flowIDMapLock unlock]; - } - }]; - - for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(toggleProfilingFlag:) - name:event - object:nil]; - } -#endif - -#if RCT_DEV - [self executeBlockOnJavaScriptQueue:^{ - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf.valid) { - return; - } - - JSContext *context = strongSelf.context.context; - RCTInstallJSCProfiler(_bridge, context.JSGlobalContextRef); - }]; - - // Inject handler used by HMR - [self addSynchronousHookWithName:@"nativeInjectHMRUpdate" usingBlock:^(NSString *sourceCode, NSString *sourceCodeURL) { - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf.valid) { - return; + }; + RCTCookieMap *cookieMap = [RCTCookieMap new]; + context[@"nativeTraceBeginAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { + NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil); + cookieMap->_cookieMap.insert({cookie, newCookie}); + }; + context[@"nativeTraceEndAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { + NSUInteger newCookie = 0; + const auto &it = cookieMap->_cookieMap.find(cookie); + if (it != cookieMap->_cookieMap.end()) { + newCookie = it->second; + cookieMap->_cookieMap.erase(it); } - - RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper; - JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString(sourceCode.UTF8String); - JSStringRef jsURL = jscWrapper->JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String); - jscWrapper->JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, NULL); - jscWrapper->JSStringRelease(jsURL); - jscWrapper->JSStringRelease(execJSString); - }]; + RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, @"JS async", nil); + }; #endif } @@ -491,7 +559,7 @@ - (void)toggleProfilingFlag:(NSNotification *)notification { [self executeBlockOnJavaScriptQueue:^{ BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling]; - [_bridge enqueueJSCall:@"Systrace.setEnabled" args:@[enabled ? @YES : @NO]]; + [self->_bridge enqueueJSCall:@"Systrace.setEnabled" args:@[enabled ? @YES : @NO]]; }]; } @@ -525,25 +593,34 @@ - (void)dealloc RCTJSCWrapperRelease(_jscWrapper); _jscWrapper = NULL; } - - if (_cookieMap) { - CFRelease(_cookieMap); - } } - (void)flushedQueue:(RCTJavaScriptCallback)onComplete { // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 - [self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete]; + [self _executeJSCall:@"flushedQueue" arguments:@[] unwrapResult:YES callback:onComplete]; } -- (void)callFunctionOnModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)args - callback:(RCTJavaScriptCallback)onComplete +- (void)_callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + flushQueue:(BOOL)flushQueue + unwrapResult:(BOOL)unwrapResult + callback:(RCTJavaScriptCallback)onComplete { // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 - [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete]; + NSString *bridgeMethod = flushQueue ? @"callFunctionReturnFlushedQueue" : @"callFunction"; + [self _executeJSCall:bridgeMethod arguments:@[module, method, args] unwrapResult:unwrapResult callback:onComplete]; +} + +- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args callback:(RCTJavaScriptCallback)onComplete +{ + [self _callFunctionOnModule:module method:method arguments:args flushQueue:YES unwrapResult:YES callback:onComplete]; +} + +- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args jsValueCallback:(RCTJavaScriptValueCallback)onComplete +{ + [self _callFunctionOnModule:module method:method arguments:args flushQueue:NO unwrapResult:NO callback:onComplete]; } - (void)invokeCallbackID:(NSNumber *)cbID @@ -551,38 +628,42 @@ - (void)invokeCallbackID:(NSNumber *)cbID callback:(RCTJavaScriptCallback)onComplete { // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 - [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete]; + [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] unwrapResult:YES callback:onComplete]; } - (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments + unwrapResult:(BOOL)unwrapResult callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @"onComplete block should not be nil"); __weak RCTJSCExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ + [self executeBlockOnJavaScriptQueue:^{ RCTJSCExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { return; } - NSError *error; - JSValueRef errorJSRef = NULL; - JSValueRef resultJSRef = NULL; + RCT_PROFILE_BEGIN_EVENT(0, @"executeJSCall", @{@"method": method, @"args": arguments}); + RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper; - JSGlobalContextRef contextJSRef = jscWrapper->JSContextGetGlobalContext(strongSelf->_context.ctx); JSContext *context = strongSelf->_context.context; - JSObjectRef globalObjectJSRef = jscWrapper->JSContextGetGlobalObject(strongSelf->_context.ctx); + JSGlobalContextRef ctx = context.JSGlobalContextRef; + JSGlobalContextRef contextJSRef = jscWrapper->JSContextGetGlobalContext(ctx); // get the BatchedBridge object + JSValueRef errorJSRef = NULL; JSValueRef batchedBridgeRef = strongSelf->_batchedBridgeRef; if (!batchedBridgeRef) { JSStringRef moduleNameJSStringRef = jscWrapper->JSStringCreateWithUTF8CString("__fbBatchedBridge"); + JSObjectRef globalObjectJSRef = jscWrapper->JSContextGetGlobalObject(ctx); batchedBridgeRef = jscWrapper->JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef); jscWrapper->JSStringRelease(moduleNameJSStringRef); strongSelf->_batchedBridgeRef = batchedBridgeRef; } + NSError *error; + JSValueRef resultJSRef = NULL; if (batchedBridgeRef != NULL && errorJSRef == NULL && !jscWrapper->JSValueIsUndefined(contextJSRef, batchedBridgeRef)) { // get method JSStringRef methodNameJSStringRef = jscWrapper->JSStringCreateWithCFString((__bridge CFStringRef)method); @@ -606,26 +687,24 @@ - (void)_executeJSCall:(NSString *)method } } + id objcValue; if (errorJSRef || error) { if (!error) { error = RCTNSErrorFromJSError(jscWrapper, contextJSRef, errorJSRef); } - onComplete(nil, error); - return; + } else { + // We often return `null` from JS when there is nothing for native side. [JSValue toValue] + // returns [NSNull null] in this case, which we don't want. + if (!jscWrapper->JSValueIsNull(contextJSRef, resultJSRef)) { + JSValue *result = [jscWrapper->JSValue valueWithJSValueRef:resultJSRef inContext:context]; + objcValue = unwrapResult ? [result toObject] : result; + } } - // Looks like making lots of JSC API calls is slower than communicating by using a JSON - // string. Also it ensures that data stuctures don't have cycles and non-serializable fields. - // see [RCTJSCExecutorTests testDeserializationPerf] - id objcValue; - // We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds - // to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster - if (!jscWrapper->JSValueIsNull(contextJSRef, resultJSRef)) { - objcValue = [[jscWrapper->JSValue valueWithJSValueRef:resultJSRef inContext:context] toObject]; - } + RCT_PROFILE_END_EVENT(0, @"js_call", nil); - onComplete(objcValue, nil); - }), 0, @"js_call", (@{@"method": method, @"args": arguments}))]; + onComplete(objcValue, error); + }]; } - (void)executeApplicationScript:(NSData *)script @@ -635,57 +714,93 @@ - (void)executeApplicationScript:(NSData *)script RCTAssertParam(script); RCTAssertParam(sourceURL); - // The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`. - uint32_t magicNumber = NSSwapLittleIntToHost(*((uint32_t *)script.bytes)); - BOOL isRAMBundle = magicNumber == RCTRAMBundleMagicNumber; - if (isRAMBundle) { + BOOL isRAMBundle = NO; + { NSError *error; - script = [self loadRAMBundle:sourceURL error:&error]; - - if (error) { + script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, &error); + if (script == nil) { if (onComplete) { onComplete(error); } return; } + } + + RCTProfileBeginFlowEvent(); + [self executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); + if (!self.isValid) { + return; + } + + if (isRAMBundle) { + registerNativeRequire(self.context.context, self); + } + + NSError *error = executeApplicationScript(script, sourceURL, self->_jscWrapper, self->_performanceLogger, + self->_context.context.JSGlobalContextRef); + if (onComplete) { + onComplete(error); + } + }]; +} + +static NSData *loadPossiblyBundledApplicationScript(NSData *script, NSURL *sourceURL, + RCTPerformanceLogger *performanceLogger, + BOOL &isRAMBundle, RandomAccessBundleData &randomAccessBundle, + NSError **error) +{ + RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / prepare bundle", nil); + + // The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`. + uint32_t magicNumber = 0; + [script getBytes:&magicNumber length:sizeof(magicNumber)]; + isRAMBundle = NSSwapLittleIntToHost(magicNumber) == RCTRAMBundleMagicNumber; + if (isRAMBundle) { + [performanceLogger markStartForTag:RCTPLRAMBundleLoad]; + script = loadRAMBundle(sourceURL, error, randomAccessBundle); + [performanceLogger markStopForTag:RCTPLRAMBundleLoad]; + [performanceLogger setValue:script.length forTag:RCTPLRAMStartupCodeSize]; + + // Reset the counters that the native require implementation uses + [performanceLogger setValue:0 forTag:RCTPLRAMNativeRequires]; + [performanceLogger setValue:0 forTag:RCTPLRAMNativeRequiresCount]; + [performanceLogger setValue:0 forTag:RCTPLRAMNativeRequiresSize]; } else { // JSStringCreateWithUTF8CString expects a null terminated C string. // RAM Bundling already provides a null terminated one. NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1]; - [nullTerminatedScript appendData:script]; [nullTerminatedScript appendBytes:"" length:1]; - script = nullTerminatedScript; } - __weak RCTJSCExecutor *weakSelf = self; - - [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf || !strongSelf.isValid) { - return; - } - - RCTPerformanceLoggerStart(RCTPLScriptExecution); + RCT_PROFILE_END_EVENT(0, @"", nil); + return script; +} - JSValueRef jsError = NULL; - RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper; - JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes); - JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String); - JSValueRef result = jscWrapper->JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, bundleURL, 0, &jsError); - jscWrapper->JSStringRelease(bundleURL); - jscWrapper->JSStringRelease(execJSString); - RCTPerformanceLoggerEnd(RCTPLScriptExecution); +static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor) +{ + __weak RCTJSCExecutor *weakExecutor = executor; + context[@"nativeRequire"] = ^(NSNumber *moduleID) { [weakExecutor _nativeRequire:moduleID]; }; +} - if (onComplete) { - NSError *error; - if (!result) { - error = RCTNSErrorFromJSError(jscWrapper, strongSelf->_context.ctx, jsError); - } - onComplete(error); - } - }), 0, @"js_call", (@{ @"url": sourceURL.absoluteString }))]; +static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJSCWrapper *jscWrapper, + RCTPerformanceLogger *performanceLogger, JSGlobalContextRef ctx) +{ + RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / execute script", + @{ @"url": sourceURL.absoluteString, @"size": @(script.length) }); + [performanceLogger markStartForTag:RCTPLScriptExecution]; + JSValueRef jsError = NULL; + JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes); + JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String); + JSValueRef result = jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError); + jscWrapper->JSStringRelease(bundleURL); + jscWrapper->JSStringRelease(execJSString); + [performanceLogger markStopForTag:RCTPLScriptExecution]; + NSError *error = result ? nil : RCTNSErrorFromJSError(jscWrapper, ctx, jsError); + RCT_PROFILE_END_EVENT(0, @"js_call", nil); + return error; } - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block @@ -715,36 +830,44 @@ - (void)injectJSONText:(NSString *)script } __weak RCTJSCExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ + RCTProfileBeginFlowEvent(); + [self executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); + RCTJSCExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { return; } + RCT_PROFILE_BEGIN_EVENT(0, @"injectJSONText", @{@"objectName": objectName}); RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper; JSStringRef execJSString = jscWrapper->JSStringCreateWithCFString((__bridge CFStringRef)script); - JSValueRef valueToInject = jscWrapper->JSValueMakeFromJSONString(strongSelf->_context.ctx, execJSString); + JSGlobalContextRef ctx = strongSelf->_context.context.JSGlobalContextRef; + JSValueRef valueToInject = jscWrapper->JSValueMakeFromJSONString(ctx, execJSString); jscWrapper->JSStringRelease(execJSString); + NSError *error; if (!valueToInject) { - NSString *errorDesc = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script]; - RCTLogError(@"%@", errorDesc); - - if (onComplete) { - NSError *error = [NSError errorWithDomain:RCTErrorDomain code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; - onComplete(error); + NSString *errorMessage = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script]; + error = [NSError errorWithDomain:RCTErrorDomain code:2 userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; + RCTLogError(@"%@", errorMessage); + } else { + JSObjectRef globalObject = jscWrapper->JSContextGetGlobalObject(ctx); + JSStringRef JSName = jscWrapper->JSStringCreateWithCFString((__bridge CFStringRef)objectName); + JSValueRef jsError = NULL; + jscWrapper->JSObjectSetProperty(ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, &jsError); + jscWrapper->JSStringRelease(JSName); + + if (jsError) { + error = RCTNSErrorFromJSError(jscWrapper, ctx, jsError); } - return; } + RCT_PROFILE_END_EVENT(0, @"js_call,json_call", nil); - JSObjectRef globalObject = jscWrapper->JSContextGetGlobalObject(strongSelf->_context.ctx); - JSStringRef JSName = jscWrapper->JSStringCreateWithCFString((__bridge CFStringRef)objectName); - jscWrapper->JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL); - jscWrapper->JSStringRelease(JSName); if (onComplete) { - onComplete(nil); + onComplete(error); } - }), 0, @"js_call,json_call", (@{@"objectName": objectName}))]; + }]; } static bool readRandomAccessModule(const RandomAccessBundleData &bundleData, size_t offset, size_t size, char *data) @@ -768,55 +891,48 @@ static void executeRandomAccessModule(RCTJSCExecutor *executor, uint32_t moduleI JSStringRef code = jscWrapper->JSStringCreateWithUTF8CString(data.get()); JSValueRef jsError = NULL; JSStringRef sourceURL = jscWrapper->JSStringCreateWithUTF8CString(url); - JSValueRef result = jscWrapper->JSEvaluateScript(executor->_context.ctx, code, NULL, sourceURL, 0, &jsError); + JSGlobalContextRef ctx = executor->_context.context.JSGlobalContextRef; + JSValueRef result = jscWrapper->JSEvaluateScript(ctx, code, NULL, sourceURL, 0, &jsError); jscWrapper->JSStringRelease(code); jscWrapper->JSStringRelease(sourceURL); if (!result) { dispatch_async(dispatch_get_main_queue(), ^{ - RCTFatal(RCTNSErrorFromJSError(jscWrapper, executor->_context.ctx, jsError)); + RCTFatal(RCTNSErrorFromJSError(jscWrapper, ctx, jsError)); [executor invalidate]; }); } } -- (void)registerNativeRequire +- (void)_nativeRequire:(NSNumber *)moduleID { - RCTPerformanceLoggerSet(RCTPLRAMNativeRequires, 0); - RCTPerformanceLoggerSet(RCTPLRAMNativeRequiresCount, 0); - RCTPerformanceLoggerSet(RCTPLRAMNativeRequiresSize, 0); - - __weak RCTJSCExecutor *weakSelf = self; - [self addSynchronousHookWithName:@"nativeRequire" usingBlock:^(NSNumber *moduleID) { - RCTJSCExecutor *strongSelf = weakSelf; - if (!strongSelf || !moduleID) { - return; - } - - RCTPerformanceLoggerAdd(RCTPLRAMNativeRequiresCount, 1); - RCTPerformanceLoggerAppendStart(RCTPLRAMNativeRequires); - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, - [@"nativeRequire_" stringByAppendingFormat:@"%@", moduleID], nil); + if (!moduleID) { + return; + } - const uint32_t ID = [moduleID unsignedIntValue]; + [_performanceLogger addValue:1 forTag:RCTPLRAMNativeRequiresCount]; + [_performanceLogger appendStartForTag:RCTPLRAMNativeRequires]; + RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, + [@"nativeRequire_" stringByAppendingFormat:@"%@", moduleID], nil); - if (ID < strongSelf->_randomAccessBundle.numTableEntries) { - ModuleData *moduleData = &strongSelf->_randomAccessBundle.table[ID]; - const uint32_t size = NSSwapLittleIntToHost(moduleData->size); + const uint32_t ID = [moduleID unsignedIntValue]; - // sparse entry in the table -- module does not exist or is contained in the startup section - if (size == 0) { - return; - } + if (ID < _randomAccessBundle.numTableEntries) { + ModuleData *moduleData = &_randomAccessBundle.table[ID]; + const uint32_t size = NSSwapLittleIntToHost(moduleData->size); - RCTPerformanceLoggerAdd(RCTPLRAMNativeRequiresSize, size); - executeRandomAccessModule(strongSelf, ID, NSSwapLittleIntToHost(moduleData->offset), size); + // sparse entry in the table -- module does not exist or is contained in the startup section + if (size == 0) { + return; } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil); - RCTPerformanceLoggerAppendEnd(RCTPLRAMNativeRequires); - }]; + [_performanceLogger addValue:size forTag:RCTPLRAMNativeRequiresSize]; + executeRandomAccessModule(self, ID, NSSwapLittleIntToHost(moduleData->offset), size); + } + + RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil); + [_performanceLogger appendStopForTag:RCTPLRAMNativeRequires]; } static RandomAccessBundleStartupCode readRAMBundle(file_ptr bundle, RandomAccessBundleData &randomAccessBundle) @@ -856,9 +972,8 @@ static RandomAccessBundleStartupCode readRAMBundle(file_ptr bundle, RandomAccess return {std::move(code), startupCodeSize}; } -- (NSData *)loadRAMBundle:(NSURL *)sourceURL error:(NSError **)error +static NSData *loadRAMBundle(NSURL *sourceURL, NSError **error, RandomAccessBundleData &randomAccessBundle) { - RCTPerformanceLoggerStart(RCTPLRAMBundleLoad); file_ptr bundle(fopen(sourceURL.path.UTF8String, "r"), fclose); if (!bundle) { if (error) { @@ -867,10 +982,7 @@ - (NSData *)loadRAMBundle:(NSURL *)sourceURL error:(NSError **)error return nil; } - [self registerNativeRequire]; - - - auto startupCode = readRAMBundle(std::move(bundle), _randomAccessBundle); + auto startupCode = readRAMBundle(std::move(bundle), randomAccessBundle); if (startupCode.isEmpty()) { if (error) { *error = RCTErrorWithMessage(@"Error loading RAM Bundle"); @@ -878,8 +990,6 @@ - (NSData *)loadRAMBundle:(NSURL *)sourceURL error:(NSError **)error return nil; } - RCTPerformanceLoggerEnd(RCTPLRAMBundleLoad); - RCTPerformanceLoggerSet(RCTPLRAMStartupCodeSize, startupCode.size); return [NSData dataWithBytesNoCopy:startupCode.code.release() length:startupCode.size freeWhenDone:YES]; } @@ -887,9 +997,52 @@ - (NSData *)loadRAMBundle:(NSURL *)sourceURL error:(NSError **)error { if (_jscWrapper->JSGlobalContextSetName != NULL) { JSStringRef JSName = _jscWrapper->JSStringCreateWithCFString((__bridge CFStringRef)name); - _jscWrapper->JSGlobalContextSetName(_context.ctx, JSName); + _jscWrapper->JSGlobalContextSetName(_context.context.JSGlobalContextRef, JSName); _jscWrapper->JSStringRelease(JSName); } } @end + +@implementation RCTJSContextProvider +{ + dispatch_semaphore_t _semaphore; + BOOL _useCustomJSCLibrary; + NSThread *_javaScriptThread; + JSContext *_context; + RCTJSCWrapper *_jscWrapper; +} + +- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary +{ + if (self = [super init]) { + _semaphore = dispatch_semaphore_create(0); + _useCustomJSCLibrary = useCustomJSCLibrary; + _javaScriptThread = newJavaScriptThread(); + [self performSelector:@selector(_createContext) onThread:_javaScriptThread withObject:nil waitUntilDone:NO]; + } + return self; +} + +- (void)_createContext +{ + _jscWrapper = RCTJSCWrapperCreate(_useCustomJSCLibrary); + _context = [_jscWrapper->JSContext new]; + configureCacheOnContext(_context, _jscWrapper); + installBasicSynchronousHooksOnContext(_context); + dispatch_semaphore_signal(_semaphore); +} + +- (RCTJSContextData)data +{ + // Be sure this method is only called once, otherwise it will hang here forever: + dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); + return { + .useCustomJSCLibrary = _useCustomJSCLibrary, + .javaScriptThread = _javaScriptThread, + .context = _context, + .jscWrapper = _jscWrapper, + }; +} + +@end diff --git a/React/Executors/RCTJSCWrapper.mm b/React/Executors/RCTJSCWrapper.mm index eb7696f09c8f8b..222ebfc661b75e 100644 --- a/React/Executors/RCTJSCWrapper.mm +++ b/React/Executors/RCTJSCWrapper.mm @@ -13,7 +13,6 @@ #import <JavaScriptCore/JavaScriptCore.h> #import "RCTLog.h" -#import "RCTPerformanceLogger.h" #include <dlfcn.h> @@ -26,9 +25,7 @@ ofType:nil inDirectory:@"Frameworks/JavaScriptCore.framework"] UTF8String]; if (path) { - RCTPerformanceLoggerStart(RCTPLJSCWrapperOpenLibrary); handler = dlopen(path, RTLD_LAZY); - RCTPerformanceLoggerEnd(RCTPLJSCWrapperOpenLibrary); if (!handler) { RCTLogWarn(@"Can't load custom JSC library: %s", dlerror()); } @@ -69,7 +66,6 @@ static void RCTSetUpCustomLibraryPointers(RCTJSCWrapper *wrapper) return; } - RCTPerformanceLoggerStart(RCTPLJSCWrapperLoadFunctions); wrapper->JSValueToStringCopy = (JSValueToStringCopyFuncType)dlsym(libraryHandle, "JSValueToStringCopy"); wrapper->JSStringCreateWithCFString = (JSStringCreateWithCFStringFuncType)dlsym(libraryHandle, "JSStringCreateWithCFString"); wrapper->JSStringCopyCFString = (JSStringCopyCFStringFuncType)dlsym(libraryHandle, "JSStringCopyCFString"); @@ -90,7 +86,6 @@ static void RCTSetUpCustomLibraryPointers(RCTJSCWrapper *wrapper) wrapper->JSContext = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSContext"); wrapper->JSValue = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSValue"); wrapper->configureJSContextForIOS = (configureJSContextForIOSFuncType)dlsym(libraryHandle, "configureJSContextForIOS"); - RCTPerformanceLoggerEnd(RCTPLJSCWrapperLoadFunctions); } RCTJSCWrapper *RCTJSCWrapperCreate(BOOL useCustomJSC) diff --git a/React/Layout/Layout.h b/React/Layout/Layout.h deleted file mode 100644 index 350308a381508a..00000000000000 --- a/React/Layout/Layout.h +++ /dev/null @@ -1,203 +0,0 @@ -/** - * 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<<c504fae9470271c5617f345667bef8ad>> - - -#ifndef __LAYOUT_H -#define __LAYOUT_H - -#include <math.h> -#ifndef __cplusplus -#include <stdbool.h> -#endif - -// Not defined in MSVC++ -#ifndef NAN -static const unsigned long __nan[2] = {0xffffffff, 0x7fffffff}; -#define NAN (*(const float *)__nan) -#endif - -#define CSS_UNDEFINED NAN - -typedef enum { - CSS_DIRECTION_INHERIT = 0, - CSS_DIRECTION_LTR, - CSS_DIRECTION_RTL -} css_direction_t; - -typedef enum { - CSS_FLEX_DIRECTION_COLUMN = 0, - CSS_FLEX_DIRECTION_COLUMN_REVERSE, - CSS_FLEX_DIRECTION_ROW, - CSS_FLEX_DIRECTION_ROW_REVERSE -} css_flex_direction_t; - -typedef enum { - CSS_JUSTIFY_FLEX_START = 0, - CSS_JUSTIFY_CENTER, - CSS_JUSTIFY_FLEX_END, - CSS_JUSTIFY_SPACE_BETWEEN, - CSS_JUSTIFY_SPACE_AROUND -} css_justify_t; - -typedef enum { - CSS_OVERFLOW_VISIBLE = 0, - CSS_OVERFLOW_HIDDEN -} css_overflow_t; - -// Note: auto is only a valid value for alignSelf. It is NOT a valid value for -// alignItems. -typedef enum { - CSS_ALIGN_AUTO = 0, - CSS_ALIGN_FLEX_START, - CSS_ALIGN_CENTER, - CSS_ALIGN_FLEX_END, - CSS_ALIGN_STRETCH -} css_align_t; - -typedef enum { - CSS_POSITION_RELATIVE = 0, - CSS_POSITION_ABSOLUTE -} css_position_type_t; - -typedef enum { - CSS_NOWRAP = 0, - CSS_WRAP -} css_wrap_type_t; - -// Note: left and top are shared between position[2] and position[4], so -// they have to be before right and bottom. -typedef enum { - CSS_LEFT = 0, - CSS_TOP, - CSS_RIGHT, - CSS_BOTTOM, - CSS_START, - CSS_END, - CSS_POSITION_COUNT -} css_position_t; - -typedef enum { - CSS_MEASURE_MODE_UNDEFINED = 0, - CSS_MEASURE_MODE_EXACTLY, - CSS_MEASURE_MODE_AT_MOST, - CSS_MEASURE_MODE_COUNT -} css_measure_mode_t; - -typedef enum { - CSS_WIDTH = 0, - CSS_HEIGHT -} css_dimension_t; - -typedef struct { - float available_width; - float available_height; - css_measure_mode_t width_measure_mode; - css_measure_mode_t height_measure_mode; - - float computed_width; - float computed_height; -} css_cached_measurement_t; - -enum { - // This value was chosen based on empiracle data. Even the most complicated - // layouts should not require more than 16 entries to fit within the cache. - CSS_MAX_CACHED_RESULT_COUNT = 16 -}; - -typedef struct { - float position[4]; - float dimensions[2]; - css_direction_t direction; - - float flex_basis; - - // Instead of recomputing the entire layout every single time, we - // cache some information to break early when nothing changed - bool should_update; - int generation_count; - css_direction_t last_parent_direction; - - int next_cached_measurements_index; - css_cached_measurement_t cached_measurements[CSS_MAX_CACHED_RESULT_COUNT]; - float measured_dimensions[2]; - - css_cached_measurement_t cached_layout; -} css_layout_t; - -typedef struct { - float dimensions[2]; -} css_dim_t; - -typedef struct { - css_direction_t direction; - css_flex_direction_t flex_direction; - css_justify_t justify_content; - css_align_t align_content; - css_align_t align_items; - css_align_t align_self; - css_position_type_t position_type; - css_wrap_type_t flex_wrap; - css_overflow_t overflow; - float flex; - float margin[6]; - float position[4]; - /** - * You should skip all the rules that contain negative values for the - * following attributes. For example: - * {padding: 10, paddingLeft: -5} - * should output: - * {left: 10 ...} - * the following two are incorrect: - * {left: -5 ...} - * {left: 0 ...} - */ - float padding[6]; - float border[6]; - float dimensions[2]; - float minDimensions[2]; - float maxDimensions[2]; -} css_style_t; - -typedef struct css_node css_node_t; -struct css_node { - css_style_t style; - css_layout_t layout; - int children_count; - int line_index; - - css_node_t* next_child; - - css_dim_t (*measure)(void *context, float width, css_measure_mode_t widthMode, float height, css_measure_mode_t heightMode); - void (*print)(void *context); - struct css_node* (*get_child)(void *context, int i); - bool (*is_dirty)(void *context); - bool (*is_text_node)(void *context); - void *context; -}; - -// Lifecycle of nodes and children -css_node_t *new_css_node(void); -void init_css_node(css_node_t *node); -void free_css_node(css_node_t *node); - -// Print utilities -typedef enum { - CSS_PRINT_LAYOUT = 1, - CSS_PRINT_STYLE = 2, - CSS_PRINT_CHILDREN = 4, -} css_print_options_t; -void print_css_node(css_node_t *node, css_print_options_t options); - -// Function that computes the layout! -void layoutNode(css_node_t *node, float availableWidth, float availableHeight, css_direction_t parentDirection); -bool isUndefined(float value); - -#endif diff --git a/React/Layout/README b/React/Layout/README deleted file mode 100644 index e04447bbc37587..00000000000000 --- a/React/Layout/README +++ /dev/null @@ -1,15 +0,0 @@ -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/383d8a6b3dcbdb978e012e29040e1a43157765c6 - -There is generated code in: - - README (this file) - - fbandroid/java/com/facebook/csslayout - - fbandroid/javatests/com/facebook/csslayout - - fbobjc/Libraries/FBReactKit/js/react-native-github/React/Layout - -The code was generated by running 'make' in the css-layout folder and running: - - scripts/sync-css-layout.sh <pathToGithubRepo> <pathToFbSourceRepo> - diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m index db9716a7eff2b5..1ae390271641a7 100644 --- a/React/Modules/RCTAppState.m +++ b/React/Modules/RCTAppState.m @@ -46,6 +46,11 @@ - (dispatch_queue_t)methodQueue return dispatch_get_main_queue(); } +- (NSDictionary *)constantsToExport +{ + return @{@"initialAppState": RCTCurrentAppBackgroundState()}; +} + #pragma mark - Lifecycle - (NSArray<NSString *> *)supportedEvents diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index f3f6a208a4dc23..6d150d0761d783 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -169,7 +169,7 @@ - (dispatch_queue_t)methodQueue - (void)clearAllData { dispatch_async(RCTGetMethodQueue(), ^{ - [_manifest removeAllObjects]; + [self->_manifest removeAllObjects]; [RCTGetCache() removeAllObjects]; RCTDeleteStorageDirectory(); }); diff --git a/React/Modules/RCTDevLoadingView.m b/React/Modules/RCTDevLoadingView.m index a451f79fce3726..f485124e99b9b0 100644 --- a/React/Modules/RCTDevLoadingView.m +++ b/React/Modules/RCTDevLoadingView.m @@ -55,12 +55,14 @@ - (void)setBridge:(RCTBridge *)bridge selector:@selector(hide) name:RCTJavaScriptDidLoadNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hide) name:RCTJavaScriptDidFailToLoadNotification object:nil]; - [self showWithURL:bridge.bundleURL]; + + if (bridge.loading) { + [self showWithURL:bridge.bundleURL]; + } } RCT_EXPORT_METHOD(showMessage:(NSString *)message color:(UIColor *)color backgroundColor:(UIColor *)backgroundColor) @@ -70,26 +72,26 @@ - (void)setBridge:(RCTBridge *)bridge } dispatch_async(dispatch_get_main_queue(), ^{ - _showDate = [NSDate date]; - if (!_window && !RCTRunningInTestEnvironment()) { + self->_showDate = [NSDate date]; + if (!self->_window && !RCTRunningInTestEnvironment()) { CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; - _window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenWidth, 22)]; - _window.windowLevel = UIWindowLevelStatusBar + 1; + self->_window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenWidth, 22)]; + self->_window.windowLevel = UIWindowLevelStatusBar + 1; // set a root VC so rotation is supported - _window.rootViewController = [UIViewController new]; + self->_window.rootViewController = [UIViewController new]; - _label = [[UILabel alloc] initWithFrame:_window.bounds]; - _label.font = [UIFont systemFontOfSize:12.0]; - _label.textAlignment = NSTextAlignmentCenter; + self->_label = [[UILabel alloc] initWithFrame:self->_window.bounds]; + self->_label.font = [UIFont systemFontOfSize:12.0]; + self->_label.textAlignment = NSTextAlignmentCenter; - [_window addSubview:_label]; + [self->_window addSubview:self->_label]; } - _label.text = message; - _label.textColor = color; - _window.backgroundColor = backgroundColor; - _window.hidden = NO; + self->_label.text = message; + self->_label.textColor = color; + self->_window.backgroundColor = backgroundColor; + self->_window.hidden = NO; }); } @@ -101,18 +103,18 @@ - (void)setBridge:(RCTBridge *)bridge dispatch_async(dispatch_get_main_queue(), ^{ const NSTimeInterval MIN_PRESENTED_TIME = 0.6; - NSTimeInterval presentedTime = [[NSDate date] timeIntervalSinceDate:_showDate]; + NSTimeInterval presentedTime = [[NSDate date] timeIntervalSinceDate:self->_showDate]; NSTimeInterval delay = MAX(0, MIN_PRESENTED_TIME - presentedTime); - CGRect windowFrame = _window.frame; + CGRect windowFrame = self->_window.frame; [UIView animateWithDuration:0.25 delay:delay options:0 animations:^{ - _window.frame = CGRectOffset(windowFrame, 0, -windowFrame.size.height); + self->_window.frame = CGRectOffset(windowFrame, 0, -windowFrame.size.height); } completion:^(__unused BOOL finished) { - _window.frame = windowFrame; - _window.hidden = YES; - _window = nil; + self->_window.frame = windowFrame; + self->_window.hidden = YES; + self->_window = nil; }]; }); } diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index 0b2fdaa9e9faa0..830bcc6c29e421 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -192,12 +192,12 @@ - (instancetype)init static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _executorOverride = [_defaults objectForKey:@"executor-override"]; + self->_executorOverride = [self->_defaults objectForKey:@"executor-override"]; }); // Delay setup until after Bridge init dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf updateSettings:_settings]; + [weakSelf updateSettings:self->_settings]; [weakSelf connectPackager]; }); @@ -389,12 +389,12 @@ - (void)jsLoaded:(NSNotification *)notification dispatch_async(dispatch_get_main_queue(), ^{ // Hit these setters again after bridge has finished loading - self.profilingEnabled = _profilingEnabled; - self.liveReloadEnabled = _liveReloadEnabled; - self.executorClass = _executorClass; + self.profilingEnabled = self->_profilingEnabled; + self.liveReloadEnabled = self->_liveReloadEnabled; + self.executorClass = self->_executorClass; // Inspector can only be shown after JS has loaded - if ([_settings[@"showInspector"] boolValue]) { + if ([self->_settings[@"showInspector"] boolValue]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [self.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; @@ -457,8 +457,8 @@ - (void)addItem:(RCTDevMenuItem *)item if (!jsDebuggingExecutorClass) { [items addObject:[RCTDevMenuItem buttonItemWithTitle:[NSString stringWithFormat:@"%@ Debugger Unavailable", _webSocketExecutorName] handler:^{ UIAlertView *alert = RCTAlertView( - [NSString stringWithFormat:@"%@ Debugger Unavailable", _webSocketExecutorName], - [NSString stringWithFormat:@"You need to include the RCTWebSocket library to enable %@ debugging", _webSocketExecutorName], + [NSString stringWithFormat:@"%@ Debugger Unavailable", self->_webSocketExecutorName], + [NSString stringWithFormat:@"You need to include the RCTWebSocket library to enable %@ debugging", self->_webSocketExecutorName], nil, @"OK", nil); @@ -476,19 +476,28 @@ - (void)addItem:(RCTDevMenuItem *)item if (_liveReloadURL) { NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; [items addObject:[RCTDevMenuItem buttonItemWithTitle:liveReloadTitle handler:^{ - weakSelf.liveReloadEnabled = !_liveReloadEnabled; + __typeof(self) strongSelf = weakSelf; + if (strongSelf) { + strongSelf.liveReloadEnabled = !strongSelf->_liveReloadEnabled; + } }]]; NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Systrace" : @"Start Systrace"; [items addObject:[RCTDevMenuItem buttonItemWithTitle:profilingTitle handler:^{ - weakSelf.profilingEnabled = !_profilingEnabled; + __typeof(self) strongSelf = weakSelf; + if (strongSelf) { + strongSelf.profilingEnabled = !strongSelf->_profilingEnabled; + } }]]; } if ([self hotLoadingAvailable]) { NSString *hotLoadingTitle = _hotLoadingEnabled ? @"Disable Hot Reloading" : @"Enable Hot Reloading"; [items addObject:[RCTDevMenuItem buttonItemWithTitle:hotLoadingTitle handler:^{ - weakSelf.hotLoadingEnabled = !_hotLoadingEnabled; + __typeof(self) strongSelf = weakSelf; + if (strongSelf) { + strongSelf.hotLoadingEnabled = !strongSelf->_hotLoadingEnabled; + } }]]; } @@ -576,7 +585,7 @@ - (void)setProfilingEnabled:(BOOL)enabled [_bridge startProfiling]; } else { [_bridge stopProfiling:^(NSData *logData) { - RCTProfileSendResult(_bridge, @"systrace", logData); + RCTProfileSendResult(self->_bridge, @"systrace", logData); }]; } } diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.h b/React/Modules/RCTI18nManager.h similarity index 59% rename from Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.h rename to React/Modules/RCTI18nManager.h index d5e1c6994e3ac3..5a36d0f2c79a2c 100644 --- a/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.h +++ b/React/Modules/RCTI18nManager.h @@ -7,12 +7,13 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import <Foundation/Foundation.h> +#import "RCTBridgeModule.h" -#import <CocoaAsyncSocket/GCDAsyncSocket.h> - -@interface PFSimpleHTTPServer : NSObject <GCDAsyncSocketDelegate> - -- (instancetype)initWithPort:(NSUInteger)port response:(NSData *)data; +/** + * @experimental + * This is a experimental module for RTL support + * This module bridges the i18n utility from RCTI18nUtil + */ +@interface RCTI18nManager : NSObject <RCTBridgeModule> @end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.h b/React/Modules/RCTI18nManager.m similarity index 50% rename from Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.h rename to React/Modules/RCTI18nManager.m index c8859b2aa81083..32371f229ae7f1 100644 --- a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.h +++ b/React/Modules/RCTI18nManager.m @@ -7,14 +7,23 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import <Foundation/Foundation.h> +#import "RCTI18nManager.h" +#import "RCTI18nUtil.h" -#import <CocoaAsyncSocket/GCDAsyncSocket.h> +@implementation RCTI18nManager -@interface PFPingServer : NSObject <GCDAsyncSocketDelegate> +RCT_EXPORT_MODULE() -- (instancetype)initWithPort:(NSUInteger)port; +RCT_EXPORT_METHOD(allowRTL:(BOOL)value) +{ + [[RCTI18nUtil sharedInstance] setAllowRTL:value]; +} -@property (readonly, nonatomic) NSInteger clientsCount; +- (NSDictionary *)constantsToExport +{ + return @{ + @"isRTL": @([[RCTI18nUtil sharedInstance] isRTL]) + }; +} @end diff --git a/React/Modules/RCTRedBox.h b/React/Modules/RCTRedBox.h index c586091a760cc8..40f5c62d274cd0 100644 --- a/React/Modules/RCTRedBox.h +++ b/React/Modules/RCTRedBox.h @@ -11,9 +11,11 @@ #import "RCTBridge.h" #import "RCTBridgeModule.h" +#import "RCTErrorCustomizer.h" @interface RCTRedBox : NSObject <RCTBridgeModule> +- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer; - (void)showError:(NSError *)error; - (void)showErrorMessage:(NSString *)message; - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details; diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index ab0a985f31904a..400042099d1340 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -12,7 +12,9 @@ #import "RCTBridge.h" #import "RCTConvert.h" #import "RCTDefines.h" +#import "RCTErrorInfo.h" #import "RCTUtils.h" +#import "RCTJSStackFrame.h" #if RCT_DEBUG @@ -20,7 +22,7 @@ @protocol RCTRedBoxWindowActionDelegate <NSObject> -- (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(NSDictionary *)stackFrame; +- (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame; - (void)reloadFromRedBoxWindow:(RCTRedBoxWindow *)redBoxWindow; @end @@ -33,7 +35,7 @@ @implementation RCTRedBoxWindow { UITableView *_stackTraceTableView; NSString *_lastErrorMessage; - NSArray<NSDictionary *> *_lastStackTrace; + NSArray<RCTJSStackFrame *> *_lastStackTrace; } - (instancetype)initWithFrame:(CGRect)frame @@ -110,7 +112,7 @@ - (void)dealloc [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate +- (void)showErrorMessage:(NSString *)message withStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate { // Show if this is a new message, or if we're updating the previous message if ((self.hidden && !isUpdate) || (!self.hidden && isUpdate && [_lastErrorMessage isEqualToString:message])) { @@ -156,9 +158,9 @@ - (void)copyStack fullStackTrace = [NSMutableString string]; } - for (NSDictionary *stackFrame in _lastStackTrace) { - [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame[@"methodName"]]]; - if (stackFrame[@"file"]) { + for (RCTJSStackFrame *stackFrame in _lastStackTrace) { + [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]]; + if (stackFrame.file) { [fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]]; } } @@ -167,15 +169,14 @@ - (void)copyStack [pb setString:fullStackTrace]; } -- (NSString *)formatFrameSource:(NSDictionary *)stackFrame +- (NSString *)formatFrameSource:(RCTJSStackFrame *)stackFrame { NSString *lineInfo = [NSString stringWithFormat:@"%@:%zd", - [stackFrame[@"file"] lastPathComponent], - [stackFrame[@"lineNumber"] integerValue]]; + [stackFrame.file lastPathComponent], + stackFrame.lineNumber]; - NSInteger column = [stackFrame[@"column"] integerValue]; - if (column != 0) { - lineInfo = [lineInfo stringByAppendingFormat:@":%zd", column]; + if (stackFrame.column != 0) { + lineInfo = [lineInfo stringByAppendingFormat:@":%zd", stackFrame.column]; } return lineInfo; } @@ -200,7 +201,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; NSUInteger index = indexPath.row; - NSDictionary *stackFrame = _lastStackTrace[index]; + RCTJSStackFrame *stackFrame = _lastStackTrace[index]; return [self reuseCell:cell forStackFrame:stackFrame]; } @@ -223,7 +224,7 @@ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString return cell; } -- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(NSDictionary *)stackFrame +- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(RCTJSStackFrame *)stackFrame { if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; @@ -238,8 +239,8 @@ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(NSDictiona cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2]; } - cell.textLabel.text = stackFrame[@"methodName"]; - if (stackFrame[@"file"]) { + cell.textLabel.text = stackFrame.methodName; + if (stackFrame.file) { cell.detailTextLabel.text = [self formatFrameSource:stackFrame]; } else { cell.detailTextLabel.text = @""; @@ -266,7 +267,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath { if (indexPath.section == 1) { NSUInteger row = indexPath.row; - NSDictionary *stackFrame = _lastStackTrace[row]; + RCTJSStackFrame *stackFrame = _lastStackTrace[row]; [_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame]; } [tableView deselectRowAtIndexPath:indexPath animated:YES]; @@ -314,12 +315,42 @@ @interface RCTRedBox () <RCTInvalidating, RCTRedBoxWindowActionDelegate> @implementation RCTRedBox { RCTRedBoxWindow *_window; + NSMutableArray<id<RCTErrorCustomizer>> *_errorCustomizers; } @synthesize bridge = _bridge; RCT_EXPORT_MODULE() +- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (!self->_errorCustomizers) { + self->_errorCustomizers = [NSMutableArray array]; + } + if (![self->_errorCustomizers containsObject:errorCustomizer]) { + [self->_errorCustomizers addObject:errorCustomizer]; + } + }); +} + +// WARNING: Should only be called from the main thread/dispatch queue. +- (RCTErrorInfo *)_customizeError:(RCTErrorInfo *)error +{ + RCTAssertMainQueue(); + + if (!self->_errorCustomizers) { + return error; + } + for (id<RCTErrorCustomizer> customizer in self->_errorCustomizers) { + RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error]; + if (newInfo) { + error = newInfo; + } + } + return error; +} + - (void)showError:(NSError *)error { [self showErrorMessage:error.localizedDescription withDetails:error.localizedFailureReason]; @@ -341,9 +372,8 @@ - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details - (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack { - // TODO #11638796: convert rawStack into something useful - message = [NSString stringWithFormat:@"%@\n\n%@", message, rawStack]; - [self showErrorMessage:message withStack:nil isUpdate:NO]; + NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack]; + [self _showErrorMessage:message withStack:stack isUpdate:NO]; } - (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack @@ -357,20 +387,30 @@ - (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary * } - (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate +{ + [self _showErrorMessage:message withStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:isUpdate]; +} + +- (void)_showErrorMessage:(NSString *)message withStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate { dispatch_async(dispatch_get_main_queue(), ^{ - if (!_window) { - _window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - _window.actionDelegate = self; + if (!self->_window) { + self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + self->_window.actionDelegate = self; } - [_window showErrorMessage:message withStack:stack isUpdate:isUpdate]; + RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message + stack:stack]; + errorInfo = [self _customizeError:errorInfo]; + [self->_window showErrorMessage:errorInfo.errorMessage + withStack:errorInfo.stack + isUpdate:isUpdate]; }); } RCT_EXPORT_METHOD(dismiss) { dispatch_async(dispatch_get_main_queue(), ^{ - [_window dismiss]; + [self->_window dismiss]; }); } @@ -379,14 +419,14 @@ - (void)invalidate [self dismiss]; } -- (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(NSDictionary *)stackFrame +- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame { if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) { RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager."); return; } - NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, NULL) dataUsingEncoding:NSUTF8StringEncoding]; + NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding]; NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length]; NSMutableURLRequest *request = [NSMutableURLRequest new]; request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:_bridge.bundleURL]; @@ -398,7 +438,7 @@ - (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(NSD [[[NSURLSession sharedSession] dataTaskWithRequest:request] resume]; } -- (void)reloadFromRedBoxWindow:(RCTRedBoxWindow *)redBoxWindow { +- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow { [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil userInfo:nil]; } @@ -418,13 +458,14 @@ - (RCTRedBox *)redBox @implementation RCTRedBox + (NSString *)moduleName { return nil; } +- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer {} - (void)showError:(NSError *)message {} - (void)showErrorMessage:(NSString *)message {} - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details {} - (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack {} - (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {} - (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {} -- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack showIfHidden:(BOOL)shouldShow {} +- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate {} - (void)dismiss {} @end diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index dd10ca9ca80ee8..2ae7df17d71108 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -15,7 +15,15 @@ #import "RCTLog.h" #import "RCTUtils.h" -@interface RCTTimer : NSObject +static const NSTimeInterval kMinimumSleepInterval = 1; + +// These timing contants should be kept in sync with the ones in `JSTimersExecution.js`. +// The duration of a frame. This assumes that we want to run at 60 fps. +static const NSTimeInterval kFrameDuration = 1.0 / 60.0; +// The minimum time left in a frame to trigger the idle callback. +static const NSTimeInterval kIdleCallbackFrameDeadline = 0.001; + +@interface _RCTTimer : NSObject @property (nonatomic, strong, readonly) NSDate *target; @property (nonatomic, assign, readonly) BOOL repeats; @@ -24,7 +32,7 @@ @interface RCTTimer : NSObject @end -@implementation RCTTimer +@implementation _RCTTimer - (instancetype)initWithCallbackID:(NSNumber *)callbackID interval:(NSTimeInterval)interval @@ -55,9 +63,37 @@ - (BOOL)updateFoundNeedsJSUpdate @end +@interface _RCTTimingProxy : NSObject + +@end + +// NSTimer retains its target, insert this class to break potential retain cycles +@implementation _RCTTimingProxy +{ + __weak id _target; +} + ++ (instancetype)proxyWithTarget:(id)target +{ + _RCTTimingProxy *proxy = [self new]; + if (proxy) { + proxy->_target = target; + } + return proxy; +} + +- (void)timerDidFire +{ + [_target timerDidFire]; +} + +@end + @implementation RCTTiming { - NSMutableDictionary<NSNumber *, RCTTimer *> *_timers; + NSMutableDictionary<NSNumber *, _RCTTimer *> *_timers; + NSTimer *_sleepTimer; + BOOL _sendIdleEvents; } @synthesize bridge = _bridge; @@ -95,6 +131,7 @@ - (void)setBridge:(RCTBridge *)bridge - (void)dealloc { + [_sleepTimer invalidate]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } @@ -111,47 +148,102 @@ - (void)invalidate - (void)stopTimers { - self.paused = YES; + if (!_paused) { + _paused = YES; + if (_pauseCallback) { + _pauseCallback(); + } + } } - (void)startTimers { - if (!_bridge || _timers.count == 0) { + if (!_bridge || ![self hasPendingTimers]) { return; } - self.paused = NO; -} - -- (void)setPaused:(BOOL)paused -{ - if (_paused != paused) { - _paused = paused; + if (_paused) { + _paused = NO; if (_pauseCallback) { _pauseCallback(); } } } +- (BOOL)hasPendingTimers +{ + return _sendIdleEvents || _timers.count > 0; +} + - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update { + NSDate *nextScheduledTarget = [NSDate distantFuture]; NSMutableArray<NSNumber *> *timersToCall = [NSMutableArray new]; - for (RCTTimer *timer in _timers.allValues) { + for (_RCTTimer *timer in _timers.allValues) { if ([timer updateFoundNeedsJSUpdate]) { [timersToCall addObject:timer.callbackID]; } if (!timer.target) { [_timers removeObjectForKey:timer.callbackID]; + } else { + nextScheduledTarget = [nextScheduledTarget earlierDate:timer.target]; } } - // call timers that need to be called + // Call timers that need to be called if (timersToCall.count > 0) { [_bridge enqueueJSCall:@"JSTimersExecution.callTimers" args:@[timersToCall]]; } - if (_timers.count == 0) { - [self stopTimers]; + if (_sendIdleEvents) { + NSTimeInterval frameElapsed = (CACurrentMediaTime() - update.timestamp); + if (kFrameDuration - frameElapsed >= kIdleCallbackFrameDeadline) { + NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970]; + NSNumber *absoluteFrameStartMS = @((currentTimestamp - frameElapsed) * 1000); + [_bridge enqueueJSCall:@"JSTimersExecution.callIdleCallbacks" args:@[absoluteFrameStartMS]]; + } + } + + // Switch to a paused state only if we didn't call any timer this frame, so if + // in response to this timer another timer is scheduled, we don't pause and unpause + // the displaylink frivolously. + if (!_sendIdleEvents && timersToCall.count == 0) { + // No need to call the pauseCallback as RCTDisplayLink will ask us about our paused + // status immediately after completing this call + if (_timers.count == 0) { + _paused = YES; + } + // If the next timer is more than 1 second out, pause and schedule an NSTimer; + else if ([nextScheduledTarget timeIntervalSinceNow] > kMinimumSleepInterval) { + [self scheduleSleepTimer:nextScheduledTarget]; + _paused = YES; + } + } +} + +- (void)scheduleSleepTimer:(NSDate *)sleepTarget +{ + if (!_sleepTimer || !_sleepTimer.valid) { + _sleepTimer = [[NSTimer alloc] initWithFireDate:sleepTarget + interval:0 + target:[_RCTTimingProxy proxyWithTarget:self] + selector:@selector(timerDidFire) + userInfo:nil + repeats:NO]; + [[NSRunLoop currentRunLoop] addTimer:_sleepTimer forMode:NSDefaultRunLoopMode]; + } else { + _sleepTimer.fireDate = [_sleepTimer.fireDate earlierDate:sleepTarget]; + } +} + +- (void)timerDidFire +{ + _sleepTimer = nil; + if (_paused) { + [self startTimers]; + + // Immediately dispatch frame, so we don't have to wait on the displaylink. + [self didUpdateFrame:nil]; } } @@ -180,18 +272,34 @@ - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update jsDuration = 0; } - RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID - interval:jsDuration - targetTime:targetTime - repeats:repeats]; + _RCTTimer *timer = [[_RCTTimer alloc] initWithCallbackID:callbackID + interval:jsDuration + targetTime:targetTime + repeats:repeats]; _timers[callbackID] = timer; - [self startTimers]; + if (_paused) { + if ([timer.target timeIntervalSinceNow] > kMinimumSleepInterval) { + [self scheduleSleepTimer:timer.target]; + } else { + [self startTimers]; + } + } } RCT_EXPORT_METHOD(deleteTimer:(nonnull NSNumber *)timerID) { [_timers removeObjectForKey:timerID]; - if (_timers.count == 0) { + if (![self hasPendingTimers]) { + [self stopTimers]; + } +} + +RCT_EXPORT_METHOD(setSendIdleEvents:(BOOL)sendIdleEvents) +{ + _sendIdleEvents = sendIdleEvents; + if (sendIdleEvents) { + [self startTimers]; + } else if (![self hasPendingTimers]) { [self stopTimers]; } } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 52d9fe0b207821..b551357427d41b 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -11,7 +11,7 @@ #import <AVFoundation/AVFoundation.h> -#import "Layout.h" +#import <CSSLayout/CSSLayout.h> #import "RCTAccessibilityManager.h" #import "RCTAnimationType.h" #import "RCTAssert.h" @@ -206,7 +206,7 @@ @implementation RCTUIManager { // Root views are only mutated on the shadow queue NSMutableSet<NSNumber *> *_rootViewTags; - NSMutableArray<dispatch_block_t> *_pendingUIBlocks; + NSMutableArray<RCTViewManagerUIBlock> *_pendingUIBlocks; // Animation RCTLayoutAnimation *_layoutAnimation; // Main thread only @@ -229,14 +229,10 @@ @implementation RCTUIManager - (void)didReceiveNewContentSizeMultiplier { - __weak RCTUIManager *weakSelf = self; dispatch_async(RCTGetUIManagerQueue(), ^{ - RCTUIManager *strongSelf = weakSelf; - if (strongSelf) { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification - object:strongSelf]; - [strongSelf batchDidComplete]; - } + [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification + object:self]; + [self batchDidComplete]; }); } @@ -271,15 +267,15 @@ - (void)invalidate dispatch_async(dispatch_get_main_queue(), ^{ RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"UIManager invalidate", nil); - for (NSNumber *rootViewTag in _rootViewTags) { - [(id<RCTInvalidating>)_viewRegistry[rootViewTag] invalidate]; + for (NSNumber *rootViewTag in self->_rootViewTags) { + [(id<RCTInvalidating>)self->_viewRegistry[rootViewTag] invalidate]; } - _rootViewTags = nil; - _shadowViewRegistry = nil; - _viewRegistry = nil; - _bridgeTransactionListeners = nil; - _bridge = nil; + self->_rootViewTags = nil; + self->_shadowViewRegistry = nil; + self->_viewRegistry = nil; + self->_bridgeTransactionListeners = nil; + self->_bridge = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"", nil); @@ -383,20 +379,19 @@ - (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSize CGRect frame = rootView.frame; // Register shadow view - __weak RCTUIManager *weakSelf = self; dispatch_async(RCTGetUIManagerQueue(), ^{ - RCTUIManager *strongSelf = weakSelf; - if (!_viewRegistry) { + if (!self->_viewRegistry) { return; } + RCTRootShadowView *shadowView = [RCTRootShadowView new]; shadowView.reactTag = reactTag; shadowView.frame = frame; shadowView.backgroundColor = rootView.backgroundColor; shadowView.viewName = NSStringFromClass([rootView class]); shadowView.sizeFlexibility = sizeFlexibility; - strongSelf->_shadowViewRegistry[shadowView.reactTag] = shadowView; - [strongSelf->_rootViewTags addObject:reactTag]; + self->_shadowViewRegistry[shadowView.reactTag] = shadowView; + [self->_rootViewTags addObject:reactTag]; }); [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification @@ -426,14 +421,11 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view NSNumber *reactTag = view.reactTag; dispatch_async(RCTGetUIManagerQueue(), ^{ - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; + RCTShadowView *shadowView = self->_shadowViewRegistry[reactTag]; RCTAssert(shadowView != nil, @"Could not locate shadow view with tag #%@", reactTag); - BOOL dirtyLayout = NO; - if (!CGRectEqualToRect(frame, shadowView.frame)) { shadowView.frame = frame; - dirtyLayout = YES; } // Trigger re-layout when size flexibility changes, as the root view might grow or @@ -442,14 +434,9 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view RCTRootShadowView *rootShadowView = (RCTRootShadowView *)shadowView; if (rootShadowView.sizeFlexibility != sizeFlexibility) { rootShadowView.sizeFlexibility = sizeFlexibility; - dirtyLayout = YES; + [self batchDidComplete]; } } - - if (dirtyLayout) { - [shadowView dirtyLayout]; - [self batchDidComplete]; - } }); } @@ -459,7 +446,7 @@ - (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view NSNumber *reactTag = view.reactTag; dispatch_async(RCTGetUIManagerQueue(), ^{ - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; + RCTShadowView *shadowView = self->_shadowViewRegistry[reactTag]; RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag); shadowView.intrinsicContentSize = size; @@ -473,14 +460,12 @@ - (void)setBackgroundColor:(UIColor *)color forView:(UIView *)view RCTAssertMainQueue(); NSNumber *reactTag = view.reactTag; - - __weak RCTUIManager *weakSelf = self; dispatch_async(RCTGetUIManagerQueue(), ^{ - RCTUIManager *strongSelf = weakSelf; - if (!_viewRegistry) { + if (!self->_viewRegistry) { return; } - RCTShadowView *shadowView = strongSelf->_shadowViewRegistry[reactTag]; + + RCTShadowView *shadowView = self->_shadowViewRegistry[reactTag]; RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag); shadowView.backgroundColor = color; [self _amendPendingUIBlocksWithStylePropagationUpdateForShadowView:shadowView]; @@ -502,8 +487,8 @@ - (void)_purgeChildren:(NSArray<id<RCTComponent>> *)children } [registry removeObjectForKey:subview.reactTag]; - if (registry == (NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_viewRegistry) { - [_bridgeTransactionListeners removeObject:subview]; + if (registry == (NSMutableDictionary<NSNumber *, id<RCTComponent>> *)self->_viewRegistry) { + [self->_bridgeTransactionListeners removeObject:subview]; } }); } @@ -515,23 +500,11 @@ - (void)addUIBlock:(RCTViewManagerUIBlock)block @"-[RCTUIManager addUIBlock:] should only be called from the " "UIManager's queue (get this using `RCTGetUIManagerQueue()`)"); - if (!block) { + if (!block || !_viewRegistry) { return; } - if (!_viewRegistry) { - return; - } - - __weak RCTUIManager *weakViewManager = self; - dispatch_block_t outerBlock = ^{ - RCTUIManager *strongViewManager = weakViewManager; - if (strongViewManager && strongViewManager->_viewRegistry) { - block(strongViewManager, strongViewManager->_viewRegistry); - } - }; - - [_pendingUIBlocks addObject:outerBlock]; + [_pendingUIBlocks addObject:block]; } - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView @@ -558,11 +531,11 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * } RCTFrameData; // Construct arrays then hand off to main thread - NSInteger count = viewsWithNewFrames.count; + NSUInteger count = viewsWithNewFrames.count; NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count]; NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count]; { - NSInteger index = 0; + NSUInteger index = 0; RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes; for (RCTShadowView *shadowView in viewsWithNewFrames) { reactTags[index] = shadowView.reactTag; @@ -609,7 +582,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * CGSize contentSize = shadowView.frame.size; dispatch_async(dispatch_get_main_queue(), ^{ - UIView *view = _viewRegistry[reactTag]; + UIView *view = self->_viewRegistry[reactTag]; RCTAssert(view != nil, @"view (for ID %@) not found", reactTag); RCTRootView *rootView = (RCTRootView *)[view superview]; @@ -802,7 +775,7 @@ - (void)_removeChildren:(NSArray<UIView *> *)children void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; - [_viewsToBeDeleted removeObject:removedChild]; + [self->_viewsToBeDeleted removeObject:removedChild]; [container removeReactSubview:removedChild]; if (animation.callback && completionsCalled == children.count) { @@ -849,7 +822,7 @@ - (void)_removeChildren:(NSArray<UIView *> *)children UIView *rootView = viewRegistry[rootReactTag]; [uiManager _purgeChildren:(NSArray<id<RCTComponent>> *)rootView.reactSubviews fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry]; - [(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry removeObjectForKey:rootReactTag]; + [(NSMutableDictionary *)viewRegistry removeObjectForKey:rootReactTag]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRemoveRootViewNotification object:uiManager @@ -1163,7 +1136,7 @@ - (void)flushUIBlocks // First copy the previous blocks into a temporary variable, then reset the // pending blocks to a new array. This guards against mutation while // processing the pending blocks in another thread. - NSArray<dispatch_block_t> *previousPendingUIBlocks = _pendingUIBlocks; + NSArray<RCTViewManagerUIBlock> *previousPendingUIBlocks = _pendingUIBlocks; _pendingUIBlocks = [NSMutableArray new]; if (previousPendingUIBlocks.count) { @@ -1173,8 +1146,8 @@ - (void)flushUIBlocks RCTProfileEndFlowEvent(); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[UIManager flushUIBlocks]", nil); @try { - for (dispatch_block_t block in previousPendingUIBlocks) { - block(); + for (RCTViewManagerUIBlock block in previousPendingUIBlocks) { + block(self, self->_viewRegistry); } } @catch (NSException *exception) { diff --git a/React/Profiler/RCTFPSGraph.m b/React/Profiler/RCTFPSGraph.m index 0dbbe9f286fe0b..0b1d1e5ed85ea7 100644 --- a/React/Profiler/RCTFPSGraph.m +++ b/React/Profiler/RCTFPSGraph.m @@ -97,7 +97,7 @@ - (void)onTick:(NSTimeInterval)timestamp _maxFPS = MAX(_maxFPS, _FPS); dispatch_async(dispatch_get_main_queue(), ^{ - _label.text = [NSString stringWithFormat:@"%lu", (unsigned long)_FPS]; + self->_label.text = [NSString stringWithFormat:@"%lu", (unsigned long)self->_FPS]; }); CGFloat scale = 60.0 / _height; diff --git a/React/Profiler/RCTPerfMonitor.m b/React/Profiler/RCTPerfMonitor.m index 947722534f211b..5f2304cba66d1d 100644 --- a/React/Profiler/RCTPerfMonitor.m +++ b/React/Profiler/RCTPerfMonitor.m @@ -329,9 +329,9 @@ - (void)show [self.container addSubview:self.jsGraph]; [self.container addSubview:self.jsGraphLabel]; [executor executeBlockOnJavaScriptQueue:^{ - _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self + self->_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(threadUpdate:)]; - [_jsDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] + [self->_jsDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; }]; } @@ -398,7 +398,7 @@ - (void)redirectLogs const void *buffer, size_t size ) { - write(_stderr, buffer, size); + write(self->_stderr, buffer, size); NSString *log = [[NSString alloc] initWithBytes:buffer length:size @@ -495,8 +495,8 @@ - (void)tap [UIView animateWithDuration:.25 animations:^{ CGRect tmp = self.container.frame; - self.container.frame = _storedMonitorFrame; - _storedMonitorFrame = tmp; + self.container.frame = self->_storedMonitorFrame; + self->_storedMonitorFrame = tmp; }]; } @@ -510,8 +510,9 @@ - (void)loadPerformanceLoggerData { NSUInteger i = 0; NSMutableArray<NSString *> *data = [NSMutableArray new]; - NSArray<NSNumber *> *values = RCTPerformanceLoggerOutput(); - for (NSString *label in RCTPerformanceLoggerLabels()) { + RCTPerformanceLogger *performanceLogger = [_bridge performanceLogger]; + NSArray<NSNumber *> *values = [performanceLogger valuesForTags]; + for (NSString *label in [performanceLogger labelsForTags]) { long long value = values[i+1].longLongValue - values[i].longLongValue; NSString *unit = @"ms"; if ([label hasSuffix:@"Size"]) { diff --git a/React/Profiler/RCTProfile.h b/React/Profiler/RCTProfile.h index fb3cca7e72c7fd..155f8070c2c03c 100644 --- a/React/Profiler/RCTProfile.h +++ b/React/Profiler/RCTProfile.h @@ -132,6 +132,8 @@ RCT_EXTERN void RCTProfileImmediateEvent(uint64_t tag, * self and _cmd to name this event for simplicity sake. * * NOTE: The block can't expect any argument + * + * DEPRECATED: this approach breaks debugging and stepping through instrumented block functions */ #define RCTProfileBlock(block, tag, category, arguments) \ ^{ \ diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 08c7d0ef7cf494..2da301fcc060ec 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -8,8 +8,11 @@ /* Begin PBXBuildFile section */ 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; }; + 008341F61D1DB34400876D9A /* RCTJSStackFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 008341F41D1DB34400876D9A /* RCTJSStackFrame.m */; }; 131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */; }; 131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */; }; + 1321C8D01D3EB50800D58318 /* CSSNodeList.c in Sources */ = {isa = PBXBuildFile; fileRef = 1321C8CE1D3EB50800D58318 /* CSSNodeList.c */; }; + 133683521D37ACA10077D0C3 /* CSSLayout.c in Sources */ = {isa = PBXBuildFile; fileRef = 133683441D37ACA10077D0C3 /* CSSLayout.c */; }; 133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */; }; 13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; }; 13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; }; @@ -78,13 +81,13 @@ 352DCFF01D19F4C20056D623 /* RCTI18nUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DCFEF1D19F4C20056D623 /* RCTI18nUtil.m */; }; 391E86A41C623EC800009732 /* RCTTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 391E86A21C623EC800009732 /* RCTTouchEvent.m */; }; 3D1E68DB1CABD13900DD7465 /* RCTDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D1E68D91CABD13900DD7465 /* RCTDisplayLink.m */; }; + 3EDCA8A51D3591E700450C31 /* RCTErrorInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */; }; 58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; }; 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; }; 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; 68EFE4EE1CF6EB3900A1DE13 /* RCTBundleURLProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; - 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; 83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */; }; 83A1FE8C1B62640A00BE0E65 /* RCTModalHostView.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A1FE8B1B62640A00BE0E65 /* RCTModalHostView.m */; }; 83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */; }; @@ -96,6 +99,7 @@ 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; }; 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; }; 85C199EE1CD2407900DAD810 /* RCTJSCWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 85C199ED1CD2407900DAD810 /* RCTJSCWrapper.mm */; }; + B233E6EA1D2D845D00BC68BA /* RCTI18nManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B233E6E91D2D845D00BC68BA /* RCTI18nManager.m */; }; B95154321D1B34B200FE7B80 /* RCTActivityIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = B95154311D1B34B200FE7B80 /* RCTActivityIndicatorView.m */; }; E9B20B7B1B500126007A2DA7 /* RCTAccessibilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */; }; /* End PBXBuildFile section */ @@ -116,10 +120,18 @@ /* Begin PBXFileReference section */ 000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSourceCode.h; sourceTree = "<group>"; }; 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSourceCode.m; sourceTree = "<group>"; }; + 008341F41D1DB34400876D9A /* RCTJSStackFrame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSStackFrame.m; sourceTree = "<group>"; }; + 008341F51D1DB34400876D9A /* RCTJSStackFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSStackFrame.h; sourceTree = "<group>"; }; + 131541CF1D3E4893006A0E08 /* CSSLayout-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSSLayout-internal.h"; sourceTree = "<group>"; }; 131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControl.h; sourceTree = "<group>"; }; 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControl.m; sourceTree = "<group>"; }; 131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControlManager.h; sourceTree = "<group>"; }; 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControlManager.m; sourceTree = "<group>"; }; + 1321C8CE1D3EB50800D58318 /* CSSNodeList.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CSSNodeList.c; sourceTree = "<group>"; }; + 1321C8CF1D3EB50800D58318 /* CSSNodeList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSNodeList.h; sourceTree = "<group>"; }; + 133683441D37ACA10077D0C3 /* CSSLayout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CSSLayout.c; sourceTree = "<group>"; }; + 133683451D37ACA10077D0C3 /* CSSLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSLayout.h; sourceTree = "<group>"; }; + 133683481D37ACA10077D0C3 /* CSSMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSMacros.h; sourceTree = "<group>"; }; 133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePicker.h; sourceTree = "<group>"; }; 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePicker.m; sourceTree = "<group>"; }; 13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = "<group>"; }; @@ -165,8 +177,6 @@ 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapOverlay.m; sourceTree = "<group>"; }; 13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeMethod.h; sourceTree = "<group>"; }; 13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootViewDelegate.h; sourceTree = "<group>"; }; - 13B07FC71A68125100A75B9A /* Layout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Layout.c; sourceTree = "<group>"; }; - 13B07FC81A68125100A75B9A /* Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Layout.h; sourceTree = "<group>"; }; 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAlertManager.h; sourceTree = "<group>"; }; 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAlertManager.m; sourceTree = "<group>"; }; 13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTExceptionsManager.h; sourceTree = "<group>"; }; @@ -269,6 +279,9 @@ 3D1E68D91CABD13900DD7465 /* RCTDisplayLink.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDisplayLink.m; sourceTree = "<group>"; }; 3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxy.h; sourceTree = "<group>"; }; 3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxyDelegate.h; sourceTree = "<group>"; }; + 3EDCA8A21D3591E700450C31 /* RCTErrorCustomizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTErrorCustomizer.h; sourceTree = "<group>"; }; + 3EDCA8A31D3591E700450C31 /* RCTErrorInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTErrorInfo.h; sourceTree = "<group>"; }; + 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTErrorInfo.m; sourceTree = "<group>"; }; 58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = "<group>"; }; 58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = "<group>"; }; 58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = "<group>"; }; @@ -310,6 +323,8 @@ 85C199EC1CD2407900DAD810 /* RCTJSCWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSCWrapper.h; sourceTree = "<group>"; }; 85C199ED1CD2407900DAD810 /* RCTJSCWrapper.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = RCTJSCWrapper.mm; sourceTree = "<group>"; }; ACDD3FDA1BC7430D00E7DE33 /* RCTBorderStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderStyle.h; sourceTree = "<group>"; }; + B233E6E81D2D843200BC68BA /* RCTI18nManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTI18nManager.h; sourceTree = "<group>"; }; + B233E6E91D2D845D00BC68BA /* RCTI18nManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTI18nManager.m; sourceTree = "<group>"; }; B95154301D1B34B200FE7B80 /* RCTActivityIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTActivityIndicatorView.h; sourceTree = "<group>"; }; B95154311D1B34B200FE7B80 /* RCTActivityIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorView.m; sourceTree = "<group>"; }; E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTTextDecorationLineType.h; sourceTree = "<group>"; }; @@ -328,6 +343,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 133683431D37ACA10077D0C3 /* CSSLayout */ = { + isa = PBXGroup; + children = ( + 131541CF1D3E4893006A0E08 /* CSSLayout-internal.h */, + 1321C8CE1D3EB50800D58318 /* CSSNodeList.c */, + 1321C8CF1D3EB50800D58318 /* CSSNodeList.h */, + 133683441D37ACA10077D0C3 /* CSSLayout.c */, + 133683451D37ACA10077D0C3 /* CSSLayout.h */, + 133683481D37ACA10077D0C3 /* CSSMacros.h */, + ); + path = CSSLayout; + sourceTree = "<group>"; + }; 134FCB381A6E7F0800051CC8 /* Executors */ = { isa = PBXGroup; children = ( @@ -339,15 +367,6 @@ path = Executors; sourceTree = "<group>"; }; - 13B07FC41A68125100A75B9A /* Layout */ = { - isa = PBXGroup; - children = ( - 13B07FC71A68125100A75B9A /* Layout.c */, - 13B07FC81A68125100A75B9A /* Layout.h */, - ); - path = Layout; - sourceTree = "<group>"; - }; 13B07FE01A69315300A75B9A /* Modules */ = { isa = PBXGroup; children = ( @@ -377,6 +396,8 @@ 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */, 352DCFEE1D19F4C20056D623 /* RCTI18nUtil.h */, 352DCFEF1D19F4C20056D623 /* RCTI18nUtil.m */, + B233E6E91D2D845D00BC68BA /* RCTI18nManager.m */, + B233E6E81D2D843200BC68BA /* RCTI18nManager.h */, 13D9FEEC1CDCD93000158BD7 /* RCTKeyboardObserver.h */, 13D9FEED1CDCD93000158BD7 /* RCTKeyboardObserver.m */, 13B07FED1A69327A00A75B9A /* RCTTiming.h */, @@ -532,8 +553,8 @@ isa = PBXGroup; children = ( 83CBBA491A601E3B00E9B192 /* Base */, + 133683431D37ACA10077D0C3 /* CSSLayout */, 134FCB381A6E7F0800051CC8 /* Executors */, - 13B07FC41A68125100A75B9A /* Layout */, 13B07FE01A69315300A75B9A /* Modules */, 1450FF7F1BCFF28A00208362 /* Profiler */, 13B07FF31A6947C200A75B9A /* Views */, @@ -544,6 +565,9 @@ 83CBBA491A601E3B00E9B192 /* Base */ = { isa = PBXGroup; children = ( + 3EDCA8A21D3591E700450C31 /* RCTErrorCustomizer.h */, + 3EDCA8A31D3591E700450C31 /* RCTErrorInfo.h */, + 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */, 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */, 68EFE4EC1CF6EB3000A1DE13 /* RCTBundleURLProvider.h */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, @@ -570,6 +594,8 @@ 83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */, 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */, 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */, + 008341F51D1DB34400876D9A /* RCTJSStackFrame.h */, + 008341F41D1DB34400876D9A /* RCTJSStackFrame.m */, 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */, 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */, 83CBBA4D1A601E3B00E9B192 /* RCTLog.h */, @@ -698,6 +724,7 @@ 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */, 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */, 352DCFF01D19F4C20056D623 /* RCTI18nUtil.m in Sources */, + 008341F61D1DB34400876D9A /* RCTJSStackFrame.m in Sources */, 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */, 131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */, 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */, @@ -706,11 +733,11 @@ 13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */, 13BCE8091C99CB9D00DD7AAD /* RCTRootShadowView.m in Sources */, 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */, + 1321C8D01D3EB50800D58318 /* CSSNodeList.c in Sources */, 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */, 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */, 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, - 832348161A77A5AA00B55238 /* Layout.c in Sources */, 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */, 14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */, 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, @@ -743,12 +770,15 @@ 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */, 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, + 3EDCA8A51D3591E700450C31 /* RCTErrorInfo.m in Sources */, 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */, 13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */, 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */, 13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */, 13D9FEEE1CDCD93000158BD7 /* RCTKeyboardObserver.m in Sources */, + B233E6EA1D2D845D00BC68BA /* RCTI18nManager.m in Sources */, + 133683521D37ACA10077D0C3 /* CSSLayout.c in Sources */, 13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */, 137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */, 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */, @@ -890,6 +920,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)", ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -908,6 +939,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)", ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index d22b41e33c68e5..97da98cc764f7f 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -343,7 +343,7 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent> } [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) { - [self propBlockForKey:key inDictionary:_viewPropBlocks](view, json); + [self propBlockForKey:key inDictionary:self->_viewPropBlocks](view, json); }]; if ([view respondsToSelector:@selector(didSetProps:)]) { @@ -358,7 +358,7 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowV } [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) { - [self propBlockForKey:key inDictionary:_shadowPropBlocks](shadowView, json); + [self propBlockForKey:key inDictionary:self->_shadowPropBlocks](shadowView, json); }]; if ([shadowView respondsToSelector:@selector(didSetProps:)]) { diff --git a/React/Views/RCTMap.m b/React/Views/RCTMap.m index fc071ec024f4a6..d620419dc33848 100644 --- a/React/Views/RCTMap.m +++ b/React/Views/RCTMap.m @@ -61,18 +61,18 @@ - (void)layoutSubviews if (_legalLabel) { dispatch_async(dispatch_get_main_queue(), ^{ - CGRect frame = _legalLabel.frame; - if (_legalLabelInsets.left) { - frame.origin.x = _legalLabelInsets.left; - } else if (_legalLabelInsets.right) { - frame.origin.x = self.frame.size.width - _legalLabelInsets.right - frame.size.width; + CGRect frame = self->_legalLabel.frame; + if (self->_legalLabelInsets.left) { + frame.origin.x = self->_legalLabelInsets.left; + } else if (self->_legalLabelInsets.right) { + frame.origin.x = self.frame.size.width - self->_legalLabelInsets.right - frame.size.width; } - if (_legalLabelInsets.top) { - frame.origin.y = _legalLabelInsets.top; - } else if (_legalLabelInsets.bottom) { - frame.origin.y = self.frame.size.height - _legalLabelInsets.bottom - frame.size.height; + if (self->_legalLabelInsets.top) { + frame.origin.y = self->_legalLabelInsets.top; + } else if (self->_legalLabelInsets.bottom) { + frame.origin.y = self.frame.size.height - self->_legalLabelInsets.bottom - frame.size.height; } - _legalLabel.frame = frame; + self->_legalLabel.frame = frame; }); } } diff --git a/React/Views/RCTMapAnnotation.h b/React/Views/RCTMapAnnotation.h index ec5b4edc32adcf..b273bfb093d0c6 100644 --- a/React/Views/RCTMapAnnotation.h +++ b/React/Views/RCTMapAnnotation.h @@ -17,10 +17,10 @@ @property (nonatomic, assign) BOOL animateDrop; @property (nonatomic, strong) UIColor *tintColor; @property (nonatomic, strong) UIImage *image; -@property (nonatomic, assign) NSInteger viewIndex; -@property (nonatomic, assign) NSInteger leftCalloutViewIndex; -@property (nonatomic, assign) NSInteger rightCalloutViewIndex; -@property (nonatomic, assign) NSInteger detailCalloutViewIndex; +@property (nonatomic, assign) NSUInteger viewIndex; +@property (nonatomic, assign) NSUInteger leftCalloutViewIndex; +@property (nonatomic, assign) NSUInteger rightCalloutViewIndex; +@property (nonatomic, assign) NSUInteger detailCalloutViewIndex; @property (nonatomic, assign) BOOL draggable; @end diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m index 3dce5ed759910b..5b2f954ef0c863 100644 --- a/React/Views/RCTModalHostView.m +++ b/React/Views/RCTModalHostView.m @@ -101,8 +101,8 @@ - (void)didMoveToWindow _modalViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; } [self.reactViewController presentViewController:_modalViewController animated:[self hasAnimationType] completion:^{ - if (_onShow) { - _onShow(nil); + if (self->_onShow) { + self->_onShow(nil); } }]; _isPresented = YES; diff --git a/React/Views/RCTNavigator.m b/React/Views/RCTNavigator.m index eb19b1f6c36579..5a987e63aa75f1 100644 --- a/React/Views/RCTNavigator.m +++ b/React/Views/RCTNavigator.m @@ -370,7 +370,7 @@ - (UIViewController *)reactViewController return _navigationController; } -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +- (BOOL)gestureRecognizerShouldBegin:(__unused UIGestureRecognizer *)gestureRecognizer { return _navigationController.viewControllers.count > 1; } @@ -395,24 +395,24 @@ - (void)navigationController:(UINavigationController *)navigationController (RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextToViewControllerKey]; // This may be triggered by a navigation controller unrelated to me: if so, ignore. - if (fromController.navigationController != _navigationController || - toController.navigationController != _navigationController) { + if (fromController.navigationController != self->_navigationController || + toController.navigationController != self->_navigationController) { return; } NSUInteger indexOfFrom = [self.reactSubviews indexOfObject:fromController.navItem]; NSUInteger indexOfTo = [self.reactSubviews indexOfObject:toController.navItem]; CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0; - _dummyView.frame = (CGRect){{destination, 0}, CGSizeZero}; - _currentlyTransitioningFrom = indexOfFrom; - _currentlyTransitioningTo = indexOfTo; + self->_dummyView.frame = (CGRect){{destination, 0}, CGSizeZero}; + self->_currentlyTransitioningFrom = indexOfFrom; + self->_currentlyTransitioningTo = indexOfTo; self.paused = NO; } completion:^(__unused id<UIViewControllerTransitionCoordinatorContext> context) { [weakSelf freeLock]; - _currentlyTransitioningFrom = 0; - _currentlyTransitioningTo = 0; - _dummyView.frame = CGRectZero; + self->_currentlyTransitioningFrom = 0; + self->_currentlyTransitioningTo = 0; + self->_dummyView.frame = CGRectZero; self.paused = YES; // Reset the parallel position tracker }]; diff --git a/React/Views/RCTRootShadowView.m b/React/Views/RCTRootShadowView.m index e012358966aa37..c3efd53d35edc2 100644 --- a/React/Views/RCTRootShadowView.m +++ b/React/Views/RCTRootShadowView.m @@ -21,7 +21,7 @@ - (instancetype)init self = [super init]; if (self) { if ([[RCTI18nUtil sharedInstance] isRTL]) { - self.cssNode->style.direction = CSS_DIRECTION_RTL; + CSSNodeStyleSetDirection(self.cssNode, CSSDirectionRTL); } } return self; @@ -33,14 +33,14 @@ - (void)applySizeConstraints case RCTRootViewSizeFlexibilityNone: break; case RCTRootViewSizeFlexibilityWidth: - self.cssNode->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; + CSSNodeStyleSetWidth(self.cssNode, CSSUndefined); break; case RCTRootViewSizeFlexibilityHeight: - self.cssNode->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + CSSNodeStyleSetHeight(self.cssNode, CSSUndefined); break; case RCTRootViewSizeFlexibilityWidthAndHeight: - self.cssNode->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; - self.cssNode->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + CSSNodeStyleSetWidth(self.cssNode, CSSUndefined); + CSSNodeStyleSetHeight(self.cssNode, CSSUndefined); break; } } @@ -49,8 +49,7 @@ - (void)applySizeConstraints { [self applySizeConstraints]; - [self fillCSSNode:self.cssNode]; - layoutNode(self.cssNode, CSS_UNDEFINED, CSS_UNDEFINED, CSS_DIRECTION_INHERIT); + CSSNodeCalculateLayout(self.cssNode, CSSUndefined, CSSUndefined, CSSDirectionInherit); NSMutableSet<RCTShadowView *> *viewsWithNewFrame = [NSMutableSet set]; [self applyLayoutNode:self.cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero]; diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index e65e900f2eb255..1f0de277630a7a 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -383,8 +383,6 @@ @implementation RCTScrollView NSHashTable *_scrollListeners; } -@synthesize nativeScrollDelegate = _nativeScrollDelegate; - - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { RCTAssertParam(eventDispatcher); @@ -528,6 +526,10 @@ - (void)updateClippedSubviews - (void)setContentInset:(UIEdgeInsets)contentInset { + if (UIEdgeInsetsEqualToEdgeInsets(contentInset, _contentInset)) { + return; + } + CGPoint contentOffset = _scrollView.contentOffset; _contentInset = contentInset; @@ -588,19 +590,6 @@ - (void)delegateMethod:(UIScrollView *)scrollView \ RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin) RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll) -- (void)setNativeScrollDelegate:(NSObject<UIScrollViewDelegate> *)nativeScrollDelegate -{ - if (nativeScrollDelegate != _nativeScrollDelegate) { - if (_nativeScrollDelegate) { - [_scrollListeners removeObject:_nativeScrollDelegate]; - } - if (nativeScrollDelegate) { - [_scrollListeners addObject:nativeScrollDelegate]; - } - _nativeScrollDelegate = nativeScrollDelegate; - } -} - - (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener { [_scrollListeners addObject:scrollListener]; @@ -608,11 +597,7 @@ - (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener - (void)removeScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener { - if (scrollListener == _nativeScrollDelegate) { - [self setNativeScrollDelegate:nil]; - } else { - [_scrollListeners removeObject:scrollListener]; - } + [_scrollListeners removeObject:scrollListener]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView @@ -653,12 +638,12 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView // Check if new or changed CGRect newFrame = subview.frame; BOOL frameChanged = NO; - if (_cachedChildFrames.count <= idx) { + if (self->_cachedChildFrames.count <= idx) { frameChanged = YES; - [_cachedChildFrames addObject:[NSValue valueWithCGRect:newFrame]]; - } else if (!CGRectEqualToRect(newFrame, [_cachedChildFrames[idx] CGRectValue])) { + [self->_cachedChildFrames addObject:[NSValue valueWithCGRect:newFrame]]; + } else if (!CGRectEqualToRect(newFrame, [self->_cachedChildFrames[idx] CGRectValue])) { frameChanged = YES; - _cachedChildFrames[idx] = [NSValue valueWithCGRect:newFrame]; + self->_cachedChildFrames[idx] = [NSValue valueWithCGRect:newFrame]; } // Create JS frame object diff --git a/React/Views/RCTScrollableProtocol.h b/React/Views/RCTScrollableProtocol.h index 61762217350ecd..90c14ebd3cc39f 100644 --- a/React/Views/RCTScrollableProtocol.h +++ b/React/Views/RCTScrollableProtocol.h @@ -15,10 +15,6 @@ */ @protocol RCTScrollableProtocol -/* - * The nativeScrollDelegate property is now deprecated please use the scrollListener API instead - */ -@property (nonatomic, weak) NSObject<UIScrollViewDelegate> *nativeScrollDelegate DEPRECATED_ATTRIBUTE; @property (nonatomic, readonly) CGSize contentSize; - (void)scrollToOffset:(CGPoint)offset; diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index a7c1e9410ddee5..58c9dbbf7d7a6d 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -9,7 +9,7 @@ #import <UIKit/UIKit.h> -#import "Layout.h" +#import <CSSLayout/CSSLayout.h> #import "RCTComponent.h" #import "RCTRootView.h" @@ -44,10 +44,9 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry - (void)removeReactSubview:(RCTShadowView *)subview NS_REQUIRES_SUPER; @property (nonatomic, weak, readonly) RCTShadowView *superview; -@property (nonatomic, assign, readonly) css_node_t *cssNode; +@property (nonatomic, assign, readonly) CSSNodeRef cssNode; @property (nonatomic, copy) NSString *viewName; @property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children -@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle; @property (nonatomic, copy) RCTDirectEventBlock onLayout; /** @@ -127,12 +126,12 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry /** * Flexbox properties. All zero/disabled by default */ -@property (nonatomic, assign) css_flex_direction_t flexDirection; -@property (nonatomic, assign) css_justify_t justifyContent; -@property (nonatomic, assign) css_align_t alignSelf; -@property (nonatomic, assign) css_align_t alignItems; -@property (nonatomic, assign) css_position_type_t position; -@property (nonatomic, assign) css_wrap_type_t flexWrap; +@property (nonatomic, assign) CSSFlexDirection flexDirection; +@property (nonatomic, assign) CSSJustify justifyContent; +@property (nonatomic, assign) CSSAlign alignSelf; +@property (nonatomic, assign) CSSAlign alignItems; +@property (nonatomic, assign) CSSPositionType position; +@property (nonatomic, assign) CSSWrapType flexWrap; @property (nonatomic, assign) CGFloat flex; /** @@ -172,23 +171,23 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry * is split into two methods so subclasses can override `applyLayoutToChildren:` * while using default implementation of `applyLayoutNode:`. */ -- (void)applyLayoutNode:(css_node_t *)node +- (void)applyLayoutNode:(CSSNodeRef)node viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER; /** * Enumerate the child nodes and tell them to apply layout. */ -- (void)applyLayoutToChildren:(css_node_t *)node +- (void)applyLayoutToChildren:(CSSNodeRef)node viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition; /** - * The following are implementation details exposed to subclasses. Do not call them directly + * Return whether or not this node acts as a leaf node in the eyes of CSSLayout. For example + * RCTShadowText has children which it does not want CSSLayout to lay out so in the eyes of + * CSSLayout it is a leaf node. */ -- (void)fillCSSNode:(css_node_t *)node NS_REQUIRES_SUPER; -- (void)dirtyLayout NS_REQUIRES_SUPER; -- (BOOL)isLayoutDirty; +- (BOOL)isCSSLeafNode; - (void)dirtyPropagation NS_REQUIRES_SUPER; - (BOOL)isPropagationDirty; diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index a15e0e7ada57d7..eafd667ae7d184 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -48,7 +48,7 @@ @implementation RCTShadowView @synthesize reactTag = _reactTag; -// css_node api +// cssNode api static void RCTPrint(void *context) { @@ -56,43 +56,53 @@ static void RCTPrint(void *context) printf("%s(%zd), ", shadowView.viewName.UTF8String, shadowView.reactTag.integerValue); } -static css_node_t *RCTGetChild(void *context, int i) -{ - RCTShadowView *shadowView = (__bridge RCTShadowView *)context; - RCTShadowView *child = [shadowView reactSubviews][i]; - return child->_cssNode; -} - -static bool RCTIsDirty(void *context) -{ - RCTShadowView *shadowView = (__bridge RCTShadowView *)context; - return [shadowView isLayoutDirty]; -} - // Enforces precedence rules, e.g. marginLeft > marginHorizontal > margin. -static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float style[CSS_POSITION_COUNT]) { - style[CSS_LEFT] = !isUndefined(metaProps[META_PROP_LEFT]) ? metaProps[META_PROP_LEFT] - : !isUndefined(metaProps[META_PROP_HORIZONTAL]) ? metaProps[META_PROP_HORIZONTAL] - : !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL] - : 0; - style[CSS_RIGHT] = !isUndefined(metaProps[META_PROP_RIGHT]) ? metaProps[META_PROP_RIGHT] - : !isUndefined(metaProps[META_PROP_HORIZONTAL]) ? metaProps[META_PROP_HORIZONTAL] - : !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL] - : 0; - style[CSS_TOP] = !isUndefined(metaProps[META_PROP_TOP]) ? metaProps[META_PROP_TOP] - : !isUndefined(metaProps[META_PROP_VERTICAL]) ? metaProps[META_PROP_VERTICAL] - : !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL] - : 0; - style[CSS_BOTTOM] = !isUndefined(metaProps[META_PROP_BOTTOM]) ? metaProps[META_PROP_BOTTOM] - : !isUndefined(metaProps[META_PROP_VERTICAL]) ? metaProps[META_PROP_VERTICAL] - : !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL] - : 0; -} - -- (void)fillCSSNode:(css_node_t *)node -{ - node->children_count = (int)_reactSubviews.count; -} +#define DEFINE_PROCESS_META_PROPS(type) \ +static void RCTProcessMetaProps##type(const float metaProps[META_PROP_COUNT], CSSNodeRef node) { \ + if (!isUndefined(metaProps[META_PROP_LEFT])) { \ + CSSNodeStyleSet##type##Left(node, metaProps[META_PROP_LEFT]); \ + } else if (!isUndefined(metaProps[META_PROP_HORIZONTAL])) { \ + CSSNodeStyleSet##type##Left(node, metaProps[META_PROP_HORIZONTAL]); \ + } else if (!isUndefined(metaProps[META_PROP_ALL])) { \ + CSSNodeStyleSet##type##Left(node, metaProps[META_PROP_ALL]); \ + } else { \ + CSSNodeStyleSet##type##Left(node, 0); \ + } \ + \ + if (!isUndefined(metaProps[META_PROP_RIGHT])) { \ + CSSNodeStyleSet##type##Right(node, metaProps[META_PROP_RIGHT]); \ + } else if (!isUndefined(metaProps[META_PROP_HORIZONTAL])) { \ + CSSNodeStyleSet##type##Right(node, metaProps[META_PROP_HORIZONTAL]); \ + } else if (!isUndefined(metaProps[META_PROP_ALL])) { \ + CSSNodeStyleSet##type##Right(node, metaProps[META_PROP_ALL]); \ + } else { \ + CSSNodeStyleSet##type##Right(node, 0); \ + } \ + \ + if (!isUndefined(metaProps[META_PROP_TOP])) { \ + CSSNodeStyleSet##type##Top(node, metaProps[META_PROP_TOP]); \ + } else if (!isUndefined(metaProps[META_PROP_VERTICAL])) { \ + CSSNodeStyleSet##type##Top(node, metaProps[META_PROP_VERTICAL]); \ + } else if (!isUndefined(metaProps[META_PROP_ALL])) { \ + CSSNodeStyleSet##type##Top(node, metaProps[META_PROP_ALL]); \ + } else { \ + CSSNodeStyleSet##type##Top(node, 0); \ + } \ + \ + if (!isUndefined(metaProps[META_PROP_BOTTOM])) { \ + CSSNodeStyleSet##type##Bottom(node, metaProps[META_PROP_BOTTOM]); \ + } else if (!isUndefined(metaProps[META_PROP_VERTICAL])) { \ + CSSNodeStyleSet##type##Bottom(node, metaProps[META_PROP_VERTICAL]); \ + } else if (!isUndefined(metaProps[META_PROP_ALL])) { \ + CSSNodeStyleSet##type##Bottom(node, metaProps[META_PROP_ALL]); \ + } else { \ + CSSNodeStyleSet##type##Bottom(node, 0); \ + } \ +} + +DEFINE_PROCESS_META_PROPS(Padding); +DEFINE_PROCESS_META_PROPS(Margin); +DEFINE_PROCESS_META_PROPS(Border); // The absolute stuff is so that we can take into account our absolute position when rounding in order to // snap to the pixel grid. For example, say you have the following structure: @@ -123,29 +133,28 @@ - (void)fillCSSNode:(css_node_t *)node // width = 213.5 - 106.5 = 107 // You'll notice that this is the same width we calculated for the parent view because we've taken its position into account. -- (void)applyLayoutNode:(css_node_t *)node +- (void)applyLayoutNode:(CSSNodeRef)node viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition { - if (!node->layout.should_update) { + if (!CSSNodeGetShouldUpdate(node)) { return; } - node->layout.should_update = false; - _layoutLifecycle = RCTUpdateLifecycleComputed; + CSSNodeSetShouldUpdate(node, false); CGPoint absoluteTopLeft = { - absolutePosition.x + node->layout.position[CSS_LEFT], - absolutePosition.y + node->layout.position[CSS_TOP] + absolutePosition.x + CSSNodeLayoutGetLeft(node), + absolutePosition.y + CSSNodeLayoutGetTop(node) }; CGPoint absoluteBottomRight = { - absolutePosition.x + node->layout.position[CSS_LEFT] + node->layout.dimensions[CSS_WIDTH], - absolutePosition.y + node->layout.position[CSS_TOP] + node->layout.dimensions[CSS_HEIGHT] + absolutePosition.x + CSSNodeLayoutGetLeft(node) + CSSNodeLayoutGetWidth(node), + absolutePosition.y + CSSNodeLayoutGetTop(node) + CSSNodeLayoutGetHeight(node) }; CGRect frame = {{ - RCTRoundPixelValue(node->layout.position[CSS_LEFT]), - RCTRoundPixelValue(node->layout.position[CSS_TOP]), + RCTRoundPixelValue(CSSNodeLayoutGetLeft(node)), + RCTRoundPixelValue(CSSNodeLayoutGetTop(node)), }, { RCTRoundPixelValue(absoluteBottomRight.x - absoluteTopLeft.x), RCTRoundPixelValue(absoluteBottomRight.y - absoluteTopLeft.y) @@ -156,19 +165,19 @@ - (void)applyLayoutNode:(css_node_t *)node [viewsWithNewFrame addObject:self]; } - absolutePosition.x += node->layout.position[CSS_LEFT]; - absolutePosition.y += node->layout.position[CSS_TOP]; + absolutePosition.x += CSSNodeLayoutGetLeft(node); + absolutePosition.y += CSSNodeLayoutGetTop(node); [self applyLayoutToChildren:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; } -- (void)applyLayoutToChildren:(css_node_t *)node +- (void)applyLayoutToChildren:(CSSNodeRef)node viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition { - for (int i = 0; i < node->children_count; ++i) { + for (unsigned int i = 0; i < CSSNodeChildCount(node); ++i) { RCTShadowView *child = (RCTShadowView *)_reactSubviews[i]; - [child applyLayoutNode:node->get_child(node->context, i) + [child applyLayoutNode:CSSNodeGetChild(node, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; } @@ -185,7 +194,7 @@ - (void)applyLayoutToChildren:(css_node_t *)node _didUpdateSubviews = NO; [self didUpdateReactSubviews]; [applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) { - UIView *view = viewRegistry[_reactTag]; + UIView *view = viewRegistry[self->_reactTag]; [view clearSortedSubviews]; [view didUpdateReactSubviews]; }]; @@ -195,7 +204,7 @@ - (void)applyLayoutToChildren:(css_node_t *)node UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp]; if (parentBackgroundColor) { [applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) { - UIView *view = viewRegistry[_reactTag]; + UIView *view = viewRegistry[self->_reactTag]; [view reactSetInheritedBackgroundColor:parentBackgroundColor]; }]; } @@ -242,19 +251,14 @@ - (void)collectUpdatedFrames:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame } if (!CGRectEqualToRect(frame, _frame)) { - _cssNode->style.position_type = CSS_POSITION_ABSOLUTE; - _cssNode->style.dimensions[CSS_WIDTH] = frame.size.width; - _cssNode->style.dimensions[CSS_HEIGHT] = frame.size.height; - _cssNode->style.position[CSS_LEFT] = frame.origin.x; - _cssNode->style.position[CSS_TOP] = frame.origin.y; - // Our parent has asked us to change our cssNode->styles. Dirty the layout - // so that we can rerun layout on this node. The request came from our parent - // so there's no need to dirty our ancestors by calling dirtyLayout. - _layoutLifecycle = RCTUpdateLifecycleDirtied; + CSSNodeStyleSetPositionType(_cssNode, CSSPositionTypeAbsolute); + CSSNodeStyleSetWidth(_cssNode, frame.size.width); + CSSNodeStyleSetHeight(_cssNode, frame.size.height); + CSSNodeStyleSetPositionLeft(_cssNode, frame.origin.x); + CSSNodeStyleSetPositionTop(_cssNode, frame.origin.y); } - [self fillCSSNode:_cssNode]; - layoutNode(_cssNode, frame.size.width, frame.size.height, CSS_DIRECTION_INHERIT); + CSSNodeCalculateLayout(_cssNode, frame.size.width, frame.size.height, CSSDirectionInherit); [self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; } @@ -279,27 +283,23 @@ - (instancetype)init { if ((self = [super init])) { - _frame = CGRectMake(0, 0, CSS_UNDEFINED, CSS_UNDEFINED); + _frame = CGRectMake(0, 0, CSSUndefined, CSSUndefined); for (unsigned int ii = 0; ii < META_PROP_COUNT; ii++) { - _paddingMetaProps[ii] = CSS_UNDEFINED; - _marginMetaProps[ii] = CSS_UNDEFINED; - _borderMetaProps[ii] = CSS_UNDEFINED; + _paddingMetaProps[ii] = CSSUndefined; + _marginMetaProps[ii] = CSSUndefined; + _borderMetaProps[ii] = CSSUndefined; } _newView = YES; - _layoutLifecycle = RCTUpdateLifecycleUninitialized; _propagationLifecycle = RCTUpdateLifecycleUninitialized; _textLifecycle = RCTUpdateLifecycleUninitialized; _reactSubviews = [NSMutableArray array]; - _cssNode = new_css_node(); - _cssNode->context = (__bridge void *)self; - _cssNode->print = RCTPrint; - _cssNode->get_child = RCTGetChild; - _cssNode->is_dirty = RCTIsDirty; - [self fillCSSNode:_cssNode]; + _cssNode = CSSNodeNew(); + CSSNodeSetContext(_cssNode, (__bridge void *)self); + CSSNodeSetPrintFunc(_cssNode, RCTPrint); } return self; } @@ -311,20 +311,12 @@ - (BOOL)isReactRootView - (void)dealloc { - free_css_node(_cssNode); -} - -- (void)dirtyLayout -{ - if (_layoutLifecycle != RCTUpdateLifecycleDirtied) { - _layoutLifecycle = RCTUpdateLifecycleDirtied; - [_superview dirtyLayout]; - } + CSSNodeFree(_cssNode); } -- (BOOL)isLayoutDirty +- (BOOL)isCSSLeafNode { - return _layoutLifecycle != RCTUpdateLifecycleComputed; + return NO; } - (void)dirtyPropagation @@ -361,23 +353,25 @@ - (void)setTextComputed - (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex { [_reactSubviews insertObject:subview atIndex:atIndex]; - _cssNode->children_count = (int)_reactSubviews.count; + if (![self isCSSLeafNode]) { + CSSNodeInsertChild(_cssNode, subview.cssNode, atIndex); + } subview->_superview = self; _didUpdateSubviews = YES; [self dirtyText]; - [self dirtyLayout]; [self dirtyPropagation]; } - (void)removeReactSubview:(RCTShadowView *)subview { [subview dirtyText]; - [subview dirtyLayout]; [subview dirtyPropagation]; _didUpdateSubviews = YES; subview->_superview = nil; [_reactSubviews removeObject:subview]; - _cssNode->children_count = (int)_reactSubviews.count; + if (![self isCSSLeafNode]) { + CSSNodeRemoveChild(_cssNode, subview.cssNode); + } } - (NSArray<RCTShadowView *> *)reactSubviews @@ -477,10 +471,10 @@ - (CGFloat)padding##prop \ - (UIEdgeInsets)paddingAsInsets { return (UIEdgeInsets){ - _cssNode->style.padding[CSS_TOP], - _cssNode->style.padding[CSS_LEFT], - _cssNode->style.padding[CSS_BOTTOM], - _cssNode->style.padding[CSS_RIGHT] + CSSNodeStyleGetPaddingTop(_cssNode), + CSSNodeStyleGetPaddingLeft(_cssNode), + CSSNodeStyleGetPaddingBottom(_cssNode), + CSSNodeStyleGetPaddingRight(_cssNode) }; } @@ -506,79 +500,75 @@ - (CGFloat)border##prop##Width \ // Dimensions -#define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp, category) \ +#define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \ - (void)set##setProp:(CGFloat)value \ { \ - _cssNode->style.category[CSS_##cssProp] = value; \ - [self dirtyLayout]; \ + CSSNodeStyleSet##cssProp(_cssNode, value); \ [self dirtyText]; \ } \ - (CGFloat)getProp \ { \ - return _cssNode->style.category[CSS_##cssProp]; \ + return CSSNodeStyleGet##cssProp(_cssNode); \ } -RCT_DIMENSION_PROPERTY(Width, width, WIDTH, dimensions) -RCT_DIMENSION_PROPERTY(Height, height, HEIGHT, dimensions) - -RCT_DIMENSION_PROPERTY(MinWidth, minWidth, WIDTH, minDimensions) -RCT_DIMENSION_PROPERTY(MaxWidth, maxWidth, WIDTH, maxDimensions) -RCT_DIMENSION_PROPERTY(MinHeight, minHeight, HEIGHT, minDimensions) -RCT_DIMENSION_PROPERTY(MaxHeight, maxHeight, HEIGHT, maxDimensions) +RCT_DIMENSION_PROPERTY(Width, width, Width) +RCT_DIMENSION_PROPERTY(Height, height, Height) +RCT_DIMENSION_PROPERTY(MinWidth, minWidth, MinWidth) +RCT_DIMENSION_PROPERTY(MinHeight, minHeight, MinHeight) +RCT_DIMENSION_PROPERTY(MaxWidth, maxWidth, MaxWidth) +RCT_DIMENSION_PROPERTY(MaxHeight, maxHeight, MaxHeight) // Position -#define RCT_POSITION_PROPERTY(setProp, getProp, cssProp) \ -RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp, position) - -RCT_POSITION_PROPERTY(Top, top, TOP) -RCT_POSITION_PROPERTY(Right, right, RIGHT) -RCT_POSITION_PROPERTY(Bottom, bottom, BOTTOM) -RCT_POSITION_PROPERTY(Left, left, LEFT) +RCT_DIMENSION_PROPERTY(Top, top, PositionTop) +RCT_DIMENSION_PROPERTY(Right, right, PositionRight) +RCT_DIMENSION_PROPERTY(Bottom, bottom, PositionBottom) +RCT_DIMENSION_PROPERTY(Left, left, PositionLeft) - (void)setFrame:(CGRect)frame { - _cssNode->style.position[CSS_LEFT] = CGRectGetMinX(frame); - _cssNode->style.position[CSS_TOP] = CGRectGetMinY(frame); - _cssNode->style.dimensions[CSS_WIDTH] = CGRectGetWidth(frame); - _cssNode->style.dimensions[CSS_HEIGHT] = CGRectGetHeight(frame); - [self dirtyLayout]; -} - -static inline BOOL RCTAssignSuggestedDimension(css_node_t *css_node, int dimension, CGFloat amount) -{ - if (amount != UIViewNoIntrinsicMetric - && isnan(css_node->style.dimensions[dimension])) { - css_node->style.dimensions[dimension] = amount; - return YES; + CSSNodeStyleSetPositionLeft(_cssNode, CGRectGetMinX(frame)); + CSSNodeStyleSetPositionTop(_cssNode, CGRectGetMinY(frame)); + CSSNodeStyleSetWidth(_cssNode, CGRectGetWidth(frame)); + CSSNodeStyleSetHeight(_cssNode, CGRectGetHeight(frame)); +} + +static inline void RCTAssignSuggestedDimension(CSSNodeRef cssNode, CSSDimension dimension, CGFloat amount) +{ + if (amount != UIViewNoIntrinsicMetric) { + switch (dimension) { + case CSSDimensionWidth: + if (isnan(CSSNodeStyleGetWidth(cssNode))) { + CSSNodeStyleSetWidth(cssNode, amount); + } + break; + case CSSDimensionHeight: + if (isnan(CSSNodeStyleGetHeight(cssNode))) { + CSSNodeStyleSetHeight(cssNode, amount); + } + break; + } } - return NO; } - (void)setIntrinsicContentSize:(CGSize)size { - if (_cssNode->style.flex == 0) { - BOOL dirty = NO; - dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_HEIGHT, size.height); - dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_WIDTH, size.width); - if (dirty) { - [self dirtyLayout]; - } + if (CSSNodeStyleGetFlex(_cssNode) == 0) { + RCTAssignSuggestedDimension(_cssNode, CSSDimensionHeight, size.height); + RCTAssignSuggestedDimension(_cssNode, CSSDimensionWidth, size.width); } } - (void)setTopLeft:(CGPoint)topLeft { - _cssNode->style.position[CSS_LEFT] = topLeft.x; - _cssNode->style.position[CSS_TOP] = topLeft.y; - [self dirtyLayout]; + CSSNodeStyleSetPositionLeft(_cssNode, topLeft.x); + CSSNodeStyleSetPositionTop(_cssNode, topLeft.y); } - (void)setSize:(CGSize)size { - _cssNode->style.dimensions[CSS_WIDTH] = size.width; - _cssNode->style.dimensions[CSS_HEIGHT] = size.height; - [self dirtyLayout]; + CSSNodeStyleSetWidth(_cssNode, size.width); + CSSNodeStyleSetHeight(_cssNode, size.height); } // Flex @@ -586,21 +576,20 @@ - (void)setSize:(CGSize)size #define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \ - (void)set##setProp:(type)value \ { \ - _cssNode->style.cssProp = value; \ - [self dirtyLayout]; \ + CSSNodeStyleSet##cssProp(_cssNode, value); \ } \ - (type)getProp \ { \ - return _cssNode->style.cssProp; \ + return CSSNodeStyleGet##cssProp(_cssNode); \ } -RCT_STYLE_PROPERTY(Flex, flex, flex, CGFloat) -RCT_STYLE_PROPERTY(FlexDirection, flexDirection, flex_direction, css_flex_direction_t) -RCT_STYLE_PROPERTY(JustifyContent, justifyContent, justify_content, css_justify_t) -RCT_STYLE_PROPERTY(AlignSelf, alignSelf, align_self, css_align_t) -RCT_STYLE_PROPERTY(AlignItems, alignItems, align_items, css_align_t) -RCT_STYLE_PROPERTY(Position, position, position_type, css_position_type_t) -RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t) +RCT_STYLE_PROPERTY(Flex, flex, Flex, CGFloat) +RCT_STYLE_PROPERTY(FlexDirection, flexDirection, FlexDirection, CSSFlexDirection) +RCT_STYLE_PROPERTY(JustifyContent, justifyContent, JustifyContent, CSSJustify) +RCT_STYLE_PROPERTY(AlignSelf, alignSelf, AlignSelf, CSSAlign) +RCT_STYLE_PROPERTY(AlignItems, alignItems, AlignItems, CSSAlign) +RCT_STYLE_PROPERTY(Position, position, PositionType, CSSPositionType) +RCT_STYLE_PROPERTY(FlexWrap, flexWrap, FlexWrap, CSSWrapType) - (void)setBackgroundColor:(UIColor *)color { @@ -626,18 +615,14 @@ - (void)didUpdateReactSubviews - (void)didSetProps:(__unused NSArray<NSString *> *)changedProps { if (_recomputePadding) { - RCTProcessMetaProps(_paddingMetaProps, _cssNode->style.padding); + RCTProcessMetaPropsPadding(_paddingMetaProps, _cssNode); } if (_recomputeMargin) { - RCTProcessMetaProps(_marginMetaProps, _cssNode->style.margin); + RCTProcessMetaPropsMargin(_marginMetaProps, _cssNode); } if (_recomputeBorder) { - RCTProcessMetaProps(_borderMetaProps, _cssNode->style.border); - } - if (_recomputePadding || _recomputeMargin || _recomputeBorder) { - [self dirtyLayout]; + RCTProcessMetaPropsBorder(_borderMetaProps, _cssNode); } - [self fillCSSNode:_cssNode]; _recomputeMargin = NO; _recomputePadding = NO; _recomputeBorder = NO; diff --git a/React/Views/RCTTabBar.m b/React/Views/RCTTabBar.m index d544d75b2efa48..e6cb93a5a3b97a 100644 --- a/React/Views/RCTTabBar.m +++ b/React/Views/RCTTabBar.m @@ -107,16 +107,16 @@ - (void)reactBridgeDidFinishTransaction [self.reactSubviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger index, __unused BOOL *stop) { RCTTabBarItem *tab = (RCTTabBarItem *)view; - UIViewController *controller = _tabController.viewControllers[index]; - if (_unselectedTintColor) { - [tab.barItem setTitleTextAttributes:@{NSForegroundColorAttributeName: _unselectedTintColor} forState:UIControlStateNormal]; + UIViewController *controller = self->_tabController.viewControllers[index]; + if (self->_unselectedTintColor) { + [tab.barItem setTitleTextAttributes:@{NSForegroundColorAttributeName: self->_unselectedTintColor} forState:UIControlStateNormal]; } [tab.barItem setTitleTextAttributes:@{NSForegroundColorAttributeName: self.tintColor} forState:UIControlStateSelected]; controller.tabBarItem = tab.barItem; if (tab.selected) { - _tabController.selectedViewController = controller; + self->_tabController.selectedViewController = controller; } }]; } diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 4b6cb0a0e4d78a..d14cc32a90671c 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -288,12 +288,12 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio RCT_EXPORT_SHADOW_PROPERTY(padding, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(flex, CGFloat) -RCT_EXPORT_SHADOW_PROPERTY(flexDirection, css_flex_direction_t) -RCT_EXPORT_SHADOW_PROPERTY(flexWrap, css_wrap_type_t) -RCT_EXPORT_SHADOW_PROPERTY(justifyContent, css_justify_t) -RCT_EXPORT_SHADOW_PROPERTY(alignItems, css_align_t) -RCT_EXPORT_SHADOW_PROPERTY(alignSelf, css_align_t) -RCT_EXPORT_SHADOW_PROPERTY(position, css_position_type_t) +RCT_EXPORT_SHADOW_PROPERTY(flexDirection, CSSFlexDirection) +RCT_EXPORT_SHADOW_PROPERTY(flexWrap, CSSWrapType) +RCT_EXPORT_SHADOW_PROPERTY(justifyContent, CSSJustify) +RCT_EXPORT_SHADOW_PROPERTY(alignItems, CSSAlign) +RCT_EXPORT_SHADOW_PROPERTY(alignSelf, CSSAlign) +RCT_EXPORT_SHADOW_PROPERTY(position, CSSPositionType) RCT_EXPORT_SHADOW_PROPERTY(onLayout, RCTDirectEventBlock) diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 5809e24be5fff3..af5f5b03b8a027 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -47,7 +47,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL) - +RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes) RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) { diff --git a/React/Views/UIView+React.h b/React/Views/UIView+React.h index 600a0b34772fba..6533890c402f9a 100644 --- a/React/Views/UIView+React.h +++ b/React/Views/UIView+React.h @@ -9,11 +9,9 @@ #import <UIKit/UIKit.h> -@class RCTShadowView; - #import "RCTComponent.h" -//TODO: let's try to eliminate this category if possible +@class RCTShadowView; @interface UIView (React) <RCTComponent> @@ -73,11 +71,14 @@ - (void)reactDidMakeFirstResponder; - (BOOL)reactRespondsToTouch:(UITouch *)touch; +#if RCT_DEV + /** Tools for debugging */ -#if RCT_DEV + @property (nonatomic, strong, setter=_DEBUG_setReactShadowView:) RCTShadowView *_DEBUG_reactShadowView; + #endif @end diff --git a/ReactAndroid/DEFS b/ReactAndroid/DEFS index 028e49c077935c..129899a8844884 100644 --- a/ReactAndroid/DEFS +++ b/ReactAndroid/DEFS @@ -32,6 +32,7 @@ JSC_DEPS = [ '//native/third-party/jsc:jsc_legacy_profiler', ] +CSSLAYOUT_TARGET = '//ReactAndroid/src/main/java/com/facebook/csslayout:csslayout' FBGLOGINIT_TARGET = '//ReactAndroid/src/main/jni/first-party/fbgloginit:fbgloginit' # React property preprocessor diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 50d7219cab64f6..52dd2258899967 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -240,7 +240,7 @@ android { jniLibs.srcDir "$buildDir/react-ndk/exported" res.srcDirs = ['src/main/res/devsupport', 'src/main/res/shell', 'src/main/res/views/modal'] java { - srcDirs = ['src/main/java', 'src/main/libraries/soloader/java'] + srcDirs = ['src/main/java', 'src/main/libraries/soloader/java', 'src/main/jni/first-party/fb/jni/java'] exclude 'com/facebook/react/processing' } } @@ -266,12 +266,13 @@ dependencies { compile 'com.android.support:recyclerview-v7:23.0.1' compile 'com.facebook.fresco:fresco:0.11.0' compile 'com.facebook.fresco:imagepipeline-okhttp3:0.11.0' + compile 'com.facebook.soloader:soloader:0.1.0' compile 'com.fasterxml.jackson.core:jackson-core:2.2.3' compile 'com.google.code.findbugs:jsr305:3.0.0' - compile 'com.squareup.okhttp3:okhttp:3.2.0' - compile 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0' - compile 'com.squareup.okhttp3:okhttp-ws:3.2.0' - compile 'com.squareup.okio:okio:1.8.0' + compile 'com.squareup.okhttp3:okhttp:3.4.1' + compile 'com.squareup.okhttp3:okhttp-urlconnection:3.4.1' + compile 'com.squareup.okhttp3:okhttp-ws:3.4.1' + compile 'com.squareup.okio:okio:1.9.0' compile 'org.webkit:android-jsc:r174650' testCompile "junit:junit:${JUNIT_VERSION}" diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/NetworkRecordingModuleMock.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/NetworkRecordingModuleMock.java index 43a5ebfcee904e..4c0b68b6d0176b 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/NetworkRecordingModuleMock.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/NetworkRecordingModuleMock.java @@ -83,6 +83,7 @@ public final void sendRequest( int requestId, ReadableArray headers, ReadableMap data, + final String responseType, boolean incrementalUpdates, int timeout) { mLastRequestId = requestId; diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java index 592324e26db9d0..37f0a4db395587 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java @@ -153,7 +153,6 @@ public void loadApp( ReactInstanceManager.Builder builder = ReactTestHelper.getReactTestFactory().getReactInstanceManagerBuilder() .setApplication(getApplication()) - .setUseOldBridge(true) .setBundleAssetName(bundleName) // By not setting a JS module name, we force the bundle to be always loaded from // assets, not the devserver, even if dev mode is enabled (such as when testing redboxes). diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK index 8f16de6cb9958f..359577f87b9ffe 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK @@ -3,8 +3,6 @@ include_defs('//ReactAndroid/DEFS') # Tests that are too flaky to run on SandCastle # TODO t11057216 stabilise them SANDCASTLE_FLAKY = [ - 'ReactHorizontalScrollViewTestCase.java', - 'ReactScrollViewTestCase.java', ] deps = [ @@ -16,6 +14,7 @@ deps = [ react_native_target('java/com/facebook/react/common:common'), react_native_target('java/com/facebook/react/modules/core:core'), react_native_target('java/com/facebook/react/modules/datepicker:datepicker'), + react_native_target('java/com/facebook/react/modules/share:share'), react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'), react_native_target('java/com/facebook/react/modules/timepicker:timepicker'), react_native_target('java/com/facebook/react/touch:touch'), @@ -36,10 +35,10 @@ deps = [ android_library( name = 'tests', srcs = glob(['**/*.java']), + deps = deps, visibility = [ 'PUBLIC', ], - deps = deps, ) android_library( diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ShareTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ShareTestCase.java new file mode 100644 index 00000000000000..13ef775f3f4145 --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ShareTestCase.java @@ -0,0 +1,109 @@ +/** + * 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. + */ + +package com.facebook.react.tests; + +import java.util.ArrayList; +import java.util.List; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Instrumentation.ActivityMonitor; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentFilter.MalformedMimeTypeException; +import android.support.v4.app.DialogFragment; + +import com.facebook.react.bridge.BaseJavaModule; +import com.facebook.react.testing.ReactInstanceSpecForTest; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.modules.share.ShareModule; +import com.facebook.react.testing.ReactAppInstrumentationTestCase; + +/** + * Test case for {@link ShareModule}. + */ +public class ShareTestCase extends ReactAppInstrumentationTestCase { + + private static interface ShareTestModule extends JavaScriptModule { + public void showShareDialog(WritableMap content, WritableMap options); + } + + private static class ShareRecordingModule extends BaseJavaModule { + + private int mOpened = 0; + private int mErrors = 0; + + @Override + public String getName() { + return "ShareRecordingModule"; + } + + @ReactMethod + public void recordOpened() { + mOpened++; + } + + @ReactMethod + public void recordError() { + mErrors++; + } + + public int getOpened() { + return mOpened; + } + + public int getErrors() { + return mErrors; + } + + } + + final ShareRecordingModule mRecordingModule = new ShareRecordingModule(); + + @Override + protected ReactInstanceSpecForTest createReactInstanceSpecForTest() { + return super.createReactInstanceSpecForTest() + .addNativeModule(mRecordingModule) + .addJSModule(ShareTestModule.class); + } + + @Override + protected String getReactApplicationKeyUnderTest() { + return "ShareTestApp"; + } + + private ShareTestModule getTestModule() { + return getReactContext().getCatalystInstance().getJSModule(ShareTestModule.class); + } + + public void testShowBasicShareDialog() { + final WritableMap content = new WritableNativeMap(); + content.putString("message", "Hello, ReactNative!"); + final WritableMap options = new WritableNativeMap(); + + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CHOOSER); + intentFilter.addCategory(Intent.CATEGORY_DEFAULT); + ActivityMonitor monitor = getInstrumentation().addMonitor(intentFilter, null, true); + + getTestModule().showShareDialog(content, options); + + waitForBridgeAndUIIdle(); + getInstrumentation().waitForIdleSync(); + + assertEquals(1, monitor.getHits()); + assertEquals(1, mRecordingModule.getOpened()); + assertEquals(0, mRecordingModule.getErrors()); + + } + +} diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java index 9558a5d912af60..40aa97813554a2 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java @@ -16,7 +16,6 @@ import android.widget.EditText; import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.common.SystemClock; import com.facebook.react.testing.ReactAppInstrumentationTestCase; import com.facebook.react.testing.ReactInstanceSpecForTest; import com.facebook.react.uimanager.PixelUtil; @@ -116,7 +115,6 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextChangedEvent( reactEditText.getId(), - SystemClock.nanoTime(), newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int) PixelUtil.toDIPFromPixel(contentHeight), @@ -125,7 +123,6 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextInputEvent( reactEditText.getId(), - SystemClock.nanoTime(), newText.toString(), "", start, @@ -150,7 +147,6 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextChangedEvent( reactEditText.getId(), - SystemClock.nanoTime(), newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int) PixelUtil.toDIPFromPixel(contentHeight), @@ -159,7 +155,6 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextInputEvent( reactEditText.getId(), - SystemClock.nanoTime(), moreText, "", start, @@ -184,7 +179,6 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextChangedEvent( reactEditText.getId(), - SystemClock.nanoTime(), newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int) PixelUtil.toDIPFromPixel(contentHeight), @@ -193,7 +187,6 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextInputEvent( reactEditText.getId(), - SystemClock.nanoTime(), moreText, "", start, diff --git a/ReactAndroid/src/androidTest/js/ShareTestModule.js b/ReactAndroid/src/androidTest/js/ShareTestModule.js new file mode 100644 index 00000000000000..dbba70103160d3 --- /dev/null +++ b/ReactAndroid/src/androidTest/js/ShareTestModule.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2013-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. + * + * @providesModule ShareTestModule + */ + +'use strict'; + +var BatchedBridge = require('BatchedBridge'); +var React = require('React'); +var RecordingModule = require('NativeModules').ShareRecordingModule; +var Share = require('Share'); +var View = require('View'); + +var ShareTestApp = React.createClass({ + render: function() { + return (<View />); + }, +}); + +var ShareTestModule = { + ShareTestApp: ShareTestApp, + showShareDialog: function(content, options) { + Share.share(content, options).then( + () => RecordingModule.recordOpened(), + ({code, message}) => RecordingModule.recordError() + ); + }, +}; + +BatchedBridge.registerCallableModule( + 'ShareTestModule', + ShareTestModule +); + +module.exports = ShareTestModule; diff --git a/ReactAndroid/src/androidTest/js/TestBundle.js b/ReactAndroid/src/androidTest/js/TestBundle.js index 960c485a371f6c..f560068a3a41e8 100644 --- a/ReactAndroid/src/androidTest/js/TestBundle.js +++ b/ReactAndroid/src/androidTest/js/TestBundle.js @@ -25,6 +25,7 @@ require('DatePickerDialogTestModule'); require('MeasureLayoutTestModule'); require('PickerAndroidTestModule'); require('ScrollViewTestModule'); +require('ShareTestModule'); require('SwipeRefreshLayoutTestModule'); require('TextInputTestModule'); require('TimePickerDialogTestModule'); @@ -74,6 +75,10 @@ var apps = [ appKey: 'ScrollViewTestApp', component: () => require('ScrollViewTestModule').ScrollViewTestApp, }, +{ + appKey: 'ShareTestApp', + component: () => require('ShareTestModule').ShareTestApp, +}, { appKey: 'SubviewsClippingTestApp', component: () => require('SubviewsClippingTestModule').App, diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSAlign.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSAlign.java index 7ca88e14636d07..3e3cb0e9893557 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSAlign.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSAlign.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java index aac36f649cfd47..deb1de6a5e62c6 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java @@ -1,14 +1,12 @@ /** * 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<<7463b170625c103400318b8a42378786>> - package com.facebook.csslayout; public class CSSCachedMeasurement { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSConstants.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSConstants.java index f0441fc416b341..01a0d597d0debb 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSConstants.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSDirection.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSDirection.java index 361a6f264efcb8..0c27a929537ce0 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSDirection.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSDirection.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSFlexDirection.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSFlexDirection.java index 4a6a492e2a2133..30f92bd04c861b 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSFlexDirection.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSFlexDirection.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSJustify.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSJustify.java index bdfd6aa5afc6e1..dfcc1cd2ede323 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSJustify.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSJustify.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java index 23bfaf248c0053..ed2b8249490316 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java @@ -1,14 +1,12 @@ /** * 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<<af6ef8054f37d4745903e857b95fe01f>> - package com.facebook.csslayout; import java.util.Arrays; diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java index a6562ff2b6dcac..70a9c8869ef7e3 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java @@ -1,14 +1,12 @@ /** * 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<<ad69450666e601bed3648b9f7b990f4d>> - package com.facebook.csslayout; /** diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSMeasureMode.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSMeasureMode.java index 6306af91d7d943..e8ebb34f078f48 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSMeasureMode.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSMeasureMode.java @@ -1,14 +1,12 @@ /** * 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<<39eafaefe66358c3854d67910eaf0dc2>> - package com.facebook.csslayout; public enum CSSMeasureMode { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java index b9cacf68043051..6bb7cf663a59a5 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java @@ -1,14 +1,12 @@ /** * 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<<da35a9f6c5a59af0d73da3e46ee60a9a>> - package com.facebook.csslayout; import javax.annotation.Nullable; diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java index 74e2efcb44d97f..29957a9a7b840c 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java @@ -1,14 +1,12 @@ /** * 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<<3bbf86ec0e75cbdbc9c741e0b3922679>> - package com.facebook.csslayout; public enum CSSOverflow { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSPositionType.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSPositionType.java index 96fba2f5d07eae..19e27dd45b4214 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSPositionType.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSPositionType.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java index 119761fb9c5a18..8c86149edc05f2 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java @@ -1,14 +1,12 @@ /** * 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<<d231dc5fd873a05ae8641a7199502a2a>> - package com.facebook.csslayout; import java.util.Arrays; diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSWrap.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSWrap.java index 476d907c724782..895bb5d5536959 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSWrap.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSWrap.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java index 6d631d37c9459f..a012a5ba8d1cac 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java @@ -1,14 +1,12 @@ /** * 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<<e2f8139d4c50e4d2a9281a4587f8c095>> - package com.facebook.csslayout; /** diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/FloatUtil.java b/ReactAndroid/src/main/java/com/facebook/csslayout/FloatUtil.java index 420a679ca3eaaa..8b211e8de587ca 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/FloatUtil.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/FloatUtil.java @@ -1,14 +1,12 @@ /** * 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 { diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java index 042b253660a4d9..bb8e90b67208c5 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java @@ -1,14 +1,12 @@ /** * 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<<c5a4eadcd6d93bc6d989cba73caa12a7>> - package com.facebook.csslayout; import com.facebook.infer.annotation.Assertions; @@ -234,20 +232,31 @@ private static boolean isMeasureDefined(CSSNode node) { // parameters don't change. layoutContext.currentGenerationCount++; - // If the caller didn't specify a height/width, use the dimensions - // specified in the style. - if (Float.isNaN(availableWidth) && node.style.dimensions[DIMENSION_WIDTH] >= 0.0) { + CSSMeasureMode widthMeasureMode = CSSMeasureMode.UNDEFINED; + CSSMeasureMode heightMeasureMode = CSSMeasureMode.UNDEFINED; + + if (!Float.isNaN(availableWidth)) { + widthMeasureMode = CSSMeasureMode.EXACTLY; + } else if (node.style.dimensions[DIMENSION_WIDTH] >= 0.0) { float marginAxisRow = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); availableWidth = node.style.dimensions[DIMENSION_WIDTH] + marginAxisRow; + widthMeasureMode = CSSMeasureMode.EXACTLY; + } else if (node.style.maxWidth >= 0.0) { + availableWidth = node.style.maxWidth; + widthMeasureMode = CSSMeasureMode.AT_MOST; } - if (Float.isNaN(availableHeight) && node.style.dimensions[DIMENSION_HEIGHT] >= 0.0) { + + if (!Float.isNaN(availableHeight)) { + heightMeasureMode = CSSMeasureMode.EXACTLY; + } else if (node.style.dimensions[DIMENSION_HEIGHT] >= 0.0) { float marginAxisColumn = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); availableHeight = node.style.dimensions[DIMENSION_HEIGHT] + marginAxisColumn; + heightMeasureMode = CSSMeasureMode.EXACTLY; + } else if (node.style.maxHeight >= 0.0) { + availableHeight = node.style.maxHeight; + heightMeasureMode = CSSMeasureMode.AT_MOST; } - CSSMeasureMode widthMeasureMode = Float.isNaN(availableWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; - CSSMeasureMode heightMeasureMode = Float.isNaN(availableHeight) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; - if (layoutNodeInternal(layoutContext, node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, true, "initial")) { setPosition(node, node.layout.direction); } @@ -543,47 +552,46 @@ private static void layoutNodeImpl( CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, boolean performLayout) { - /** START_GENERATED **/ - + Assertions.assertCondition(Float.isNaN(availableWidth) ? widthMeasureMode == CSSMeasureMode.UNDEFINED : true, "availableWidth is indefinite so widthMeasureMode must be CSSMeasureMode.UNDEFINED"); Assertions.assertCondition(Float.isNaN(availableHeight) ? heightMeasureMode == CSSMeasureMode.UNDEFINED : true, "availableHeight is indefinite so heightMeasureMode must be CSSMeasureMode.UNDEFINED"); - + float paddingAndBorderAxisRow = ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]))); float paddingAndBorderAxisColumn = ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))); float marginAxisRow = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); float marginAxisColumn = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); - + // Set the resolved resolution in the node's layout. CSSDirection direction = resolveDirection(node, parentDirection); node.layout.direction = direction; - + // For content (text) nodes, determine the dimensions based on the text contents. if (isMeasureDefined(node)) { float innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; float innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; - + if (widthMeasureMode == CSSMeasureMode.EXACTLY && heightMeasureMode == CSSMeasureMode.EXACTLY) { - + // Don't bother sizing the text if both dimensions are already defined. node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); } else if (innerWidth <= 0 || innerHeight <= 0) { - + // Don't bother sizing the text if there's no horizontal or vertical space. node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); } else { - + // Measure the text under the current constraints. MeasureOutput measureDim = node.measure( - + layoutContext.measureOutput, innerWidth, widthMeasureMode, innerHeight, heightMeasureMode ); - + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, (widthMeasureMode == CSSMeasureMode.UNDEFINED || widthMeasureMode == CSSMeasureMode.AT_MOST) ? measureDim.width + paddingAndBorderAxisRow : @@ -593,10 +601,10 @@ private static void layoutNodeImpl( measureDim.height + paddingAndBorderAxisColumn : availableHeight - marginAxisColumn); } - + return; } - + // For nodes with no children, use the available values if they were provided, or // the minimum size as indicated by the padding and border sizes. int childCount = node.getChildCount(); @@ -611,7 +619,7 @@ private static void layoutNodeImpl( availableHeight - marginAxisColumn); return; } - + // If we're not being asked to perform a full layout, we can handle a number of common // cases here without incurring the cost of the remaining function. if (!performLayout) { @@ -623,19 +631,19 @@ private static void layoutNodeImpl( node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); return; } - + if (widthMeasureMode == CSSMeasureMode.AT_MOST && availableWidth <= 0) { node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, Float.isNaN(availableHeight) ? 0 : (availableHeight - marginAxisColumn)); return; } - + if (heightMeasureMode == CSSMeasureMode.AT_MOST && availableHeight <= 0) { node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, Float.isNaN(availableWidth) ? 0 : (availableWidth - marginAxisRow)); node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); return; } - + // If we're being asked to use an exact width/height, there's no need to measure the children. if (widthMeasureMode == CSSMeasureMode.EXACTLY && heightMeasureMode == CSSMeasureMode.EXACTLY) { node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); @@ -643,32 +651,32 @@ private static void layoutNodeImpl( return; } } - + // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM int mainAxis = resolveAxis(getFlexDirection(node), direction); int crossAxis = getCrossFlexDirection(mainAxis, direction); boolean isMainAxisRow = (mainAxis == CSS_FLEX_DIRECTION_ROW || mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE); CSSJustify justifyContent = node.style.justifyContent; boolean isNodeFlexWrap = (node.style.flexWrap == CSSWrap.WRAP); - + CSSNode firstAbsoluteChild = null; CSSNode currentAbsoluteChild = null; - + float leadingPaddingAndBorderMain = (node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])); float trailingPaddingAndBorderMain = (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); float leadingPaddingAndBorderCross = (node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])); float paddingAndBorderAxisMain = ((node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))); float paddingAndBorderAxisCross = ((node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (node.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + node.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))); - + CSSMeasureMode measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; CSSMeasureMode measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; - + // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; float availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; - + // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM CSSNode child; int i; @@ -678,17 +686,17 @@ private static void layoutNodeImpl( CSSMeasureMode childHeightMeasureMode; for (i = 0; i < childCount; i++) { child = node.getChildAt(i); - + if (performLayout) { // Set the initial position (relative to the parent). CSSDirection childDirection = resolveDirection(child, direction); setPosition(child, childDirection); } - + // Absolute-positioned children don't participate in flex layout. Add them // to a list that we can process later. if (child.style.positionType == CSSPositionType.ABSOLUTE) { - + // Store a private linked list of absolutely positioned children // so that we can efficiently traverse them later. if (firstAbsoluteChild == null) { @@ -700,27 +708,27 @@ private static void layoutNodeImpl( currentAbsoluteChild = child; child.nextChild = null; } else { - + if (isMainAxisRow && (child.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { - + // The width is definite, so use that as the flex basis. child.layout.flexBasis = Math.max(child.style.dimensions[DIMENSION_WIDTH], ((child.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + child.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW])) + (child.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]) + child.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])))); } else if (!isMainAxisRow && (child.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { - + // The height is definite, so use that as the flex basis. child.layout.flexBasis = Math.max(child.style.dimensions[DIMENSION_HEIGHT], ((child.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + child.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (child.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + child.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])))); } else if (!isFlexBasisAuto(child) && !Float.isNaN(availableInnerMainDim)) { - + // If the basis isn't 'auto', it is assumed to be zero. child.layout.flexBasis = Math.max(0, ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])))); } else { - + // Compute the flex basis and hypothetical main size (i.e. the clamped flex basis). childWidth = CSSConstants.UNDEFINED; childHeight = CSSConstants.UNDEFINED; childWidthMeasureMode = CSSMeasureMode.UNDEFINED; childHeightMeasureMode = CSSMeasureMode.UNDEFINED; - + if ((child.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { childWidth = child.style.dimensions[DIMENSION_WIDTH] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); childWidthMeasureMode = CSSMeasureMode.EXACTLY; @@ -729,7 +737,7 @@ private static void layoutNodeImpl( childHeight = child.style.dimensions[DIMENSION_HEIGHT] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); childHeightMeasureMode = CSSMeasureMode.EXACTLY; } - + // According to the spec, if the main size is not definite and the // child's inline axis is parallel to the main axis (i.e. it's // horizontal), the child should be sized using "UNDEFINED" in @@ -738,7 +746,7 @@ private static void layoutNodeImpl( childWidth = availableInnerWidth; childWidthMeasureMode = CSSMeasureMode.AT_MOST; } - + // The W3C spec doesn't say anything about the 'overflow' property, // but all major browsers appear to implement the following logic. if (node.style.overflow == CSSOverflow.HIDDEN) { @@ -747,7 +755,7 @@ private static void layoutNodeImpl( childHeightMeasureMode = CSSMeasureMode.AT_MOST; } } - + // If child has no defined size in the cross axis and is set to stretch, set the cross // axis to be measured exactly with the available inner width if (!isMainAxisRow && @@ -766,76 +774,76 @@ private static void layoutNodeImpl( childHeight = availableInnerHeight; childHeightMeasureMode = CSSMeasureMode.EXACTLY; } - + // Measure the child layoutNodeInternal(layoutContext, child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "measure"); - + child.layout.flexBasis = Math.max(isMainAxisRow ? child.layout.measuredDimensions[DIMENSION_WIDTH] : child.layout.measuredDimensions[DIMENSION_HEIGHT], ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])))); } } } - + // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES - + // Indexes of children that represent the first and last items in the line. int startOfLineIndex = 0; int endOfLineIndex = 0; - + // Number of lines. int lineCount = 0; - + // Accumulated cross dimensions of all lines so far. float totalLineCrossDim = 0; - + // Max main dimension of all the lines. float maxLineMainDim = 0; - + while (endOfLineIndex < childCount) { - + // Number of items on the currently line. May be different than the difference // between start and end indicates because we skip over absolute-positioned items. int itemsOnLine = 0; - + // sizeConsumedOnCurrentLine is accumulation of the dimensions and margin // of all the children on the current line. This will be used in order to // either set the dimensions of the node if none already exist or to compute // the remaining space left for the flexible children. float sizeConsumedOnCurrentLine = 0; - + float totalFlexGrowFactors = 0; float totalFlexShrinkScaledFactors = 0; - + i = startOfLineIndex; - + // Maintain a linked list of the child nodes that can shrink and/or grow. CSSNode firstRelativeChild = null; CSSNode currentRelativeChild = null; - + // Add items to the current line until it's full or we run out of items. while (i < childCount) { child = node.getChildAt(i); child.lineIndex = lineCount; - + if (child.style.positionType != CSSPositionType.ABSOLUTE) { float outerFlexBasis = child.layout.flexBasis + (child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); - + // If this is a multi-line flow and this item pushes us over the available size, we've // hit the end of the current line. Break out of the loop and lay out the current line. if (sizeConsumedOnCurrentLine + outerFlexBasis > availableInnerMainDim && isNodeFlexWrap && itemsOnLine > 0) { break; } - + sizeConsumedOnCurrentLine += outerFlexBasis; itemsOnLine++; - + if ((child.style.positionType == CSSPositionType.RELATIVE && child.style.flex != 0)) { totalFlexGrowFactors += getFlexGrowFactor(child); - + // Unlike the grow factor, the shrink factor is scaled relative to the child // dimension. totalFlexShrinkScaledFactors += getFlexShrinkFactor(child) * child.layout.flexBasis; } - + // Store a private linked list of children that need to be layed out. if (firstRelativeChild == null) { firstRelativeChild = child; @@ -846,20 +854,20 @@ private static void layoutNodeImpl( currentRelativeChild = child; child.nextChild = null; } - + i++; endOfLineIndex++; } - + // If we don't need to measure the cross axis, we can skip the entire flex step. boolean canSkipFlex = !performLayout && measureModeCrossDim == CSSMeasureMode.EXACTLY; - + // In order to position the elements in the main axis, we have two // controls. The space between the beginning and the first element // and the space between each two elements. float leadingMainDim = 0; float betweenMainDim = 0; - + // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS // Calculate the remaining available space that needs to be allocated. // If the main dimension size isn't known, it is computed based on @@ -873,17 +881,17 @@ private static void layoutNodeImpl( // its content. Consequently, remainingFreeSpace is 0 - sizeConsumedOnCurrentLine. remainingFreeSpace = -sizeConsumedOnCurrentLine; } - + float originalRemainingFreeSpace = remainingFreeSpace; float deltaFreeSpace = 0; - + if (!canSkipFlex) { float childFlexBasis; float flexShrinkScaledFactor; float flexGrowFactor; float baseMainSize; float boundMainSize; - + // Do two passes over the flex items to figure out how to distribute the remaining space. // The first pass finds the items whose min/max constraints trigger, freezes them at those // sizes, and excludes those sizes from the remaining space. The second pass sets the size @@ -896,17 +904,17 @@ private static void layoutNodeImpl( // that needs to be repeated a variable number of times. The algorithm implemented here // won't handle all cases but it was simpler to implement and it mitigates performance // concerns because we know exactly how many passes it'll do. - + // First pass: detect the flex items whose min/max constraints trigger float deltaFlexShrinkScaledFactors = 0; float deltaFlexGrowFactors = 0; currentRelativeChild = firstRelativeChild; while (currentRelativeChild != null) { childFlexBasis = currentRelativeChild.layout.flexBasis; - + if (remainingFreeSpace < 0) { flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; - + // Is this child able to shrink? if (flexShrinkScaledFactor != 0) { baseMainSize = childFlexBasis + @@ -922,7 +930,7 @@ private static void layoutNodeImpl( } } else if (remainingFreeSpace > 0) { flexGrowFactor = getFlexGrowFactor(currentRelativeChild); - + // Is this child able to grow? if (flexGrowFactor != 0) { baseMainSize = childFlexBasis + @@ -937,24 +945,24 @@ private static void layoutNodeImpl( } } } - + currentRelativeChild = currentRelativeChild.nextChild; } - + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; totalFlexGrowFactors += deltaFlexGrowFactors; remainingFreeSpace += deltaFreeSpace; - + // Second pass: resolve the sizes of the flexible items deltaFreeSpace = 0; currentRelativeChild = firstRelativeChild; while (currentRelativeChild != null) { childFlexBasis = currentRelativeChild.layout.flexBasis; float updatedMainSize = childFlexBasis; - + if (remainingFreeSpace < 0) { flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; - + // Is this child able to shrink? if (flexShrinkScaledFactor != 0) { updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis + @@ -962,20 +970,20 @@ private static void layoutNodeImpl( } } else if (remainingFreeSpace > 0) { flexGrowFactor = getFlexGrowFactor(currentRelativeChild); - + // Is this child able to grow? if (flexGrowFactor != 0) { updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor); } } - + deltaFreeSpace -= updatedMainSize - childFlexBasis; - + if (isMainAxisRow) { childWidth = updatedMainSize + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); childWidthMeasureMode = CSSMeasureMode.EXACTLY; - + if (!Float.isNaN(availableInnerCrossDim) && !(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0) && heightMeasureMode == CSSMeasureMode.EXACTLY && @@ -992,7 +1000,7 @@ private static void layoutNodeImpl( } else { childHeight = updatedMainSize + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); childHeightMeasureMode = CSSMeasureMode.EXACTLY; - + if (!Float.isNaN(availableInnerCrossDim) && !(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0) && widthMeasureMode == CSSMeasureMode.EXACTLY && @@ -1007,32 +1015,32 @@ private static void layoutNodeImpl( childWidthMeasureMode = CSSMeasureMode.EXACTLY; } } - + boolean requiresStretchLayout = !(currentRelativeChild.style.dimensions[dim[crossAxis]] >= 0.0) && getAlignItem(node, currentRelativeChild) == CSSAlign.STRETCH; - + // Recursively call the layout algorithm for this child with the updated main size. layoutNodeInternal(layoutContext, currentRelativeChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, performLayout && !requiresStretchLayout, "flex"); - + currentRelativeChild = currentRelativeChild.nextChild; } } - + remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace; - + // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION - + // At this point, all the children have their dimensions set in the main axis. // Their dimensions are also set in the cross axis with the exception of items // that are aligned "stretch". We need to compute these stretch values and // set the final positions. - + // If we are using "at most" rules in the main axis, we won't distribute // any remaining space at this point. if (measureModeMainDim == CSSMeasureMode.AT_MOST) { remainingFreeSpace = 0; } - + // Use justifyContent to figure out how to allocate the remaining space // available in the main axis. if (justifyContent != CSSJustify.FLEX_START) { @@ -1053,13 +1061,13 @@ private static void layoutNodeImpl( leadingMainDim = betweenMainDim / 2; } } - + float mainDim = leadingPaddingAndBorderMain + leadingMainDim; float crossDim = 0; - + for (i = startOfLineIndex; i < endOfLineIndex; ++i) { child = node.getChildAt(i); - + if (child.style.positionType == CSSPositionType.ABSOLUTE && !Float.isNaN(child.style.position[leading[mainAxis]])) { if (performLayout) { @@ -1076,7 +1084,7 @@ private static void layoutNodeImpl( // we put it at the current accumulated offset. child.layout.position[pos[mainAxis]] += mainDim; } - + // Now that we placed the element, we need to update the variables. // We need to do that only for relative elements. Absolute elements // do not take part in that phase. @@ -1090,7 +1098,7 @@ private static void layoutNodeImpl( // The main dimension is the sum of all the elements dimension plus // the spacing. mainDim += betweenMainDim + (child.layout.measuredDimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); - + // The cross dimension is the max of the elements dimension since there // can only be one element in that cross dimension. crossDim = Math.max(crossDim, (child.layout.measuredDimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))); @@ -1098,33 +1106,33 @@ private static void layoutNodeImpl( } } } - + mainDim += trailingPaddingAndBorderMain; - + float containerCrossAxis = availableInnerCrossDim; if (measureModeCrossDim == CSSMeasureMode.UNDEFINED || measureModeCrossDim == CSSMeasureMode.AT_MOST) { // Compute the cross axis from the max cross dimension of the children. containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; - + if (measureModeCrossDim == CSSMeasureMode.AT_MOST) { containerCrossAxis = Math.min(containerCrossAxis, availableInnerCrossDim); } } - + // If there's no flex wrap, the cross dimension is defined by the container. if (!isNodeFlexWrap && measureModeCrossDim == CSSMeasureMode.EXACTLY) { crossDim = availableInnerCrossDim; } - + // Clamp to the min/max size specified on the container. crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; - + // STEP 7: CROSS-AXIS ALIGNMENT // We can skip child alignment if we're just measuring the container. if (performLayout) { for (i = startOfLineIndex; i < endOfLineIndex; ++i) { child = node.getChildAt(i); - + if (child.style.positionType == CSSPositionType.ABSOLUTE) { // If the child is absolutely positioned and has a top/left/bottom/right // set, override all the previously computed positions to set it correctly. @@ -1138,18 +1146,18 @@ private static void layoutNodeImpl( } } else { float leadingCrossDim = leadingPaddingAndBorderCross; - + // For a relative children, we're either using alignItems (parent) or // alignSelf (child) in order to determine the position in the cross axis CSSAlign alignItem = getAlignItem(node, child); - + // If the child uses align stretch, we need to lay it out one more time, this time // forcing the cross-axis size to be the computed cross size for the current line. if (alignItem == CSSAlign.STRETCH) { childWidth = child.layout.measuredDimensions[DIMENSION_WIDTH] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); childHeight = child.layout.measuredDimensions[DIMENSION_HEIGHT] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); boolean isCrossSizeDefinite = false; - + if (isMainAxisRow) { isCrossSizeDefinite = (child.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0); childHeight = crossDim; @@ -1157,7 +1165,7 @@ private static void layoutNodeImpl( isCrossSizeDefinite = (child.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0); childWidth = crossDim; } - + // If the child defines a definite size for its cross axis, there's no need to stretch. if (!isCrossSizeDefinite) { childWidthMeasureMode = Float.isNaN(childWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; @@ -1166,36 +1174,36 @@ private static void layoutNodeImpl( } } else if (alignItem != CSSAlign.FLEX_START) { float remainingCrossDim = containerCrossAxis - (child.layout.measuredDimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])); - + if (alignItem == CSSAlign.CENTER) { leadingCrossDim += remainingCrossDim / 2; } else { // CSSAlign.FLEX_END leadingCrossDim += remainingCrossDim; } } - + // And we apply the position child.layout.position[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim; } } } - + totalLineCrossDim += crossDim; maxLineMainDim = Math.max(maxLineMainDim, mainDim); - + // Reset variables for new line. lineCount++; startOfLineIndex = endOfLineIndex; endOfLineIndex = startOfLineIndex; } - + // STEP 8: MULTI-LINE CONTENT ALIGNMENT if (lineCount > 1 && performLayout && !Float.isNaN(availableInnerCrossDim)) { float remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim; - + float crossDimLead = 0; float currentLead = leadingPaddingAndBorderCross; - + CSSAlign alignContent = node.style.alignContent; if (alignContent == CSSAlign.FLEX_END) { currentLead += remainingAlignContentDim; @@ -1206,12 +1214,12 @@ private static void layoutNodeImpl( crossDimLead = (remainingAlignContentDim / lineCount); } } - + int endIndex = 0; for (i = 0; i < lineCount; ++i) { int startIndex = endIndex; int j; - + // compute the line's height and find the endIndex float lineHeight = 0; for (j = startIndex; j < childCount; ++j) { @@ -1229,14 +1237,14 @@ private static void layoutNodeImpl( } endIndex = j; lineHeight += crossDimLead; - + if (performLayout) { for (j = startIndex; j < endIndex; ++j) { child = node.getChildAt(j); if (child.style.positionType != CSSPositionType.RELATIVE) { continue; } - + CSSAlign alignContentAlignItem = getAlignItem(node, child); if (alignContentAlignItem == CSSAlign.FLEX_START) { child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); @@ -1252,15 +1260,15 @@ private static void layoutNodeImpl( } } } - + currentLead += lineHeight; } } - + // STEP 9: COMPUTING FINAL DIMENSIONS node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); - + // If the user didn't specify a width or height for the node, set the // dimensions based on the children. if (measureModeMainDim == CSSMeasureMode.UNDEFINED) { @@ -1273,7 +1281,7 @@ private static void layoutNodeImpl( boundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim)), paddingAndBorderAxisMain); } - + if (measureModeCrossDim == CSSMeasureMode.UNDEFINED) { // Clamp the size to the min/max size, if specified, and make sure it // doesn't go below the padding and border amount. @@ -1284,48 +1292,48 @@ private static void layoutNodeImpl( boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)), paddingAndBorderAxisCross); } - + // STEP 10: SETTING TRAILING POSITIONS FOR CHILDREN if (performLayout) { boolean needsMainTrailingPos = false; boolean needsCrossTrailingPos = false; - + if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsMainTrailingPos = true; } - + if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsCrossTrailingPos = true; } - + // Set trailing position if necessary. if (needsMainTrailingPos || needsCrossTrailingPos) { for (i = 0; i < childCount; ++i) { child = node.getChildAt(i); - + if (needsMainTrailingPos) { child.layout.position[trailing[mainAxis]] = node.layout.measuredDimensions[dim[mainAxis]] - (child.style.positionType == CSSPositionType.ABSOLUTE ? 0 : child.layout.measuredDimensions[dim[mainAxis]]) - child.layout.position[pos[mainAxis]]; } - + if (needsCrossTrailingPos) { child.layout.position[trailing[crossAxis]] = node.layout.measuredDimensions[dim[crossAxis]] - (child.style.positionType == CSSPositionType.ABSOLUTE ? 0 : child.layout.measuredDimensions[dim[crossAxis]]) - child.layout.position[pos[crossAxis]]; } } } } - + // STEP 11: SIZING AND POSITIONING ABSOLUTE CHILDREN currentAbsoluteChild = firstAbsoluteChild; while (currentAbsoluteChild != null) { // Now that we know the bounds of the container, perform layout again on the // absolutely-positioned children. if (performLayout) { - + childWidth = CSSConstants.UNDEFINED; childHeight = CSSConstants.UNDEFINED; - + if ((currentAbsoluteChild.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { childWidth = currentAbsoluteChild.style.dimensions[DIMENSION_WIDTH] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); } else { @@ -1337,7 +1345,7 @@ private static void layoutNodeImpl( childWidth = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW, childWidth); } } - + if ((currentAbsoluteChild.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { childHeight = currentAbsoluteChild.style.dimensions[DIMENSION_HEIGHT] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); } else { @@ -1349,12 +1357,12 @@ private static void layoutNodeImpl( childHeight = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN, childHeight); } } - + // If we're still missing one or the other dimension, measure the content. if (Float.isNaN(childWidth) || Float.isNaN(childHeight)) { childWidthMeasureMode = Float.isNaN(childWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; childHeightMeasureMode = Float.isNaN(childHeight) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; - + // According to the spec, if the main size is not definite and the // child's inline axis is parallel to the main axis (i.e. it's // horizontal), the child should be sized using "UNDEFINED" in @@ -1363,7 +1371,7 @@ private static void layoutNodeImpl( childWidth = availableInnerWidth; childWidthMeasureMode = CSSMeasureMode.AT_MOST; } - + // The W3C spec doesn't say anything about the 'overflow' property, // but all major browsers appear to implement the following logic. if (node.style.overflow == CSSOverflow.HIDDEN) { @@ -1372,14 +1380,14 @@ private static void layoutNodeImpl( childHeightMeasureMode = CSSMeasureMode.AT_MOST; } } - + layoutNodeInternal(layoutContext, currentAbsoluteChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "abs-measure"); childWidth = currentAbsoluteChild.layout.measuredDimensions[DIMENSION_WIDTH] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); childHeight = currentAbsoluteChild.layout.measuredDimensions[DIMENSION_HEIGHT] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); } - + layoutNodeInternal(layoutContext, currentAbsoluteChild, childWidth, childHeight, direction, CSSMeasureMode.EXACTLY, CSSMeasureMode.EXACTLY, true, "abs-layout"); - + if (!Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_ROW]]) && !!Float.isNaN(currentAbsoluteChild.style.position[leading[CSS_FLEX_DIRECTION_ROW]])) { currentAbsoluteChild.layout.position[leading[CSS_FLEX_DIRECTION_ROW]] = @@ -1387,7 +1395,7 @@ private static void layoutNodeImpl( currentAbsoluteChild.layout.measuredDimensions[dim[CSS_FLEX_DIRECTION_ROW]] - (Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_ROW]]) ? 0 : currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_ROW]]); } - + if (!Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_COLUMN]]) && !!Float.isNaN(currentAbsoluteChild.style.position[leading[CSS_FLEX_DIRECTION_COLUMN]])) { currentAbsoluteChild.layout.position[leading[CSS_FLEX_DIRECTION_COLUMN]] = @@ -1396,9 +1404,8 @@ private static void layoutNodeImpl( (Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_COLUMN]]) ? 0 : currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_COLUMN]]); } } - + currentAbsoluteChild = currentAbsoluteChild.nextChild; } - /** END_GENERATED **/ } } diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/MeasureOutput.java b/ReactAndroid/src/main/java/com/facebook/csslayout/MeasureOutput.java index 8c0190db5569e4..65adfdf1d95d94 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/MeasureOutput.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/MeasureOutput.java @@ -1,14 +1,12 @@ /** * 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; /** diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README b/ReactAndroid/src/main/java/com/facebook/csslayout/README index e04447bbc37587..9d85b26ff7a12f 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/README +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README @@ -1,7 +1,7 @@ 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/383d8a6b3dcbdb978e012e29040e1a43157765c6 +HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/a1f36b53f5464c8ee7abc311765dc3ecb1b879c6 There is generated code in: - README (this file) diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/Spacing.java b/ReactAndroid/src/main/java/com/facebook/csslayout/Spacing.java index eaab77eb41ac0a..25a555fee46943 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/Spacing.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/Spacing.java @@ -1,14 +1,12 @@ /** * 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<<3177826257fea8b5ac1fc9d1d514935a>> - package com.facebook.csslayout; import javax.annotation.Nullable; diff --git a/ReactAndroid/src/main/java/com/facebook/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/jni/BUCK index 220371dbaae689..a5eb40afc3db91 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/jni/BUCK @@ -3,13 +3,15 @@ include_defs('//ReactAndroid/DEFS') android_library( name = 'jni', srcs = glob(['**/*.java']), + proguard_config = 'fbjni.pro', deps = [ react_native_dep('java/com/facebook/proguard/annotations:annotations'), react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), + react_native_dep('third-party/java/jsr-305:jsr-305'), ], visibility = [ 'PUBLIC', - ], + ] ) project_config( diff --git a/ReactAndroid/src/main/java/com/facebook/jni/Countable.java b/ReactAndroid/src/main/java/com/facebook/jni/Countable.java index 75892f48ca638c..a319e187aac35f 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/Countable.java +++ b/ReactAndroid/src/main/java/com/facebook/jni/Countable.java @@ -1,15 +1,9 @@ -/** - * 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. - */ +// Copyright 2004-present Facebook. All Rights Reserved. package com.facebook.jni; import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; /** * A Java Object that has native memory allocated corresponding to this instance. @@ -23,14 +17,15 @@ */ @DoNotStrip public class Countable { + + static { + SoLoader.loadLibrary("fb"); + } + // Private C++ instance @DoNotStrip private long mInstance = 0; - public Countable() { - Prerequisites.ensure(); - } - public native void dispose(); protected void finalize() throws Throwable { diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java b/ReactAndroid/src/main/java/com/facebook/jni/CppException.java deleted file mode 100644 index a0c845dd62878a..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java b/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java index fcb4ca33623fdd..e0b864b08d1d38 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java +++ b/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java @@ -1,15 +1,9 @@ -/** - * 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. - */ +// Copyright 2004-present Facebook. All Rights Reserved. package com.facebook.jni; import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; /** * This object holds a native C++ member for hybrid Java/C++ objects. @@ -24,14 +18,15 @@ */ @DoNotStrip public class HybridData { + + static { + SoLoader.loadLibrary("fb"); + } + // 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 @@ -47,4 +42,8 @@ protected void finalize() throws Throwable { resetNative(); super.finalize(); } + + public boolean isValid() { + return mNativePointer != 0; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/jni/IteratorHelper.java b/ReactAndroid/src/main/java/com/facebook/jni/IteratorHelper.java new file mode 100644 index 00000000000000..aca1cb50fb04e9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/IteratorHelper.java @@ -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.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +import javax.annotation.Nullable; + +import java.util.Iterator; + +/** + * To iterate over an Iterator from C++ requires two calls per entry: hasNext() + * and next(). This helper reduces it to one call and one field get per entry. + * It does not use a generic argument, since in C++, the types will be erased, + * anyway. This is *not* a {@link java.util.Iterator}. + */ +@DoNotStrip +public class IteratorHelper { + private final Iterator mIterator; + + // This is private, but accessed via JNI. + @DoNotStrip + private @Nullable Object mElement; + + @DoNotStrip + public IteratorHelper(Iterator iterator) { + mIterator = iterator; + } + + @DoNotStrip + public IteratorHelper(Iterable iterable) { + mIterator = iterable.iterator(); + } + + /** + * Moves the helper to the next entry in the map, if any. Returns true iff + * there is an entry to read. + */ + @DoNotStrip + boolean hasNext() { + if (mIterator.hasNext()) { + mElement = mIterator.next(); + return true; + } else { + mElement = null; + return false; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/MapIteratorHelper.java b/ReactAndroid/src/main/java/com/facebook/jni/MapIteratorHelper.java new file mode 100644 index 00000000000000..aa9283ed2f898a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/MapIteratorHelper.java @@ -0,0 +1,53 @@ +/** + * 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 javax.annotation.Nullable; + +import java.util.Iterator; +import java.util.Map; + +import com.facebook.proguard.annotations.DoNotStrip; + +/** + * To iterate over a Map from C++ requires four calls per entry: hasNext(), + * next(), getKey(), getValue(). This helper reduces it to one call and two + * field gets per entry. It does not use a generic argument, since in C++, the + * types will be erased, anyway. This is *not* a {@link java.util.Iterator}. + */ +@DoNotStrip +public class MapIteratorHelper { + @DoNotStrip private final Iterator<Map.Entry> mIterator; + @DoNotStrip private @Nullable Object mKey; + @DoNotStrip private @Nullable Object mValue; + + @DoNotStrip + public MapIteratorHelper(Map map) { + mIterator = map.entrySet().iterator(); + } + + /** + * Moves the helper to the next entry in the map, if any. Returns true iff + * there is an entry to read. + */ + @DoNotStrip + boolean hasNext() { + if (mIterator.hasNext()) { + Map.Entry entry = mIterator.next(); + mKey = entry.getKey(); + mValue = entry.getValue(); + return true; + } else { + mKey = null; + mValue = null; + return false; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java b/ReactAndroid/src/main/java/com/facebook/jni/NativeRunnable.java similarity index 57% rename from ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java rename to ReactAndroid/src/main/java/com/facebook/jni/NativeRunnable.java index 13090a18ce30a3..151cc8ad80f0a3 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java +++ b/ReactAndroid/src/main/java/com/facebook/jni/NativeRunnable.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * @@ -9,19 +9,20 @@ package com.facebook.jni; +import com.facebook.jni.HybridData; import com.facebook.proguard.annotations.DoNotStrip; +/** + * A Runnable that has a native run implementation. + */ @DoNotStrip -public class CppSystemErrorException extends CppException { - int errorCode; +public class NativeRunnable implements Runnable { - @DoNotStrip - public CppSystemErrorException(String message, int errorCode) { - super(message); - this.errorCode = errorCode; - } + private final HybridData mHybridData; - public int getErrorCode() { - return errorCode; + private NativeRunnable(HybridData hybridData) { + mHybridData = hybridData; } + + public native void run(); } diff --git a/ReactAndroid/src/main/java/com/facebook/jni/Prerequisites.java b/ReactAndroid/src/main/java/com/facebook/jni/Prerequisites.java deleted file mode 100644 index 5f279d0b2a6c7d..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/jni/Prerequisites.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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("fb"); - } - - // 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; - } -} - diff --git a/ReactAndroid/src/main/java/com/facebook/jni/ThreadScopeSupport.java b/ReactAndroid/src/main/java/com/facebook/jni/ThreadScopeSupport.java new file mode 100644 index 00000000000000..89610c43798d66 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/ThreadScopeSupport.java @@ -0,0 +1,22 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; + +@DoNotStrip +public class ThreadScopeSupport { + static { + SoLoader.loadLibrary("fb"); + } + + // This is just used for ThreadScope::withClassLoader to have a java function + // in the stack so that jni has access to the correct classloader. + @DoNotStrip + private static void runStdFunction(long ptr) { + runStdFunctionImpl(ptr); + } + + private static native void runStdFunctionImpl(long ptr); +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java b/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java deleted file mode 100644 index 45e9bfe0cee0dd..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/fbjni.pro b/ReactAndroid/src/main/java/com/facebook/jni/fbjni.pro new file mode 100644 index 00000000000000..5b5b6454d32adf --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/fbjni.pro @@ -0,0 +1,11 @@ +# For common use cases for the hybrid pattern, keep symbols which may +# be referenced only from C++. + +-keepclassmembers class * { + com.facebook.jni.HybridData *; + <init>(com.facebook.jni.HybridData); +} + +-keepclasseswithmembers class * { + com.facebook.jni.HybridData *; +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 71818459b393a0..4aa08512f627ad 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -23,6 +23,7 @@ import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.cxxbridge.JSBundleLoader; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.devsupport.DevSupportManager; import com.facebook.react.devsupport.RedBoxHandler; @@ -31,7 +32,7 @@ import com.facebook.react.uimanager.ViewManager; /** - * This class is managing instances of {@link CatalystInstance}. It expose a way to configure + * This class is managing instances of {@link CatalystInstance}. It exposes 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. @@ -126,6 +127,11 @@ public abstract void onHostResume( */ public abstract String getSourceUrl(); + /** + * The JS Bundle file that this Instance Manager was constructed with. + */ + public abstract @Nullable String getJSBundleFile(); + /** * Attach given {@param rootView} to a catalyst instance manager and start JS application using * JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently @@ -184,6 +190,7 @@ public static class Builder { protected final List<ReactPackage> mPackages = new ArrayList<>(); protected @Nullable String mJSBundleFile; + protected @Nullable JSBundleLoader mJSBundleLoader; protected @Nullable String mJSMainModuleName; protected @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; protected @Nullable Application mApplication; @@ -225,6 +232,19 @@ public Builder setBundleAssetName(String bundleAssetName) { */ public Builder setJSBundleFile(String jsBundleFile) { mJSBundleFile = jsBundleFile; + mJSBundleLoader = null; + return this; + } + + /** + * Bundle loader to use when setting up JS environment. This supersedes + * prior invcations of {@link setJSBundleFile} and {@link setBundleAssetName}. + * + * Example: {@code JSBundleLoader.createFileLoader(application, bundleFile)} + */ + public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) { + mJSBundleLoader = jsBundleLoader; + mJSBundleFile = null; return this; } @@ -326,12 +346,20 @@ public Builder setUseOldBridge(boolean enable) { * </ul> */ public ReactInstanceManager build() { + Assertions.assertNotNull( + mApplication, + "Application property has not been set with this builder"); + Assertions.assertCondition( - mUseDeveloperSupport || mJSBundleFile != null, + mJSBundleLoader == null || !mUseOldBridge, + "JSBundleLoader can't be used with the old bridge"); + + Assertions.assertCondition( + mUseDeveloperSupport || mJSBundleFile != null || mJSBundleLoader != null, "JS Bundle File has to be provided when dev support is disabled"); Assertions.assertCondition( - mJSMainModuleName != null || mJSBundleFile != null, + mJSMainModuleName != null || mJSBundleFile != null || mJSBundleLoader != null, "Either MainModuleName or JS Bundle File needs to be provided"); if (mUIImplementationProvider == null) { @@ -341,9 +369,7 @@ public ReactInstanceManager build() { if (mUseOldBridge) { return new ReactInstanceManagerImpl( - Assertions.assertNotNull( - mApplication, - "Application property has not been set with this builder"), + mApplication, mCurrentActivity, mDefaultHardwareBackBtnHandler, mJSBundleFile, @@ -358,12 +384,11 @@ public ReactInstanceManager build() { mRedBoxHandler); } else { return new XReactInstanceManagerImpl( - Assertions.assertNotNull( - mApplication, - "Application property has not been set with this builder"), + mApplication, mCurrentActivity, mDefaultHardwareBackBtnHandler, - mJSBundleFile, + (mJSBundleLoader == null && mJSBundleFile != null) ? + JSBundleLoader.createFileLoader(mApplication, mJSBundleFile) : mJSBundleLoader, mJSMainModuleName, mPackages, mUseDeveloperSupport, diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index 68d024369b8803..d33fd10d2acfe4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -80,8 +80,8 @@ import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START; /** - * 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 + * This class manages instances of {@link CatalystInstance}. It exposes a way to configure + * catalyst instances 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. * @@ -629,6 +629,11 @@ public String getSourceUrl() { return Assertions.assertNotNull(mSourceUrl); } + @Override + public @Nullable String getJSBundleFile() { + return mJSBundleFile; + } + /** * Attach given {@param rootView} to a catalyst instance manager and start JS application using * JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java index 54e893f1e4e43d..b302024426b3bb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -61,7 +61,6 @@ public void clear() { protected ReactInstanceManager createReactInstanceManager() { ReactInstanceManager.Builder builder = ReactInstanceManager.builder() - .setUseOldBridge(true) .setApplication(mApplication) .setJSMainModuleName(getJSMainModuleName()) .setUseDeveloperSupport(getUseDeveloperSupport()) diff --git a/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java index e510d129cbb9c6..adc4ff8d454938 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java @@ -109,7 +109,7 @@ private @Nullable ReactContextInitAsyncTask mReactContextInitAsyncTask; /* accessed from any thread */ - private @Nullable String mJSBundleFile; /* path to JS bundle on file system */ + private final @Nullable JSBundleLoader mBundleLoader; /* path to JS bundle on file system */ private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */ private final List<ReactPackage> mPackages; private final DevSupportManager mDevSupportManager; @@ -275,7 +275,7 @@ public T get() throws Exception { Context applicationContext, @Nullable Activity currentActivity, @Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler, - @Nullable String jsBundleFile, + @Nullable JSBundleLoader bundleLoader, @Nullable String jsMainModuleName, List<ReactPackage> packages, boolean useDeveloperSupport, @@ -295,7 +295,7 @@ public T get() throws Exception { mApplicationContext = applicationContext; mCurrentActivity = currentActivity; mDefaultBackButtonImpl = defaultHardwareBackBtnHandler; - mJSBundleFile = jsBundleFile; + mBundleLoader = bundleLoader; mJSMainModuleName = jsMainModuleName; mPackages = packages; mUseDeveloperSupport = useDeveloperSupport; @@ -382,7 +382,7 @@ private void recreateReactContextInBackgroundInner() { // If there is a up-to-date bundle downloaded from server, // with remote JS debugging disabled, always use that. onJSBundleLoadedFromServer(); - } else if (mJSBundleFile == null) { + } else if (mBundleLoader == null) { mDevSupportManager.handleReloadJS(); } else { mDevSupportManager.isPackagerRunning( @@ -398,7 +398,7 @@ public void run() { } else { // If dev server is down, disable the remote JS debugging. devSettings.setRemoteJSDebugEnabled(false); - recreateReactContextInBackgroundFromBundleFile(); + recreateReactContextInBackgroundFromBundleLoader(); } } }); @@ -408,13 +408,13 @@ public void run() { return; } - recreateReactContextInBackgroundFromBundleFile(); + recreateReactContextInBackgroundFromBundleLoader(); } - private void recreateReactContextInBackgroundFromBundleFile() { + private void recreateReactContextInBackgroundFromBundleLoader() { recreateReactContextInBackground( new JSCJavaScriptExecutor.Factory(mJSCConfig.getConfigMap()), - JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile)); + mBundleLoader); } /** @@ -610,6 +610,14 @@ public String getSourceUrl() { return Assertions.assertNotNull(mSourceUrl); } + @Override + public @Nullable String getJSBundleFile() { + if (mBundleLoader == null) { + return null; + } + return mBundleLoader.getSourceUrl(); + } + /** * Attach given {@param rootView} to a catalyst instance manager and start JS application using * JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index 2987512efc1953..46235d2230b209 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -32,8 +32,7 @@ void callFunction( ExecutorToken executorToken, String module, String method, - NativeArray arguments, - String tracingName); + NativeArray arguments); /** * Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration * (besides the UI thread) to finish running. Must be called from the UI thread so that we can diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 8def48cb504973..7e4683a3765095 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -164,8 +164,7 @@ public void callFunction( ExecutorToken executorToken, String module, String method, - NativeArray arguments, - String tracingName) { + NativeArray arguments) { if (mIsBeingDestroyed) { FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed."); return; @@ -178,10 +177,11 @@ public void callFunction( incrementPendingJSCalls(); - Assertions.assertNotNull(mBridge).callFunction(executorToken, - module, - method, arguments, tracingName); - } + Assertions.assertNotNull(mBridge).callFunction( + executorToken, + module, + method, arguments); + } } // This is called from java code, so it won't be stripped anyway, but proguard will rename it, diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSPackagerWebSocketClient.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSPackagerWebSocketClient.java index e3379017f6cc83..04759d15589803 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSPackagerWebSocketClient.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSPackagerWebSocketClient.java @@ -11,10 +11,11 @@ import javax.annotation.Nullable; import java.io.IOException; -import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.TimeUnit; +import android.os.Handler; +import android.os.Looper; + import com.facebook.common.logging.FLog; import com.fasterxml.jackson.core.JsonFactory; @@ -33,9 +34,13 @@ * A wrapper around WebSocketClient that recognizes packager's message format. */ public class JSPackagerWebSocketClient implements WebSocketListener { - private static final String TAG = "JSPackagerWebSocketClient"; + + private static final int RECONNECT_DELAY_MS = 2000; + private final String mUrl; + private final Handler mHandler; + private boolean mSuppressConnectionErrors; public interface JSPackagerCallback { void onMessage(String target, String action); @@ -48,6 +53,7 @@ public JSPackagerWebSocketClient(String url, JSPackagerCallback callback) { super(); mUrl = url; mCallback = callback; + mHandler = new Handler(Looper.getMainLooper()); } public void connect() { @@ -63,12 +69,17 @@ public void connect() { } private void reconnect() { - new Timer().schedule(new TimerTask() { - @Override - public void run() { - connect(); - } - }, 2000); + if (!mSuppressConnectionErrors) { + FLog.w(TAG, "Couldn't connect to packager, will silently retry"); + mSuppressConnectionErrors = true; + } + mHandler.postDelayed( + new Runnable() { + @Override + public void run() { + connect(); + } + }, RECONNECT_DELAY_MS); } public void closeQuietly() { @@ -137,13 +148,16 @@ public void onMessage(ResponseBody response) throws IOException { @Override public void onFailure(IOException e, Response response) { - abort("Websocket exception", e); + if (mWebSocket != null) { + abort("Websocket exception", e); + } reconnect(); } @Override public void onOpen(WebSocket webSocket, Response response) { mWebSocket = webSocket; + mSuppressConnectionErrors = false; } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistration.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistration.java index 42b518e472218d..afa32d964b7827 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistration.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistration.java @@ -13,10 +13,8 @@ import java.lang.reflect.Method; import java.util.Arrays; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; import com.facebook.react.common.build.ReactBuildConfig; @@ -28,11 +26,9 @@ public class JavaScriptModuleRegistration { private final Class<? extends JavaScriptModule> mModuleInterface; - private final Map<Method, String> mMethodsToTracingNames; public JavaScriptModuleRegistration(Class<? extends JavaScriptModule> moduleInterface) { mModuleInterface = moduleInterface; - mMethodsToTracingNames = new HashMap<>(); if (ReactBuildConfig.DEBUG) { Set<String> methodNames = new LinkedHashSet<>(); @@ -46,15 +42,6 @@ public JavaScriptModuleRegistration(Class<? extends JavaScriptModule> moduleInte } } - public String getTracingName(Method method) { - String name = mMethodsToTracingNames.get(method); - if (name == null) { - name = "JSCall__" + getName() + "_" + method.getName(); - mMethodsToTracingNames.put(method, name); - } - return name; - } - public Class<? extends JavaScriptModule> getModuleInterface() { return mModuleInterface; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java index 466afcccf58778..e14bc332fc462f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java @@ -106,14 +106,13 @@ public JavaScriptModuleInvocationHandler( FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away..."); return null; } - String tracingName = mModuleRegistration.getTracingName(method); NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray(); mCatalystInstance.callFunction( executorToken, mModuleRegistration.getName(), method.getName(), - jsArgs, - tracingName); + jsArgs + ); return null; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java index 2e69e78f94075b..d966ce2ff51111 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java @@ -88,7 +88,7 @@ private native void initialize( */ public native void loadScriptFromAssets(AssetManager assetManager, String assetName); public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL); - public native void callFunction(ExecutorToken executorToken, String module, String method, NativeArray arguments, String tracingName); + public native void callFunction(ExecutorToken executorToken, String module, String method, NativeArray arguments); public native void invokeCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments); public native void setGlobalVariable(String propertyName, String jsonEncodedArgument); public native boolean supportsProfiling(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/SystemClock.java b/ReactAndroid/src/main/java/com/facebook/react/common/SystemClock.java index 29c31b416c5f75..348855edbbfa0f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/SystemClock.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/SystemClock.java @@ -22,4 +22,9 @@ public static long currentTimeMillis() { public static long nanoTime() { return System.nanoTime(); } + + public static long uptimeMillis() { + return android.os.SystemClock.uptimeMillis(); + } + } diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java index b23335e05cefa5..7bef0a38b70796 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java @@ -163,6 +163,7 @@ private native void initializeBridge(ReactCallback callback, /* package */ native void loadScriptFromAssets(AssetManager assetManager, String assetURL); /* package */ native void loadScriptFromFile(String fileName, String sourceURL); + /* package */ native void loadScriptFromOptimizedBundle(String path, String sourceURL, int flags); @Override public void runJSBundle() { @@ -182,16 +183,14 @@ private native void callJSFunction( ExecutorToken token, String module, String method, - NativeArray arguments, - String tracingName); + NativeArray arguments); @Override public void callFunction( ExecutorToken executorToken, final String module, final String method, - final NativeArray arguments, - final String tracingName) { + final NativeArray arguments) { if (mDestroyed) { FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed."); return; @@ -200,7 +199,7 @@ public void callFunction( throw new RuntimeException("Attempt to call JS function before JS bundle is loaded."); } - callJSFunction(executorToken, module, method, arguments, tracingName); + callJSFunction(executorToken, module, method, arguments); } private native void callJSCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments); diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSBundleLoader.java index c55b30ce692049..311b4fa41c3074 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSBundleLoader.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSBundleLoader.java @@ -14,6 +14,8 @@ import com.facebook.react.devsupport.DebugServerException; import com.facebook.react.devsupport.DevServerHelper; +import java.io.File; + /** * A class that stores JS bundle information and allows {@link CatalystInstance} to load a correct * bundle through {@link ReactBridge}. @@ -92,6 +94,19 @@ public String getSourceUrl() { }; } + public static JSBundleLoader createUnpackingBundleLoader( + final Context context, + final String sourceURL, + final String bundleName) { + return UnpackingJSBundleLoader.newBuilder() + .setContext(context) + .setSourceURL(sourceURL) + .setDestinationPath(new File(context.getFilesDir(), "optimized-bundle")) + .checkAndUnpackFile(bundleName + ".meta", "bundle.meta") + .unpackFile(bundleName, "bundle.js") + .build(); + } + public abstract void loadScript(CatalystInstanceImpl instance); public abstract String getSourceUrl(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoader.java new file mode 100644 index 00000000000000..722b3c59668cf4 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoader.java @@ -0,0 +1,348 @@ +/** + * 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.cxxbridge; + +import android.content.Context; +import android.content.res.AssetManager; + +import com.facebook.infer.annotation.Assertions; +import com.facebook.soloader.FileLocker; +import com.facebook.soloader.SysUtil; +import com.facebook.systrace.Systrace; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.Arrays; + +import javax.annotation.Nullable; + +import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; + +/** + * JSBundleLoader capable of unpacking specified files necessary for executing + * JS bundle stored in optimized format. + */ +public class UnpackingJSBundleLoader extends JSBundleLoader { + + /** + * Flag passed to loadScriptFromOptimizedBundle to let the bridge know that + * the unpacked unpacked js source file. + */ + static final int UNPACKED_JS_SOURCE = (1 << 0); + + /** + * Name of the lock files. Multiple processes can be spawned off the same app + * and we need to guarantee that at most one unpacks files at any time. To + * make that work any process is required to hold file system lock on + * LOCK_FILE when checking whether files should be unpacked and unpacking + * them. + */ + static final String LOCK_FILE = "unpacking-bundle-loader.lock"; + + /** + * Existence of this file indicates that the last unpacking operation finished + * before the app was killed or crashed. File with this name is created in the + * destination directory as the last one. If it is present it means that + * all the files that needed to be fsynced were fsynced and their content is + * what it should be. + */ + static final String DOT_UNPACKED_FILE = ".unpacked"; + + private static final int IO_BUFFER_SIZE = 16 * 1024; + + /** + * Where all the files should go to. + */ + private final File mDirectoryPath; + + private final String mSourceURL; + private final Context mContext; + + /** + * Description of what needs to be unpacked. + */ + private final Unpacker[] mUnpackers; + + /* package */ UnpackingJSBundleLoader(Builder builder) { + mContext = Assertions.assertNotNull(builder.context); + mDirectoryPath = Assertions.assertNotNull(builder.destinationPath); + mSourceURL = Assertions.assertNotNull(builder.sourceURL); + mUnpackers = builder.unpackers.toArray(new Unpacker[builder.unpackers.size()]); + } + + /** + * Checks if any file needs to be extracted again, and if so, clears the destination + * directory and unpacks everything again. + */ + /* package */ void prepare() { + final File lockFilePath = new File(mContext.getFilesDir(), LOCK_FILE); + Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "UnpackingJSBundleLoader.prepare"); + try (FileLocker lock = FileLocker.lock(lockFilePath)) { + prepareLocked(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } finally { + Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + + private void prepareLocked() throws IOException { + final File dotFinishedFilePath = new File(mDirectoryPath, DOT_UNPACKED_FILE); + boolean shouldReconstruct = !mDirectoryPath.exists() || !dotFinishedFilePath.exists(); + + byte[] buffer = new byte[IO_BUFFER_SIZE]; + for (int i = 0; i < mUnpackers.length && !shouldReconstruct; ++i) { + shouldReconstruct = mUnpackers[i].shouldReconstructDir(mContext, buffer); + } + + if (!shouldReconstruct) { + return; + } + + boolean succeeded = false; + try { + SysUtil.dumbDeleteRecursive(mDirectoryPath); + if (!mDirectoryPath.mkdirs()) { + throw new IOException("Coult not create the destination directory"); + } + + for (Unpacker unpacker : mUnpackers) { + unpacker.unpack(mContext, buffer); + } + + if (!dotFinishedFilePath.createNewFile()) { + throw new IOException("Could not create .unpacked file"); + } + + // It would be nice to fsync a few directories and files here. The thing is, if we crash and + // lose some data then it should be noticed on the next prepare invocation and the directory + // will be reconstructed. It is only crucial to fsync those files whose content is not + // verified on each start. Everything else is a tradeoff between perf with no crashes + // situation and perf when user experiences crashes. Fortunately Unpackers corresponding + // to files whose content is not checked handle fsyncs themselves. + + succeeded = true; + } finally { + // In case of failure do yourself a favor and remove partially initialized state. + if (!succeeded) { + SysUtil.dumbDeleteRecursive(mDirectoryPath); + } + } + } + + @Override + public void loadScript(CatalystInstanceImpl instance) { + prepare(); + instance.loadScriptFromOptimizedBundle( + mDirectoryPath.getPath(), + mSourceURL, + UNPACKED_JS_SOURCE); + } + + @Override + public String getSourceUrl() { + return mSourceURL; + } + + static void fsync(File path) throws IOException { + try (RandomAccessFile file = new RandomAccessFile(path, "r")) { + file.getFD().sync(); + } + } + + /** + * Reads all the bytes (but no more that maxSize) from given input stream through ioBuffer + * and returns byte array containing all the read bytes. + */ + static byte[] readBytes(InputStream is, byte[] ioBuffer, int maxSize) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copyBytes(baos, is, ioBuffer, maxSize); + return baos.toByteArray(); + } + + /** + * Pumps all the bytes (but no more that maxSize) from given input stream through ioBuffer + * to given output stream and returns number of moved bytes. + */ + static int copyBytes( + OutputStream os, + InputStream is, + byte[] ioBuffer, + int maxSize) throws IOException { + int totalSize = 0; + while (totalSize < maxSize) { + int rc = is.read(ioBuffer, 0, Math.min(maxSize - totalSize, ioBuffer.length)); + if (rc == -1) { + break; + } + os.write(ioBuffer, 0, rc); + totalSize += rc; + } + return totalSize; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + private @Nullable Context context; + private @Nullable File destinationPath; + private @Nullable String sourceURL; + private final ArrayList<Unpacker> unpackers; + + public Builder() { + this.unpackers = new ArrayList<Unpacker>(); + context = null; + destinationPath = null; + sourceURL = null; + } + + public Builder setContext(Context context) { + this.context = context; + return this; + } + + public Builder setDestinationPath(File destinationPath) { + this.destinationPath = destinationPath; + return this; + } + + public Builder setSourceURL(String sourceURL) { + this.sourceURL = sourceURL; + return this; + } + + /** + * Adds a file for unpacking. Content of extracted file is not checked on each + * start against content of the file bundled in apk. + */ + public Builder unpackFile(String nameInApk, String destFileName) { + unpackers.add(new ExistenceCheckingUnpacker(nameInApk, destFileName)); + return this; + } + + /** + * Adds a file for unpacking. Content of extracted file is compared on each + * start with content of the same file bundled in apk. It is usefull for + * detecting bundle/app changes. + */ + public Builder checkAndUnpackFile(String nameInApk, String destFileName) { + unpackers.add(new ContentCheckingUnpacker(nameInApk, destFileName)); + return this; + } + + /** + * Adds arbitrary unpacker. Usefull for injecting mocks. + */ + Builder addUnpacker(Unpacker u) { + unpackers.add(u); + return this; + } + + public UnpackingJSBundleLoader build() { + Assertions.assertNotNull(destinationPath); + for (int i = 0; i < unpackers.size(); ++i) { + unpackers.get(i).setDestinationDirectory(destinationPath); + } + return new UnpackingJSBundleLoader(this); + } + } + + /** + * Abstraction for dealing with unpacking single file from apk. + */ + static abstract class Unpacker { + protected final String mNameInApk; + private final String mFileName; + protected @Nullable File mDestinationFilePath; + + public Unpacker(String nameInApk, String fileName) { + mNameInApk = nameInApk; + mFileName = fileName; + } + + public void setDestinationDirectory(File destinationDirectoryPath) { + mDestinationFilePath = new File(destinationDirectoryPath, mFileName); + } + + public abstract boolean shouldReconstructDir(Context context, byte[] ioBuffer) + throws IOException; + + public void unpack(Context context, byte[] ioBuffer) throws IOException { + AssetManager am = context.getAssets(); + try (InputStream is = am.open(mNameInApk, AssetManager.ACCESS_STREAMING)) { + try (FileOutputStream fileOutputStream = new FileOutputStream( + Assertions.assertNotNull(mDestinationFilePath))) { + copyBytes(fileOutputStream, is, ioBuffer, Integer.MAX_VALUE); + } + } + } + } + + /** + * Deals with unpacking files whose content is not checked on each start and + * need to be fsynced after unpacking. + */ + static class ExistenceCheckingUnpacker extends Unpacker { + public ExistenceCheckingUnpacker(String nameInApk, String fileName) { + super(nameInApk, fileName); + } + + @Override + public boolean shouldReconstructDir(Context context, byte[] ioBuffer) { + return !Assertions.assertNotNull(mDestinationFilePath).exists(); + } + + @Override + public void unpack(Context context, byte[] ioBuffer) throws IOException { + super.unpack(context, ioBuffer); + fsync(Assertions.assertNotNull(mDestinationFilePath)); + } + } + + /** + * Deals with unpacking files whose content is checked on each start and thus + * do not require fsync. + */ + static class ContentCheckingUnpacker extends Unpacker { + public ContentCheckingUnpacker(String nameInApk, String fileName) { + super(nameInApk, fileName); + } + + @Override + public boolean shouldReconstructDir(Context context, byte[] ioBuffer) throws IOException { + if (!Assertions.assertNotNull(mDestinationFilePath).exists()) { + return true; + } + + AssetManager am = context.getAssets(); + final byte[] assetContent; + try (InputStream assetStream = am.open(mNameInApk, AssetManager.ACCESS_STREAMING)) { + assetContent = readBytes(assetStream, ioBuffer, Integer.MAX_VALUE); + } + + final byte[] fileContent; + try (InputStream fileStream = new FileInputStream( + Assertions.assertNotNull(mDestinationFilePath))) { + fileContent = readBytes(fileStream, ioBuffer, assetContent.length + 1); + } + + return !Arrays.equals(assetContent, fileContent); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index dd823f84ada663..8bb5423e9c471a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -79,6 +79,7 @@ public class DevSupportManagerImpl implements DevSupportManager { private static final int JAVA_ERROR_COOKIE = -1; + private static final int JSEXCEPTION_ERROR_COOKIE = -1; private static final String JS_BUNDLE_FILE_NAME = "ReactNativeDevBundle.js"; private static enum ErrorType { JS, @@ -194,7 +195,13 @@ public void onReceive(Context context, Intent intent) { public void handleException(Exception e) { if (mIsDevSupportEnabled) { FLog.e(ReactConstants.TAG, "Exception in native call from JS", e); - showNewJavaError(e.getMessage(), e); + if (e instanceof JSException) { + // TODO #11638796: convert the stack into something useful + showNewError(e.getMessage() + "\n\n" + ((JSException) e).getStack(), new StackFrame[] {}, + JSEXCEPTION_ERROR_COOKIE, ErrorType.JS); + } else { + showNewJavaError(e.getMessage(), e); + } } else { mDefaultNativeModuleCallExceptionHandler.handleException(e); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSException.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSException.java new file mode 100644 index 00000000000000..ca83f57bb7b7da --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSException.java @@ -0,0 +1,37 @@ +/** + * 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.devsupport; + +import com.facebook.proguard.annotations.DoNotStrip; + +/** + * This represents an error evaluating JavaScript. It includes the usual + * message, and the raw JS stack where the error occurred (which may be empty). + */ + +@DoNotStrip +public class JSException extends Exception { + private final String mStack; + + @DoNotStrip + public JSException(String message, String stack, Throwable cause) { + super(message, cause); + mStack = stack; + } + + public JSException(String message, String stack) { + super(message); + mStack = stack; + } + + public String getStack() { + return mStack; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java index 007f3607ae6f64..a2b1bb2a8f5e86 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java @@ -9,6 +9,8 @@ package com.facebook.react.devsupport; +import javax.annotation.Nullable; + import java.io.File; import com.facebook.react.bridge.ReadableArray; @@ -91,9 +93,10 @@ public String getFileName() { * Convert a JavaScript stack trace (see {@code parseErrorStack} JS module) to an array of * {@link StackFrame}s. */ - public static StackFrame[] convertJsStackTrace(ReadableArray stack) { - StackFrame[] result = new StackFrame[stack.size()]; - for (int i = 0; i < stack.size(); i++) { + public static StackFrame[] convertJsStackTrace(@Nullable ReadableArray stack) { + int size = stack != null ? stack.size() : 0; + StackFrame[] result = new StackFrame[size]; + for (int i = 0; i < size; i++) { ReadableMap frame = stack.getMap(i); String methodName = frame.getString("methodName"); String fileName = frame.getString("file"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java index 9e0ba2a3d72893..e8ff7c906482f1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java @@ -15,6 +15,7 @@ @SupportsWebWorkers public interface JSTimersExecution extends JavaScriptModule { - - public void callTimers(WritableArray timerIDs); + void callTimers(WritableArray timerIDs); + void callIdleCallbacks(double frameTime); + void emitTimeDriftWarning(String warningMessage); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java index d0ee51cfcd191f..2fc348e8d5c4b5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java @@ -9,14 +9,6 @@ package com.facebook.react.modules.core; -import javax.annotation.Nullable; - -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; -import java.util.PriorityQueue; -import java.util.concurrent.atomic.AtomicBoolean; - import android.util.SparseArray; import android.view.Choreographer; @@ -28,17 +20,38 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableArray; import com.facebook.react.common.SystemClock; import com.facebook.react.devsupport.DevSupportManager; import com.facebook.react.uimanager.ReactChoreographer; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.Nullable; + /** * Native module for JS timer execution. Timers fire on frame boundaries. */ public final class Timing extends ReactContextBaseJavaModule implements LifecycleEventListener, OnExecutorUnregisteredListener { + // These timing contants should be kept in sync with the ones in `JSTimersExecution.js`. + // The minimum time in milliseconds left in the frame to call idle callbacks. + private static final float IDLE_CALLBACK_FRAME_DEADLINE_MS = 1.f; + // The total duration of a frame in milliseconds, this assumes that devices run at 60 fps. + // TODO: Lower frame duration on devices that are too slow to run consistently + // at 60 fps. + private static final float FRAME_DURATION_MS = 1000.f / 60.f; + private final DevSupportManager mDevSupportManager; private static class Timer { @@ -63,7 +76,7 @@ private Timer( } } - private class FrameCallback implements Choreographer.FrameCallback { + private class TimerFrameCallback implements Choreographer.FrameCallback { // Temporary map for constructing the individual arrays of timers per ExecutorToken private final HashMap<ExecutorToken, WritableArray> mTimersToCall = new HashMap<>(); @@ -91,7 +104,7 @@ public void doFrame(long frameTimeNanos) { timer.mTargetTime = frameTimeMillis + timer.mInterval; mTimers.add(timer); } else { - mTimerIdsToTimers.remove(timer.mCallbackID); + mTimerIdsToTimers.remove(timer.mExecutorToken); } } } @@ -107,13 +120,84 @@ public void doFrame(long frameTimeNanos) { } } + private class IdleFrameCallback implements Choreographer.FrameCallback { + + @Override + public void doFrame(long frameTimeNanos) { + if (isPaused.get()) { + return; + } + + // If the JS thread is busy for multiple frames we cancel any other pending runnable. + if (mCurrentIdleCallbackRunnable != null) { + mCurrentIdleCallbackRunnable.cancel(); + } + + mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos); + getReactApplicationContext().runOnJSQueueThread(mCurrentIdleCallbackRunnable); + + Assertions.assertNotNull(mReactChoreographer).postFrameCallback( + ReactChoreographer.CallbackType.IDLE_EVENT, + this); + } + } + + private class IdleCallbackRunnable implements Runnable { + private volatile boolean mCancelled = false; + private final long mFrameStartTime; + + public IdleCallbackRunnable(long frameStartTime) { + mFrameStartTime = frameStartTime; + } + + @Override + public void run() { + if (mCancelled) { + return; + } + + long frameTimeMillis = mFrameStartTime / 1000000; + long timeSinceBoot = SystemClock.uptimeMillis(); + long frameTimeElapsed = timeSinceBoot - frameTimeMillis; + long time = SystemClock.currentTimeMillis(); + long absoluteFrameStartTime = time - frameTimeElapsed; + + if (FRAME_DURATION_MS - (float)frameTimeElapsed < IDLE_CALLBACK_FRAME_DEADLINE_MS) { + return; + } + + mIdleCallbackContextsToCall.clear(); + synchronized (mIdleCallbackGuard) { + mIdleCallbackContextsToCall.addAll(mSendIdleEventsExecutorTokens); + } + + for (ExecutorToken context : mIdleCallbackContextsToCall) { + getReactApplicationContext().getJSModule(context, JSTimersExecution.class) + .callIdleCallbacks(absoluteFrameStartTime); + } + + mCurrentIdleCallbackRunnable = null; + } + + public void cancel() { + mCancelled = true; + } + } + private final Object mTimerGuard = new Object(); + private final Object mIdleCallbackGuard = new Object(); private final PriorityQueue<Timer> mTimers; - private final HashMap<ExecutorToken, SparseArray<Timer>> mTimerIdsToTimers; + private final Map<ExecutorToken, SparseArray<Timer>> mTimerIdsToTimers; private final AtomicBoolean isPaused = new AtomicBoolean(true); - private final FrameCallback mFrameCallback = new FrameCallback(); + private final TimerFrameCallback mTimerFrameCallback = new TimerFrameCallback(); + private final IdleFrameCallback mIdleFrameCallback = new IdleFrameCallback(); + private @Nullable IdleCallbackRunnable mCurrentIdleCallbackRunnable; private @Nullable ReactChoreographer mReactChoreographer; private boolean mFrameCallbackPosted = false; + private boolean mFrameIdleCallbackPosted = false; + private final Set<ExecutorToken> mSendIdleEventsExecutorTokens; + // Temporary array used to dipatch idle callbacks on the JS thread. + private final List<ExecutorToken> mIdleCallbackContextsToCall; public Timing(ReactApplicationContext reactContext, DevSupportManager devSupportManager) { super(reactContext); @@ -135,6 +219,8 @@ public int compare(Timer lhs, Timer rhs) { } }); mTimerIdsToTimers = new HashMap<>(); + mSendIdleEventsExecutorTokens = new HashSet<>(); + mIdleCallbackContextsToCall = new ArrayList<>(); } @Override @@ -148,11 +234,13 @@ public void initialize() { public void onHostPause() { isPaused.set(true); clearChoreographerCallback(); + clearChoreographerIdleCallback(); } @Override public void onHostDestroy() { clearChoreographerCallback(); + clearChoreographerIdleCallback(); } @Override @@ -161,18 +249,25 @@ public void onHostResume() { // TODO(5195192) Investigate possible problems related to restarting all tasks at the same // moment setChoreographerCallback(); + + synchronized (mIdleCallbackGuard) { + if (mSendIdleEventsExecutorTokens.size() > 0) { + setChoreographerIdleCallback(); + } + } } @Override public void onCatalystInstanceDestroy() { clearChoreographerCallback(); + clearChoreographerIdleCallback(); } private void setChoreographerCallback() { if (!mFrameCallbackPosted) { Assertions.assertNotNull(mReactChoreographer).postFrameCallback( ReactChoreographer.CallbackType.TIMERS_EVENTS, - mFrameCallback); + mTimerFrameCallback); mFrameCallbackPosted = true; } } @@ -181,14 +276,32 @@ private void clearChoreographerCallback() { if (mFrameCallbackPosted) { Assertions.assertNotNull(mReactChoreographer).removeFrameCallback( ReactChoreographer.CallbackType.TIMERS_EVENTS, - mFrameCallback); + mTimerFrameCallback); mFrameCallbackPosted = false; } } + private void setChoreographerIdleCallback() { + if (!mFrameIdleCallbackPosted) { + Assertions.assertNotNull(mReactChoreographer).postFrameCallback( + ReactChoreographer.CallbackType.IDLE_EVENT, + mIdleFrameCallback); + mFrameIdleCallbackPosted = true; + } + } + + private void clearChoreographerIdleCallback() { + if (mFrameIdleCallbackPosted) { + Assertions.assertNotNull(mReactChoreographer).removeFrameCallback( + ReactChoreographer.CallbackType.IDLE_EVENT, + mIdleFrameCallback); + mFrameIdleCallbackPosted = false; + } + } + @Override public String getName() { - return "RKTiming"; + return "RCTTiming"; } @Override @@ -208,6 +321,10 @@ public void onExecutorDestroyed(ExecutorToken executorToken) { mTimers.remove(timer); } } + + synchronized (mIdleCallbackGuard) { + mSendIdleEventsExecutorTokens.remove(executorToken); + } } @ReactMethod @@ -226,11 +343,10 @@ public void createTimer( if (mDevSupportManager.getDevSupportEnabled()) { long driftTime = Math.abs(remoteTime - deviceTime); if (driftTime > 60000) { - throw new RuntimeException( - "Debugger and device times have drifted by more than 60s." + - "Please correct this by running adb shell " + - "\"date `date +%m%d%H%M%Y.%S`\" on your debugger machine." - ); + getReactApplicationContext().getJSModule(executorToken, JSTimersExecution.class) + .emitTimeDriftWarning( + "Debugger and device times have drifted by more than 60s. Please correct this by " + + "running adb shell \"date `date +%m%d%H%M%Y.%S`\" on your debugger machine."); } } @@ -269,8 +385,32 @@ public void deleteTimer(ExecutorToken executorToken, int timerId) { return; } // We may have already called/removed it - mTimerIdsToTimers.remove(timerId); + mTimerIdsToTimers.remove(executorToken); mTimers.remove(timer); } } + + @ReactMethod + public void setSendIdleEvents(ExecutorToken executorToken, boolean sendIdleEvents) { + synchronized (mIdleCallbackGuard) { + if (sendIdleEvents) { + mSendIdleEventsExecutorTokens.add(executorToken); + } else { + mSendIdleEventsExecutorTokens.remove(executorToken); + } + } + + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mIdleCallbackGuard) { + if (mSendIdleEventsExecutorTokens.size() > 0) { + setChoreographerIdleCallback(); + } else { + clearChoreographerIdleCallback(); + } + } + } + }); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/BUCK new file mode 100644 index 00000000000000..ea33bd1ee75861 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/BUCK @@ -0,0 +1,19 @@ +include_defs('//ReactAndroid/DEFS') + +android_library( + name = 'i18nmanager', + srcs = glob(['*.java']), + deps = [ + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/common:common'), + ], + visibility = [ + 'PUBLIC', + ], +) + +project_config( + src_target = ':i18nmanager', +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.java new file mode 100644 index 00000000000000..49f30ddbd0c180 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.java @@ -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.modules.i18nmanager; + +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.common.MapBuilder; + +import java.util.Map; + + +/** + * {@link NativeModule} that allows JS to set allowRTL and get isRTL status. + */ +public class I18nManagerModule extends ReactContextBaseJavaModule { + + private final I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); + + public I18nManagerModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return "I18nManager"; + } + + @Override + public Map<String, Object> getConstants() { + final Map<String, Object> constants = MapBuilder.newHashMap(); + constants.put("isRTL", sharedI18nUtilInstance.isRTL( + getReactApplicationContext() + )); + return constants; + } + + @ReactMethod + public void allowRTL(boolean value) { + sharedI18nUtilInstance.setAllowRTL( + getReactApplicationContext(), + value + ); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java new file mode 100644 index 00000000000000..e0199b26554b19 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java @@ -0,0 +1,53 @@ +/** + * 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.modules.i18nmanager; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; + +public class I18nUtil { + private static I18nUtil sharedI18nUtilInstance = null; + + private static final String MY_PREFS_NAME = + "com.facebook.react.modules.i18nmanager.I18nUtil"; + private static final String KEY_FOR_PREFS = + "RCTI18nUtil_allowRTL"; + + private I18nUtil() { + // Exists only to defeat instantiation. + } + + public static I18nUtil getInstance() { + if(sharedI18nUtilInstance == null) { + sharedI18nUtilInstance = new I18nUtil(); + } + return sharedI18nUtilInstance; + } + + // If set allowRTL on the JS side, + // the RN app will automatically have a RTL layout. + public boolean isRTL(Context context) { + return allowRTL(context); + } + + private boolean allowRTL(Context context) { + SharedPreferences prefs = + context.getSharedPreferences(MY_PREFS_NAME, Context.MODE_PRIVATE); + return prefs.getBoolean(KEY_FOR_PREFS, false); + } + + public void setAllowRTL(Context context, boolean allowRTL) { + SharedPreferences.Editor editor = + context.getSharedPreferences(MY_PREFS_NAME, Context.MODE_PRIVATE).edit(); + editor.putBoolean(KEY_FOR_PREFS, allowRTL); + editor.apply(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java index 34648f1f164f5a..54730146d2d6d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java @@ -9,7 +9,10 @@ package com.facebook.react.modules.image; +import javax.annotation.Nullable; + import android.net.Uri; +import android.util.SparseArray; import com.facebook.common.executors.CallerThreadExecutor; import com.facebook.common.references.CloseableReference; @@ -21,19 +24,23 @@ import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableMap; -public class ImageLoaderModule extends ReactContextBaseJavaModule { +public class ImageLoaderModule extends ReactContextBaseJavaModule implements + LifecycleEventListener { private static final String ERROR_INVALID_URI = "E_INVALID_URI"; private static final String ERROR_PREFETCH_FAILURE = "E_PREFETCH_FAILURE"; private static final String ERROR_GET_SIZE_FAILURE = "E_GET_SIZE_FAILURE"; private final Object mCallerContext; + private final Object mEnqueuedRequestMonitor = new Object(); + private final SparseArray<DataSource<Void>> mEnqueuedRequests = new SparseArray<>(); public ImageLoaderModule(ReactApplicationContext reactContext) { super(reactContext); @@ -50,9 +57,16 @@ public String getName() { return "ImageLoader"; } + /** + * Fetch the width and height of the given image. + * + * @param uriString the URI of the remote image to prefetch + * @param promise the promise that is fulfilled when the image is successfully prefetched + * or rejected when there is an error + */ @ReactMethod public void getSize( - String uriString, + final String uriString, final Promise promise) { if (uriString == null || uriString.isEmpty()) { promise.reject(ERROR_INVALID_URI, "Cannot get the size of an image for an empty URI"); @@ -105,11 +119,16 @@ protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> data * Prefetches the given image to the Fresco image disk cache. * * @param uriString the URI of the remote image to prefetch + * @param requestId the client-supplied request ID used to identify this request * @param promise the promise that is fulfilled when the image is successfully prefetched * or rejected when there is an error */ @ReactMethod - public void prefetchImage(String uriString, final Promise promise) { + public void prefetchImage( + final String uriString, + final int requestId, + final Promise promise) + { if (uriString == null || uriString.isEmpty()) { promise.reject(ERROR_INVALID_URI, "Cannot prefetch an image for an empty URI"); return; @@ -127,6 +146,7 @@ protected void onNewResultImpl(DataSource<Void> dataSource) { return; } try { + removeRequest(requestId); promise.resolve(true); } finally { dataSource.close(); @@ -136,12 +156,58 @@ protected void onNewResultImpl(DataSource<Void> dataSource) { @Override protected void onFailureImpl(DataSource<Void> dataSource) { try { + removeRequest(requestId); promise.reject(ERROR_PREFETCH_FAILURE, dataSource.getFailureCause()); } finally { dataSource.close(); } } }; + registerRequest(requestId, prefetchSource); prefetchSource.subscribe(prefetchSubscriber, CallerThreadExecutor.getInstance()); } + + @ReactMethod + public void abortRequest(final int requestId) { + DataSource<Void> request = removeRequest(requestId); + if (request != null) { + request.close(); + } + } + + private void registerRequest(int requestId, DataSource<Void> request) { + synchronized (mEnqueuedRequestMonitor) { + mEnqueuedRequests.put(requestId, request); + } + } + + private @Nullable DataSource<Void> removeRequest(int requestId) { + synchronized (mEnqueuedRequestMonitor) { + DataSource<Void> request = mEnqueuedRequests.get(requestId); + mEnqueuedRequests.remove(requestId); + return request; + } + } + + @Override + public void onHostResume() { + } + + @Override + public void onHostPause() { + } + + @Override + public void onHostDestroy() { + // cancel all requests + synchronized (mEnqueuedRequestMonitor) { + for (int i = 0, size = mEnqueuedRequests.size(); i < size; i++) { + @Nullable DataSource<Void> enqueuedRequest = mEnqueuedRequests.valueAt(i); + if (enqueuedRequest != null) { + enqueuedRequest.close(); + } + } + mEnqueuedRequests.clear(); + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index 9c42dfcd59130e..50f31239ca413a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -9,6 +9,8 @@ package com.facebook.react.modules.network; +import android.util.Base64; + import javax.annotation.Nullable; import java.io.IOException; @@ -34,6 +36,7 @@ import okhttp3.Call; import okhttp3.Callback; import okhttp3.Headers; +import okhttp3.Interceptor; import okhttp3.JavaNetCookieJar; import okhttp3.MediaType; import okhttp3.MultipartBody; @@ -157,6 +160,7 @@ public void sendRequest( final int requestId, ReadableArray headers, ReadableMap data, + final String responseType, final boolean useIncrementalUpdates, int timeout) { Request.Builder requestBuilder = new Request.Builder().url(url); @@ -165,18 +169,54 @@ public void sendRequest( requestBuilder.tag(requestId); } - OkHttpClient client = mClient; + final RCTDeviceEventEmitter eventEmitter = getEventEmitter(executorToken); + OkHttpClient.Builder clientBuilder = mClient.newBuilder(); + + // If JS is listening for progress updates, install a ProgressResponseBody that intercepts the + // response and counts bytes received. + if (useIncrementalUpdates) { + clientBuilder.addNetworkInterceptor(new Interceptor() { + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { + Response originalResponse = chain.proceed(chain.request()); + ProgressResponseBody responseBody = new ProgressResponseBody( + originalResponse.body(), + new ProgressListener() { + long last = System.nanoTime(); + + @Override + public void onProgress(long bytesWritten, long contentLength, boolean done) { + long now = System.nanoTime(); + if (!done && !shouldDispatch(now, last)) { + return; + } + if (responseType.equals("text")) { + // For 'text' responses we continuously send response data with progress info to + // JS below, so no need to do anything here. + return; + } + ResponseUtil.onDataReceivedProgress( + eventEmitter, + requestId, + bytesWritten, + contentLength); + last = now; + } + }); + return originalResponse.newBuilder().body(responseBody).build(); + } + }); + } + // If the current timeout does not equal the passed in timeout, we need to clone the existing // client and set the timeout explicitly on the clone. This is cheap as everything else is // shared under the hood. // See https://github.com/square/okhttp/wiki/Recipes#per-call-configuration for more information if (timeout != mClient.connectTimeoutMillis()) { - client = mClient.newBuilder() - .readTimeout(timeout, TimeUnit.MILLISECONDS) - .build(); + clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS); } + OkHttpClient client = clientBuilder.build(); - final RCTDeviceEventEmitter eventEmitter = getEventEmitter(executorToken); Headers requestHeaders = extractHeaders(headers, data); if (requestHeaders == null) { ResponseUtil.onRequestError(eventEmitter, requestId, "Unrecognized headers format", null); @@ -247,11 +287,11 @@ public void sendRequest( method, RequestBodyUtil.createProgressRequest( multipartBuilder.build(), - new ProgressRequestListener() { + new ProgressListener() { long last = System.nanoTime(); @Override - public void onRequestProgress(long bytesWritten, long contentLength, boolean done) { + public void onProgress(long bytesWritten, long contentLength, boolean done) { long now = System.nanoTime(); if (done || shouldDispatch(now, last)) { ResponseUtil.onDataSend(eventEmitter, requestId, bytesWritten, contentLength); @@ -292,13 +332,23 @@ public void onResponse(Call call, Response response) throws IOException { ResponseBody responseBody = response.body(); try { - if (useIncrementalUpdates) { + // If JS wants progress updates during the download, and it requested a text response, + // periodically send response data updates to JS. + if (useIncrementalUpdates && responseType.equals("text")) { readWithProgress(eventEmitter, requestId, responseBody); ResponseUtil.onRequestSuccess(eventEmitter, requestId); - } else { - ResponseUtil.onDataReceived(eventEmitter, requestId, responseBody.string()); - ResponseUtil.onRequestSuccess(eventEmitter, requestId); + return; + } + + // Otherwise send the data in one big chunk, in the format that JS requested. + String responseString = ""; + if (responseType.equals("text")) { + responseString = responseBody.string(); + } else if (responseType.equals("base64")) { + responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP); } + ResponseUtil.onDataReceived(eventEmitter, requestId, responseString); + ResponseUtil.onRequestSuccess(eventEmitter, requestId); } catch (IOException e) { ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e); } @@ -310,12 +360,27 @@ private void readWithProgress( RCTDeviceEventEmitter eventEmitter, int requestId, ResponseBody responseBody) throws IOException { + long totalBytesRead = -1; + long contentLength = -1; + try { + ProgressResponseBody progressResponseBody = (ProgressResponseBody) responseBody; + totalBytesRead = progressResponseBody.totalBytesRead(); + contentLength = progressResponseBody.contentLength(); + } catch (ClassCastException e) { + // Ignore + } + Reader reader = responseBody.charStream(); try { char[] buffer = new char[MAX_CHUNK_SIZE_BETWEEN_FLUSHES]; int read; while ((read = reader.read(buffer)) != -1) { - ResponseUtil.onDataReceived(eventEmitter, requestId, new String(buffer, 0, read)); + ResponseUtil.onIncrementalDataReceived( + eventEmitter, + requestId, + new String(buffer, 0, read), + totalBytesRead, + contentLength); } } finally { reader.close(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestListener.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressListener.java similarity index 73% rename from ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestListener.java rename to ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressListener.java index 10230e6dcb9c56..5cca72845cde8d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestListener.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressListener.java @@ -6,10 +6,9 @@ * 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.modules.network; +package com.facebook.react.modules.network; -public interface ProgressRequestListener { - void onRequestProgress(long bytesWritten, long contentLength, boolean done); +public interface ProgressListener { + void onProgress(long bytesWritten, long contentLength, boolean done); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java index 0e511f926dda19..9676071aa714b7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java @@ -12,22 +12,19 @@ import java.io.IOException; import okhttp3.MediaType; import okhttp3.RequestBody; -import okhttp3.internal.Util; import okio.BufferedSink; import okio.Buffer; import okio.Sink; import okio.ForwardingSink; -import okio.ByteString; import okio.Okio; -import okio.Source; public class ProgressRequestBody extends RequestBody { private final RequestBody mRequestBody; - private final ProgressRequestListener mProgressListener; + private final ProgressListener mProgressListener; private BufferedSink mBufferedSink; - public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) { + public ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) { mRequestBody = requestBody; mProgressListener = progressListener; } @@ -63,7 +60,8 @@ public void write(Buffer source, long byteCount) throws IOException { contentLength = contentLength(); } bytesWritten += byteCount; - mProgressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength); + mProgressListener.onProgress( + bytesWritten, contentLength, bytesWritten == contentLength); } }; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressResponseBody.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressResponseBody.java new file mode 100644 index 00000000000000..4ec808caa07d46 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressResponseBody.java @@ -0,0 +1,62 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.modules.network; + +import java.io.IOException; +import javax.annotation.Nullable; + +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; +import okio.Source; + +public class ProgressResponseBody extends ResponseBody { + + private final ResponseBody mResponseBody; + private final ProgressListener mProgressListener; + private @Nullable BufferedSource mBufferedSource; + private long mTotalBytesRead; + + public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) { + this.mResponseBody = responseBody; + this.mProgressListener = progressListener; + mTotalBytesRead = 0L; + } + + @Override + public MediaType contentType() { + return mResponseBody.contentType(); + } + + @Override + public long contentLength() { + return mResponseBody.contentLength(); + } + + public long totalBytesRead() { + return mTotalBytesRead; + } + + @Override public BufferedSource source() { + if (mBufferedSource == null) { + mBufferedSource = Okio.buffer(source(mResponseBody.source())); + } + return mBufferedSource; + } + + private Source source(Source source) { + return new ForwardingSource(source) { + @Override public long read(Buffer sink, long byteCount) throws IOException { + long bytesRead = super.read(sink, byteCount); + // read() returns the number of bytes read, or -1 if this source is exhausted. + mTotalBytesRead += bytesRead != -1 ? bytesRead : 0; + mProgressListener.onProgress( + mTotalBytesRead, mResponseBody.contentLength(), bytesRead == -1); + return bytesRead; + } + }; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java index 1d5a5e1d916a05..551382ca66b99e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java @@ -117,7 +117,9 @@ public void writeTo(BufferedSink sink) throws IOException { /** * Creates a ProgressRequestBody that can be used for showing uploading progress */ - public static ProgressRequestBody createProgressRequest(RequestBody requestBody, ProgressRequestListener listener) { + public static ProgressRequestBody createProgressRequest( + RequestBody requestBody, + ProgressListener listener) { return new ProgressRequestBody(requestBody, listener); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java index eb2b6e2bbb6a5d..5b589e195b0ccc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java @@ -33,6 +33,34 @@ public static void onDataSend( eventEmitter.emit("didSendNetworkData", args); } + public static void onIncrementalDataReceived( + RCTDeviceEventEmitter eventEmitter, + int requestId, + String data, + long progress, + long total) { + WritableArray args = Arguments.createArray(); + args.pushInt(requestId); + args.pushString(data); + args.pushInt((int) progress); + args.pushInt((int) total); + + eventEmitter.emit("didReceiveNetworkIncrementalData", args); + } + + public static void onDataReceivedProgress( + RCTDeviceEventEmitter eventEmitter, + int requestId, + long progress, + long total) { + WritableArray args = Arguments.createArray(); + args.pushInt(requestId); + args.pushInt((int) progress); + args.pushInt((int) total); + + eventEmitter.emit("didReceiveNetworkDataProgress", args); + } + public static void onDataReceived( RCTDeviceEventEmitter eventEmitter, int requestId, diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/share/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/share/BUCK new file mode 100644 index 00000000000000..4e95826b0dda6c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/share/BUCK @@ -0,0 +1,20 @@ +include_defs('//ReactAndroid/DEFS') + +android_library( + name = 'share', + srcs = glob(['**/*.java']), + deps = [ + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/common:common'), + react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + ], + visibility = [ + 'PUBLIC', + ], +) + +project_config( + src_target = ':share', +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/share/ShareModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/share/ShareModule.java new file mode 100644 index 00000000000000..eee2a42ca8f5c7 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/share/ShareModule.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2016-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.modules.share; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.common.ReactConstants; + +/** + * Intent module. Launch other activities or open URLs. + */ +public class ShareModule extends ReactContextBaseJavaModule { + + /* package */ static final String ACTION_SHARED = "sharedAction"; + /* package */ static final String ERROR_INVALID_CONTENT = "E_INVALID_CONTENT"; + /* package */ static final String ERROR_UNABLE_TO_OPEN_DIALOG = "E_UNABLE_TO_OPEN_DIALOG"; + + public ShareModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return "ShareModule"; + } + + /** + * Open a chooser dialog to send text content to other apps. + * + * Refer http://developer.android.com/intl/ko/training/sharing/send.html + * + * @param content the data to send + * @param dialogTitle the title of the chooser dialog + */ + @ReactMethod + public void share(ReadableMap content, String dialogTitle, Promise promise) { + if (content == null) { + promise.reject(ERROR_INVALID_CONTENT, "Content cannot be null"); + return; + } + + try { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setTypeAndNormalize("text/plain"); + + if (content.hasKey("title")) { + intent.putExtra(Intent.EXTRA_SUBJECT, content.getString("title")); + } + + if (content.hasKey("message")) { + intent.putExtra(Intent.EXTRA_TEXT, content.getString("message")); + } + + Intent chooser = Intent.createChooser(intent, dialogTitle); + chooser.addCategory(Intent.CATEGORY_DEFAULT); + + Activity currentActivity = getCurrentActivity(); + if (currentActivity != null) { + currentActivity.startActivity(chooser); + } else { + getReactApplicationContext().startActivity(chooser); + } + WritableMap result = Arguments.createMap(); + result.putString("action", ACTION_SHARED); + promise.resolve(result); + + } catch (Exception e) { + promise.reject(ERROR_UNABLE_TO_OPEN_DIALOG, "Failed to open share dialog"); + } + + } + +} \ No newline at end of file diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/ToastModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/toast/ToastModule.java index af128a96149532..243c917a6fe207 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/ToastModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/toast/ToastModule.java @@ -9,11 +9,11 @@ package com.facebook.react.modules.toast; +import android.view.Gravity; import android.widget.Toast; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.common.MapBuilder; @@ -29,6 +29,10 @@ public class ToastModule extends ReactContextBaseJavaModule { private static final String DURATION_SHORT_KEY = "SHORT"; private static final String DURATION_LONG_KEY = "LONG"; + private static final String GRAVITY_TOP_KEY = "TOP"; + private static final String GRAVITY_BOTTOM_KEY = "BOTTOM"; + private static final String GRAVITY_CENTER = "CENTER"; + public ToastModule(ReactApplicationContext reactContext) { super(reactContext); } @@ -43,6 +47,9 @@ public Map<String, Object> getConstants() { final Map<String, Object> constants = MapBuilder.newHashMap(); constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT); constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG); + constants.put(GRAVITY_TOP_KEY, Gravity.TOP | Gravity.CENTER_HORIZONTAL); + constants.put(GRAVITY_BOTTOM_KEY, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); + constants.put(GRAVITY_CENTER, Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); return constants; } @@ -50,9 +57,21 @@ public Map<String, Object> getConstants() { public void show(final String message, final int duration) { UiThreadUtil.runOnUiThread(new Runnable() { @Override - public void run(){ + public void run() { Toast.makeText(getReactApplicationContext(), message, duration).show(); } }); } + + @ReactMethod + public void showWithGravity(final String message, final int duration, final int gravity) { + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast toast = Toast.makeText(getReactApplicationContext(), message, duration); + toast.setGravity(gravity, 0, 0); + toast.show(); + } + }); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java index 5e3686a73f419d..3c6799efa467e8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java @@ -184,10 +184,6 @@ public void close(int code, String reason, int id) { if (client == null) { // WebSocket is already closed // Don't do anything, mirror the behaviour on web - FLog.w( - ReactConstants.TAG, - "Cannot close WebSocket. Unknown WebSocket id " + id); - return; } try { diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK index 9882cbdbd18a01..5acaa434288060 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK @@ -8,6 +8,7 @@ android_library( react_native_dep('third-party/android/support/v4:lib-support-v4'), react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/animated:animated'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), react_native_target('java/com/facebook/react/devsupport:devsupport'), @@ -19,12 +20,14 @@ android_library( react_native_target('java/com/facebook/react/modules/debug:debug'), react_native_target('java/com/facebook/react/modules/dialog:dialog'), react_native_target('java/com/facebook/react/modules/fresco:fresco'), + react_native_target('java/com/facebook/react/modules/i18nmanager:i18nmanager'), react_native_target('java/com/facebook/react/modules/image:image'), react_native_target('java/com/facebook/react/modules/intent:intent'), react_native_target('java/com/facebook/react/modules/location:location'), react_native_target('java/com/facebook/react/modules/netinfo:netinfo'), react_native_target('java/com/facebook/react/modules/network:network'), react_native_target('java/com/facebook/react/modules/permissions:permissions'), + react_native_target('java/com/facebook/react/modules/share:share'), react_native_target('java/com/facebook/react/modules/statusbar:statusbar'), react_native_target('java/com/facebook/react/modules/storage:storage'), react_native_target('java/com/facebook/react/modules/timepicker:timepicker'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index a07429e7ead6dc..878247c0dbce80 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -14,6 +14,7 @@ import java.util.List; import com.facebook.react.ReactPackage; +import com.facebook.react.animated.NativeAnimatedModule; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; @@ -25,12 +26,14 @@ import com.facebook.react.modules.datepicker.DatePickerDialogModule; import com.facebook.react.modules.dialog.DialogModule; import com.facebook.react.modules.fresco.FrescoModule; +import com.facebook.react.modules.i18nmanager.I18nManagerModule; import com.facebook.react.modules.image.ImageLoaderModule; import com.facebook.react.modules.intent.IntentModule; import com.facebook.react.modules.location.LocationModule; import com.facebook.react.modules.netinfo.NetInfoModule; import com.facebook.react.modules.network.NetworkingModule; import com.facebook.react.modules.permissions.PermissionsModule; +import com.facebook.react.modules.share.ShareModule; import com.facebook.react.modules.statusbar.StatusBarModule; import com.facebook.react.modules.storage.AsyncStorageModule; import com.facebook.react.modules.timepicker.TimePickerDialogModule; @@ -77,14 +80,17 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte new DatePickerDialogModule(reactContext), new DialogModule(reactContext), new FrescoModule(reactContext), + new I18nManagerModule(reactContext), new ImageEditingManager(reactContext), new ImageLoaderModule(reactContext), new ImageStoreManager(reactContext), new IntentModule(reactContext), new LocationModule(reactContext), + new NativeAnimatedModule(reactContext), new NetworkingModule(reactContext), new NetInfoModule(reactContext), new PermissionsModule(reactContext), + new ShareModule(reactContext), new StatusBarModule(reactContext), new TimePickerDialogModule(reactContext), new ToastModule(reactContext), diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK index b39e66c9255c93..3a0956b2754ebc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK @@ -9,17 +9,18 @@ android_library( 'layoutanimation/*.java' ]), deps = [ - react_native_target('java/com/facebook/react/animation:animation'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/touch:touch'), + CSSLAYOUT_TARGET, react_native_dep('java/com/facebook/systrace:systrace'), react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), + react_native_dep('third-party/android/support/v4:lib-support-v4'), react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_dep('third-party/android/support/v4:lib-support-v4'), + react_native_target('java/com/facebook/react/animation:animation'), + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/common:common'), + react_native_target('java/com/facebook/react/modules/i18nmanager:i18nmanager'), + react_native_target('java/com/facebook/react/touch:touch'), + react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java index bbe5fc99530861..97d366a53853c1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.java @@ -15,7 +15,6 @@ import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.TouchEvent; import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; @@ -82,7 +81,6 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) { eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag, - SystemClock.nanoTime(), TouchEventType.START, ev, mTargetCoordinates[0], @@ -105,7 +103,6 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) { eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag, - SystemClock.nanoTime(), TouchEventType.END, ev, mTargetCoordinates[0], @@ -117,7 +114,6 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) { eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag, - SystemClock.nanoTime(), TouchEventType.MOVE, ev, mTargetCoordinates[0], @@ -128,7 +124,6 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) { eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag, - SystemClock.nanoTime(), TouchEventType.START, ev, mTargetCoordinates[0], @@ -139,7 +134,6 @@ public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) { eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag, - SystemClock.nanoTime(), TouchEventType.END, ev, mTargetCoordinates[0], @@ -180,7 +174,6 @@ private void dispatchCancelEvent(MotionEvent androidEvent, EventDispatcher event Assertions.assertNotNull(eventDispatcher).dispatchEvent( TouchEvent.obtain( mTargetTag, - SystemClock.nanoTime(), TouchEventType.CANCEL, androidEvent, mTargetCoordinates[0], diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index b6c66d30ffdba5..3d6ad539a58129 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -9,9 +9,6 @@ package com.facebook.react.uimanager; -import javax.annotation.Nullable; -import javax.annotation.concurrent.NotThreadSafe; - import android.content.res.Resources; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -39,6 +36,9 @@ import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; +import javax.annotation.Nullable; +import javax.annotation.concurrent.NotThreadSafe; + /** * Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between * native view names used in JS and corresponding instances of {@link ViewManager}. The @@ -232,17 +232,20 @@ private static String constructManageChildrenErrorMessage( @Nullable int[] tagsToDelete) { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("View tag:" + viewToManage.getId() + "\n"); - stringBuilder.append(" children(" + viewManager.getChildCount(viewToManage) + "): [\n"); - for (int index=0; index<viewManager.getChildCount(viewToManage); index+=16) { - for (int innerOffset=0; - ((index+innerOffset) < viewManager.getChildCount(viewToManage)) && innerOffset < 16; - innerOffset++) { - stringBuilder.append(viewManager.getChildAt(viewToManage, index+innerOffset).getId() + ","); + if (null != viewToManage) { + stringBuilder.append("View tag:" + viewToManage.getId() + "\n"); + stringBuilder.append(" children(" + viewManager.getChildCount(viewToManage) + "): [\n"); + for (int index=0; index<viewManager.getChildCount(viewToManage); index+=16) { + for (int innerOffset=0; + ((index+innerOffset) < viewManager.getChildCount(viewToManage)) && innerOffset < 16; + innerOffset++) { + stringBuilder.append(viewManager.getChildAt(viewToManage, index+innerOffset).getId() + ","); + } + stringBuilder.append("\n"); } - stringBuilder.append("\n"); + stringBuilder.append(" ],\n"); } - stringBuilder.append(" ],\n"); + if (indicesToRemove != null) { stringBuilder.append(" indicesToRemove(" + indicesToRemove.length + "): [\n"); for (int index = 0; index < indicesToRemove.length; index += 16) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java index 7b9741bd8ddbd5..2d921bb0f1c230 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java @@ -52,6 +52,16 @@ */ public class NativeViewHierarchyOptimizer { + private static class NodeIndexPair { + public final ReactShadowNode node; + public final int index; + + NodeIndexPair(ReactShadowNode node, int index) { + this.node = node; + this.index = index; + } + } + private static final boolean ENABLED = true; private final UIViewOperationQueue mUIViewOperationQueue; @@ -221,21 +231,39 @@ public void onBatchComplete() { mTagsWithLayoutVisited.clear(); } + private NodeIndexPair walkUpUntilNonLayoutOnly( + ReactShadowNode node, + int indexInNativeChildren) { + while (node.isLayoutOnly()) { + ReactShadowNode parent = node.getParent(); + if (parent == null) { + return null; + } + + indexInNativeChildren = indexInNativeChildren + parent.getNativeOffsetForChild(node); + node = parent; + } + + return new NodeIndexPair(node, indexInNativeChildren); + } + private void addNodeToNode(ReactShadowNode parent, ReactShadowNode child, int index) { int indexInNativeChildren = parent.getNativeOffsetForChild(parent.getChildAt(index)); - boolean parentIsLayoutOnly = parent.isLayoutOnly(); - boolean childIsLayoutOnly = child.isLayoutOnly(); - - // Switch on the four cases of: - // add (layout-only|not layout-only) to (layout-only|not layout-only) - if (!parentIsLayoutOnly && !childIsLayoutOnly) { - addNonLayoutNodeToNonLayoutNode(parent, child, indexInNativeChildren); - } else if (!childIsLayoutOnly) { - addNonLayoutOnlyNodeToLayoutOnlyNode(parent, child, indexInNativeChildren); - } else if (!parentIsLayoutOnly) { - addLayoutOnlyNodeToNonLayoutOnlyNode(parent, child, indexInNativeChildren); + if (parent.isLayoutOnly()) { + NodeIndexPair result = walkUpUntilNonLayoutOnly(parent, indexInNativeChildren); + if (result == null) { + // If the parent hasn't been attached to its native parent yet, don't issue commands to the + // native hierarchy. We'll do that when the parent node actually gets attached somewhere. + return; + } + parent = result.node; + indexInNativeChildren = result.index; + } + + if (!child.isLayoutOnly()) { + addNonLayoutNode(parent, child, indexInNativeChildren); } else { - addLayoutOnlyNodeToLayoutOnlyNode(parent, child, indexInNativeChildren); + addLayoutOnlyNode(parent, child, indexInNativeChildren); } } @@ -262,73 +290,14 @@ private void removeNodeFromParent(ReactShadowNode nodeToRemove, boolean shouldDe } } - private void addLayoutOnlyNodeToLayoutOnlyNode( - ReactShadowNode parent, - ReactShadowNode child, - int index) { - ReactShadowNode parentParent = parent.getParent(); - - // If the parent hasn't been attached to its parent yet, don't issue commands to the native - // hierarchy. We'll do that when the parent node actually gets attached somewhere. - if (parentParent == null) { - return; - } - - int transformedIndex = index + parentParent.getNativeOffsetForChild(parent); - if (parentParent.isLayoutOnly()) { - addLayoutOnlyNodeToLayoutOnlyNode(parentParent, child, transformedIndex); - } else { - addLayoutOnlyNodeToNonLayoutOnlyNode(parentParent, child, transformedIndex); - } - } - - private void addNonLayoutOnlyNodeToLayoutOnlyNode( - ReactShadowNode layoutOnlyNode, - ReactShadowNode nonLayoutOnlyNode, - int index) { - ReactShadowNode parent = layoutOnlyNode.getParent(); - - // If the parent hasn't been attached to its parent yet, don't issue commands to the native - // hierarchy. We'll do that when the parent node actually gets attached somewhere. - if (parent == null) { - return; - } - - int transformedIndex = index + parent.getNativeOffsetForChild(layoutOnlyNode); - if (parent.isLayoutOnly()) { - addNonLayoutOnlyNodeToLayoutOnlyNode(parent, nonLayoutOnlyNode, transformedIndex); - } else { - addNonLayoutNodeToNonLayoutNode(parent, nonLayoutOnlyNode, transformedIndex); - } - } - - private void addLayoutOnlyNodeToNonLayoutOnlyNode( + private void addLayoutOnlyNode( ReactShadowNode nonLayoutOnlyNode, ReactShadowNode layoutOnlyNode, int index) { - // Add all of the layout-only node's children to its parent instead - int currentIndex = index; - for (int i = 0; i < layoutOnlyNode.getChildCount(); i++) { - ReactShadowNode childToAdd = layoutOnlyNode.getChildAt(i); - Assertions.assertCondition(childToAdd.getNativeParent() == null); - - if (childToAdd.isLayoutOnly()) { - // Adding this layout-only child could result in adding multiple native views - int childCountBefore = nonLayoutOnlyNode.getNativeChildCount(); - addLayoutOnlyNodeToNonLayoutOnlyNode( - nonLayoutOnlyNode, - childToAdd, - currentIndex); - int childCountAfter = nonLayoutOnlyNode.getNativeChildCount(); - currentIndex += childCountAfter - childCountBefore; - } else { - addNonLayoutNodeToNonLayoutNode(nonLayoutOnlyNode, childToAdd, currentIndex); - currentIndex++; - } - } + addGrandchildren(nonLayoutOnlyNode, layoutOnlyNode, index); } - private void addNonLayoutNodeToNonLayoutNode( + private void addNonLayoutNode( ReactShadowNode parent, ReactShadowNode child, int index) { @@ -340,6 +309,31 @@ private void addNonLayoutNodeToNonLayoutNode( null); } + private void addGrandchildren( + ReactShadowNode nativeParent, + ReactShadowNode child, + int index) { + Assertions.assertCondition(!nativeParent.isLayoutOnly()); + + // `child` can't hold native children. Add all of `child`'s children to `parent`. + int currentIndex = index; + for (int i = 0; i < child.getChildCount(); i++) { + ReactShadowNode grandchild = child.getChildAt(i); + Assertions.assertCondition(grandchild.getNativeParent() == null); + + if (grandchild.isLayoutOnly()) { + // Adding this child could result in adding multiple native views + int grandchildCountBefore = nativeParent.getNativeChildCount(); + addLayoutOnlyNode(nativeParent, grandchild, currentIndex); + int grandchildCountAfter = nativeParent.getNativeChildCount(); + currentIndex += grandchildCountAfter - grandchildCountBefore; + } else { + addNonLayoutNode(nativeParent, grandchild, currentIndex); + currentIndex++; + } + } + } + private void applyLayoutBase(ReactShadowNode node) { int tag = node.getReactTag(); if (mTagsWithLayoutVisited.get(tag)) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java index c2b5c8a6e9d8b9..118a1bbdc3ed6c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java @@ -13,7 +13,6 @@ import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.RCTEventEmitter; @@ -45,7 +44,7 @@ private OnLayoutEvent() { } protected void init(int viewTag, int x, int y, int width, int height) { - super.init(viewTag, SystemClock.nanoTime()); + super.init(viewTag); mX = x; mY = y; mWidth = width; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactChoreographer.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactChoreographer.java index 5ceed3f6cb919f..cbd6408cbee9e9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactChoreographer.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactChoreographer.java @@ -46,6 +46,12 @@ public static enum CallbackType { * Events that make JS do things. */ TIMERS_EVENTS(3), + + /** + * Event used to trigger the idle callback. Called after all UI work has been + * dispatched to JS. + */ + IDLE_EVENT(4), ; private final int mOrder; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index b1d52cf0f38525..729077313590c0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -14,6 +14,7 @@ import java.util.List; import com.facebook.csslayout.CSSLayoutContext; +import com.facebook.csslayout.CSSDirection; import com.facebook.infer.annotation.Assertions; import com.facebook.react.animation.Animation; import com.facebook.react.bridge.Arguments; @@ -23,6 +24,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableArray; +import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.systrace.Systrace; @@ -40,6 +42,7 @@ public class UIImplementation { private final UIViewOperationQueue mOperationsQueue; private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer; private final int[] mMeasureBuffer = new int[4]; + private final ReactApplicationContext mReactContext; public UIImplementation(ReactApplicationContext reactContext, List<ViewManager> viewManagers) { this(reactContext, new ViewManagerRegistry(viewManagers)); @@ -47,13 +50,16 @@ public UIImplementation(ReactApplicationContext reactContext, List<ViewManager> private UIImplementation(ReactApplicationContext reactContext, ViewManagerRegistry viewManagers) { this( + reactContext, viewManagers, new UIViewOperationQueue(reactContext, new NativeViewHierarchyManager(viewManagers))); } protected UIImplementation( + ReactApplicationContext reactContext, ViewManagerRegistry viewManagers, UIViewOperationQueue operationsQueue) { + mReactContext = reactContext; mViewManagers = viewManagers; mOperationsQueue = operationsQueue; mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( @@ -63,6 +69,10 @@ protected UIImplementation( protected ReactShadowNode createRootShadowNode() { ReactShadowNode rootCSSNode = new ReactShadowNode(); + I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); + if (sharedI18nUtilInstance.isRTL(mReactContext)) { + rootCSSNode.setDirection(CSSDirection.RTL); + } rootCSSNode.setViewClassName("Root"); return rootCSSNode; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index b04af822a124cb..033ea83e051e79 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -264,9 +264,11 @@ public void manageChildren( public void setChildren( int viewTag, ReadableArray childrenTags) { - FLog.d( - ReactConstants.TAG, - "(UIManager.setChildren) tag: " + viewTag + ", children: " + childrenTags); + if (DEBUG) { + FLog.d( + ReactConstants.TAG, + "(UIManager.setChildren) tag: " + viewTag + ", children: " + childrenTags); + } mUIImplementation.setChildren(viewTag, childrenTags); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java index d3bdab1be52a73..7b5528bf20daee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java @@ -71,11 +71,12 @@ /* package */ static Map getDirectEventTypeConstants() { return MapBuilder.builder() - .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange")) - .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart")) - .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish")) - .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError")) + .put("topContentSizeChange", MapBuilder.of("registrationName", "onContentSizeChange")) .put("topLayout", MapBuilder.of("registrationName", "onLayout")) + .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError")) + .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish")) + .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart")) + .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange")) .build(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java index a5094e7d5b8db4..6a728db8f98e56 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java @@ -9,15 +9,16 @@ package com.facebook.react.uimanager; -import android.view.View; -import android.view.ViewGroup; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.WeakHashMap; +import android.view.View; +import android.view.ViewGroup; + /** * Class providing children management API for view managers of classes extending ViewGroup. */ @@ -45,6 +46,19 @@ public void addView(T parent, View child, int index) { reorderChildrenByZIndex(parent); } + /** + * Convenience method for batching a set of addView calls + * Note that this adds the views to the beginning of the ViewGroup + * + * @param parent the parent ViewGroup + * @param views the set of views to add + */ + public void addViews(T parent, List<View> views) { + for (int i = 0, size = views.size(); i < size; i++) { + addView(parent, views.get(i), i); + } + } + public static void setViewZIndex(View view, int zIndex) { mZIndexHash.put(view, zIndex); // zIndex prop gets set BEFORE the view is added, so parent may be null. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java index 968527fc327353..6f1be09e8e1d47 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java @@ -72,7 +72,7 @@ public class ViewProps { public static final String LINE_HEIGHT = "lineHeight"; public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing"; public static final String NUMBER_OF_LINES = "numberOfLines"; - public static final String LINE_BREAK_MODE = "lineBreakMode"; + public static final String LINE_BREAK_MODE = "ellipsizeMode"; public static final String ON = "on"; public static final String RESIZE_MODE = "resizeMode"; public static final String TEXT_ALIGN = "textAlign"; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java index 40c2845c8adb52..d54e6ab6a780e5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java @@ -9,6 +9,8 @@ package com.facebook.react.uimanager.events; +import com.facebook.react.common.SystemClock; + /** * A UI event that can be dispatched to JS. * @@ -28,16 +30,16 @@ public abstract class Event<T extends Event> { protected Event() { } - protected Event(int viewTag, long timestampMs) { - init(viewTag, timestampMs); + protected Event(int viewTag) { + init(viewTag); } /** * This method needs to be called before event is sent to event dispatcher. */ - protected void init(int viewTag, long timestampMs) { + protected void init(int viewTag) { mViewTag = viewTag; - mTimestampMs = timestampMs; + mTimestampMs = SystemClock.nanoTime(); mInitialized = true; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java index a7df8fb3eab968..0262f0bd085459 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java @@ -33,7 +33,6 @@ public class TouchEvent extends Event<TouchEvent> { public static TouchEvent obtain( int viewTag, - long timestampMs, TouchEventType touchEventType, MotionEvent motionEventToCopy, float viewX, @@ -45,7 +44,6 @@ public static TouchEvent obtain( } event.init( viewTag, - timestampMs, touchEventType, motionEventToCopy, viewX, @@ -67,13 +65,12 @@ private TouchEvent() { private void init( int viewTag, - long timestampMs, TouchEventType touchEventType, MotionEvent motionEventToCopy, float viewX, float viewY, TouchEventCoalescingKeyHelper touchEventCoalescingKeyHelper) { - super.init(viewTag, timestampMs); + super.init(viewTag); short coalescingKey = 0; int action = (motionEventToCopy.getAction() & MotionEvent.ACTION_MASK); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java index 44ba41ba63030d..5af6d179453a06 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java @@ -52,11 +52,11 @@ public String getName() { @Override public ReactShadowNode createShadowNodeInstance() { - if (mClassName == CLASS_GROUP) { + if (CLASS_GROUP.equals(mClassName)) { return new ARTGroupShadowNode(); - } else if (mClassName == CLASS_SHAPE) { + } else if (CLASS_SHAPE.equals(mClassName)) { return new ARTShapeShadowNode(); - } else if (mClassName == CLASS_TEXT) { + } else if (CLASS_TEXT.equals(mClassName)) { return new ARTTextShadowNode(); } else { throw new IllegalStateException("Unexpected type " + mClassName); @@ -65,11 +65,11 @@ public ReactShadowNode createShadowNodeInstance() { @Override public Class<? extends ReactShadowNode> getShadowNodeClass() { - if (mClassName == CLASS_GROUP) { + if (CLASS_GROUP.equals(mClassName)) { return ARTGroupShadowNode.class; - } else if (mClassName == CLASS_SHAPE) { + } else if (CLASS_SHAPE.equals(mClassName)) { return ARTShapeShadowNode.class; - } else if (mClassName == CLASS_TEXT) { + } else if (CLASS_TEXT.equals(mClassName)) { return ARTTextShadowNode.class; } else { throw new IllegalStateException("Unexpected type " + mClassName); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/art/BUCK index d7612a1e463b03..dff98f8329a2db 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/art/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/BUCK @@ -4,13 +4,13 @@ android_library( name = 'art', srcs = glob(['*.java']), deps = [ + CSSLAYOUT_TARGET, + react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), - react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK index 8aea7907e06fe7..8797a578d1ce39 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK @@ -4,15 +4,15 @@ android_library( name = 'drawer', srcs = glob(['**/*.java']), deps = [ - react_native_target('java/com/facebook/csslayout:csslayout'), + CSSLAYOUT_TARGET, + react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), + react_native_dep('third-party/android/support/v4:lib-support-v4'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/views/scroll:scroll'), - react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_dep('third-party/android/support/v4:lib-support-v4'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java index 0595d0fd07627c..0014b95260b82d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java @@ -26,7 +26,6 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.common.MapBuilder; import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIManagerModule; @@ -188,25 +187,25 @@ public DrawerEventEmitter(DrawerLayout drawerLayout, EventDispatcher eventDispat @Override public void onDrawerSlide(View view, float v) { mEventDispatcher.dispatchEvent( - new DrawerSlideEvent(mDrawerLayout.getId(), SystemClock.nanoTime(), v)); + new DrawerSlideEvent(mDrawerLayout.getId(), v)); } @Override public void onDrawerOpened(View view) { mEventDispatcher.dispatchEvent( - new DrawerOpenedEvent(mDrawerLayout.getId(), SystemClock.nanoTime())); + new DrawerOpenedEvent(mDrawerLayout.getId())); } @Override public void onDrawerClosed(View view) { mEventDispatcher.dispatchEvent( - new DrawerClosedEvent(mDrawerLayout.getId(), SystemClock.nanoTime())); + new DrawerClosedEvent(mDrawerLayout.getId())); } @Override public void onDrawerStateChanged(int i) { mEventDispatcher.dispatchEvent( - new DrawerStateChangedEvent(mDrawerLayout.getId(), SystemClock.nanoTime(), i)); + new DrawerStateChangedEvent(mDrawerLayout.getId(), i)); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerClosedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerClosedEvent.java index 83bc9f50f8387b..a6845037d89702 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerClosedEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerClosedEvent.java @@ -17,8 +17,8 @@ public class DrawerClosedEvent extends Event<DrawerClosedEvent> { public static final String EVENT_NAME = "topDrawerClosed"; - public DrawerClosedEvent(int viewId, long timestampMs) { - super(viewId, timestampMs); + public DrawerClosedEvent(int viewId) { + super(viewId); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerOpenedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerOpenedEvent.java index 916c301c96570e..b94d5937ae4510 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerOpenedEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerOpenedEvent.java @@ -17,8 +17,8 @@ public class DrawerOpenedEvent extends Event<DrawerOpenedEvent> { public static final String EVENT_NAME = "topDrawerOpened"; - public DrawerOpenedEvent(int viewId, long timestampMs) { - super(viewId, timestampMs); + public DrawerOpenedEvent(int viewId) { + super(viewId); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerSlideEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerSlideEvent.java index b35bbc8d0fbbad..5010ee6befa480 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerSlideEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerSlideEvent.java @@ -23,8 +23,8 @@ public class DrawerSlideEvent extends Event<DrawerSlideEvent> { private final float mOffset; - public DrawerSlideEvent(int viewId, long timestampMs, float offset) { - super(viewId, timestampMs); + public DrawerSlideEvent(int viewId, float offset) { + super(viewId); mOffset = offset; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerStateChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerStateChangedEvent.java index dc6c9cd9c3410b..a4d9294875201b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerStateChangedEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/events/DrawerStateChangedEvent.java @@ -20,8 +20,8 @@ public class DrawerStateChangedEvent extends Event<DrawerStateChangedEvent> { private final int mDrawerState; - public DrawerStateChangedEvent(int viewId, long timestampMs, int drawerState) { - super(viewId, timestampMs); + public DrawerStateChangedEvent(int viewId, int drawerState) { + super(viewId); mDrawerState = drawerState; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK index 3eb51d8180baf5..9c604fac33bd8b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK @@ -4,21 +4,21 @@ android_library( name = 'image', srcs = glob(['*.java']), deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_target('java/com/facebook/react/views/imagehelper:withmultisource'), + CSSLAYOUT_TARGET, react_native_dep('libraries/fresco/fresco-react-native:fbcore'), - react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'), react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'), + react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'), react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'), - react_native_dep('third-party/android/support/v4:lib-support-v4'), react_native_dep('third-party/android/support-annotations:android-support-annotations'), + react_native_dep('third-party/android/support/v4:lib-support-v4'), react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), -], + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/common:common'), + react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), + react_native_target('java/com/facebook/react/views/imagehelper:withmultisource'), + ], visibility = [ 'PUBLIC', ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java index 7dde489d8b839e..cee5ebc34885f7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java @@ -37,16 +37,15 @@ public class ImageLoadEvent extends Event<ImageLoadEvent> { private final int mEventType; private final @Nullable String mImageUri; - public ImageLoadEvent(int viewId, long timestampMs, @ImageEventType int eventType) { - this(viewId, timestampMs, eventType, null); + public ImageLoadEvent(int viewId, @ImageEventType int eventType) { + this(viewId, eventType, null); } public ImageLoadEvent( int viewId, - long timestampMs, @ImageEventType int eventType, @Nullable String imageUri) { - super(viewId, timestampMs); + super(viewId); mEventType = eventType; mImageUri = imageUri; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index a864b140c81379..ad3ac5cf961f71 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -51,7 +51,6 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.EventDispatcher; @@ -192,7 +191,7 @@ public void setShouldNotifyLoadEvents(boolean shouldNotify) { @Override public void onSubmit(String id, Object callerContext) { mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_START)); + new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_START)); } @Override @@ -202,18 +201,18 @@ public void onFinalImageSet( @Nullable Animatable animatable) { if (imageInfo != null) { mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD)); + new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD)); mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_END)); + new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_END)); } } @Override public void onFailure(String id, Throwable throwable) { mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_ERROR)); + new ImageLoadEvent(getId(), ImageLoadEvent.ON_ERROR)); mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_END)); + new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_END)); } }; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK index 8d46dbf5e8d484..0a3c4c98e5c94f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK @@ -4,16 +4,16 @@ android_library( name = 'modal', srcs = glob(['*.java']), deps = [ - react_native_target('res:modal'), + CSSLAYOUT_TARGET, + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/touch:touch'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/views/view:view'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('res:modal'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java index 2fb42c594f6b27..52fc41ad372583 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java @@ -17,7 +17,6 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ThemedReactContext; @@ -85,14 +84,14 @@ protected void addEventEmitters( new ReactModalHostView.OnRequestCloseListener() { @Override public void onRequestClose(DialogInterface dialog) { - dispatcher.dispatchEvent(new RequestCloseEvent(view.getId(), SystemClock.nanoTime())); + dispatcher.dispatchEvent(new RequestCloseEvent(view.getId())); } }); view.setOnShowListener( new DialogInterface.OnShowListener() { @Override public void onShow(DialogInterface dialog) { - dispatcher.dispatchEvent(new ShowEvent(view.getId(), SystemClock.nanoTime())); + dispatcher.dispatchEvent(new ShowEvent(view.getId())); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/RequestCloseEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/RequestCloseEvent.java index 0ddf35fb6b66b2..133488760441bf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/RequestCloseEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/RequestCloseEvent.java @@ -19,8 +19,8 @@ public static final String EVENT_NAME = "topRequestClose"; - protected RequestCloseEvent(int viewTag, long timestampMs) { - super(viewTag, timestampMs); + protected RequestCloseEvent(int viewTag) { + super(viewTag); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ShowEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ShowEvent.java index 2c447b746ab18c..a0851ccf75e6c9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ShowEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ShowEvent.java @@ -19,8 +19,8 @@ public static final String EVENT_NAME = "topShow"; - protected ShowEvent(int viewTag, long timestampMs) { - super(viewTag, timestampMs); + protected ShowEvent(int viewTag) { + super(viewTag); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java index 11ea4c839fc974..5e1e9ccf33a236 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java @@ -22,7 +22,6 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIManagerModule; @@ -157,7 +156,7 @@ public PickerEventEmitter(ReactPicker reactPicker, EventDispatcher eventDispatch @Override public void onItemSelected(int position) { mEventDispatcher.dispatchEvent( new PickerItemSelectEvent( - mReactPicker.getId(), SystemClock.nanoTime(), position)); + mReactPicker.getId(), position)); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java index cabe53fdc41f4d..3eb45f48b74424 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java @@ -19,8 +19,8 @@ public class PickerItemSelectEvent extends Event<PickerItemSelectEvent> { private final int mPosition; - public PickerItemSelectEvent(int id, long uptimeMillis, int position) { - super(id, uptimeMillis); + public PickerItemSelectEvent(int id, int position) { + super(id); mPosition = position; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK index fed1ed6a0503b4..6888ecf5f9fbf0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK @@ -4,13 +4,13 @@ android_library( name = 'progressbar', srcs = glob(['*.java']), deps = [ + CSSLAYOUT_TARGET, + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java index e7941872c1babf..d7ec7e48f8f0ab 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java @@ -19,8 +19,8 @@ public class ContentSizeChangeEvent extends Event<ContentSizeChangeEvent> { private final int mWidth; private final int mHeight; - public ContentSizeChangeEvent(int viewTag, long timestampMs, int width, int height) { - super(viewTag, timestampMs); + public ContentSizeChangeEvent(int viewTag, int width, int height) { + super(viewTag); mWidth = width; mHeight = height; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java index df3f2c57542ab3..a9d64b95acca4e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java @@ -14,7 +14,6 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.common.SystemClock; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.NativeGestureUtil; @@ -344,7 +343,6 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) { ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher() .dispatchEvent(ScrollEvent.obtain( getId(), - SystemClock.nanoTime(), ScrollEventType.SCROLL, 0, /* offsetX = 0, horizontal scrolling only */ calculateAbsoluteOffset(), @@ -359,7 +357,6 @@ private void onTotalChildrenHeightChange(int newTotalChildrenHeight) { ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher() .dispatchEvent(new ContentSizeChangeEvent( getId(), - SystemClock.nanoTime(), getWidth(), newTotalChildrenHeight)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java index dfac0da4e10585..b82c03f74734e8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java @@ -85,9 +85,6 @@ public void scrollTo( Map getExportedCustomDirectEventTypeConstants() { return MapBuilder.builder() .put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll")) - .put( - ContentSizeChangeEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onContentSizeChange")) .build(); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java index fd46f677b7954b..966c50bb30a797 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -13,7 +13,6 @@ import android.view.ViewGroup; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.UIManagerModule; /** @@ -57,7 +56,6 @@ private static void emitScrollEvent(ViewGroup scrollView, ScrollEventType scroll reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( ScrollEvent.obtain( scrollView.getId(), - SystemClock.nanoTime(), scrollEventType, scrollView.getScrollX(), scrollView.getScrollY(), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java index dde0a6018a278e..25606a48960621 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java @@ -40,7 +40,6 @@ public class ScrollEvent extends Event<ScrollEvent> { public static ScrollEvent obtain( int viewTag, - long timestampMs, ScrollEventType scrollEventType, int scrollX, int scrollY, @@ -54,7 +53,6 @@ public static ScrollEvent obtain( } event.init( viewTag, - timestampMs, scrollEventType, scrollX, scrollY, @@ -75,7 +73,6 @@ private ScrollEvent() { private void init( int viewTag, - long timestampMs, ScrollEventType scrollEventType, int scrollX, int scrollY, @@ -83,7 +80,7 @@ private void init( int contentHeight, int scrollViewWidth, int scrollViewHeight) { - super.init(viewTag, timestampMs); + super.init(viewTag); mScrollEventType = scrollEventType; mScrollX = scrollX; mScrollY = scrollY; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK index 2b87a27d46413a..a6c5ecaf3cc697 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK @@ -4,14 +4,14 @@ android_library( name = 'slider', srcs = glob(['*.java']), deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + CSSLAYOUT_TARGET, react_native_dep('android_res/android/support/v7/appcompat-orig:res-for-react-native'), react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'), react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/common:common'), + react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderEvent.java index 20448246d7c19e..f45dc71f301794 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderEvent.java @@ -24,8 +24,8 @@ public class ReactSliderEvent extends Event<ReactSliderEvent> { private final double mValue; private final boolean mFromUser; - public ReactSliderEvent(int viewId, long timestampMs, double value, boolean fromUser) { - super(viewId, timestampMs); + public ReactSliderEvent(int viewId, double value, boolean fromUser) { + super(viewId); mValue = value; mFromUser = fromUser; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java index ec3f66d8073c02..d02771d3e5c073 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java @@ -20,7 +20,6 @@ import com.facebook.csslayout.MeasureOutput; import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; @@ -81,7 +80,6 @@ public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( new ReactSliderEvent( seekbar.getId(), - SystemClock.nanoTime(), ((ReactSlider)seekbar).toRealProgress(progress), fromUser)); } @@ -96,7 +94,6 @@ public void onStopTrackingTouch(SeekBar seekbar) { reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( new ReactSlidingCompleteEvent( seekbar.getId(), - SystemClock.nanoTime(), ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); } }; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSlidingCompleteEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSlidingCompleteEvent.java index d9a698d75dbaf0..1dceb63ee4c685 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSlidingCompleteEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSlidingCompleteEvent.java @@ -23,8 +23,8 @@ public class ReactSlidingCompleteEvent extends Event<ReactSlidingCompleteEvent> private final double mValue; - public ReactSlidingCompleteEvent(int viewId, long timestampMs, double value) { - super(viewId, timestampMs); + public ReactSlidingCompleteEvent(int viewId, double value) { + super(viewId); mValue = value; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java index d8325d2bce495b..0745d2adbecee7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java @@ -14,8 +14,8 @@ public class RefreshEvent extends Event<RefreshEvent> { - protected RefreshEvent(int viewTag, long timestampMs) { - super(viewTag, timestampMs); + protected RefreshEvent(int viewTag) { + super(viewTag); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java index 1ae2bacf5a36b9..c3ff029b5cd2ad 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java @@ -19,7 +19,6 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewGroupManager; @@ -89,7 +88,7 @@ protected void addEventEmitters( @Override public void onRefresh() { reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher() - .dispatchEvent(new RefreshEvent(view.getId(), SystemClock.nanoTime())); + .dispatchEvent(new RefreshEvent(view.getId())); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK index f7ff35e9d8227b..5a0e842f59898f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK @@ -4,13 +4,13 @@ android_library( name = 'switchview', srcs = glob(['*.java']), deps = [ + CSSLAYOUT_TARGET, + react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'), - react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchEvent.java index e88b1252cc7bdf..0b60076448979a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchEvent.java @@ -23,8 +23,8 @@ private final boolean mIsChecked; - public ReactSwitchEvent(int viewId, long timestampMs, boolean isChecked) { - super(viewId, timestampMs); + public ReactSwitchEvent(int viewId, boolean isChecked) { + super(viewId); mIsChecked = isChecked; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java index 1db17fc222d11c..2e6ef890d87e93 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java @@ -18,7 +18,6 @@ import com.facebook.csslayout.CSSNode; import com.facebook.csslayout.MeasureOutput; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; @@ -78,7 +77,6 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( new ReactSwitchEvent( buttonView.getId(), - SystemClock.nanoTime(), isChecked)); } }; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK index 8d1e02e6c3d883..53bab845baaec8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK @@ -4,13 +4,13 @@ android_library( name = 'text', srcs = glob(['*.java']), deps = [ + CSSLAYOUT_TARGET, + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index ae623bf0e69763..5178281a1d8c7a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -190,12 +190,17 @@ protected static Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode) { } textCSSNode.mContainsImages = false; + textCSSNode.mHeightOfTallestInlineImage = Float.NaN; // While setting the Spans on the final text, we also check whether any of them are images for (int i = ops.size() - 1; i >= 0; i--) { SetSpanOperation op = ops.get(i); if (op.what instanceof TextInlineImageSpan) { + int height = ((TextInlineImageSpan)op.what).getHeight(); textCSSNode.mContainsImages = true; + if (Float.isNaN(textCSSNode.mHeightOfTallestInlineImage) || height > textCSSNode.mHeightOfTallestInlineImage) { + textCSSNode.mHeightOfTallestInlineImage = height; + } } op.execute(sb); } @@ -226,6 +231,14 @@ public void measure( // technically, width should never be negative, but there is currently a bug in boolean unconstrainedWidth = widthMode == CSSMeasureMode.UNDEFINED || width < 0; + float effectiveLineHeight = reactCSSNode.getEffectiveLineHeight(); + float lineSpacingExtra = 0; + float lineSpacingMultiplier = 1; + if (!Float.isNaN(effectiveLineHeight)) { + lineSpacingExtra = effectiveLineHeight; + lineSpacingMultiplier = 0; + } + if (boring == null && (unconstrainedWidth || (!CSSConstants.isUndefined(desiredWidth) && desiredWidth <= width))) { @@ -236,8 +249,8 @@ public void measure( textPaint, (int) Math.ceil(desiredWidth), Layout.Alignment.ALIGN_NORMAL, - 1, - 0, + lineSpacingMultiplier, + lineSpacingExtra, true); } else if (boring != null && (unconstrainedWidth || boring.width <= width)) { // Is used for single-line, boring text when the width is either unknown or bigger @@ -247,8 +260,8 @@ public void measure( textPaint, boring.width, Layout.Alignment.ALIGN_NORMAL, - 1, - 0, + lineSpacingMultiplier, + lineSpacingExtra, boring, true); } else { @@ -258,8 +271,8 @@ public void measure( textPaint, (int) width, Layout.Alignment.ALIGN_NORMAL, - 1, - 0, + lineSpacingMultiplier, + lineSpacingExtra, true); } @@ -269,13 +282,6 @@ public void measure( reactCSSNode.mNumberOfLines < layout.getLineCount()) { measureOutput.height = layout.getLineBottom(reactCSSNode.mNumberOfLines - 1); } - if (reactCSSNode.mLineHeight != UNSET) { - int lines = reactCSSNode.mNumberOfLines != UNSET - ? Math.min(reactCSSNode.mNumberOfLines, layout.getLineCount()) - : layout.getLineCount(); - float lineHeight = PixelUtil.toPixelFromSP(reactCSSNode.mLineHeight); - measureOutput.height = lineHeight * lines; - } } }; @@ -293,7 +299,7 @@ private static int parseNumericFontWeight(String fontWeightString) { 100 * (fontWeightString.charAt(0) - '0') : -1; } - private int mLineHeight = UNSET; + private float mLineHeight = Float.NaN; private boolean mIsColorSet = false; private int mColor; private boolean mIsBackgroundColorSet = false; @@ -340,6 +346,7 @@ private static int parseNumericFontWeight(String fontWeightString) { private final boolean mIsVirtual; protected boolean mContainsImages = false; + private float mHeightOfTallestInlineImage = Float.NaN; public ReactTextShadowNode(boolean isVirtual) { mIsVirtual = isVirtual; @@ -348,6 +355,15 @@ public ReactTextShadowNode(boolean isVirtual) { } } + // Returns a line height which takes into account the requested line height + // and the height of the inline images. + public float getEffectiveLineHeight() { + boolean useInlineViewHeight = !Float.isNaN(mLineHeight) && + !Float.isNaN(mHeightOfTallestInlineImage) && + mHeightOfTallestInlineImage > mLineHeight; + return useInlineViewHeight ? mHeightOfTallestInlineImage : mLineHeight; + } + @Override public void onBeforeLayout() { if (mIsVirtual) { @@ -380,7 +396,7 @@ public void setNumberOfLines(int numberOfLines) { @ReactProp(name = ViewProps.LINE_HEIGHT, defaultInt = UNSET) public void setLineHeight(int lineHeight) { - mLineHeight = lineHeight; + mLineHeight = lineHeight == UNSET ? Float.NaN : PixelUtil.toPixelFromSP(lineHeight); markUpdated(); } @@ -530,7 +546,7 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { super.onCollectExtraUpdates(uiViewOperationQueue); if (mPreparedSpannableText != null) { ReactTextUpdate reactTextUpdate = - new ReactTextUpdate(mPreparedSpannableText, UNSET, mContainsImages, getPadding()); + new ReactTextUpdate(mPreparedSpannableText, UNSET, mContainsImages, getPadding(), getEffectiveLineHeight()); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java index dca68669d1d72e..5acb167c56acf0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java @@ -27,12 +27,14 @@ public class ReactTextUpdate { private final float mPaddingTop; private final float mPaddingRight; private final float mPaddingBottom; + private final float mLineHeight; public ReactTextUpdate( Spannable text, int jsEventCounter, boolean containsImages, - Spacing padding) { + Spacing padding, + float lineHeight) { mText = text; mJsEventCounter = jsEventCounter; mContainsImages = containsImages; @@ -40,6 +42,7 @@ public ReactTextUpdate( mPaddingTop = padding.get(Spacing.TOP); mPaddingRight = padding.get(Spacing.RIGHT); mPaddingBottom = padding.get(Spacing.BOTTOM); + mLineHeight = lineHeight; } public Spannable getText() { @@ -69,4 +72,8 @@ public float getPaddingRight() { public float getPaddingBottom() { return mPaddingBottom; } + + public float getLineHeight() { + return mLineHeight; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 71bb5ccf10d245..9ef3df91a8ab58 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -17,6 +17,7 @@ import android.view.ViewGroup; import android.widget.TextView; +import com.facebook.csslayout.FloatUtil; import com.facebook.react.uimanager.ReactCompoundView; public class ReactTextView extends TextView implements ReactCompoundView { @@ -28,6 +29,7 @@ public class ReactTextView extends TextView implements ReactCompoundView { private int mDefaultGravityHorizontal; private int mDefaultGravityVertical; private boolean mTextIsSelectable; + private float mLineHeight = Float.NaN; public ReactTextView(Context context) { super(context); @@ -50,6 +52,16 @@ public void setText(ReactTextUpdate update) { (int) Math.ceil(update.getPaddingTop()), (int) Math.ceil(update.getPaddingRight()), (int) Math.ceil(update.getPaddingBottom())); + + float nextLineHeight = update.getLineHeight(); + if (!FloatUtil.floatsEqual(mLineHeight, nextLineHeight)) { + mLineHeight = nextLineHeight; + if (Float.isNaN(mLineHeight)) { // NaN will be used if property gets reset + setLineSpacing(0, 1); + } else { + setLineSpacing(mLineHeight, 0); + } + } } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index 97e780a6d0f59e..7d2ed43e7689b9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -74,16 +74,16 @@ public void setTextAlign(ReactTextView view, @Nullable String textAlign) { } @ReactProp(name = ViewProps.LINE_BREAK_MODE) - public void setLineBreakMode(ReactTextView view, @Nullable String lineBreakMode) { - if(lineBreakMode == null) { + public void setLineBreakMode(ReactTextView view, @Nullable String ellipsizeMode) { + if(ellipsizeMode == null) { return; } - if (lineBreakMode.equals("head")) { + if (ellipsizeMode.equals("head")) { view.setEllipsize(TextUtils.TruncateAt.START); - } else if (lineBreakMode.equals("middle")) { + } else if (ellipsizeMode.equals("middle")) { view.setEllipsize(TextUtils.TruncateAt.MIDDLE); - } else if (lineBreakMode.equals("tail")) { + } else if (ellipsizeMode.equals("tail")) { view.setEllipsize(TextUtils.TruncateAt.END); } } @@ -103,15 +103,6 @@ public void setTextAlignVertical(ReactTextView view, @Nullable String textAlignV } } - @ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN) - public void setLineHeight(ReactTextView view, float lineHeight) { - if (Float.isNaN(lineHeight)) { // NaN will be used if property gets reset - view.setLineSpacing(0, 1); - } else { - view.setLineSpacing(PixelUtil.toPixelFromSP(lineHeight), 0); - } - } - @ReactProp(name = "selectable") public void setSelectable(ReactTextView view, boolean isSelectable) { view.setTextIsSelectable(isSelectable); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java index 0308ee20c28c87..c987260fd0f380 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java @@ -67,4 +67,14 @@ public static void possiblyUpdateInlineImageSpans(Spannable spannable, TextView * Set the textview that will contain this span. */ public abstract void setTextView(TextView textView); + + /** + * Get the width of the span. + */ + public abstract int getWidth(); + + /** + * Get the height of the span. + */ + public abstract int getHeight(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK index e8ddbceb6d9568..b504439b04f44f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK @@ -4,18 +4,18 @@ android_library( name = 'frescosupport', srcs = glob(['*.java']), deps = [ - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_target('java/com/facebook/react/views/text:text'), + CSSLAYOUT_TARGET, react_native_dep('libraries/fresco/fresco-react-native:fbcore'), react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'), react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'), react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'), react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/common:common'), + react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), + react_native_target('java/com/facebook/react/views/text:text'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java index 0613457e59f7b2..0c0c5c1246768c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java @@ -146,13 +146,21 @@ public void draw( canvas.save(); - int transY = bottom - mDrawable.getBounds().bottom; - // Align to baseline by default - transY -= paint.getFontMetricsInt().descent; + int transY = y - mDrawable.getBounds().bottom; canvas.translate(x, transY); mDrawable.draw(canvas); canvas.restore(); } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK index bd050607db1534..056b5be1da17c3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK @@ -4,9 +4,9 @@ android_library( name = 'textinput', srcs = glob(['*.java']), deps = [ + CSSLAYOUT_TARGET, react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), react_native_target('java/com/facebook/react/modules/core:core'), diff --git a/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.h b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java similarity index 64% rename from Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.h rename to ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java index e504b34bbb043b..fab6bbc1e34d1a 100644 --- a/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.h +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java @@ -7,10 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import <UIKit/UIKit.h> +package com.facebook.react.views.textinput; -@interface PFAppDelegate : UIResponder <UIApplicationDelegate, NSURLConnectionDataDelegate> - -@property (strong, nonatomic) UIWindow *window; - -@end +public interface ContentSizeWatcher { + public void onLayout(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java new file mode 100644 index 00000000000000..73839812ca68f6 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java @@ -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.views.textinput; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +/** + * Event emitted by EditText native view when content size changes. + */ +public class ReactContentSizeChangedEvent extends Event<ReactTextChangedEvent> { + + public static final String EVENT_NAME = "topContentSizeChange"; + + private int mContentWidth; + private int mContentHeight; + + public ReactContentSizeChangedEvent( + int viewId, + int contentSizeWidth, + int contentSizeHeight) { + super(viewId); + mContentWidth = contentSizeWidth; + mContentHeight = contentSizeHeight; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); + } + + private WritableMap serializeEventData() { + WritableMap eventData = Arguments.createMap(); + + WritableMap contentSize = Arguments.createMap(); + contentSize.putDouble("width", mContentWidth); + contentSize.putDouble("height", mContentHeight); + eventData.putMap("contentSize", contentSize); + + eventData.putInt("target", getViewTag()); + return eventData; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 7f9a13d7d27b7a..d5f9c604229a8a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -71,6 +71,7 @@ public class ReactEditText extends EditText { private boolean mContainsImages; private boolean mBlurOnSubmit; private @Nullable SelectionWatcher mSelectionWatcher; + private @Nullable ContentSizeWatcher mContentSizeWatcher; private final InternalKeyListener mKeyListener; private static final KeyListener sKeyListener = QwertyKeyListener.getInstanceForFullKeyboard(); @@ -102,15 +103,30 @@ public ReactEditText(Context context) { // TODO: t6408636 verify if we should schedule a layout after a View does a requestLayout() @Override public boolean isLayoutRequested() { - return false; + // If we are watching and updating container height based on content size + // then we don't want to scroll right away. This isn't perfect -- you might + // want to limit the height the text input can grow to. Possible solution + // is to add another prop that determines whether we should scroll to end + // of text. + if (mContentSizeWatcher != null) { + return isMultiline(); + } else { + return false; + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (mContentSizeWatcher != null) { + mContentSizeWatcher.onLayout(); + } } // Consume 'Enter' key events: TextView tries to give focus to the next TextInput, but it can't // since we only allow JS to change focus, which in turn causes TextView to crash. @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ENTER && - ((getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0 )) { + if (keyCode == KeyEvent.KEYCODE_ENTER && !isMultiline()) { hideSoftKeyboard(); return true; } @@ -162,6 +178,10 @@ public void removeTextChangedListener(TextWatcher watcher) { } } + public void setContentSizeWatcher(ContentSizeWatcher contentSizeWatcher) { + mContentSizeWatcher = contentSizeWatcher; + } + @Override protected void onSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); @@ -212,7 +232,7 @@ public void setInputType(int type) { mStagedInputType = type; // Input type password defaults to monospace font, so we need to re-apply the font super.setTypeface(tf); - + // We override the KeyListener so that all keys on the soft input keyboard as well as hardware // keyboards work. Some KeyListeners like DigitsKeyListener will display the keyboard but not // accept all input from it @@ -329,6 +349,10 @@ private TextWatcherDelegator getTextWatcherDelegator() { return mTextWatcherDelegator; } + private boolean isMultiline() { + return (getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0; + } + /* package */ void setGravityHorizontal(int gravityHorizontal) { if (gravityHorizontal == 0) { gravityHorizontal = mDefaultGravityHorizontal; @@ -447,7 +471,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { @Override public void afterTextChanged(Editable s) { if (!mIsSettingTextFromJS && mListeners != null) { - for (android.text.TextWatcher listener : mListeners) { + for (TextWatcher listener : mListeners) { listener.afterTextChanged(s); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.java index 268b5c465e8c83..2345f15c5ef65a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.java @@ -29,12 +29,11 @@ public class ReactTextChangedEvent extends Event<ReactTextChangedEvent> { public ReactTextChangedEvent( int viewId, - long timestampMs, String text, int contentSizeWidth, int contentSizeHeight, int eventCount) { - super(viewId, timestampMs); + super(viewId); mText = text; mContentWidth = contentSizeWidth; mContentHeight = contentSizeHeight; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputBlurEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputBlurEvent.java index 2b77c314083bf6..a51d01677e8107 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputBlurEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputBlurEvent.java @@ -21,10 +21,8 @@ private static final String EVENT_NAME = "topBlur"; - public ReactTextInputBlurEvent( - int viewId, - long timestampMs) { - super(viewId, timestampMs); + public ReactTextInputBlurEvent(int viewId) { + super(viewId); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEndEditingEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEndEditingEvent.java index ff99d68f7f9abe..9c6975cf7a9869 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEndEditingEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEndEditingEvent.java @@ -26,9 +26,8 @@ class ReactTextInputEndEditingEvent extends Event<ReactTextInputEndEditingEvent> public ReactTextInputEndEditingEvent( int viewId, - long timestampMs, String text) { - super(viewId, timestampMs); + super(viewId); mText = text; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEvent.java index 3d8f4ccbbab963..caab8c136c0eb4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEvent.java @@ -29,12 +29,11 @@ public class ReactTextInputEvent extends Event<ReactTextInputEvent> { public ReactTextInputEvent( int viewId, - long timestampMs, String text, String previousText, int rangeStart, int rangeEnd) { - super(viewId, timestampMs); + super(viewId); mText = text; mPreviousText = previousText; mRangeStart = rangeStart; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputFocusEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputFocusEvent.java index e593e851e872af..3ca67c3d944008 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputFocusEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputFocusEvent.java @@ -21,10 +21,8 @@ private static final String EVENT_NAME = "topFocus"; - public ReactTextInputFocusEvent( - int viewId, - long timestampMs) { - super(viewId, timestampMs); + public ReactTextInputFocusEvent(int viewId) { + super(viewId); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index ebb82644490040..f1ad86b33ca55a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -33,7 +33,6 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.BaseViewManager; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.PixelUtil; @@ -67,7 +66,7 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout private static final String KEYBOARD_TYPE_PHONE_PAD = "phone-pad"; private static final InputFilter[] EMPTY_FILTERS = new InputFilter[0]; private static final int UNSET = -1; - + @Override public String getName() { return REACT_CLASS; @@ -245,6 +244,15 @@ public void setBlurOnSubmit(ReactEditText view, boolean blurOnSubmit) { view.setBlurOnSubmit(blurOnSubmit); } + @ReactProp(name = "onContentSizeChange", defaultBoolean = false) + public void setOnContentSizeChange(final ReactEditText view, boolean onContentSizeChange) { + if (onContentSizeChange) { + view.setContentSizeWatcher(new ReactContentSizeWatcher(view)); + } else { + view.setContentSizeWatcher(null); + } + } + @ReactProp(name = "placeholder") public void setPlaceholder(ReactEditText view, @Nullable String placeholder) { view.setHint(placeholder); @@ -551,15 +559,17 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { if (count == before && newText.equals(oldText)) { return; } + + // TODO: remove contentSize from onTextChanged entirely now that onChangeContentSize exists? int contentWidth = mEditText.getWidth(); int contentHeight = mEditText.getHeight(); // Use instead size of text content within EditText when available if (mEditText.getLayout() != null) { contentWidth = mEditText.getCompoundPaddingLeft() + mEditText.getLayout().getWidth() + - mEditText.getCompoundPaddingRight(); + mEditText.getCompoundPaddingRight(); contentHeight = mEditText.getCompoundPaddingTop() + mEditText.getLayout().getHeight() + - mEditText.getCompoundPaddingTop(); + mEditText.getCompoundPaddingTop(); } // The event that contains the event counter and updates it must be sent first. @@ -567,7 +577,6 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { mEventDispatcher.dispatchEvent( new ReactTextChangedEvent( mEditText.getId(), - SystemClock.nanoTime(), s.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int) PixelUtil.toDIPFromPixel(contentHeight), @@ -576,7 +585,6 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { mEventDispatcher.dispatchEvent( new ReactTextInputEvent( mEditText.getId(), - SystemClock.nanoTime(), newText, oldText, start, @@ -601,18 +609,15 @@ public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { eventDispatcher.dispatchEvent( new ReactTextInputFocusEvent( - editText.getId(), - SystemClock.nanoTime())); + editText.getId())); } else { eventDispatcher.dispatchEvent( new ReactTextInputBlurEvent( - editText.getId(), - SystemClock.nanoTime())); + editText.getId())); eventDispatcher.dispatchEvent( new ReactTextInputEndEditingEvent( editText.getId(), - SystemClock.nanoTime(), editText.getText().toString())); } } @@ -630,7 +635,6 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent keyEvent) { eventDispatcher.dispatchEvent( new ReactTextInputSubmitEditingEvent( editText.getId(), - SystemClock.nanoTime(), editText.getText().toString())); } if (actionId == EditorInfo.IME_ACTION_NEXT || @@ -645,6 +649,44 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent keyEvent) { }); } + private class ReactContentSizeWatcher implements ContentSizeWatcher { + private ReactEditText mEditText; + private EventDispatcher mEventDispatcher; + private int mPreviousContentWidth = 0; + private int mPreviousContentHeight = 0; + + public ReactContentSizeWatcher(ReactEditText editText) { + mEditText = editText; + ReactContext reactContext = (ReactContext) editText.getContext(); + mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); + } + + @Override + public void onLayout() { + int contentWidth = mEditText.getWidth(); + int contentHeight = mEditText.getHeight(); + + // Use instead size of text content within EditText when available + if (mEditText.getLayout() != null) { + contentWidth = mEditText.getCompoundPaddingLeft() + mEditText.getLayout().getWidth() + + mEditText.getCompoundPaddingRight(); + contentHeight = mEditText.getCompoundPaddingTop() + mEditText.getLayout().getHeight() + + mEditText.getCompoundPaddingTop(); + } + + if (contentWidth != mPreviousContentWidth || contentHeight != mPreviousContentHeight) { + mPreviousContentHeight = contentHeight; + mPreviousContentWidth = contentWidth; + + mEventDispatcher.dispatchEvent( + new ReactContentSizeChangedEvent( + mEditText.getId(), + (int) PixelUtil.toDIPFromPixel(contentWidth), + (int) PixelUtil.toDIPFromPixel(contentHeight))); + } + } + } + private class ReactSelectionWatcher implements SelectionWatcher { private ReactEditText mReactEditText; @@ -667,7 +709,6 @@ public void onSelectionChanged(int start, int end) { mEventDispatcher.dispatchEvent( new ReactTextInputSelectionEvent( mReactEditText.getId(), - SystemClock.nanoTime(), start, end ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.java index 8c6b18d9ba6b9d..aec92dde04e543 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.java @@ -27,10 +27,9 @@ public ReactTextInputSelectionEvent( int viewId, - long timestampMs, int selectionStart, int selectionEnd) { - super(viewId, timestampMs); + super(viewId); mSelectionStart = selectionStart; mSelectionEnd = selectionEnd; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java index c75e9797beda38..cc71f6b88c13b0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java @@ -119,7 +119,7 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { if (mJsEventCount != UNSET) { Spannable preparedSpannableText = fromTextCSSNode(this); ReactTextUpdate reactTextUpdate = - new ReactTextUpdate(preparedSpannableText, mJsEventCount, mContainsImages, getPadding()); + new ReactTextUpdate(preparedSpannableText, mJsEventCount, mContainsImages, getPadding(), getEffectiveLineHeight()); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSubmitEditingEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSubmitEditingEvent.java index 0b378dbb85cbe6..32b9755a0129d3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSubmitEditingEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSubmitEditingEvent.java @@ -26,9 +26,8 @@ public ReactTextInputSubmitEditingEvent( int viewId, - long timestampMs, String text) { - super(viewId, timestampMs); + super(viewId); mText = text; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK index df52d70d075366..4c0a41beec10df 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK @@ -4,19 +4,19 @@ android_library( name = 'toolbar', srcs = glob(['**/*.java']), deps = [ - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + CSSLAYOUT_TARGET, react_native_dep('android_res/android/support/v7/appcompat-orig:res-for-react-native'), - react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'), react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'), + react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'), react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'), - react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'), react_native_dep('third-party/android/support/v4:lib-support-v4'), + react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'), react_native_dep('third-party/java/jsr-305:jsr-305'), -], + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/common:common'), + react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), + ], visibility = [ 'PUBLIC', ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java index 9f162f9f0b4145..dab4649d9cfea2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java @@ -25,7 +25,6 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIManagerModule; @@ -131,7 +130,7 @@ protected void addEventEmitters(final ThemedReactContext reactContext, final Rea @Override public void onClick(View v) { mEventDispatcher.dispatchEvent( - new ToolbarClickEvent(view.getId(), SystemClock.nanoTime(), -1)); + new ToolbarClickEvent(view.getId(), -1)); } }); @@ -142,7 +141,6 @@ public boolean onMenuItemClick(MenuItem menuItem) { mEventDispatcher.dispatchEvent( new ToolbarClickEvent( view.getId(), - SystemClock.nanoTime(), menuItem.getOrder())); return true; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/events/ToolbarClickEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/events/ToolbarClickEvent.java index 4e6bfa0ff508b2..2391a2147ef718 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/events/ToolbarClickEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/events/ToolbarClickEvent.java @@ -22,8 +22,8 @@ public class ToolbarClickEvent extends Event<ToolbarClickEvent> { private static final String EVENT_NAME = "topSelect"; private final int position; - public ToolbarClickEvent(int viewId, long timestampMs, int position) { - super(viewId, timestampMs); + public ToolbarClickEvent(int viewId, int position) { + super(viewId); this.position = position; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK index dc06dec1873bf9..76826b3023b30f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK @@ -4,14 +4,14 @@ android_library( name = 'view', srcs = glob(['*.java']), deps = [ + CSSLAYOUT_TARGET, + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/touch:touch'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), ], visibility = [ 'PUBLIC', diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollEvent.java index 2bd0503bfd90a2..79a4cca91468f9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollEvent.java @@ -31,8 +31,8 @@ private final int mPosition; private final float mOffset; - protected PageScrollEvent(int viewTag, long timestampMs, int position, float offset) { - super(viewTag, timestampMs); + protected PageScrollEvent(int viewTag, int position, float offset) { + super(viewTag); mPosition = position; mOffset = offset; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java index 668c0337ef6970..d3a5a7c4a10302 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java @@ -26,8 +26,8 @@ class PageScrollStateChangedEvent extends Event<PageScrollStateChangedEvent> { private final String mPageScrollState; - protected PageScrollStateChangedEvent(int viewTag, long timestampMs, String pageScrollState) { - super(viewTag, timestampMs); + protected PageScrollStateChangedEvent(int viewTag, String pageScrollState) { + super(viewTag); mPageScrollState = pageScrollState; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageSelectedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageSelectedEvent.java index 0b68dcbed073ac..6145c08230bcc2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageSelectedEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageSelectedEvent.java @@ -26,8 +26,8 @@ private final int mPosition; - protected PageSelectedEvent(int viewTag, long timestampMs, int position) { - super(viewTag, timestampMs); + protected PageSelectedEvent(int viewTag, int position) { + super(viewTag); mPosition = position; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java index 8b86beb76f6721..c5a2ea12f71557 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java @@ -19,7 +19,6 @@ import android.view.ViewGroup; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.common.SystemClock; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.NativeGestureUtil; @@ -29,11 +28,12 @@ * views to custom {@link PagerAdapter} instance which is used by {@link NativeViewHierarchyManager} * to add children nodes according to react views hierarchy. */ -/* package */ class ReactViewPager extends ViewPager { +public class ReactViewPager extends ViewPager { private class Adapter extends PagerAdapter { - private List<View> mViews = new ArrayList<>(); + private final List<View> mViews = new ArrayList<>(); + private boolean mIsViewPagerInIntentionallyInconsistentState = false; void addView(View child, int index) { mViews.add(index, child); @@ -58,6 +58,32 @@ void removeViewAt(int index) { setOffscreenPageLimit(mViews.size()); } + /** + * Replace a set of views to the ViewPager adapter and update the ViewPager + */ + void setViews(List<View> views) { + mViews.clear(); + mViews.addAll(views); + notifyDataSetChanged(); + + // we want to make sure we return POSITION_NONE for every view here, since this is only + // called after a removeAllViewsFromAdapter + mIsViewPagerInIntentionallyInconsistentState = false; + } + + /** + * Remove all the views from the adapter and de-parents them from the ViewPager + * After calling this, it is expected that notifyDataSetChanged should be called soon + * afterwards. + */ + void removeAllViewsFromAdapter(ViewPager pager) { + mViews.clear(); + pager.removeAllViews(); + // set this, so that when the next addViews is called, we return POSITION_NONE for every + // entry so we can remove whichever views we need to and add the ones that we need to. + mIsViewPagerInIntentionallyInconsistentState = true; + } + View getViewAt(int index) { return mViews.get(index); } @@ -67,6 +93,13 @@ public int getCount() { return mViews.size(); } + @Override + public int getItemPosition(Object object) { + // if we've removed all views, we want to return POSITION_NONE intentionally + return mIsViewPagerInIntentionallyInconsistentState || !mViews.contains(object) ? + POSITION_NONE : mViews.indexOf(object); + } + @Override public Object instantiateItem(ViewGroup container, int position) { View view = mViews.get(position); @@ -76,8 +109,7 @@ public Object instantiateItem(ViewGroup container, int position) { @Override public void destroyItem(ViewGroup container, int position, Object object) { - View view = mViews.get(position); - container.removeView(view); + container.removeView((View) object); } @Override @@ -91,14 +123,14 @@ private class PageChangeListener implements OnPageChangeListener { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { mEventDispatcher.dispatchEvent( - new PageScrollEvent(getId(), SystemClock.nanoTime(), position, positionOffset)); + new PageScrollEvent(getId(), position, positionOffset)); } @Override public void onPageSelected(int position) { if (!mIsCurrentItemFromJs) { mEventDispatcher.dispatchEvent( - new PageSelectedEvent(getId(), SystemClock.nanoTime(), position)); + new PageSelectedEvent(getId(), position)); } } @@ -119,7 +151,7 @@ public void onPageScrollStateChanged(int state) { throw new IllegalStateException("Unsupported pageScrollState"); } mEventDispatcher.dispatchEvent( - new PageScrollStateChangedEvent(getId(), SystemClock.nanoTime(), pageScrollState)); + new PageScrollStateChangedEvent(getId(), pageScrollState)); } } @@ -187,4 +219,12 @@ public void setScrollEnabled(boolean scrollEnabled) { /*package*/ View getViewFromAdapter(int index) { return getAdapter().getViewAt(index); } + + public void setViews(List<View> views) { + getAdapter().setViews(views); + } + + public void removeAllViewsFromAdapter() { + getAdapter().removeAllViewsFromAdapter(this); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 11350d16e24639..04ad34ce937e96 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -18,6 +18,7 @@ import android.graphics.Bitmap; import android.os.Build; import android.text.TextUtils; +import android.webkit.GeolocationPermissions; import android.webkit.WebView; import android.webkit.WebViewClient; import android.webkit.WebChromeClient; @@ -33,7 +34,6 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.SystemClock; import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; @@ -41,6 +41,8 @@ import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; +import android.content.Intent; +import android.net.Uri; /** * Manages instances of {@link WebView} @@ -107,10 +109,21 @@ public void onPageStarted(WebView webView, String url, Bitmap favicon) { webView, new TopLoadingStartEvent( webView.getId(), - SystemClock.nanoTime(), createWebViewEvent(webView, url))); } + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (url.startsWith("http://") || url.startsWith("https://")) { + return false; + } else { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + view.getContext().startActivity(intent); + return true; + } + } + @Override public void onReceivedError( WebView webView, @@ -130,7 +143,7 @@ public void onReceivedError( dispatchEvent( webView, - new TopLoadingErrorEvent(webView.getId(), SystemClock.nanoTime(), eventData)); + new TopLoadingErrorEvent(webView.getId(), eventData)); } @Override @@ -141,7 +154,6 @@ public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload webView, new TopLoadingStartEvent( webView.getId(), - SystemClock.nanoTime(), createWebViewEvent(webView, url))); } @@ -150,7 +162,6 @@ private void emitFinishEvent(WebView webView, String url) { webView, new TopLoadingFinishEvent( webView.getId(), - SystemClock.nanoTime(), createWebViewEvent(webView, url))); } @@ -245,7 +256,12 @@ public String getName() { @Override protected WebView createViewInstance(ThemedReactContext reactContext) { ReactWebView webView = new ReactWebView(reactContext); - webView.setWebChromeClient(new WebChromeClient()); + webView.setWebChromeClient(new WebChromeClient() { + @Override + public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { + callback.invoke(origin, true, false); + } + }); reactContext.addLifecycleEventListener(webView); mWebViewConfig.configWebView(webView); webView.getSettings().setBuiltInZoomControls(true); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java index 4c32cab15f57e4..795b29f69a4533 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java @@ -21,8 +21,8 @@ public class TopLoadingErrorEvent extends Event<TopLoadingErrorEvent> { public static final String EVENT_NAME = "topLoadingError"; private WritableMap mEventData; - public TopLoadingErrorEvent(int viewId, long timestampMs, WritableMap eventData) { - super(viewId, timestampMs); + public TopLoadingErrorEvent(int viewId, WritableMap eventData) { + super(viewId); mEventData = eventData; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java index f970a80f9d9574..e858fbc176dff9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java @@ -21,8 +21,8 @@ public class TopLoadingFinishEvent extends Event<TopLoadingFinishEvent> { public static final String EVENT_NAME = "topLoadingFinish"; private WritableMap mEventData; - public TopLoadingFinishEvent(int viewId, long timestampMs, WritableMap eventData) { - super(viewId, timestampMs); + public TopLoadingFinishEvent(int viewId, WritableMap eventData) { + super(viewId); mEventData = eventData; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java index df54394c6a5277..171cf6e6515712 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java @@ -21,8 +21,8 @@ public class TopLoadingStartEvent extends Event<TopLoadingStartEvent> { public static final String EVENT_NAME = "topLoadingStart"; private WritableMap mEventData; - public TopLoadingStartEvent(int viewId, long timestampMs, WritableMap eventData) { - super(viewId, timestampMs); + public TopLoadingStartEvent(int viewId, WritableMap eventData) { + super(viewId); mEventData = eventData; } diff --git a/ReactAndroid/src/main/jni/first-party/fb/Android.mk b/ReactAndroid/src/main/jni/first-party/fb/Android.mk index 6eb6eeab9d7dd8..aaf198bce73ff1 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/Android.mk +++ b/ReactAndroid/src/main/jni/first-party/fb/Android.mk @@ -22,7 +22,7 @@ LOCAL_SRC_FILES:= \ LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include -LOCAL_CFLAGS := -DLOG_TAG=\"libfb\" -DDISABLE_XPLAT -fexceptions -frtti +LOCAL_CFLAGS := -DLOG_TAG=\"libfb\" -DDISABLE_CPUCAP -DDISABLE_XPLAT -fexceptions -frtti LOCAL_CFLAGS += -Wall -Werror # include/utils/threads.h has unused parameters LOCAL_CFLAGS += -Wno-unused-parameter diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h index bf6c36162afc80..64f9937a65776c 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h @@ -8,6 +8,7 @@ */ #pragma once +#include <functional> #include <string> #include <jni.h> @@ -21,44 +22,35 @@ struct Environment { // May be null if this thread isn't attached to the JVM FBEXPORT static JNIEnv* current(); static void initialize(JavaVM* vm); + + // There are subtle issues with calling the next functions directly. It is + // much better to always use a ThreadScope to manage attaching/detaching for + // you. FBEXPORT static JNIEnv* ensureCurrentThreadIsAttached(); FBEXPORT static void detachCurrentThread(); }; /** - * RAII Object that attaches a thread to the JVM. Failing to detach from a - * thread before it - * exits will cause a crash, as will calling Detach an extra time, and this - * guard class helps - * keep that straight. In addition, it remembers whether it performed the attach - * or not, so it - * is safe to nest it with itself or with non-fbjni code that manages the - * attachment correctly. + * RAII Object that attaches a thread to the JVM. Failing to detach from a thread before it + * exits will cause a crash, as will calling Detach an extra time, and this guard class helps + * keep that straight. In addition, it remembers whether it performed the attach or not, so it + * is safe to nest it with itself or with non-fbjni code that manages the attachment correctly. * * Potential concerns: - * - Attaching to the JVM is fast (~100us on MotoG), but ideally you would - * attach while the + * - Attaching to the JVM is fast (~100us on MotoG), but ideally you would attach while the * app is not busy. - * - Having a thread detach at arbitrary points is not safe in Dalvik; you need - * to be sure that - * there is no Java code on the current stack or you run the risk of a crash - * like: + * - Having a thread detach at arbitrary points is not safe in Dalvik; you need to be sure that + * there is no Java code on the current stack or you run the risk of a crash like: * ERROR: detaching thread with interp frames (count=18) - * (More detail at - * https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo) - * ThreadScope won't do a detach if the thread was already attached before - * the guard is + * (More detail at https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo) + * ThreadScope won't do a detach if the thread was already attached before the guard is * instantiated, but there's probably some usage that could trip this up. - * - Newly attached C++ threads only get the bootstrap class loader -- i.e. - * java language - * classes, not any of our application's classes. This will be different - * behavior than threads - * that were initiated on the Java side. A workaround is to pass a global - * reference for a - * class or instance to the new thread; this bypasses the need for the class - * loader. - * (See - * http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread) + * - Newly attached C++ threads only get the bootstrap class loader -- i.e. java language + * classes, not any of our application's classes. This will be different behavior than threads + * that were initiated on the Java side. A workaround is to pass a global reference for a + * class or instance to the new thread; this bypasses the need for the class loader. + * (See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread) + * If you need access to the application's classes, you can use ThreadScope::WithClassLoader. */ class FBEXPORT ThreadScope { public: @@ -69,6 +61,15 @@ class FBEXPORT ThreadScope { ThreadScope& operator=(ThreadScope&&) = delete; ~ThreadScope(); + /** + * This runs the closure in a scope with fbjni's classloader. This should be + * the same classloader as the rest of the application and thus anything + * running in the closure will have access to the same classes as in a normal + * java-create thread. + */ + static void WithClassLoader(std::function<void()>&& runnable); + + static void OnLoad(); private: bool attachedWithThisScope_; }; diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h index 5db5293044a68e..9da51c406e989f 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h @@ -29,11 +29,31 @@ # endif #endif +// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as +// a C++ exception. +#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \ + ::facebook::jni::throwPendingJniExceptionAsCppException() + +// If the condition is true, throws a JniException object, which wraps the pending JNI Java +// exception if any. If no pending exception is found, throws a JniException object that wraps a +// RuntimeException throwable. +#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \ + ::facebook::jni::throwCppExceptionIf(CONDITION) + /// @cond INTERNAL namespace facebook { namespace jni { +FBEXPORT void throwPendingJniExceptionAsCppException(); +FBEXPORT void throwCppExceptionIf(bool condition); + +[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable); +[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg); +template<typename... Args> +[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args); + + /** * This needs to be called at library load time, typically in your JNI_OnLoad method. * diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h index 136ca82f1917f4..8630aa6bf49d80 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h @@ -25,6 +25,10 @@ class AContext : public JavaClass<AContext> { return method(self()); } + local_ref<JFile::javaobject> getFilesDir() { + static auto method = getClass()->getMethod<JFile::javaobject()>("getFilesDir"); + return method(self()); + } }; } diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h index f5f861ba3c6d11..40fb94b7b5a0d7 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h @@ -341,6 +341,13 @@ struct Convert<const char*> { }; } +// jthrowable ////////////////////////////////////////////////////////////////////////////////////// + +inline local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) { + static auto meth = javaClassStatic()->getMethod<javaobject(javaobject)>("initCause"); + return meth(self(), cause.get()); +} + // jtypeArray ////////////////////////////////////////////////////////////////////////////////////// namespace detail { diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h index 62b8bb7fe810bf..9e0bb87641f1fe 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h @@ -212,7 +212,7 @@ class FBEXPORT JavaClass : public Base { /// the Java class actually has (i.e. with static create() functions). template<typename... Args> static local_ref<T> newInstance(Args... args) { - return detail::newInstance<JavaClass>(args...); + return detail::newInstance<T>(args...); } javaobject self() const noexcept; @@ -344,6 +344,8 @@ FBEXPORT local_ref<JString> make_jstring(const std::string& modifiedUtf8); class FBEXPORT JThrowable : public JavaClass<JThrowable, JObject, jthrowable> { public: static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;"; + + local_ref<JThrowable> initCause(alias_ref<JThrowable> cause); }; namespace detail { diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h index 8edb871c2d3c12..ce2cc70bdc3ffd 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h @@ -29,30 +29,26 @@ #include <fb/visibility.h> #include "Common.h" - -// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as -// a C++ exception. -#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \ - ::facebook::jni::throwPendingJniExceptionAsCppException() - -// If the condition is true, throws a JniException object, which wraps the pending JNI Java -// exception if any. If no pending exception is found, throws a JniException object that wraps a -// RuntimeException throwable. -#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \ - ::facebook::jni::throwCppExceptionIf(CONDITION) +#include "References.h" +#include "CoreClasses.h" namespace facebook { namespace jni { -namespace internal { - void initExceptionHelpers(); -} +class JThrowable; -/** - * Before using any of the state initialized above, call this. It - * will assert if initialization has not yet occurred. - */ -FBEXPORT void assertIfExceptionsNotInitialized(); +class JCppException : public JavaClass<JCppException, JThrowable> { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppException;"; + + static local_ref<JCppException> create(const char* str) { + return newInstance(make_jstring(str)); + } + + static local_ref<JCppException> create(const std::exception& ex) { + return newInstance(make_jstring(ex.what())); + } +}; // JniException //////////////////////////////////////////////////////////////////////////////////// @@ -67,23 +63,22 @@ FBEXPORT void assertIfExceptionsNotInitialized(); class FBEXPORT JniException : public std::exception { public: JniException(); + ~JniException(); - explicit JniException(jthrowable throwable); + explicit JniException(alias_ref<jthrowable> throwable); JniException(JniException &&rhs); JniException(const JniException &other); - ~JniException() noexcept; - - jthrowable getThrowable() const noexcept; + local_ref<JThrowable> getThrowable() const noexcept; virtual const char* what() const noexcept; void setJavaException() const noexcept; private: - jthrowable throwableGlobalRef_; + global_ref<JThrowable> throwable_; mutable std::string what_; mutable bool isMessageExtracted_; const static std::string kExceptionMessageFailure_; @@ -95,16 +90,8 @@ class FBEXPORT JniException : public std::exception { // Functions that throw C++ exceptions -FBEXPORT void throwPendingJniExceptionAsCppException(); - -FBEXPORT void throwCppExceptionIf(bool condition); - static const int kMaxExceptionMessageBufferSize = 512; -[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable); - -[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg); - // These methods are the preferred way to throw a Java exception from // a C++ function. They create and throw a C++ exception which wraps // a Java exception, so the C++ flow is interrupted. Then, when @@ -113,7 +100,6 @@ static const int kMaxExceptionMessageBufferSize = 512; // thrown to the java caller. template<typename... Args> [[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args) { - assertIfExceptionsNotInitialized(); int msgSize = snprintf(nullptr, 0, fmt, args...); char *msg = (char*) alloca(msgSize + 1); @@ -123,7 +109,7 @@ template<typename... Args> // Identifies any pending C++ exception and throws it as a Java exception. If the exception can't // be thrown, it aborts the program. This is a noexcept function at C++ level. -void translatePendingCppExceptionToJavaException() noexcept; +FBEXPORT void translatePendingCppExceptionToJavaException() noexcept; // For convenience, some exception names in java.lang are available here. diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/JThread.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/JThread.h new file mode 100644 index 00000000000000..60b3cac20ab56b --- /dev/null +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/JThread.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016-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. + */ + +#pragma once + +#include "CoreClasses.h" +#include "NativeRunnable.h" + +namespace facebook { +namespace jni { + +class JThread : public JavaClass<JThread> { + public: + static constexpr const char* kJavaDescriptor = "Ljava/lang/Thread;"; + + void start() { + static auto method = javaClassStatic()->getMethod<void()>("start"); + method(self()); + } + + void join() { + static auto method = javaClassStatic()->getMethod<void()>("join"); + method(self()); + } + + static local_ref<JThread> create(std::function<void()>&& runnable) { + auto jrunnable = JNativeRunnable::newObjectCxxArgs(std::move(runnable)); + return newInstance(static_ref_cast<JRunnable::javaobject>(jrunnable)); + } +}; + +} +} diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h index dda35925dbc07c..45cf2d1bd80260 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h @@ -66,7 +66,7 @@ local_ref<JArrayClass<jobject>::javaobject> makeArgsArray(Args... args) { } -bool needsSlowPath(alias_ref<jobject> obj) { +inline bool needsSlowPath(alias_ref<jobject> obj) { #if defined(__ANDROID__) // On Android 6.0, art crashes when attempting to call a function on a Proxy. // So, when we detect that case we must use the safe, slow workaround. That is, @@ -88,19 +88,6 @@ bool needsSlowPath(alias_ref<jobject> obj) { } -template <typename... Args> -local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args) { - static auto invoke = findClassStatic("java/lang/reflect/Method") - ->getMethod<jobject(jobject, JArrayClass<jobject>::javaobject)>("invoke"); - // TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod. - auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE)); - FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); - if (!reflected) throw JniException(); - auto argsArray = makeArgsArray(args...); - // No need to check for exceptions since invoke is itself a JMethod that will do that for us. - return invoke(reflected, self.get(), argsArray.get()); -} - template<typename... Args> inline void JMethod<void(Args...)>::operator()(alias_ref<jobject> self, Args... args) { const auto env = Environment::current(); @@ -285,6 +272,19 @@ class JNonvirtualMethod<R(Args...)> : public JMethodBase { friend class JClass; }; +template <typename... Args> +local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args) { + static auto invoke = findClassStatic("java/lang/reflect/Method") + ->getMethod<jobject(jobject, JArrayClass<jobject>::javaobject)>("invoke"); + // TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod. + auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE)); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + if (!reflected) throw std::runtime_error("Unable to get reflected java.lang.reflect.Method"); + auto argsArray = makeArgsArray(args...); + // No need to check for exceptions since invoke is itself a JMethod that will do that for us. + return invoke(reflected, self.get(), argsArray.get()); +} + // JField<T> /////////////////////////////////////////////////////////////////////////////////////// diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/NativeRunnable.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/NativeRunnable.h new file mode 100644 index 00000000000000..68da6a25fb4c7d --- /dev/null +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/NativeRunnable.h @@ -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. + */ + +#pragma once + +#include "CoreClasses.h" +#include "Hybrid.h" +#include "Registration.h" + +#include <functional> + +namespace facebook { +namespace jni { + +struct JRunnable : public JavaClass<JRunnable> { + static auto constexpr kJavaDescriptor = "Ljava/lang/Runnable;"; +}; + +struct JNativeRunnable : public HybridClass<JNativeRunnable, JRunnable> { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/NativeRunnable;"; + + JNativeRunnable(std::function<void()>&& runnable) : runnable_(std::move(runnable)) {} + + static void OnLoad() { + registerHybrid({ + makeNativeMethod("run", JNativeRunnable::run), + }); + } + + void run() { + runnable_(); + } + + private: + std::function<void()> runnable_; +}; + + +} // namespace jni +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h index 5f69a3b41503f6..9106dd2532a229 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h @@ -13,8 +13,6 @@ #include <new> #include <atomic> -#include "Exceptions.h" - namespace facebook { namespace jni { diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h b/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h index 2763e62be8d708..7278fc848f8dd4 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h @@ -23,8 +23,8 @@ namespace detail { void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength); size_t modifiedLength(const std::string& str); size_t modifiedLength(const uint8_t* str, size_t* length); -std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len); -std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len); +std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept; +std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len) noexcept; } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp index 60f63ef6bf7259..e8ea51dc06877e 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp @@ -12,12 +12,40 @@ #include <fb/StaticInitialized.h> #include <fb/ThreadLocal.h> #include <fb/Environment.h> +#include <fb/fbjni/CoreClasses.h> +#include <fb/fbjni/NativeRunnable.h> + +#include <functional> namespace facebook { namespace jni { -static StaticInitialized<ThreadLocal<JNIEnv>> g_env; -static JavaVM* g_vm = nullptr; +namespace { +StaticInitialized<ThreadLocal<JNIEnv>> g_env; +JavaVM* g_vm = nullptr; + +struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> { + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/ThreadScopeSupport;"; + + // These reinterpret_casts are a totally dangerous pattern. Don't use them. Use HybridData instead. + static void runStdFunction(std::function<void()>&& func) { + static auto method = javaClassStatic()->getStaticMethod<void(jlong)>("runStdFunction"); + method(javaClassStatic(), reinterpret_cast<jlong>(&func)); + } + + static void runStdFunctionImpl(alias_ref<JClass>, jlong ptr) { + (*reinterpret_cast<std::function<void()>*>(ptr))(); + } + + static void OnLoad() { + // We need the javaClassStatic so that the class lookup is cached and that + // runStdFunction can be called from a ThreadScope-attached thread. + javaClassStatic()->registerNatives({ + makeNativeMethod("runStdFunctionImpl", runStdFunctionImpl), + }); + } +}; +} /* static */ JNIEnv* Environment::current() { @@ -25,6 +53,7 @@ JNIEnv* Environment::current() { if ((env == nullptr) && (g_vm != nullptr)) { if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM"); + // TODO(cjhopman): This should throw an exception. env = nullptr; } else { g_env->reset(env); @@ -85,5 +114,19 @@ ThreadScope::~ThreadScope() { } } +/* static */ +void ThreadScope::OnLoad() { + // These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading. + JThreadScopeSupport::OnLoad(); +} + +/* static */ +void ThreadScope::WithClassLoader(std::function<void()>&& runnable) { + // TODO(cjhopman): If the classloader is already available in this scope, we + // shouldn't have to jump through java. + ThreadScope ts; + JThreadScopeSupport::runStdFunction(std::move(runnable)); +} + } } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp index e7d38054cfd5a1..bfc99214a904a6 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp @@ -7,9 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#include "fb/fbjni.h" +#include <fb/fbjni/CoreClasses.h> #include <fb/assert.h> +#include <fb/log.h> #include <alloca.h> #include <cstdlib> @@ -21,234 +22,113 @@ #include <jni.h> + namespace facebook { namespace jni { -// CommonJniExceptions ///////////////////////////////////////////////////////////////////////////// - -class FBEXPORT CommonJniExceptions { +namespace { +class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> { public: - static void init(); + static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;"; - static jclass getThrowableClass() { - return throwableClass_; + static local_ref<JRuntimeException> create(const char* str) { + return newInstance(make_jstring(str)); } - static jclass getUnknownCppExceptionClass() { - return unknownCppExceptionClass_; + static local_ref<JRuntimeException> create() { + return newInstance(); } +}; + +class JIOException : public JavaClass<JIOException, JThrowable> { + public: + static auto constexpr kJavaDescriptor = "Ljava/io/IOException;"; - static jthrowable getUnknownCppExceptionObject() { - return unknownCppExceptionObject_; + static local_ref<JIOException> create(const char* str) { + return newInstance(make_jstring(str)); } +}; + +class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;"; - static jthrowable getRuntimeExceptionObject() { - return runtimeExceptionObject_; + static local_ref<JOutOfMemoryError> create(const char* str) { + return newInstance(make_jstring(str)); } +}; + +class JArrayIndexOutOfBoundsException : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;"; - private: - static jclass throwableClass_; - static jclass unknownCppExceptionClass_; - static jthrowable unknownCppExceptionObject_; - static jthrowable runtimeExceptionObject_; + static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) { + return newInstance(make_jstring(str)); + } }; -// The variables in this class are all JNI global references and are intentionally leaked because -// we assume this library cannot be unloaded. These global references are created manually instead -// of using global_ref from References.h to avoid circular dependency. -jclass CommonJniExceptions::throwableClass_ = nullptr; -jclass CommonJniExceptions::unknownCppExceptionClass_ = nullptr; -jthrowable CommonJniExceptions::unknownCppExceptionObject_ = nullptr; -jthrowable CommonJniExceptions::runtimeExceptionObject_ = nullptr; - - -// Variable to guarantee that fallback exceptions have been initialized early. We don't want to -// do pure dynamic initialization -- we want to warn programmers early that they need to run the -// helpers at library load time instead of lazily getting them when the exception helpers are -// first used. -static std::atomic<bool> gIsInitialized(false); - -void CommonJniExceptions::init() { - JNIEnv* env = internal::getEnv(); - FBASSERTMSGF(env, "Could not get JNI Environment"); - - // Throwable class - jclass localThrowableClass = env->FindClass("java/lang/Throwable"); - FBASSERT(localThrowableClass); - throwableClass_ = static_cast<jclass>(env->NewGlobalRef(localThrowableClass)); - FBASSERT(throwableClass_); - env->DeleteLocalRef(localThrowableClass); - - // UnknownCppException class - jclass localUnknownCppExceptionClass = env->FindClass("com/facebook/jni/UnknownCppException"); - FBASSERT(localUnknownCppExceptionClass); - jmethodID unknownCppExceptionConstructorMID = env->GetMethodID( - localUnknownCppExceptionClass, - "<init>", - "()V"); - FBASSERT(unknownCppExceptionConstructorMID); - unknownCppExceptionClass_ = static_cast<jclass>(env->NewGlobalRef(localUnknownCppExceptionClass)); - FBASSERT(unknownCppExceptionClass_); - env->DeleteLocalRef(localUnknownCppExceptionClass); - - // UnknownCppException object - jthrowable localUnknownCppExceptionObject = static_cast<jthrowable>(env->NewObject( - unknownCppExceptionClass_, - unknownCppExceptionConstructorMID)); - FBASSERT(localUnknownCppExceptionObject); - unknownCppExceptionObject_ = static_cast<jthrowable>(env->NewGlobalRef( - localUnknownCppExceptionObject)); - FBASSERT(unknownCppExceptionObject_); - env->DeleteLocalRef(localUnknownCppExceptionObject); - - // RuntimeException object - jclass localRuntimeExceptionClass = env->FindClass("java/lang/RuntimeException"); - FBASSERT(localRuntimeExceptionClass); - - jmethodID runtimeExceptionConstructorMID = env->GetMethodID( - localRuntimeExceptionClass, - "<init>", - "()V"); - FBASSERT(runtimeExceptionConstructorMID); - jthrowable localRuntimeExceptionObject = static_cast<jthrowable>(env->NewObject( - localRuntimeExceptionClass, - runtimeExceptionConstructorMID)); - FBASSERT(localRuntimeExceptionObject); - runtimeExceptionObject_ = static_cast<jthrowable>(env->NewGlobalRef(localRuntimeExceptionObject)); - FBASSERT(runtimeExceptionObject_); - - env->DeleteLocalRef(localRuntimeExceptionClass); - env->DeleteLocalRef(localRuntimeExceptionObject); -} +class JUnknownCppException : public JavaClass<JUnknownCppException, JThrowable> { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;"; + static local_ref<JUnknownCppException> create() { + return newInstance(); + } -// initExceptionHelpers() ////////////////////////////////////////////////////////////////////////// + static local_ref<JUnknownCppException> create(const char* str) { + return newInstance(make_jstring(str)); + } +}; -void internal::initExceptionHelpers() { - CommonJniExceptions::init(); - gIsInitialized.store(true, std::memory_order_seq_cst); -} +class JCppSystemErrorException : public JavaClass<JCppSystemErrorException, JThrowable> { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;"; -void assertIfExceptionsNotInitialized() { - // Use relaxed memory order because we don't need memory barriers. - // The real init-once enforcement is done by the compiler for the - // "static" in initExceptionHelpers. - FBASSERTMSGF(gIsInitialized.load(std::memory_order_relaxed), - "initExceptionHelpers was never called!"); -} + static local_ref<JCppSystemErrorException> create(const std::system_error& e) { + return newInstance(make_jstring(e.what()), e.code().value()); + } +}; // Exception throwing & translating functions ////////////////////////////////////////////////////// // Functions that throw Java exceptions -namespace { - -void setJavaExceptionAndAbortOnFailure(jthrowable throwable) noexcept { - assertIfExceptionsNotInitialized(); - JNIEnv* env = internal::getEnv(); +void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) { + auto env = Environment::current(); if (throwable) { - env->Throw(throwable); + env->Throw(throwable.get()); } if (env->ExceptionCheck() != JNI_TRUE) { std::abort(); } } -void setDefaultException() noexcept { - assertIfExceptionsNotInitialized(); - setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getRuntimeExceptionObject()); -} - -void setCppSystemErrorExceptionInJava(const std::system_error& ex) noexcept { - assertIfExceptionsNotInitialized(); - JNIEnv* env = internal::getEnv(); - jclass cppSystemErrorExceptionClass = env->FindClass( - "com/facebook/jni/CppSystemErrorException"); - if (!cppSystemErrorExceptionClass) { - setDefaultException(); - return; - } - jmethodID constructorMID = env->GetMethodID( - cppSystemErrorExceptionClass, - "<init>", - "(Ljava/lang/String;I)V"); - if (!constructorMID) { - setDefaultException(); - return; - } - jthrowable cppSystemErrorExceptionObject = static_cast<jthrowable>(env->NewObject( - cppSystemErrorExceptionClass, - constructorMID, - env->NewStringUTF(ex.what()), - ex.code().value())); - setJavaExceptionAndAbortOnFailure(cppSystemErrorExceptionObject); -} - -template<typename... ARGS> -void setNewJavaException(jclass exceptionClass, const char* fmt, ARGS... args) { - assertIfExceptionsNotInitialized(); - int msgSize = snprintf(nullptr, 0, fmt, args...); - JNIEnv* env = internal::getEnv(); - - try { - char *msg = (char*) alloca(msgSize + 1); - snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...); - env->ThrowNew(exceptionClass, msg); - } catch (...) { - env->ThrowNew(exceptionClass, ""); - } - - if (env->ExceptionCheck() != JNI_TRUE) { - setDefaultException(); - } -} - -void setNewJavaException(jclass exceptionClass, const char* msg) { - assertIfExceptionsNotInitialized(); - setNewJavaException(exceptionClass, "%s", msg); -} - -template<typename... ARGS> -void setNewJavaException(const char* className, const char* fmt, ARGS... args) { - assertIfExceptionsNotInitialized(); - JNIEnv* env = internal::getEnv(); - jclass exceptionClass = env->FindClass(className); - if (env->ExceptionCheck() != JNI_TRUE && !exceptionClass) { - // If FindClass() has failed but no exception has been thrown, throw a default exception. - setDefaultException(); - return; - } - setNewJavaException(exceptionClass, fmt, args...); -} - } // Functions that throw C++ exceptions // TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated -FBEXPORT void throwPendingJniExceptionAsCppException() { - assertIfExceptionsNotInitialized(); - JNIEnv* env = internal::getEnv(); +void throwPendingJniExceptionAsCppException() { + JNIEnv* env = Environment::current(); if (env->ExceptionCheck() == JNI_FALSE) { return; } - jthrowable throwable = env->ExceptionOccurred(); + auto throwable = adopt_local(env->ExceptionOccurred()); if (!throwable) { throw std::runtime_error("Unable to get pending JNI exception."); } - env->ExceptionClear(); + throw JniException(throwable); } void throwCppExceptionIf(bool condition) { - assertIfExceptionsNotInitialized(); if (!condition) { return; } - JNIEnv* env = internal::getEnv(); + auto env = Environment::current(); if (env->ExceptionCheck() == JNI_TRUE) { throwPendingJniExceptionAsCppException(); return; @@ -257,13 +137,11 @@ void throwCppExceptionIf(bool condition) { throw JniException(); } -FBEXPORT void throwNewJavaException(jthrowable throwable) { - throw JniException(throwable); +void throwNewJavaException(jthrowable throwable) { + throw JniException(wrap_alias(throwable)); } -FBEXPORT void throwNewJavaException( - const char* throwableName, - const char* msg) { +void throwNewJavaException(const char* throwableName, const char* msg) { // If anything of the fbjni calls fail, an exception of a suitable // form will be thrown, which is what we want. auto throwableClass = findClassLocal(throwableName); @@ -275,34 +153,80 @@ FBEXPORT void throwNewJavaException( // Translate C++ to Java Exception -FBEXPORT void translatePendingCppExceptionToJavaException() noexcept { - assertIfExceptionsNotInitialized(); +namespace { + +// The implementation std::rethrow_if_nested uses a dynamic_cast to determine +// if the exception is a nested_exception. If the exception is from a library +// built with -fno-rtti, then that will crash. This avoids that. +void rethrow_if_nested() { + try { + throw; + } catch (const std::nested_exception& e) { + e.rethrow_nested(); + } catch (...) { + } +} + +// For each exception in the chain of the currently handled exception, func +// will be called with that exception as the currently handled exception (in +// reverse order, i.e. innermost first). +void denest(std::function<void()> func) { try { + throw; + } catch (const std::exception& e) { + try { + rethrow_if_nested(); + } catch (...) { + denest(func); + } + func(); + } catch (...) { + func(); + } +} +} + +void translatePendingCppExceptionToJavaException() noexcept { + local_ref<JThrowable> previous; + auto func = [&previous] () { + local_ref<JThrowable> current; try { throw; } catch(const JniException& ex) { - ex.setJavaException(); + current = ex.getThrowable(); } catch(const std::ios_base::failure& ex) { - setNewJavaException("java/io/IOException", ex.what()); + current = JIOException::create(ex.what()); } catch(const std::bad_alloc& ex) { - setNewJavaException("java/lang/OutOfMemoryError", ex.what()); + current = JOutOfMemoryError::create(ex.what()); } catch(const std::out_of_range& ex) { - setNewJavaException("java/lang/ArrayIndexOutOfBoundsException", ex.what()); + current = JArrayIndexOutOfBoundsException::create(ex.what()); } catch(const std::system_error& ex) { - setCppSystemErrorExceptionInJava(ex); + current = JCppSystemErrorException::create(ex); } catch(const std::runtime_error& ex) { - setNewJavaException("java/lang/RuntimeException", ex.what()); + current = JRuntimeException::create(ex.what()); } catch(const std::exception& ex) { - setNewJavaException("com/facebook/jni/CppException", ex.what()); + current = JCppException::create(ex.what()); } catch(const char* msg) { - setNewJavaException(CommonJniExceptions::getUnknownCppExceptionClass(), msg); + current = JUnknownCppException::create(msg); } catch(...) { - setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getUnknownCppExceptionObject()); + current = JUnknownCppException::create(); } - } catch(...) { - // This block aborts the program, if something bad happens when handling exceptions, thus - // keeping this function noexcept. - std::abort(); + if (previous) { + current->initCause(previous); + } + previous = current; + }; + + try { + denest(func); + setJavaExceptionAndAbortOnFailure(previous); + } catch (std::exception& e) { + FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException: %s", e.what()); + // rethrow the exception and let the noexcept handling abort. + throw; + } catch (...) { + FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException"); + throw; } } @@ -310,79 +234,41 @@ FBEXPORT void translatePendingCppExceptionToJavaException() noexcept { const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message."; -JniException::JniException() : JniException(CommonJniExceptions::getRuntimeExceptionObject()) { } +JniException::JniException() : JniException(JRuntimeException::create()) { } -JniException::JniException(jthrowable throwable) : isMessageExtracted_(false) { - assertIfExceptionsNotInitialized(); - throwableGlobalRef_ = static_cast<jthrowable>(internal::getEnv()->NewGlobalRef(throwable)); - if (!throwableGlobalRef_) { - throw std::bad_alloc(); - } +JniException::JniException(alias_ref<jthrowable> throwable) : isMessageExtracted_(false) { + throwable_ = make_global(throwable); } JniException::JniException(JniException &&rhs) - : throwableGlobalRef_(std::move(rhs.throwableGlobalRef_)), + : throwable_(std::move(rhs.throwable_)), what_(std::move(rhs.what_)), isMessageExtracted_(rhs.isMessageExtracted_) { - rhs.throwableGlobalRef_ = nullptr; } JniException::JniException(const JniException &rhs) : what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) { - JNIEnv* env = internal::getEnv(); - if (rhs.getThrowable()) { - throwableGlobalRef_ = static_cast<jthrowable>(env->NewGlobalRef(rhs.getThrowable())); - if (!throwableGlobalRef_) { - throw std::bad_alloc(); - } - } else { - throwableGlobalRef_ = nullptr; - } + throwable_ = make_global(rhs.throwable_); } -JniException::~JniException() noexcept { - if (throwableGlobalRef_) { - internal::getEnv()->DeleteGlobalRef(throwableGlobalRef_); - } +JniException::~JniException() { + ThreadScope ts; + throwable_.reset(); } -jthrowable JniException::getThrowable() const noexcept { - return throwableGlobalRef_; +local_ref<JThrowable> JniException::getThrowable() const noexcept { + return make_local(throwable_); } // TODO 6900503: consider making this thread-safe. void JniException::populateWhat() const noexcept { - JNIEnv* env = internal::getEnv(); - - jmethodID toStringMID = env->GetMethodID( - CommonJniExceptions::getThrowableClass(), - "toString", - "()Ljava/lang/String;"); - jstring messageJString = (jstring) env->CallObjectMethod( - throwableGlobalRef_, - toStringMID); - - isMessageExtracted_ = true; - - if (env->ExceptionCheck()) { - env->ExceptionClear(); - what_ = kExceptionMessageFailure_; - return; - } - - const char* chars = env->GetStringUTFChars(messageJString, nullptr); - if (!chars) { - what_ = kExceptionMessageFailure_; - return; - } - + ThreadScope ts; try { - what_ = std::string(chars); + what_ = throwable_->toString(); + isMessageExtracted_ = true; } catch(...) { what_ = kExceptionMessageFailure_; } - - env->ReleaseStringUTFChars(messageJString, chars); } const char* JniException::what() const noexcept { @@ -393,7 +279,7 @@ const char* JniException::what() const noexcept { } void JniException::setJavaException() const noexcept { - setJavaExceptionAndAbortOnFailure(throwableGlobalRef_); + setJavaExceptionAndAbortOnFailure(throwable_); } }} diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp index 1a0e8d703a1236..2e197f07598397 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp @@ -156,7 +156,7 @@ void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size modified[j++] = '\0'; } -std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) { +std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept { // Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient std::string utf8(len, 0); size_t j = 0; @@ -230,7 +230,7 @@ size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) { return utf8StringLen; } -std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) { +std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept { if (!utf16String || utf16StringLen <= 0) { return ""; } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp index d5041613378886..0334729c8e20b6 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp @@ -10,10 +10,13 @@ #include <jni/Countable.h> #include <fb/Environment.h> #include <fb/fbjni.h> +#include <fb/fbjni/NativeRunnable.h> using namespace facebook::jni; void initialize_fbjni() { CountableOnLoad(Environment::current()); HybridDataOnLoad(); + JNativeRunnable::OnLoad(); + ThreadScope::OnLoad(); } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp index e2bd795fab64e4..de9423c2019204 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp @@ -26,7 +26,6 @@ jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept { std::call_once(flag, [vm] { try { Environment::initialize(vm); - internal::initExceptionHelpers(); } catch (std::exception& ex) { error_occured = true; try { @@ -58,6 +57,9 @@ jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept { alias_ref<JClass> findClassStatic(const char* name) { const auto env = internal::getEnv(); + if (!env) { + throw std::runtime_error("Unable to retrieve JNIEnv*."); + } auto cls = env->FindClass(name); FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); auto leaking_ref = (jclass)env->NewGlobalRef(cls); @@ -67,6 +69,9 @@ alias_ref<JClass> findClassStatic(const char* name) { local_ref<JClass> findClassLocal(const char* name) { const auto env = internal::getEnv(); + if (!env) { + throw std::runtime_error("Unable to retrieve JNIEnv*."); + } auto cls = env->FindClass(name); FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); return adopt_local(cls); diff --git a/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp b/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp index c3d25df1ac5952..f915ecef29d606 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp @@ -2,6 +2,7 @@ #include <fb/lyra.h> +#include <ios> #include <memory> #include <vector> @@ -15,6 +16,21 @@ namespace lyra { namespace { +class IosFlagsSaver { + ios_base& ios_; + ios_base::fmtflags flags_; + + public: + IosFlagsSaver(ios_base& ios) + : ios_(ios), + flags_(ios.flags()) + {} + + ~IosFlagsSaver() { + ios_.flags(flags_); + } +}; + struct BacktraceState { size_t skip; vector<InstructionPointer>& stackTrace; @@ -71,6 +87,8 @@ void getStackTraceSymbols(vector<StackTraceElement>& symbols, } ostream& operator<<(ostream& out, const StackTraceElement& elm) { + IosFlagsSaver flags{out}; + // TODO(t10748683): Add build id to the output out << "{dso=" << elm.libraryName() << " offset=" << hex << showbase << elm.libraryOffset(); @@ -88,6 +106,8 @@ ostream& operator<<(ostream& out, const StackTraceElement& elm) { // TODO(t10737667): The implement a tool that parse the stack trace and // symbolicate it ostream& operator<<(ostream& out, const vector<StackTraceElement>& trace) { + IosFlagsSaver flags{out}; + auto i = 0; out << "Backtrace:\n"; for (auto& elm : trace) { diff --git a/ReactAndroid/src/main/jni/first-party/fb/onload.cpp b/ReactAndroid/src/main/jni/first-party/fb/onload.cpp index 9357940a4ff151..2caebc37a4eb38 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/onload.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/onload.cpp @@ -8,6 +8,9 @@ */ #include <jni.h> +#ifndef DISABLE_CPUCAP +#include <fb/CpuCapabilities.h> +#endif #include <fb/fbjni.h> using namespace facebook::jni; @@ -20,6 +23,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { initialize_fbjni(); #ifndef DISABLE_XPLAT initialize_xplatinit(); +#endif +#ifndef DISABLE_CPUCAP + initialize_cpucapabilities(); #endif }); } diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp index ee5bcb16cc44f3..8940b86fd0e79e 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.cpp +++ b/ReactAndroid/src/main/jni/react/Bridge.cpp @@ -49,10 +49,11 @@ void Bridge::callFunction( ExecutorToken executorToken, const std::string& moduleId, const std::string& methodId, - const folly::dynamic& arguments, - const std::string& tracingName) { + const folly::dynamic& arguments) { #ifdef WITH_FBSYSTRACE int systraceCookie = m_systraceCookie++; + std::string tracingName = fbsystrace_is_tracing(TRACE_TAG_REACT_CXX_BRIDGE) ? + folly::to<std::string>("JSCall__", moduleId, '_', methodId) : std::string(); FbSystraceAsyncFlow::begin( TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str(), @@ -60,14 +61,14 @@ void Bridge::callFunction( #endif #ifdef WITH_FBSYSTRACE - runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) { + runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName = std::move(tracingName), systraceCookie] (JSExecutor* executor) { FbSystraceAsyncFlow::end( TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str(), systraceCookie); FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str()); #else - runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName] (JSExecutor* executor) { + runOnExecutorQueue(executorToken, [moduleId, methodId, arguments] (JSExecutor* executor) { #endif // This is safe because we are running on the executor's thread: it won't // destruct until after it's been unregistered (which we check above) and diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h index b2c004a5855bb5..3920fdd7db01a4 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.h +++ b/ReactAndroid/src/main/jni/react/Bridge.h @@ -68,8 +68,7 @@ class Bridge { ExecutorToken executorToken, const std::string& module, const std::string& method, - const folly::dynamic& args, - const std::string& tracingName); + const folly::dynamic& args); /** * Invokes a callback with the cbID, and optional additional arguments in JS. diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index a26a79742500e2..c76c24ed18d4b6 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -68,25 +68,6 @@ static JSValueRef nativeInjectHMRUpdate( const JSValueRef arguments[], JSValueRef *exception); -static std::string executeJSCallWithJSC( - JSGlobalContextRef ctx, - const std::string& methodName, - const std::vector<folly::dynamic>& arguments) { - #ifdef WITH_FBSYSTRACE - FbSystraceSection s( - TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall", - "method", methodName); - #endif - - // Evaluate script with JSC - folly::dynamic jsonArgs(arguments.begin(), arguments.end()); - auto js = folly::to<std::string>( - "__fbBatchedBridge.", methodName, ".apply(null, ", - folly::toJson(jsonArgs), ")"); - auto result = evaluateScript(ctx, String(js.c_str()), nullptr); - return Value(ctx, result).toJSONString(); -} - std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(Bridge *bridge) { return std::unique_ptr<JSExecutor>(new JSCExecutor(bridge, cacheDir_, m_jscConfig)); } @@ -202,6 +183,8 @@ void JSCExecutor::terminateOnJSVMThread() { m_batchedBridge.reset(); m_flushedQueueObj.reset(); + m_callFunctionObj.reset(); + m_invokeCallbackObj.reset(); s_globalContextRefToJSCExecutor.erase(m_context); JSGlobalContextRelease(m_context); @@ -266,6 +249,8 @@ bool JSCExecutor::ensureBatchedBridgeObject() { } m_batchedBridge = folly::make_unique<Object>(batchedBridgeValue.asObject()); m_flushedQueueObj = folly::make_unique<Object>(m_batchedBridge->getProperty("flushedQueue").asObject()); + m_callFunctionObj = folly::make_unique<Object>(m_batchedBridge->getProperty("callFunctionReturnFlushedQueue").asObject()); + m_invokeCallbackObj = folly::make_unique<Object>(m_batchedBridge->getProperty("invokeCallbackAndReturnFlushedQueue").asObject()); return true; } @@ -290,6 +275,10 @@ void JSCExecutor::flush() { } void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) { +#ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.callFunction"); +#endif + if (!ensureBatchedBridgeObject()) { throwJSExecutionException( "Couldn't call JS module %s, method %s: bridge configuration isn't available. This " @@ -298,27 +287,35 @@ void JSCExecutor::callFunction(const std::string& moduleId, const std::string& m methodId.c_str()); } - std::vector<folly::dynamic> call { - moduleId, - methodId, - std::move(arguments), + String argsString = String(folly::toJson(std::move(arguments)).c_str()); + String moduleIdStr(moduleId.c_str()); + String methodIdStr(methodId.c_str()); + JSValueRef args[] = { + JSValueMakeString(m_context, moduleIdStr), + JSValueMakeString(m_context, methodIdStr), + Value::fromJSON(m_context, argsString) }; - std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call)); - m_bridge->callNativeModules(*this, calls, true); + auto result = m_callFunctionObj->callAsFunction(3, args); + m_bridge->callNativeModules(*this, result.toJSONString(), true); } void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { +#ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.invokeCallback"); +#endif + if (!ensureBatchedBridgeObject()) { throwJSExecutionException( "Couldn't invoke JS callback %d: bridge configuration isn't available. This shouldn't be possible. Congratulations.", (int) callbackId); } - std::vector<folly::dynamic> call { - (double) callbackId, - std::move(arguments) + String argsString = String(folly::toJson(std::move(arguments)).c_str()); + JSValueRef args[] = { + JSValueMakeNumber(m_context, callbackId), + Value::fromJSON(m_context, argsString) }; - std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call)); - m_bridge->callNativeModules(*this, calls, true); + auto result = m_invokeCallbackObj->callAsFunction(2, args); + m_bridge->callNativeModules(*this, result.toJSONString(), true); } void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index acd6d14cc386f8..5faca08ba817d8 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -91,6 +91,8 @@ class JSCExecutor : public JSExecutor { folly::dynamic m_jscConfig; std::unique_ptr<Object> m_batchedBridge; std::unique_ptr<Object> m_flushedQueueObj; + std::unique_ptr<Object> m_callFunctionObj; + std::unique_ptr<Object> m_invokeCallbackObj; /** * WebWorker constructor. Must be invoked from thread this Executor will run on. diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 63fee95574aaca..2f2f92cec3845c 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -293,7 +293,7 @@ static void loadScriptFromFile(JNIEnv* env, jobject obj, jstring fileName, jstri } static void callFunction(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jstring module, jstring method, - NativeArray::jhybridobject args, jstring tracingName) { + NativeArray::jhybridobject args) { auto bridge = extractRefPtr<CountableBridge>(env, obj); auto arguments = cthis(wrap_alias(args)); try { @@ -301,8 +301,7 @@ static void callFunction(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)), fromJString(env, module), fromJString(env, method), - std::move(arguments->array), - fromJString(env, tracingName) + std::move(arguments->array) ); } catch (...) { translatePendingCppExceptionToJavaException(); diff --git a/ReactAndroid/src/main/jni/react/test/.gitignore b/ReactAndroid/src/main/jni/react/test/.gitignore deleted file mode 100644 index 74c331c8a4c9c8..00000000000000 --- a/ReactAndroid/src/main/jni/react/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -libs -obj diff --git a/ReactAndroid/src/main/jni/react/test/Android.mk b/ReactAndroid/src/main/jni/react/test/Android.mk deleted file mode 100644 index 3bd10a2b002612..00000000000000 --- a/ReactAndroid/src/main/jni/react/test/Android.mk +++ /dev/null @@ -1,26 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - jscexecutor.cpp \ - jsclogging.cpp \ - value.cpp \ - methodcall.cpp \ - -LOCAL_SHARED_LIBRARIES := \ - libfb \ - libreactnative \ - libjsc - -LOCAL_STATIC_LIBRARIES := \ - libgoogletest - -LOCAL_MODULE := reactnative_test - -LOCAL_CFLAGS += $(BUCK_DEP_CFLAGS) -LOCAL_LDFLAGS += $(BUCK_DEP_LDFLAGS) - -include $(BUILD_EXECUTABLE) - -$(call import-module,react) diff --git a/ReactAndroid/src/main/jni/react/test/jni/Android.mk b/ReactAndroid/src/main/jni/react/test/jni/Android.mk deleted file mode 100644 index e8af25f6e7a0cc..00000000000000 --- a/ReactAndroid/src/main/jni/react/test/jni/Android.mk +++ /dev/null @@ -1 +0,0 @@ -include ../Android.mk diff --git a/ReactAndroid/src/main/jni/react/test/jni/Application.mk b/ReactAndroid/src/main/jni/react/test/jni/Application.mk deleted file mode 100644 index c87636021d0d02..00000000000000 --- a/ReactAndroid/src/main/jni/react/test/jni/Application.mk +++ /dev/null @@ -1,6 +0,0 @@ -ROOT := $(abspath $(call my-dir))/../../.. -include $(ROOT)/Application.mk - -APP_ABI := armeabi-v7a x86 -APP_STL := gnustl_shared -APP_BUILD_SCRIPT := Android.mk diff --git a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp deleted file mode 100644 index 1de276f2093e3e..00000000000000 --- a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#include <gtest/gtest.h> -#include <react/JSCExecutor.h> - -using namespace facebook; -using namespace facebook::react; - -TEST(JSCExecutor, Initialize) { - JSCExecutor executor; -} - -TEST(JSCExecutor, Two) { - JSCExecutor exec1; - JSCExecutor exec2; -} - -static std::vector<MethodCall> executeForMethodCalls( - JSCExecutor& e, - int moduleId, - int methodId, - std::vector<MethodArgument> args = std::vector<MethodArgument>()) { - return parseMethodCalls(e.callFunction(moduleId, methodId, std::move(args))); -} - -TEST(JSCExecutor, CallFunction) { - auto jsText = "" - "var Bridge = {" - " callFunctionReturnFlushedQueue: function (module, method, args) {" - " return [[module + 1], [method + 1], [args]];" - " }," - "};" - "function require() { return Bridge; }" - ""; - JSCExecutor e; - e.loadApplicationScript(jsText, ""); - std::vector<MethodArgument> args; - args.emplace_back(true); - args.emplace_back(0.4); - args.emplace_back("hello, world"); - args.emplace_back(4.0); - auto returnedCalls = executeForMethodCalls(e, 10, 9, args); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - EXPECT_EQ(11, returnedCall.moduleId); - EXPECT_EQ(10, returnedCall.methodId); - ASSERT_EQ(4, returnedCall.arguments.size()); - EXPECT_EQ(args[0], returnedCall.arguments[0]); - EXPECT_EQ(args[1], returnedCall.arguments[1]); - EXPECT_EQ(args[2], returnedCall.arguments[2]); - EXPECT_EQ(MethodArgument(4.0), returnedCall.arguments[3]); -} - -TEST(JSCExecutor, CallFunctionWithMap) { - auto jsText = "" - "var Bridge = {" - " callFunctionReturnFlushedQueue: function (module, method, args) {" - " var s = args[0].foo + args[0].bar + args[0].baz;" - " return [[module], [method], [[s]]];" - " }," - "};" - "function require() { return Bridge; }" - ""; - JSCExecutor e; - e.loadApplicationScript(jsText, ""); - std::vector<MethodArgument> args; - std::map<std::string, MethodArgument> map { - { "foo", MethodArgument("hello") }, - { "bar", MethodArgument(4.0) }, - { "baz", MethodArgument(true) }, - }; - args.emplace_back(std::move(map)); - auto returnedCalls = executeForMethodCalls(e, 10, 9, args); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::String, returnedCall.arguments[0].type); - EXPECT_EQ("hello4true", returnedCall.arguments[0].string); -} - -TEST(JSCExecutor, CallFunctionReturningMap) { - auto jsText = "" - "var Bridge = {" - " callFunctionReturnFlushedQueue: function (module, method, args) {" - " var s = { foo: 4, bar: true };" - " return [[module], [method], [[s]]];" - " }," - "};" - "function require() { return Bridge; }" - ""; - JSCExecutor e; - e.loadApplicationScript(jsText, ""); - auto returnedCalls = executeForMethodCalls(e, 10, 9); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Map, returnedCall.arguments[0].type); - auto& returnedMap = returnedCall.arguments[0].map; - auto fooIter = returnedMap.find("foo"); - ASSERT_NE(returnedMap.end(), fooIter); - EXPECT_EQ(MethodArgument(4.0), fooIter->second); - auto barIter = returnedMap.find("bar"); - ASSERT_NE(returnedMap.end(), barIter); - EXPECT_EQ(MethodArgument(true), barIter->second); -} - -TEST(JSCExecutor, CallFunctionWithArray) { - auto jsText = "" - "var Bridge = {" - " callFunctionReturnFlushedQueue: function (module, method, args) {" - " var s = args[0][0]+ args[0][1] + args[0][2] + args[0].length;" - " return [[module], [method], [[s]]];" - " }," - "};" - "function require() { return Bridge; }" - ""; - JSCExecutor e; - e.loadApplicationScript(jsText, ""); - std::vector<MethodArgument> args; - std::vector<MethodArgument> array { - MethodArgument("hello"), - MethodArgument(4.0), - MethodArgument(true), - }; - args.emplace_back(std::move(array)); - auto returnedCalls = executeForMethodCalls(e, 10, 9, args); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::String, returnedCall.arguments[0].type); - EXPECT_EQ("hello4true3", returnedCall.arguments[0].string); -} - -TEST(JSCExecutor, CallFunctionReturningNumberArray) { - auto jsText = "" - "var Bridge = {" - " callFunctionReturnFlushedQueue: function (module, method, args) {" - " var s = [3, 1, 4];" - " return [[module], [method], [[s]]];" - " }," - "};" - "function require() { return Bridge; }" - ""; - JSCExecutor e; - e.loadApplicationScript(jsText, ""); - auto returnedCalls = executeForMethodCalls(e, 10, 9); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Array, returnedCall.arguments[0].type); - - auto& array = returnedCall.arguments[0].array; - EXPECT_EQ(3, array.size()); - EXPECT_EQ(MethodArgument(3.0), array[0]); - EXPECT_EQ(MethodArgument(4.0), array[2]); -} - -TEST(JSCExecutor, SetSimpleGlobalVariable) { - auto jsText = "" - "var Bridge = {" - " callFunctionReturnFlushedQueue: function (module, method, args) {" - " return [[module], [method], [[__foo]]];" - " }," - "};" - "function require() { return Bridge; }" - ""; - JSCExecutor e; - e.loadApplicationScript(jsText, ""); - e.setGlobalVariable("__foo", "42"); - auto returnedCalls = executeForMethodCalls(e, 10, 9); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Number, returnedCall.arguments[0].type); - ASSERT_EQ(MethodArgument(42.0), returnedCall.arguments[0]); -} - -TEST(JSCExecutor, SetObjectGlobalVariable) { - auto jsText = "" - "var Bridge = {" - " callFunctionReturnFlushedQueue: function (module, method, args) {" - " return [[module], [method], [[__foo]]];" - " }," - "};" - "function require() { return Bridge; }" - ""; - JSCExecutor e; - e.loadApplicationScript(jsText, ""); - auto jsonObject = "" - "{" - " \"foo\": \"hello\"," - " \"bar\": 4," - " \"baz\": true" - "}" - ""; - e.setGlobalVariable("__foo", jsonObject); - auto returnedCalls = executeForMethodCalls(e, 10, 9); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Map, returnedCall.arguments[0].type); - auto& returnedMap = returnedCall.arguments[0].map; - auto fooIter = returnedMap.find("foo"); - ASSERT_NE(returnedMap.end(), fooIter); - EXPECT_EQ(MethodArgument("hello"), fooIter->second); - auto barIter = returnedMap.find("bar"); - ASSERT_NE(returnedMap.end(), barIter); - EXPECT_EQ(MethodArgument(4.0), barIter->second); - auto bazIter = returnedMap.find("baz"); - ASSERT_NE(returnedMap.end(), bazIter); - EXPECT_EQ(MethodArgument(true), bazIter->second); -} - -int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} - diff --git a/ReactAndroid/src/main/jni/react/test/methodcall.cpp b/ReactAndroid/src/main/jni/react/test/methodcall.cpp deleted file mode 100644 index 5abc0f306e76ca..00000000000000 --- a/ReactAndroid/src/main/jni/react/test/methodcall.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#include <gtest/gtest.h> -#include <react/MethodCall.h> - -using namespace facebook; -using namespace facebook::react; - -TEST(parseMethodCalls, SingleReturnCallNoArgs) { - auto jsText = "[[7],[3],[[]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(0, returnedCall.arguments.size()); - ASSERT_EQ(7, returnedCall.moduleId); - ASSERT_EQ(3, returnedCall.methodId); -} - -TEST(parseMethodCalls, InvalidReturnFormat) { - ASSERT_TRUE(parseMethodCalls("{\"foo\":1}").empty()); - ASSERT_TRUE(parseMethodCalls("[{\"foo\":1}]").empty()); - ASSERT_TRUE(parseMethodCalls("[1,4,{\"foo\":2}]").empty()); - ASSERT_TRUE(parseMethodCalls("[[1],[4],{\"foo\":2}]").empty()); - ASSERT_TRUE(parseMethodCalls("[[1],[4],[]]").empty()); -} - -TEST(parseMethodCalls, NumberReturn) { - auto jsText = "[[0],[0],[[\"foobar\"]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::String, returnedCall.arguments[0].type); - ASSERT_EQ("foobar", returnedCall.arguments[0].string); -} - -TEST(parseMethodCalls, StringReturn) { - auto jsText = "[[0],[0],[[42.16]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Number, returnedCall.arguments[0].type); - ASSERT_EQ(42.16, returnedCall.arguments[0].number); -} - -TEST(parseMethodCalls, BooleanReturn) { - auto jsText = "[[0],[0],[[false]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Boolean, returnedCall.arguments[0].type); - ASSERT_FALSE(returnedCall.arguments[0].boolean); -} - -TEST(parseMethodCalls, NullReturn) { - auto jsText = "[[0],[0],[[null]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Null, returnedCall.arguments[0].type); -} - -TEST(parseMethodCalls, MapReturn) { - auto jsText = "[[0],[0],[[{\"foo\": \"hello\", \"bar\": 4.0, \"baz\": true}]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Map, returnedCall.arguments[0].type); - auto& returnedMap = returnedCall.arguments[0].map; - auto fooIter = returnedMap.find("foo"); - ASSERT_NE(returnedMap.end(), fooIter); - EXPECT_EQ(MethodArgument("hello"), fooIter->second); - auto barIter = returnedMap.find("bar"); - ASSERT_NE(returnedMap.end(), barIter); - EXPECT_EQ(MethodArgument(4.0), barIter->second); - auto bazIter = returnedMap.find("baz"); - ASSERT_NE(returnedMap.end(), bazIter); - EXPECT_EQ(MethodArgument(true), bazIter->second); -} - -TEST(parseMethodCalls, ArrayReturn) { - auto jsText = "[[0],[0],[[[\"foo\", 42.0, false]]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(1, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::Array, returnedCall.arguments[0].type); - auto& returnedArray = returnedCall.arguments[0].array; - ASSERT_EQ(3, returnedArray.size()); - ASSERT_EQ(MethodArgument("foo"), returnedArray[0]); - ASSERT_EQ(MethodArgument(42.0), returnedArray[1]); - ASSERT_EQ(MethodArgument(false), returnedArray[2]); -} - -TEST(parseMethodCalls, ReturnMultipleParams) { - auto jsText = "[[0],[0],[[\"foo\", 14, null, false]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(1, returnedCalls.size()); - auto returnedCall = returnedCalls[0]; - ASSERT_EQ(4, returnedCall.arguments.size()); - ASSERT_EQ(MethodArgument::Type::String, returnedCall.arguments[0].type); - ASSERT_EQ(MethodArgument::Type::Number, returnedCall.arguments[1].type); - ASSERT_EQ(MethodArgument::Type::Null, returnedCall.arguments[2].type); - ASSERT_EQ(MethodArgument::Type::Boolean, returnedCall.arguments[3].type); -} - -TEST(parseMethodCalls, ParseTwoCalls) { - auto jsText = "[[0,0],[1,1],[[],[]]]"; - auto returnedCalls = parseMethodCalls(jsText); - ASSERT_EQ(2, returnedCalls.size()); -} diff --git a/ReactAndroid/src/main/jni/react/test/run b/ReactAndroid/src/main/jni/react/test/run deleted file mode 100755 index f162920ffd39df..00000000000000 --- a/ReactAndroid/src/main/jni/react/test/run +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -ABI=`adb shell getprop ro.product.cpu.abi | tr -d '\r'` -OUTPUT_DIR=/data/local/tmp - -cd $(dirname $0) -ndk-build -j8 - -if [[ "$?" != "0" ]]; then - echo "Compile failed" - exit 1 -fi - -# Copy over files -for f in libs/$ABI/*; do - adb push $f $OUTPUT_DIR; -done - -# Execute -adb shell "LD_LIBRARY_PATH=$OUTPUT_DIR $OUTPUT_DIR/reactnative_test $*" diff --git a/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.cpp b/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.cpp index 64fa80f98100f2..196e333a549fdf 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.cpp +++ b/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.cpp @@ -101,6 +101,8 @@ void CatalystInstanceImpl::registerNatives() { "(Landroid/content/res/AssetManager;Ljava/lang/String;)V", CatalystInstanceImpl::loadScriptFromAssets), makeNativeMethod("loadScriptFromFile", CatalystInstanceImpl::loadScriptFromFile), + makeNativeMethod("loadScriptFromOptimizedBundle", + CatalystInstanceImpl::loadScriptFromOptimizedBundle), makeNativeMethod("callJSFunction", CatalystInstanceImpl::callJSFunction), makeNativeMethod("callJSCallback", CatalystInstanceImpl::callJSCallback), makeNativeMethod("getMainExecutorToken", CatalystInstanceImpl::getMainExecutorToken), @@ -161,8 +163,9 @@ void CatalystInstanceImpl::loadScriptFromAssets(jobject assetManager, folly::make_unique<JniJSModulesUnbundle>(manager, sourceURL), std::move(script), sourceURL); + return; } else { - instance_->loadScriptFromString(std::move(script), std::move(sourceURL)); + instance_->loadScriptFromString(std::move(script), sourceURL); } } @@ -172,9 +175,16 @@ void CatalystInstanceImpl::loadScriptFromFile(jni::alias_ref<jstring> fileName, sourceURL); } +void CatalystInstanceImpl::loadScriptFromOptimizedBundle(const std::string& bundlePath, + const std::string& sourceURL, + jint flags) { + return instance_->loadScriptFromOptimizedBundle(std::move(bundlePath), + std::move(sourceURL), + flags); +} + void CatalystInstanceImpl::callJSFunction( - JExecutorToken* token, std::string module, std::string method, NativeArray* arguments, - const std::string& tracingName) { + JExecutorToken* token, std::string module, std::string method, NativeArray* arguments) { // We want to share the C++ code, and on iOS, modules pass module/method // names as strings all the way through to JS, and there's no way to do // string -> id mapping on the objc side. So on Android, we convert the @@ -183,10 +193,9 @@ void CatalystInstanceImpl::callJSFunction( // strings otherwise. Eventually, we'll probably want to modify the stack // from the JS proxy through here to use strings, too. instance_->callJSFunction(token->getExecutorToken(nullptr), - module, - method, - std::move(arguments->array), - tracingName); + std::move(module), + std::move(method), + std::move(arguments->array)); } void CatalystInstanceImpl::callJSCallback(JExecutorToken* token, jint callbackId, NativeArray* arguments) { diff --git a/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.h b/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.h index cddc4e03c12eb1..d0221af9783e04 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.h +++ b/ReactAndroid/src/main/jni/xreact/jni/CatalystInstanceImpl.h @@ -49,8 +49,8 @@ class CatalystInstanceImpl : public jni::HybridClass<CatalystInstanceImpl> { ModuleRegistryHolder* mrh); void loadScriptFromAssets(jobject assetManager, const std::string& assetURL); void loadScriptFromFile(jni::alias_ref<jstring> fileName, const std::string& sourceURL); - void callJSFunction(JExecutorToken* token, std::string module, std::string method, NativeArray* arguments, - const std::string& tracingName); + void loadScriptFromOptimizedBundle(const std::string& bundlePath, const std::string& sourceURL, jint flags); + void callJSFunction(JExecutorToken* token, std::string module, std::string method, NativeArray* arguments); void callJSCallback(JExecutorToken* token, jint callbackId, NativeArray* arguments); local_ref<JExecutorToken::JavaPart> getMainExecutorToken(); void setGlobalVariable(std::string propName, diff --git a/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp b/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp index 6cba3235cd2f83..2510c2841bed0f 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp +++ b/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp @@ -9,11 +9,37 @@ #include <folly/Memory.h> #include <fb/fbjni.h> +#include <cxxreact/JSCHelpers.h> + #include "JNativeRunnable.h" namespace facebook { namespace react { +namespace { + +struct JavaJSException : jni::JavaClass<JavaJSException, JThrowable> { + static constexpr auto kJavaDescriptor = "Lcom/facebook/react/devsupport/JSException;"; + + static local_ref<JavaJSException> create(const char* message, const char* stack, + const std::exception& ex) { + local_ref<jthrowable> cause = jni::JCppException::create(ex); + return newInstance(make_jstring(message), make_jstring(stack), cause.get()); + } +}; + +std::function<void()> wrapRunnable(std::function<void()>&& runnable) { + return [runnable=std::move(runnable)] { + try { + runnable(); + } catch (const JSException& ex) { + throwNewJavaException(JavaJSException::create(ex.what(), ex.getStack().c_str(), ex).get()); + } + }; +} + +} + JMessageQueueThread::JMessageQueueThread(alias_ref<JavaMessageQueueThread::javaobject> jobj) : m_jobj(make_global(jobj)) { } @@ -21,7 +47,7 @@ JMessageQueueThread::JMessageQueueThread(alias_ref<JavaMessageQueueThread::javao void JMessageQueueThread::runOnQueue(std::function<void()>&& runnable) { static auto method = JavaMessageQueueThread::javaClassStatic()-> getMethod<void(Runnable::javaobject)>("runOnQueue"); - method(m_jobj, JNativeRunnable::newObjectCxxArgs(runnable).get()); + method(m_jobj, JNativeRunnable::newObjectCxxArgs(wrapRunnable(std::move(runnable))).get()); } void JMessageQueueThread::runOnQueueSync(std::function<void()>&& runnable) { @@ -29,7 +55,7 @@ void JMessageQueueThread::runOnQueueSync(std::function<void()>&& runnable) { getMethod<jboolean()>("isOnThread"); if (jIsOnThread(m_jobj)) { - runnable(); + wrapRunnable(std::move(runnable))(); } else { std::mutex signalMutex; std::condition_variable signalCv; diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ApkSoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ApkSoSource.java deleted file mode 100644 index eba1eeaf586503..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ApkSoSource.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * 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.soloader; - -import java.io.File; -import java.io.IOException; -import android.content.Context; - -import java.util.jar.JarFile; -import java.util.jar.JarEntry; - -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -import android.os.Build; -import android.system.Os; -import android.system.ErrnoException; - -import java.util.HashMap; -import java.util.Map; -import java.util.Enumeration; - -import java.io.InputStream; -import java.io.FileOutputStream; - -import android.util.Log; - -/** - * {@link SoSource} that extracts libraries from an APK to the filesystem. - */ -public class ApkSoSource extends DirectorySoSource { - - private static final String TAG = SoLoader.TAG; - private static final boolean DEBUG = SoLoader.DEBUG; - - /** - * Make a new ApkSoSource that extracts DSOs from our APK instead of relying on the system to do - * the extraction for us. - * - * @param context Application context - */ - public ApkSoSource(Context context) throws IOException { - // - // Initialize a normal DirectorySoSource that will load from our extraction directory. At this - // point, the directory may be empty or contain obsolete libraries, but that's okay. - // - - super(SysUtil.createLibsDirectory(context), DirectorySoSource.RESOLVE_DEPENDENCIES); - - // - // Synchronize the contents of that directory with the library payload in our APK, deleting and - // extracting as needed. - // - - try (JarFile apk = new JarFile(context.getApplicationInfo().publicSourceDir)) { - File libsDir = super.soDirectory; - - if (DEBUG) { - Log.v(TAG, "synchronizing log directory: " + libsDir); - } - - Map<String, SoInfo> providedLibraries = findProvidedLibraries(apk); - try (FileLocker lock = SysUtil.lockLibsDirectory(context)) { - // Delete files in libsDir that we don't provide or that are out of date. Forget about any - // libraries that are up-to-date already so we don't unpack them below. - File extantFiles[] = libsDir.listFiles(); - for (int i = 0; i < extantFiles.length; ++i) { - File extantFile = extantFiles[i]; - - if (DEBUG) { - Log.v(TAG, "considering libdir file: " + extantFile); - } - - String name = extantFile.getName(); - SoInfo so = providedLibraries.get(name); - boolean shouldDelete = - (so == null || - so.entry.getSize() != extantFile.length() || - so.entry.getTime() != extantFile.lastModified()); - boolean upToDate = (so != null && !shouldDelete); - - if (shouldDelete) { - if (DEBUG) { - Log.v(TAG, "deleting obsolete or unexpected file: " + extantFile); - } - SysUtil.deleteOrThrow(extantFile); - } - - if (upToDate) { - if (DEBUG) { - Log.v(TAG, "found up-to-date library: " + extantFile); - } - providedLibraries.remove(name); - } - } - - // Now extract any libraries left in providedLibraries; we removed all the up-to-date ones. - for (SoInfo so : providedLibraries.values()) { - JarEntry entry = so.entry; - try (InputStream is = apk.getInputStream(entry)) { - if (DEBUG) { - Log.v(TAG, "extracting library: " + so.soName); - } - SysUtil.reliablyCopyExecutable( - is, - new File(libsDir, so.soName), - entry.getSize(), - entry.getTime()); - } - - SysUtil.freeCopyBuffer(); - } - } - } - } - - /** - * Find the shared libraries provided in this APK and supported on this system. Each returend - * SoInfo points to the most preferred version of that library bundled with the given APK: for - * example, if we're on an armv7-a system and we have both arm and armv7-a versions of libfoo, the - * returned entry for libfoo points to the armv7-a version of libfoo. - * - * The caller owns the returned value and may mutate it. - * - * @param apk Opened application APK file - * @return Map of sonames to SoInfo instances - */ - private static Map<String, SoInfo> findProvidedLibraries(JarFile apk) { - // Subgroup 1: ABI. Subgroup 2: soname. - Pattern libPattern = Pattern.compile("^lib/([^/]+)/([^/]+\\.so)$"); - HashMap<String, SoInfo> providedLibraries = new HashMap<>(); - String[] supportedAbis = SysUtil.getSupportedAbis(); - Enumeration<JarEntry> entries = apk.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - Matcher m = libPattern.matcher(entry.getName()); - if (m.matches()) { - String libraryAbi = m.group(1); - String soName = m.group(2); - int abiScore = SysUtil.findAbiScore(supportedAbis, libraryAbi); - if (abiScore >= 0) { - SoInfo so = providedLibraries.get(soName); - if (so == null || abiScore < so.abiScore) { - providedLibraries.put(soName, new SoInfo(soName, entry, abiScore)); - } - } - } - } - - return providedLibraries; - } - - private static final class SoInfo { - public final String soName; - public final JarEntry entry; - public final int abiScore; - - SoInfo(String soName, JarEntry entry, int abiScore) { - this.soName = soName; - this.entry = entry; - this.abiScore = abiScore; - } - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/BUCK b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/BUCK index 322ba2204e2016..f9c872b0f87f4c 100644 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/BUCK +++ b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/BUCK @@ -1,20 +1,11 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'soloader', - srcs = glob(['*.java']), - proguard_config = 'soloader.pro', - deps = [ - react_native_dep('third-party/java/jsr-305:jsr-305'), - # Be very careful adding new dependencies here, because this code - # has to run very early in the app startup process. - # Definitely do *not* depend on lib-base or guava. - ], - visibility = [ - 'PUBLIC', - ], +android_prebuilt_aar( + name = 'soloader', + aar = ':soloader-binary-aar', + visibility = ['PUBLIC'], ) -project_config( - src_target = ':soloader', +remote_file( + name = 'soloader-binary-aar', + url = 'mvn:com.facebook.soloader:soloader:aar:0.1.0', + sha1 = '918573465c94c6bc9bad48ef259f1e0cd6543c1b', ) diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/DirectorySoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/DirectorySoSource.java deleted file mode 100644 index 47cdb02320ddc6..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/DirectorySoSource.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * 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.soloader; - -import java.io.File; -import java.io.IOException; - -/** - * {@link SoSource} that finds shared libraries in a given directory. - */ -public class DirectorySoSource extends SoSource { - - public static final int RESOLVE_DEPENDENCIES = 1; - public static final int ON_LD_LIBRARY_PATH = 2; - - protected final File soDirectory; - private final int flags; - - /** - * Make a new DirectorySoSource. If {@code flags} contains {@code RESOLVE_DEPENDENCIES}, - * recursively load dependencies for shared objects loaded from this directory. (We shouldn't - * need to resolve dependencies for libraries loaded from system directories: the dynamic linker - * is smart enough to do it on its own there.) - */ - public DirectorySoSource(File soDirectory, int flags) { - this.soDirectory = soDirectory; - this.flags = flags; - } - - @Override - public int loadLibrary(String soName, int loadFlags) throws IOException { - File soFile = new File(soDirectory, soName); - if (!soFile.exists()) { - return LOAD_RESULT_NOT_FOUND; - } - - if ((loadFlags & LOAD_FLAG_ALLOW_IMPLICIT_PROVISION) != 0 && - (flags & ON_LD_LIBRARY_PATH) != 0) { - return LOAD_RESULT_IMPLICITLY_PROVIDED; - } - - if ((flags & RESOLVE_DEPENDENCIES) != 0) { - String dependencies[] = MinElf.extract_DT_NEEDED(soFile); - for (int i = 0; i < dependencies.length; ++i) { - String dependency = dependencies[i]; - if (dependency.startsWith("/")) { - continue; - } - - SoLoader.loadLibraryBySoName( - dependency, - (loadFlags | LOAD_FLAG_ALLOW_IMPLICIT_PROVISION)); - } - } - - System.load(soFile.getAbsolutePath()); - return LOAD_RESULT_LOADED; - } - - @Override - public File unpackLibrary(String soName) throws IOException { - File soFile = new File(soDirectory, soName); - if (soFile.exists()) { - return soFile; - } - - return null; - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Dyn.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Dyn.java deleted file mode 100644 index a9ec0713dd1722..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Dyn.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf32_Dyn { - public static final int d_tag = 0x0; - public static final int d_un = 0x4; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Ehdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Ehdr.java deleted file mode 100644 index a398ffe7898f3d..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Ehdr.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf32_Ehdr { - public static final int e_ident = 0x0; - public static final int e_type = 0x10; - public static final int e_machine = 0x12; - public static final int e_version = 0x14; - public static final int e_entry = 0x18; - public static final int e_phoff = 0x1c; - public static final int e_shoff = 0x20; - public static final int e_flags = 0x24; - public static final int e_ehsize = 0x28; - public static final int e_phentsize = 0x2a; - public static final int e_phnum = 0x2c; - public static final int e_shentsize = 0x2e; - public static final int e_shnum = 0x30; - public static final int e_shstrndx = 0x32; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Phdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Phdr.java deleted file mode 100644 index 95e2c27b29555b..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Phdr.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf32_Phdr { - public static final int p_type = 0x0; - public static final int p_offset = 0x4; - public static final int p_vaddr = 0x8; - public static final int p_paddr = 0xc; - public static final int p_filesz = 0x10; - public static final int p_memsz = 0x14; - public static final int p_flags = 0x18; - public static final int p_align = 0x1c; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Shdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Shdr.java deleted file mode 100644 index 35fc8599cd0880..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Shdr.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf32_Shdr { - public static final int sh_name = 0x0; - public static final int sh_type = 0x4; - public static final int sh_flags = 0x8; - public static final int sh_addr = 0xc; - public static final int sh_offset = 0x10; - public static final int sh_size = 0x14; - public static final int sh_link = 0x18; - public static final int sh_info = 0x1c; - public static final int sh_addralign = 0x20; - public static final int sh_entsize = 0x24; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Dyn.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Dyn.java deleted file mode 100644 index 89f2ddbdf13e37..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Dyn.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf64_Dyn { - public static final int d_tag = 0x0; - public static final int d_un = 0x8; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Ehdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Ehdr.java deleted file mode 100644 index 4f6fa44ce28f7a..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Ehdr.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf64_Ehdr { - public static final int e_ident = 0x0; - public static final int e_type = 0x10; - public static final int e_machine = 0x12; - public static final int e_version = 0x14; - public static final int e_entry = 0x18; - public static final int e_phoff = 0x20; - public static final int e_shoff = 0x28; - public static final int e_flags = 0x30; - public static final int e_ehsize = 0x34; - public static final int e_phentsize = 0x36; - public static final int e_phnum = 0x38; - public static final int e_shentsize = 0x3a; - public static final int e_shnum = 0x3c; - public static final int e_shstrndx = 0x3e; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Phdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Phdr.java deleted file mode 100644 index b6436cbcb08e8a..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Phdr.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf64_Phdr { - public static final int p_type = 0x0; - public static final int p_flags = 0x4; - public static final int p_offset = 0x8; - public static final int p_vaddr = 0x10; - public static final int p_paddr = 0x18; - public static final int p_filesz = 0x20; - public static final int p_memsz = 0x28; - public static final int p_align = 0x30; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Shdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Shdr.java deleted file mode 100644 index 36e8693d467096..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Shdr.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 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. - */ -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -public final class Elf64_Shdr { - public static final int sh_name = 0x0; - public static final int sh_type = 0x4; - public static final int sh_flags = 0x8; - public static final int sh_addr = 0x10; - public static final int sh_offset = 0x18; - public static final int sh_size = 0x20; - public static final int sh_link = 0x28; - public static final int sh_info = 0x2c; - public static final int sh_addralign = 0x30; - public static final int sh_entsize = 0x38; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ExoSoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ExoSoSource.java deleted file mode 100644 index 1520aa1c962ca2..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ExoSoSource.java +++ /dev/null @@ -1,177 +0,0 @@ -/** - * 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.soloader; - -import java.io.File; -import java.io.IOException; -import android.content.Context; - -import java.util.jar.JarFile; -import java.util.jar.JarEntry; - -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -import android.os.Build; -import android.system.Os; -import android.system.ErrnoException; - -import java.util.HashMap; -import java.util.Map; -import java.util.Enumeration; - -import java.io.InputStream; -import java.io.FileOutputStream; -import java.io.FileInputStream; -import java.io.BufferedReader; -import java.io.FileReader; - -import android.util.Log; - -/** - * {@link SoSource} that retrieves libraries from an exopackage repository. - */ -public class ExoSoSource extends DirectorySoSource { - - private static final String TAG = SoLoader.TAG; - private static final boolean DEBUG = SoLoader.DEBUG; - - /** - * @param context Application context - */ - public ExoSoSource(Context context) throws IOException { - // - // Initialize a normal DirectorySoSource that will load from our extraction directory. At this - // point, the directory may be empty or contain obsolete libraries, but that's okay. - // - - super(SysUtil.createLibsDirectory(context), DirectorySoSource.RESOLVE_DEPENDENCIES); - - // - // Synchronize the contents of that directory with the library payload in our APK, deleting and - // extracting as needed. - // - - File libsDir = super.soDirectory; - - if (DEBUG) { - Log.v(TAG, "synchronizing log directory: " + libsDir); - } - - Map<String, File> providedLibraries = findProvidedLibraries(context); - try (FileLocker lock = SysUtil.lockLibsDirectory(context)) { - // Delete files in libsDir that we don't provide or that are out of date. Forget about any - // libraries that are up-to-date already so we don't unpack them below. - File extantFiles[] = libsDir.listFiles(); - for (int i = 0; i < extantFiles.length; ++i) { - File extantFile = extantFiles[i]; - - if (DEBUG) { - Log.v(TAG, "considering libdir file: " + extantFile); - } - - String name = extantFile.getName(); - File sourceFile = providedLibraries.get(name); - boolean shouldDelete = - (sourceFile == null || - sourceFile.length() != extantFile.length() || - sourceFile.lastModified() != extantFile.lastModified()); - boolean upToDate = (sourceFile != null && !shouldDelete); - - if (shouldDelete) { - if (DEBUG) { - Log.v(TAG, "deleting obsolete or unexpected file: " + extantFile); - } - SysUtil.deleteOrThrow(extantFile); - } - - if (upToDate) { - if (DEBUG) { - Log.v(TAG, "found up-to-date library: " + extantFile); - } - providedLibraries.remove(name); - } - } - - // Now extract any libraries left in providedLibraries; we removed all the up-to-date ones. - for (String soName : providedLibraries.keySet()) { - File sourceFile = providedLibraries.get(soName); - try (InputStream is = new FileInputStream(sourceFile)) { - if (DEBUG) { - Log.v(TAG, "extracting library: " + soName); - } - SysUtil.reliablyCopyExecutable( - is, - new File(libsDir, soName), - sourceFile.length(), - sourceFile.lastModified()); - } - - SysUtil.freeCopyBuffer(); - } - } - } - - /** - * Find the shared libraries provided through the exopackage directory and supported on this - * system. Each returend SoInfo points to the most preferred version of that library included in - * our exopackage directory: for example, if we're on an armv7-a system and we have both arm and - * armv7-a versions of libfoo, the returned entry for libfoo points to the armv7-a version of - * libfoo. - * - * The caller owns the returned value and may mutate it. - * - * @param context Application context - * @return Map of sonames to providing files - */ - private static Map<String, File> findProvidedLibraries(Context context) throws IOException { - File exoDir = new File( - "/data/local/tmp/exopackage/" - + context.getPackageName() - + "/native-libs/"); - - HashMap<String, File> providedLibraries = new HashMap<>(); - for (String abi : SysUtil.getSupportedAbis()) { - File abiDir = new File(exoDir, abi); - if (!abiDir.isDirectory()) { - continue; - } - - File metadata = new File(abiDir, "metadata.txt"); - if (!metadata.isFile()) { - continue; - } - - try (FileReader fr = new FileReader(metadata); - BufferedReader br = new BufferedReader(fr)) { - String line; - while ((line = br.readLine()) != null) { - if (line.length() == 0) { - continue; - } - - int sep = line.indexOf(' '); - if (sep == -1) { - throw new RuntimeException("illegal line in exopackage metadata: [" + line + "]"); - } - - String soName = line.substring(0, sep) + ".so"; - String backingFile = line.substring(sep + 1); - - if (!providedLibraries.containsKey(soName)) { - providedLibraries.put(soName, new File(abiDir, backingFile)); - } - } - } - } - - return providedLibraries; - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/FileLocker.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/FileLocker.java deleted file mode 100644 index 96a11f994810a8..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/FileLocker.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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.soloader; -import java.io.FileOutputStream; -import java.io.File; -import java.io.IOException; -import java.nio.channels.FileLock; -import java.io.Closeable; - -public final class FileLocker implements Closeable { - - private final FileOutputStream mLockFileOutputStream; - private final FileLock mLock; - - public static FileLocker lock(File lockFile) throws IOException { - return new FileLocker(lockFile); - } - - private FileLocker(File lockFile) throws IOException { - mLockFileOutputStream = new FileOutputStream(lockFile); - FileLock lock = null; - try { - lock = mLockFileOutputStream.getChannel().lock(); - } finally { - if (lock == null) { - mLockFileOutputStream.close(); - } - } - - mLock = lock; - } - - @Override - public void close() throws IOException { - try { - mLock.release(); - } finally { - mLockFileOutputStream.close(); - } - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/MinElf.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/MinElf.java deleted file mode 100644 index 0477ad71dc996f..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/MinElf.java +++ /dev/null @@ -1,282 +0,0 @@ -/** - * 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.soloader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.File; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; - -/** - * Extract SoLoader boottsrap information from an ELF file. This is not a general purpose ELF - * library. - * - * See specification at http://www.sco.com/developers/gabi/latest/contents.html. You will not be - * able to verify the operation of the functions below without having read the ELF specification. - */ -public final class MinElf { - - public static final int ELF_MAGIC = 0x464c457f; - - public static final int DT_NULL = 0; - public static final int DT_NEEDED = 1; - public static final int DT_STRTAB = 5; - - public static final int PT_LOAD = 1; - public static final int PT_DYNAMIC = 2; - - public static final int PN_XNUM = 0xFFFF; - - public static String[] extract_DT_NEEDED(File elfFile) throws IOException { - FileInputStream is = new FileInputStream(elfFile); - try { - return extract_DT_NEEDED(is.getChannel()); - } finally { - is.close(); // Won't throw - } - } - - /** - * Treating {@code bb} as an ELF file, extract all the DT_NEEDED entries from its dynamic section. - * - * @param fc FileChannel referring to ELF file - * @return Array of strings, one for each DT_NEEDED entry, in file order - */ - public static String[] extract_DT_NEEDED(FileChannel fc) - throws IOException { - - // - // All constants below are fixed by the ELF specification and are the offsets of fields within - // the elf.h data structures. - // - - ByteBuffer bb = ByteBuffer.allocate(8 /* largest read unit */); - - // Read ELF header. - - bb.order(ByteOrder.LITTLE_ENDIAN); - if (getu32(fc, bb, Elf32_Ehdr.e_ident) != ELF_MAGIC) { - throw new ElfError("file is not ELF"); - } - - boolean is32 = (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x4) == 1); - if (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x5) == 2) { - bb.order(ByteOrder.BIG_ENDIAN); - } - - // Offsets above are identical in 32- and 64-bit cases. - - // Find the offset of the dynamic linking information. - - long e_phoff = is32 - ? getu32(fc, bb, Elf32_Ehdr.e_phoff) - : get64(fc, bb, Elf64_Ehdr.e_phoff); - - long e_phnum = is32 - ? getu16(fc, bb, Elf32_Ehdr.e_phnum) - : getu16(fc, bb, Elf64_Ehdr.e_phnum); - - int e_phentsize = is32 - ? getu16(fc, bb, Elf32_Ehdr.e_phentsize) - : getu16(fc, bb, Elf64_Ehdr.e_phentsize); - - if (e_phnum == PN_XNUM) { // Overflowed into section[0].sh_info - - long e_shoff = is32 - ? getu32(fc, bb, Elf32_Ehdr.e_shoff) - : get64(fc, bb, Elf64_Ehdr.e_shoff); - - long sh_info = is32 - ? getu32(fc, bb, e_shoff + Elf32_Shdr.sh_info) - : getu32(fc, bb, e_shoff + Elf64_Shdr.sh_info); - - e_phnum = sh_info; - } - - long dynStart = 0; - long phdr = e_phoff; - - for (long i = 0; i < e_phnum; ++i) { - long p_type = is32 - ? getu32(fc, bb, phdr + Elf32_Phdr.p_type) - : getu32(fc, bb, phdr + Elf64_Phdr.p_type); - - if (p_type == PT_DYNAMIC) { - long p_offset = is32 - ? getu32(fc, bb, phdr + Elf32_Phdr.p_offset) - : get64(fc, bb, phdr + Elf64_Phdr.p_offset); - - dynStart = p_offset; - break; - } - - phdr += e_phentsize; - } - - if (dynStart == 0) { - throw new ElfError("ELF file does not contain dynamic linking information"); - } - - // Walk the items in the dynamic section, counting the DT_NEEDED entries. Also remember where - // the string table for those entries lives. That table is a pointer, which we translate to an - // offset below. - - long d_tag; - int nr_DT_NEEDED = 0; - long dyn = dynStart; - long ptr_DT_STRTAB = 0; - - do { - d_tag = is32 - ? getu32(fc, bb, dyn + Elf32_Dyn.d_tag) - : get64(fc, bb, dyn + Elf64_Dyn.d_tag); - - if (d_tag == DT_NEEDED) { - if (nr_DT_NEEDED == Integer.MAX_VALUE) { - throw new ElfError("malformed DT_NEEDED section"); - } - - nr_DT_NEEDED += 1; - } else if (d_tag == DT_STRTAB) { - ptr_DT_STRTAB = is32 - ? getu32(fc, bb, dyn + Elf32_Dyn.d_un) - : get64(fc, bb, dyn + Elf64_Dyn.d_un); - } - - dyn += is32 ? 8 : 16; - } while (d_tag != DT_NULL); - - if (ptr_DT_STRTAB == 0) { - throw new ElfError("Dynamic section string-table not found"); - } - - // Translate the runtime string table pointer we found above to a file offset. - - long off_DT_STRTAB = 0; - phdr = e_phoff; - - for (int i = 0; i < e_phnum; ++i) { - long p_type = is32 - ? getu32(fc, bb, phdr + Elf32_Phdr.p_type) - : getu32(fc, bb, phdr + Elf64_Phdr.p_type); - - if (p_type == PT_LOAD) { - long p_vaddr = is32 - ? getu32(fc, bb, phdr + Elf32_Phdr.p_vaddr) - : get64(fc, bb, phdr + Elf64_Phdr.p_vaddr); - - long p_memsz = is32 - ? getu32(fc, bb, phdr + Elf32_Phdr.p_memsz) - : get64(fc, bb, phdr + Elf64_Phdr.p_memsz); - - if (p_vaddr <= ptr_DT_STRTAB && ptr_DT_STRTAB < p_vaddr + p_memsz) { - long p_offset = is32 - ? getu32(fc, bb, phdr + Elf32_Phdr.p_offset) - : get64(fc, bb, phdr + Elf64_Phdr.p_offset); - - off_DT_STRTAB = p_offset + (ptr_DT_STRTAB - p_vaddr); - break; - } - } - - phdr += e_phentsize; - } - - if (off_DT_STRTAB == 0) { - throw new ElfError("did not find file offset of DT_STRTAB table"); - } - - String[] needed = new String[nr_DT_NEEDED]; - - nr_DT_NEEDED = 0; - dyn = dynStart; - - do { - d_tag = is32 - ? getu32(fc, bb, dyn + Elf32_Dyn.d_tag) - : get64(fc, bb, dyn + Elf64_Dyn.d_tag); - - if (d_tag == DT_NEEDED) { - long d_val = is32 - ? getu32(fc, bb, dyn + Elf32_Dyn.d_un) - : get64(fc, bb, dyn + Elf64_Dyn.d_un); - - needed[nr_DT_NEEDED] = getSz(fc, bb, off_DT_STRTAB + d_val); - if (nr_DT_NEEDED == Integer.MAX_VALUE) { - throw new ElfError("malformed DT_NEEDED section"); - } - - nr_DT_NEEDED += 1; - } - - dyn += is32 ? 8 : 16; - } while (d_tag != DT_NULL); - - if (nr_DT_NEEDED != needed.length) { - throw new ElfError("malformed DT_NEEDED section"); - } - - return needed; - } - - private static String getSz(FileChannel fc, ByteBuffer bb, long offset) - throws IOException { - StringBuilder sb = new StringBuilder(); - short b; - while ((b = getu8(fc, bb, offset++)) != 0) { - sb.append((char) b); - } - - return sb.toString(); - } - - private static void read(FileChannel fc, ByteBuffer bb, int sz, long offset) - throws IOException { - bb.position(0); - bb.limit(sz); - if (fc.read(bb, offset) != sz) { - throw new ElfError("ELF file truncated"); - } - - bb.position(0); - } - - private static long get64(FileChannel fc, ByteBuffer bb, long offset) - throws IOException { - read(fc, bb, 8, offset); - return bb.getLong(); - } - - private static long getu32(FileChannel fc, ByteBuffer bb, long offset) - throws IOException { - read(fc, bb, 4, offset); - return bb.getInt() & 0xFFFFFFFFL; // signed -> unsigned - } - - private static int getu16(FileChannel fc, ByteBuffer bb, long offset) - throws IOException { - read(fc, bb, 2, offset); - return bb.getShort() & (int) 0xFFFF; // signed -> unsigned - } - - private static short getu8(FileChannel fc, ByteBuffer bb, long offset) - throws IOException { - read(fc, bb, 1, offset); - return (short) (bb.get() & 0xFF); // signed -> unsigned - } - - private static class ElfError extends RuntimeException { - ElfError(String why) { - super(why); - } - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NativeLibrary.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NativeLibrary.java deleted file mode 100644 index 7277474d6acce3..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NativeLibrary.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * 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.soloader; - -import java.util.List; - -import android.util.Log; - -/** - * This is the base class for all the classes representing certain native library. - * For loading native libraries we should always inherit from this class and provide relevant - * information (libraries to load, code to test native call, dependencies?). - * <p> - * This instances should be singletons provided by DI. - * <p> - * This is a basic template but could be improved if we find the need. - */ -public abstract class NativeLibrary { - private static final String TAG = NativeLibrary.class.getName(); - - private final Object mLock; - private List<String> mLibraryNames; - private Boolean mLoadLibraries; - private boolean mLibrariesLoaded; - private volatile UnsatisfiedLinkError mLinkError; - - protected NativeLibrary(List<String> libraryNames) { - mLock = new Object(); - mLoadLibraries = true; - mLibrariesLoaded = false; - mLinkError = null; - mLibraryNames = libraryNames; - } - - /** - * safe loading of native libs - * @return true if native libs loaded properly, false otherwise - */ - public boolean loadLibraries() { - synchronized (mLock) { - if (mLoadLibraries == false) { - return mLibrariesLoaded; - } - try { - for (String name: mLibraryNames) { - SoLoader.loadLibrary(name); - } - initialNativeCheck(); - mLibrariesLoaded = true; - mLibraryNames = null; - } catch (UnsatisfiedLinkError error) { - Log.e(TAG, "Failed to load native lib: ", error); - mLinkError = error; - mLibrariesLoaded = false; - } - mLoadLibraries = false; - return mLibrariesLoaded; - } - } - - /** - * loads libraries (if not loaded yet), throws on failure - * @throws UnsatisfiedLinkError - */ - - public void ensureLoaded() throws UnsatisfiedLinkError { - if (!loadLibraries()) { - throw mLinkError; - } - } - - /** - * Override this method to make some concrete (quick and harmless) native call. - * This avoids lazy-loading some phones (LG) use when we call loadLibrary. If there's a problem - * we'll face an UnsupportedLinkError when first using the feature instead of here. - * This check force a check right when intended. - * This way clients of this library can know if it's loaded for sure or not. - * @throws UnsatisfiedLinkError if there was an error loading native library - */ - protected void initialNativeCheck() throws UnsatisfiedLinkError { - } - - public UnsatisfiedLinkError getError() { - return mLinkError; - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NoopSoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NoopSoSource.java deleted file mode 100644 index cd5d15e48eeeed..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NoopSoSource.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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.soloader; - -import java.io.File; - -/** - * {@link SoSource} that does nothing and pretends to successfully load all libraries. - */ -public class NoopSoSource extends SoSource { - @Override - public int loadLibrary(String soName, int loadFlags) { - return LOAD_RESULT_LOADED; - } - - @Override - public File unpackLibrary(String soName) { - throw new UnsupportedOperationException( - "unpacking not supported in test mode"); - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoLoader.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoLoader.java deleted file mode 100644 index a070ed9a96898a..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoLoader.java +++ /dev/null @@ -1,237 +0,0 @@ -/** - * 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.soloader; - -import java.io.BufferedOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.HashSet; -import java.util.ArrayList; -import java.io.FileNotFoundException; - -import java.util.Set; - -import javax.annotation.Nullable; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Build; -import android.os.StatFs; -import android.util.Log; - -import android.content.pm.ApplicationInfo; - -/** - * Note that {@link com.facebook.base.app.DelegatingApplication} will automatically register itself - * with SoLoader before running application-specific code; most applications do not need to call - * {@link #init} explicitly. - */ -@SuppressLint({ - "BadMethodUse-android.util.Log.v", - "BadMethodUse-android.util.Log.d", - "BadMethodUse-android.util.Log.i", - "BadMethodUse-android.util.Log.w", - "BadMethodUse-android.util.Log.e", -}) -public class SoLoader { - - /* package */ static final String TAG = "SoLoader"; - /* package */ static final boolean DEBUG = false; - - /** - * Ordered list of sources to consult when trying to load a shared library or one of its - * dependencies. {@code null} indicates that SoLoader is uninitialized. - */ - @Nullable private static SoSource[] sSoSources = null; - - /** - * Records the sonames (e.g., "libdistract.so") of shared libraries we've loaded. - */ - private static final Set<String> sLoadedLibraries = new HashSet<>(); - - /** - * Initializes native code loading for this app; this class's other static facilities cannot be - * used until this {@link #init} is called. This method is idempotent: calls after the first are - * ignored. - * - * @param context - application context. - * @param isNativeExopackageEnabled - whether native exopackage feature is enabled in the build. - */ - public static synchronized void init(@Nullable Context context, boolean isNativeExopackageEnabled) { - if (sSoSources == null) { - ArrayList<SoSource> soSources = new ArrayList<>(); - - // - // Add SoSource objects for each of the system library directories. - // - - String LD_LIBRARY_PATH = System.getenv("LD_LIBRARY_PATH"); - if (LD_LIBRARY_PATH == null) { - LD_LIBRARY_PATH = "/vendor/lib:/system/lib"; - } - - String[] systemLibraryDirectories = LD_LIBRARY_PATH.split(":"); - for (int i = 0; i < systemLibraryDirectories.length; ++i) { - // Don't pass DirectorySoSource.RESOLVE_DEPENDENCIES for directories we find on - // LD_LIBRARY_PATH: Bionic's dynamic linker is capable of correctly resolving dependencies - // these libraries have on each other, so doing that ourselves would be a waste. - File systemSoDirectory = new File(systemLibraryDirectories[i]); - soSources.add( - new DirectorySoSource( - systemSoDirectory, - DirectorySoSource.ON_LD_LIBRARY_PATH)); - } - - // - // We can only proceed forward if we have a Context. The prominent case - // where we don't have a Context is barebones dalvikvm instantiations. In - // that case, the caller is responsible for providing a correct LD_LIBRARY_PATH. - // - - if (context != null) { - // - // Prepend our own SoSource for our own DSOs. - // - - ApplicationInfo applicationInfo = context.getApplicationInfo(); - boolean isSystemApplication = - (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && - (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0; - - try { - if (isNativeExopackageEnabled) { - soSources.add(0, new ExoSoSource(context)); - } else if (isSystemApplication) { - soSources.add(0, new ApkSoSource(context)); - } else { - // Delete the old libs directory if we don't need it. - SysUtil.dumbDeleteRecrusive(SysUtil.getLibsDirectory(context)); - - int ourSoSourceFlags = 0; - - // On old versions of Android, Bionic doesn't add our library directory to its internal - // search path, and the system doesn't resolve dependencies between modules we ship. On - // these systems, we resolve dependencies ourselves. On other systems, Bionic's built-in - // resolver suffices. - - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) { - ourSoSourceFlags |= DirectorySoSource.RESOLVE_DEPENDENCIES; - } - - SoSource ourSoSource = new DirectorySoSource( - new File(applicationInfo.nativeLibraryDir), - ourSoSourceFlags); - - soSources.add(0, ourSoSource); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - sSoSources = soSources.toArray(new SoSource[soSources.size()]); - } - } - - /** - * Turn shared-library loading into a no-op. Useful in special circumstances. - */ - public static void setInTestMode() { - sSoSources = new SoSource[]{new NoopSoSource()}; - } - - /** - * Load a shared library, initializing any JNI binding it contains. - * - * @param shortName Name of library to find, without "lib" prefix or ".so" suffix - */ - public static synchronized void loadLibrary(String shortName) - throws UnsatisfiedLinkError - { - if (sSoSources == null) { - // This should never happen during normal operation, - // but if we're running in a non-Android environment, - // fall back to System.loadLibrary. - if ("http://www.android.com/".equals(System.getProperty("java.vendor.url"))) { - // This will throw. - assertInitialized(); - } else { - // Not on an Android system. Ask the JVM to load for us. - System.loadLibrary(shortName); - return; - } - } - - try { - loadLibraryBySoName(System.mapLibraryName(shortName), 0); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - /** - * Unpack library and its dependencies, returning the location of the unpacked library file. All - * non-system dependencies of the given library will either be on LD_LIBRARY_PATH or will be in - * the same directory as the returned File. - * - * @param shortName Name of library to find, without "lib" prefix or ".so" suffix - * @return Unpacked DSO location - */ - public static File unpackLibraryAndDependencies(String shortName) - throws UnsatisfiedLinkError - { - assertInitialized(); - try { - return unpackLibraryBySoName(System.mapLibraryName(shortName)); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - /* package */ static void loadLibraryBySoName(String soName, int loadFlags) throws IOException { - int result = sLoadedLibraries.contains(soName) - ? SoSource.LOAD_RESULT_LOADED - : SoSource.LOAD_RESULT_NOT_FOUND; - - for (int i = 0; result == SoSource.LOAD_RESULT_NOT_FOUND && i < sSoSources.length; ++i) { - result = sSoSources[i].loadLibrary(soName, loadFlags); - } - - if (result == SoSource.LOAD_RESULT_NOT_FOUND) { - throw new UnsatisfiedLinkError("could find DSO to load: " + soName); - } - - if (result == SoSource.LOAD_RESULT_LOADED) { - sLoadedLibraries.add(soName); - } - } - - /* package */ static File unpackLibraryBySoName(String soName) throws IOException { - for (int i = 0; i < sSoSources.length; ++i) { - File unpacked = sSoSources[i].unpackLibrary(soName); - if (unpacked != null) { - return unpacked; - } - } - - throw new FileNotFoundException(soName); - } - - private static void assertInitialized() { - if (sSoSources == null) { - throw new RuntimeException("SoLoader.init() not yet called"); - } - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoSource.java deleted file mode 100644 index 016013e15af73a..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoSource.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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.soloader; - -import java.io.File; -import java.io.IOException; - -abstract public class SoSource { - - /** - * This SoSource doesn't know how to provide the given library. - */ - public static final int LOAD_RESULT_NOT_FOUND = 0; - - /** - * This SoSource loaded the given library. - */ - public static final int LOAD_RESULT_LOADED = 1; - - /** - * This SoSource did not load the library, but verified that the system loader will load it if - * some other library depends on it. Returned only if LOAD_FLAG_ALLOW_IMPLICIT_PROVISION is - * provided to loadLibrary. - */ - public static final int LOAD_RESULT_IMPLICITLY_PROVIDED = 2; - - /** - * Allow loadLibrary to implicitly provide the library instead of actually loading it. - */ - public static final int LOAD_FLAG_ALLOW_IMPLICIT_PROVISION = 1; - - /** - * Load a shared library library into this process. This routine is independent of - * {@link #loadLibrary}. - * - * @param soName Name of library to load - * @param loadFlags Zero or more of the LOAD_FLAG_XXX constants. - * @return One of the LOAD_RESULT_XXX constants. - */ - abstract public int loadLibrary(String soName, int LoadFlags) throws IOException; - - /** - * Ensure that a shared library exists on disk somewhere. This routine is independent of - * {@link #loadLibrary}. - * - * @param soName Name of library to load - * @return File if library found; {@code null} if not. - */ - abstract public File unpackLibrary(String soName) throws IOException; -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SysUtil.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SysUtil.java deleted file mode 100644 index 91f28583e1469c..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SysUtil.java +++ /dev/null @@ -1,205 +0,0 @@ -/** - * 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.soloader; - -import java.io.File; -import java.io.IOException; -import android.content.Context; - -import java.util.jar.JarFile; -import java.util.jar.JarEntry; - -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -import android.os.Build; -import android.system.Os; -import android.system.ErrnoException; - -import java.util.HashMap; -import java.util.Map; -import java.util.Enumeration; - -import java.io.InputStream; -import java.io.FileOutputStream; -import java.io.FileDescriptor; - -/*package*/ final class SysUtil { - - private static byte[] cachedBuffer = null; - - /** - * Copy from an inputstream to a named filesystem file. Take care to ensure that we can detect - * incomplete copies and that the copied bytes make it to stable storage before returning. - * The destination file will be marked executable. - * - * This routine caches an internal buffer between invocations; after making a sequence of calls - * {@link #reliablyCopyExecutable} calls, call {@link #freeCopyBuffer} to release this buffer. - * - * @param is Stream from which to copy - * @param destination File to which to write - * @param expectedSize Number of bytes we expect to write; -1 if unknown - * @param time Modification time to which to set file on success; must be in the past - */ - public static void reliablyCopyExecutable( - InputStream is, - File destination, - long expectedSize, - long time) throws IOException { - destination.delete(); - try (FileOutputStream os = new FileOutputStream(destination)) { - byte buffer[]; - if (cachedBuffer == null) { - cachedBuffer = buffer = new byte[16384]; - } else { - buffer = cachedBuffer; - } - - int nrBytes; - if (expectedSize > 0) { - fallocateIfSupported(os.getFD(), expectedSize); - } - - while ((nrBytes = is.read(buffer, 0, buffer.length)) >= 0) { - os.write(buffer, 0, nrBytes); - } - - os.getFD().sync(); - destination.setExecutable(true); - destination.setLastModified(time); - os.getFD().sync(); - } - } - - /** - * Free the internal buffer cache for {@link #reliablyCopyExecutable}. - */ - public static void freeCopyBuffer() { - cachedBuffer = null; - } - - /** - * Determine how preferred a given ABI is on this system. - * - * @param supportedAbis ABIs on this system - * @param abi ABI of a shared library we might want to unpack - * @return -1 if not supported or an integer, smaller being more preferred - */ - public static int findAbiScore(String[] supportedAbis, String abi) { - for (int i = 0; i < supportedAbis.length; ++i) { - if (supportedAbis[i] != null && abi.equals(supportedAbis[i])) { - return i; - } - } - - return -1; - } - - public static void deleteOrThrow(File file) throws IOException { - if (!file.delete()) { - throw new IOException("could not delete file " + file); - } - } - - /** - * Return an list of ABIs we supported on this device ordered according to preference. Use a - * separate inner class to isolate the version-dependent call where it won't cause the whole - * class to fail preverification. - * - * @return Ordered array of supported ABIs - */ - public static String[] getSupportedAbis() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return new String[]{Build.CPU_ABI, Build.CPU_ABI2}; - } else { - return LollipopSysdeps.getSupportedAbis(); - } - } - - /** - * Pre-allocate disk space for a file if we can do that - * on this version of the OS. - * - * @param fd File descriptor for file - * @param length Number of bytes to allocate. - */ - public static void fallocateIfSupported(FileDescriptor fd, long length) throws IOException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - LollipopSysdeps.fallocate(fd, length); - } - } - - public static FileLocker lockLibsDirectory(Context context) throws IOException { - File lockFile = new File(context.getApplicationInfo().dataDir, "libs-dir-lock"); - return FileLocker.lock(lockFile); - } - - /** - * Return the directory into which we put our self-extracted native libraries. - * - * @param context Application context - * @return File pointing to an existing directory - */ - /* package */ static File getLibsDirectory(Context context) { - return new File(context.getApplicationInfo().dataDir, "app_libs"); - } - - /** - * Return the directory into which we put our self-extracted native libraries and make sure it - * exists. - */ - /* package */ static File createLibsDirectory(Context context) { - File libsDirectory = getLibsDirectory(context); - if (!libsDirectory.isDirectory() && !libsDirectory.mkdirs()) { - throw new RuntimeException("could not create libs directory"); - } - - return libsDirectory; - } - - /** - * Delete a directory and its contents. - * - * WARNING: Java APIs do not let us distinguish directories from symbolic links to directories. - * Consequently, if the directory contains symbolic links to directories, we will attempt to - * delete the contents of pointed-to directories. - * - * @param file File or directory to delete - */ - /* package */ static void dumbDeleteRecrusive(File file) throws IOException { - if (file.isDirectory()) { - for (File entry : file.listFiles()) { - dumbDeleteRecrusive(entry); - } - } - - if (!file.delete() && file.exists()) { - throw new IOException("could not delete: " + file); - } - } - - /** - * Encapsulate Lollipop-specific calls into an independent class so we don't fail preverification - * downlevel. - */ - private static final class LollipopSysdeps { - public static String[] getSupportedAbis() { - return Build.SUPPORTED_32_BIT_ABIS; // We ain't doing no newfangled 64-bit - } - - public static void fallocate(FileDescriptor fd, long length) throws IOException { - try { - Os.posix_fallocate(fd, 0, length); - } catch (ErrnoException ex) { - throw new IOException(ex.toString(), ex); - } - } - } -} diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/genstructs.sh b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/genstructs.sh deleted file mode 100644 index a7bcd49a581a82..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/genstructs.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -# -# This script generates Java structures that contain the offsets of -# fields in various ELF ABI structures. com.facebook.soloader.MinElf -# uses these structures while parsing ELF files. -# - -set -euo pipefail - -struct2java() { - ../../../../scripts/struct2java.py "$@" -} - -declare -a structs=(Elf32_Ehdr Elf64_Ehdr) -structs+=(Elf32_Ehdr Elf64_Ehdr) -structs+=(Elf32_Phdr Elf64_Phdr) -structs+=(Elf32_Shdr Elf64_Shdr) -structs+=(Elf32_Dyn Elf64_Dyn) - -for struct in "${structs[@]}"; do - cat > elfhdr.c <<EOF -#include <elf.h> -static const $struct a; -EOF - gcc -g -c -o elfhdr.o elfhdr.c - cat > $struct.java <<EOF -// Copyright 2004-present Facebook. All Rights Reserved. -// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh. -package com.facebook.soloader; -EOF - struct2java elfhdr.o $struct >> $struct.java -done - -rm -f elfhdr.o elfhdr.c diff --git a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/soloader.pro b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/soloader.pro deleted file mode 100644 index 4a832314c5492e..00000000000000 --- a/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/soloader.pro +++ /dev/null @@ -1,6 +0,0 @@ -# Ensure that methods from LollipopSysdeps don't get inlined. LollipopSysdeps.fallocate references -# an exception that isn't present prior to Lollipop, which trips up the verifier if the class is -# loaded on a pre-Lollipop OS. --keep class com.facebook.soloader.SysUtil$LollipopSysdeps { - public <methods>; -} diff --git a/ReactAndroid/src/main/third-party/java/okhttp/BUCK b/ReactAndroid/src/main/third-party/java/okhttp/BUCK index 07d870bdbbe52d..a6d9925259e7f6 100644 --- a/ReactAndroid/src/main/third-party/java/okhttp/BUCK +++ b/ReactAndroid/src/main/third-party/java/okhttp/BUCK @@ -6,8 +6,8 @@ prebuilt_jar( remote_file( name = 'okhttp3-binary-jar', - url = 'mvn:com.squareup.okhttp3:okhttp:jar:3.2.0', - sha1 = 'f7873a2ebde246a45c2a8d6f3247108b4c88a879' + url = 'mvn:com.squareup.okhttp3:okhttp:jar:3.4.1', + sha1 = 'c7c4f9e35c2fd5900da24f9872e3971801f08ce0' ) prebuilt_jar( @@ -18,8 +18,8 @@ prebuilt_jar( remote_file( name = 'okhttp3-urlconnection-binary-jar', - url = 'mvn:com.squareup.okhttp3:okhttp-urlconnection:jar:3.2.0', - sha1 = '6f8a4b1435c9e0a6f9c5fe4a1be46627b848fd0c' + url = 'mvn:com.squareup.okhttp3:okhttp-urlconnection:jar:3.4.1', + sha1 = '63994437f62bc861bc20c605d12962f7246116d1' ) prebuilt_jar( @@ -30,6 +30,6 @@ prebuilt_jar( remote_file( name = 'okhttp3-ws-binary-jar', - url = 'mvn:com.squareup.okhttp3:okhttp-ws:jar:3.2.0', - sha1 = '1ea229d6984444c8c58b8e97ba4c8429d9d135b3', + url = 'mvn:com.squareup.okhttp3:okhttp-ws:jar:3.4.1', + sha1 = '8ace66ef7002d98f633377c9e67daeeb196d8c3b', ) diff --git a/ReactAndroid/src/main/third-party/java/okio/BUCK b/ReactAndroid/src/main/third-party/java/okio/BUCK index 3f3339840c4265..900a79732a3541 100644 --- a/ReactAndroid/src/main/third-party/java/okio/BUCK +++ b/ReactAndroid/src/main/third-party/java/okio/BUCK @@ -6,6 +6,6 @@ prebuilt_jar( remote_file( name = 'okio-binary-jar', - url = 'mvn:com.squareup.okio:okio:jar:1.8.0', - sha1 = '05ea7af56cc7c567ed9856d99efb30740e9b17ff', + url = 'mvn:com.squareup.okio:okio:jar:1.9.0', + sha1 = 'f824591a0016efbaeddb8300bee54832a1398cfa', ) diff --git a/ReactAndroid/src/test/java/com/facebook/react/BUCK b/ReactAndroid/src/test/java/com/facebook/react/BUCK index 9d3626d2319731..7ce47967f3edee 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/BUCK @@ -6,6 +6,7 @@ robolectric3_test( contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'], srcs = glob(['*.java']), deps = [ + CSSLAYOUT_TARGET, react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'), react_native_dep('third-party/java/fest:fest'), react_native_dep('third-party/java/jsr-305:jsr-305'), @@ -14,7 +15,6 @@ robolectric3_test( react_native_dep('third-party/java/okhttp:okhttp3'), react_native_dep('third-party/java/okio:okio'), react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), - react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/animation:animation'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK new file mode 100644 index 00000000000000..da5b9ff3d5d060 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK @@ -0,0 +1,41 @@ +include_defs('//ReactAndroid/DEFS') + +STANDARD_TEST_SRCS = [ + '*Test.java', +] + +android_library( + name = 'testhelpers', + srcs = glob(['*.java'], excludes = STANDARD_TEST_SRCS), + deps = [ + react_native_dep('third-party/java/junit:junit'), + react_native_dep('third-party/java/mockito:mockito'), + ], + visibility = [ + 'PUBLIC' + ], +) + +robolectric3_test( + name = 'bridge', + # Please change the contact to the oncall of your team + contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'], + srcs = glob(STANDARD_TEST_SRCS), + deps = [ + ':testhelpers', + react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'), + react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), + react_native_dep('third-party/java/junit:junit'), + react_native_dep('third-party/java/mockito:mockito'), + react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), + react_native_target('java/com/facebook/react/bridge:bridge'), + react_native_target('java/com/facebook/react/cxxbridge:bridge'), + ], + visibility = [ + 'PUBLIC' + ], +) + +project_config( + test_target = ':bridge', +) diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ContentCheckingUnpackerTest.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ContentCheckingUnpackerTest.java new file mode 100644 index 00000000000000..23ba3ca67f8509 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ContentCheckingUnpackerTest.java @@ -0,0 +1,85 @@ +/** + * 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.cxxbridge; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.runner.RunWith; +import org.junit.Test; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RobolectricTestRunner; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; + +@PrepareForTest({UnpackingJSBundleLoader.class}) +@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" }) +@RunWith(RobolectricTestRunner.class) +public class ContentCheckingUnpackerTest extends UnpackerTestBase { + @Rule + public PowerMockRule rule = new PowerMockRule(); + + private UnpackingJSBundleLoader.ContentCheckingUnpacker mUnpacker; + + @Before + public void setUp() throws IOException { + super.setUp(); + mUnpacker = new UnpackingJSBundleLoader.ContentCheckingUnpacker( + NAME_IN_APK, + DESTINATION_NAME); + mUnpacker.setDestinationDirectory(folder.getRoot()); + } + + @Test + public void testReconstructsIfFileDoesNotExist() throws IOException { + assertTrue(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); + } + + @Test + public void testReconstructsIfContentDoesNotMatch() throws IOException { + try (FileOutputStream fos = new FileOutputStream(mDestinationPath)) { + fos.write(ASSET_DATA, 0, ASSET_DATA.length - 1); + fos.write((byte) (ASSET_DATA[ASSET_DATA.length - 1] + 1)); + } + assertTrue(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); + } + + @Test + public void testDoesNotReconstructIfContentMatches() throws IOException { + try (FileOutputStream fos = new FileOutputStream(mDestinationPath)) { + fos.write(ASSET_DATA); + } + assertFalse(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); + } + + @Test + public void testUnpacksFile() throws IOException { + mUnpacker.unpack(mContext, mIOBuffer); + assertTrue(mDestinationPath.exists()); + try (InputStream is = new FileInputStream(mDestinationPath)) { + byte[] storedData = UnpackingJSBundleLoader.readBytes(is, mIOBuffer, Integer.MAX_VALUE); + assertArrayEquals(ASSET_DATA, storedData); + } + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ExistenceCheckingUnpackerTest.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ExistenceCheckingUnpackerTest.java new file mode 100644 index 00000000000000..2608bcab3dc437 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ExistenceCheckingUnpackerTest.java @@ -0,0 +1,78 @@ +/** + * 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.cxxbridge; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.runner.RunWith; +import org.junit.Test; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RobolectricTestRunner; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.times; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; + +@PrepareForTest({UnpackingJSBundleLoader.class}) +@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" }) +@RunWith(RobolectricTestRunner.class) +public class ExistenceCheckingUnpackerTest extends UnpackerTestBase { + @Rule + public PowerMockRule rule = new PowerMockRule(); + + private UnpackingJSBundleLoader.ExistenceCheckingUnpacker mUnpacker; + + @Before + public void setUp() throws IOException { + super.setUp(); + mUnpacker = new UnpackingJSBundleLoader.ExistenceCheckingUnpacker( + NAME_IN_APK, + DESTINATION_NAME); + mUnpacker.setDestinationDirectory(folder.getRoot()); + } + + @Test + public void testReconstructsIfFileDoesNotExist() { + assertTrue(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); + } + + @Test + public void testDoesNotReconstructIfFileExists() throws IOException { + mDestinationPath.createNewFile(); + assertFalse(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); + } + + @Test + public void testUnpacksFile() throws IOException { + mUnpacker.unpack(mContext, mIOBuffer); + assertTrue(mDestinationPath.exists()); + try (InputStream is = new FileInputStream(mDestinationPath)) { + byte[] storedData = UnpackingJSBundleLoader.readBytes(is, mIOBuffer, Integer.MAX_VALUE); + assertArrayEquals(ASSET_DATA, storedData); + } + } + + @Test + public void testFsyncsAfterUnpacking() throws IOException { + mockStatic(UnpackingJSBundleLoader.class); + mUnpacker.unpack(mContext, mIOBuffer); + verifyStatic(times(1)); + UnpackingJSBundleLoader.fsync(mDestinationPath); + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackerTestBase.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackerTestBase.java new file mode 100644 index 00000000000000..a2865a612ab868 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackerTestBase.java @@ -0,0 +1,95 @@ +/** + * 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.cxxbridge; + +import android.content.Context; +import android.content.res.AssetManager; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; + +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UnpackerTestBase { + static final String NAME_IN_APK = "nameInApk"; + static final String DESTINATION_NAME = "destination"; + static final byte[] ASSET_DATA = new byte[]{(byte) 1, (byte) 101, (byte) 50}; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + File mDestinationPath; + byte[] mIOBuffer; + + Context mContext; + AssetManager mAssetManager; + + public void setUp() throws IOException { + mDestinationPath = new File(folder.getRoot(), DESTINATION_NAME); + mIOBuffer = new byte[16 * 1024]; + + mContext = mock(Context.class); + mAssetManager = mock(AssetManager.class); + + when(mContext.getAssets()).thenReturn(mAssetManager); + when(mAssetManager.open(eq(NAME_IN_APK), anyInt())) + .then(new Answer<FileInputStream>() { + @Override + public FileInputStream answer(InvocationOnMock invocation) throws Throwable { + final ByteArrayInputStream bais = new ByteArrayInputStream(ASSET_DATA); + final FileInputStream fis = mock(FileInputStream.class); + when(fis.read()) + .then(new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + return bais.read(); + } + }); + when(fis.read(any(byte[].class))) + .then(new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + return bais.read((byte[]) invocation.getArguments()[0]); + } + }); + when(fis.read(any(byte[].class), any(int.class), any(int.class))) + .then(new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + return bais.read( + (byte[]) invocation.getArguments()[0], + (int) invocation.getArguments()[1], + (int) invocation.getArguments()[2]); + } + }); + when(fis.available()).then(new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + return bais.available(); + } + }); + return fis; + } + }); + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoaderTest.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoaderTest.java new file mode 100644 index 00000000000000..016ecb2012b045 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoaderTest.java @@ -0,0 +1,184 @@ +/** + * 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.cxxbridge; + +import android.content.Context; + +import com.facebook.soloader.SoLoader; + +import java.io.File; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.Test; +import org.robolectric.RobolectricTestRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(RobolectricTestRunner.class) +public class UnpackingJSBundleLoaderTest { + static { + SoLoader.setInTestMode(); + } + + private static final String URL = "http://this.is.an.url"; + private static final int MOCK_UNPACKERS_NUM = 2; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private File mDestinationPath; + private File mFilesPath; + + private UnpackingJSBundleLoader.Builder mBuilder; + private Context mContext; + private CatalystInstanceImpl mCatalystInstanceImpl; + private UnpackingJSBundleLoader.Unpacker[] mMockUnpackers; + + @Before + public void setUp() throws IOException { + mDestinationPath = folder.newFolder("destination"); + mFilesPath = folder.newFolder("files"); + + mContext = mock(Context.class); + when(mContext.getFilesDir()).thenReturn(mFilesPath); + + mCatalystInstanceImpl = mock(CatalystInstanceImpl.class); + + mBuilder = UnpackingJSBundleLoader.newBuilder() + .setDestinationPath(mDestinationPath) + .setSourceURL(URL) + .setContext(mContext); + + mMockUnpackers = new UnpackingJSBundleLoader.Unpacker[MOCK_UNPACKERS_NUM]; + for (int i = 0; i < mMockUnpackers.length; ++i) { + mMockUnpackers[i] = mock(UnpackingJSBundleLoader.Unpacker.class); + } + } + + private void addUnpackers() { + for (UnpackingJSBundleLoader.Unpacker unpacker : mMockUnpackers) { + mBuilder.addUnpacker(unpacker); + } + } + + @Test + public void testGetSourceUrl() { + assertEquals(URL, mBuilder.build().getSourceUrl()); + } + + @Test + public void testCreatesDotUnpackedFile() throws IOException { + mBuilder.build().prepare(); + assertTrue(new File(mDestinationPath, UnpackingJSBundleLoader.DOT_UNPACKED_FILE).exists()); + } + + @Test + public void testCreatesLockFile() throws IOException { + mBuilder.build().prepare(); + assertTrue(new File(mFilesPath, UnpackingJSBundleLoader.LOCK_FILE).exists()); + } + + @Test + public void testCallsAppropriateInstanceMethod() throws IOException { + mBuilder.build().loadScript(mCatalystInstanceImpl); + verify(mCatalystInstanceImpl).loadScriptFromOptimizedBundle( + eq(mDestinationPath.getPath()), + eq(URL), + eq(UnpackingJSBundleLoader.UNPACKED_JS_SOURCE)); + verifyNoMoreInteractions(mCatalystInstanceImpl); + } + + @Test + public void testLoadScriptUnpacks() { + mBuilder.build().loadScript(mCatalystInstanceImpl); + assertTrue(new File(mDestinationPath, UnpackingJSBundleLoader.DOT_UNPACKED_FILE).exists()); + } + + @Test + public void testPrepareCallDoesNotRecreateDirIfNotNecessary() throws IOException { + mBuilder.build().prepare(); + + addUnpackers(); + mBuilder.build().prepare(); + for (UnpackingJSBundleLoader.Unpacker unpacker : mMockUnpackers) { + verify(unpacker).setDestinationDirectory(mDestinationPath); + verify(unpacker).shouldReconstructDir( + same(mContext), + any(byte[].class)); + verifyNoMoreInteractions(unpacker); + } + } + + @Test + public void testShouldReconstructDirForcesRecreation() throws IOException { + mBuilder.build().prepare(); + + addUnpackers(); + when(mMockUnpackers[0].shouldReconstructDir( + same(mContext), + any(byte[].class))) + .thenReturn(true); + mBuilder.build().prepare(); + + verify(mMockUnpackers[0]).shouldReconstructDir( + same(mContext), + any(byte[].class)); + for (UnpackingJSBundleLoader.Unpacker unpacker : mMockUnpackers) { + verify(unpacker).setDestinationDirectory(mDestinationPath); + verify(unpacker).unpack( + same(mContext), + any(byte[].class)); + verifyNoMoreInteractions(unpacker); + } + } + + @Test + public void testDirectoryReconstructionRemovesDir() throws IOException { + mBuilder.build().prepare(); + final File aFile = new File(mDestinationPath, "a_file"); + aFile.createNewFile(); + + when(mMockUnpackers[0].shouldReconstructDir( + same(mContext), + any(byte[].class))) + .thenReturn(true); + addUnpackers(); + mBuilder.build().prepare(); + + assertFalse(aFile.exists()); + } + + @Test(expected = RuntimeException.class) + public void testDropsDirectoryOnException() throws IOException { + doThrow(new IOException("An expected IOException")) + .when(mMockUnpackers[0]).unpack( + same(mContext), + any(byte[].class)); + try { + mBuilder.addUnpacker(mMockUnpackers[0]).build().prepare(); + } finally { + assertFalse(mDestinationPath.exists()); + } + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK index c03d50ff4bb6fc..33400cb7a8c5c7 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK @@ -6,14 +6,15 @@ robolectric3_test( name = 'modules', srcs = glob(['**/*.java']), deps = [ + CSSLAYOUT_TARGET, react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'), react_native_dep('third-party/java/fest:fest'), + react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_dep('third-party/java/junit:junit'), react_native_dep('third-party/java/mockito:mockito'), react_native_dep('third-party/java/okhttp:okhttp3'), react_native_dep('third-party/java/okio:okio'), react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), - react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/animation:animation'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), @@ -24,6 +25,7 @@ robolectric3_test( react_native_target('java/com/facebook/react/modules/debug:debug'), react_native_target('java/com/facebook/react/modules/dialog:dialog'), react_native_target('java/com/facebook/react/modules/network:network'), + react_native_target('java/com/facebook/react/modules/share:share'), react_native_target('java/com/facebook/react/modules/storage:storage'), react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'), react_native_target('java/com/facebook/react/touch:touch'), diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java index c06a99f9adef47..8849002c068e16 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java @@ -61,11 +61,12 @@ Call.class, RequestBodyUtil.class, ProgressRequestBody.class, - ProgressRequestListener.class, + ProgressListener.class, MultipartBody.class, MultipartBody.Builder.class, NetworkingModule.class, OkHttpClient.class, + OkHttpClient.Builder.class, OkHttpCallUtil.class}) @RunWith(RobolectricTestRunner.class) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) @@ -84,6 +85,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return callMock; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); @@ -91,11 +95,12 @@ public Object answer(InvocationOnMock invocation) throws Throwable { mock(ExecutorToken.class), "GET", "http://somedomain/foo", - 0, - JavaOnlyArray.of(), - null, - true, - 0); + /* requestId */ 0, + /* headers */ JavaOnlyArray.of(), + /* body */ null, + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class); verify(httpClient).newCall(argumentCaptor.capture()); @@ -112,6 +117,9 @@ public void testFailGetWithInvalidHeadersStruct() throws Exception { when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter); OkHttpClient httpClient = mock(OkHttpClient.class); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient); List<JavaOnlyArray> invalidHeaders = Arrays.asList(JavaOnlyArray.of("foo")); @@ -122,11 +130,12 @@ public void testFailGetWithInvalidHeadersStruct() throws Exception { mock(ExecutorToken.class), "GET", "http://somedoman/foo", - 0, - JavaOnlyArray.from(invalidHeaders), - null, - true, - 0); + /* requestId */ 0, + /* headers */ JavaOnlyArray.from(invalidHeaders), + /* body */ null, + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); verifyErrorEmit(emitter, 0); } @@ -138,6 +147,9 @@ public void testFailPostWithoutContentType() throws Exception { when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter); OkHttpClient httpClient = mock(OkHttpClient.class); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient); JavaOnlyMap body = new JavaOnlyMap(); @@ -152,8 +164,9 @@ public void testFailPostWithoutContentType() throws Exception { 0, JavaOnlyArray.of(), body, - true, - 0); + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); verifyErrorEmit(emitter, 0); } @@ -196,6 +209,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return callMock; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); @@ -209,8 +225,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { 0, JavaOnlyArray.of(JavaOnlyArray.of("Content-Type", "text/plain")), body, - true, - 0); + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class); verify(httpClient).newCall(argumentCaptor.capture()); @@ -234,6 +251,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return callMock; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); @@ -248,8 +268,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { 0, JavaOnlyArray.from(headers), null, - true, - 0); + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class); verify(httpClient).newCall(argumentCaptor.capture()); Headers requestHeaders = argumentCaptor.getValue().headers(); @@ -265,7 +286,8 @@ public void testMultipartPostRequestSimple() throws Exception { .thenReturn(mock(InputStream.class)); when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))) .thenReturn(mock(RequestBody.class)); - when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressRequestListener.class))).thenCallRealMethod(); + when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressListener.class))) + .thenCallRealMethod(); JavaOnlyMap body = new JavaOnlyMap(); JavaOnlyArray formData = new JavaOnlyArray(); @@ -288,6 +310,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return callMock; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.sendRequest( @@ -297,8 +322,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { 0, new JavaOnlyArray(), body, - true, - 0); + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); // verify url, method, headers ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class); @@ -320,7 +346,8 @@ public void testMultipartPostRequestHeaders() throws Exception { .thenReturn(mock(InputStream.class)); when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))) .thenReturn(mock(RequestBody.class)); - when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressRequestListener.class))).thenCallRealMethod(); + when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressListener.class))) + .thenCallRealMethod(); List<JavaOnlyArray> headers = Arrays.asList( JavaOnlyArray.of("Accept", "text/plain"), @@ -348,6 +375,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return callMock; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.sendRequest( @@ -357,8 +387,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { 0, JavaOnlyArray.from(headers), body, - true, - 0); + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); // verify url, method, headers ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class); @@ -383,7 +414,8 @@ public void testMultipartPostRequestBody() throws Exception { when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class))) .thenReturn(inputStream); when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))).thenCallRealMethod(); - when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressRequestListener.class))).thenCallRealMethod(); + when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressListener.class))) + .thenCallRealMethod(); when(inputStream.available()).thenReturn("imageUri".length()); final MultipartBody.Builder multipartBuilder = mock(MultipartBody.Builder.class); @@ -445,6 +477,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return callMock; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); @@ -455,8 +490,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { 0, JavaOnlyArray.from(headers), body, - true, - 0); + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0); // verify RequestBodyPart for image PowerMockito.verifyStatic(times(1)); @@ -503,6 +539,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return calls[(Integer) request.tag() - 1]; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); networkingModule.initialize(); @@ -515,7 +554,8 @@ public Object answer(InvocationOnMock invocation) throws Throwable { idx + 1, JavaOnlyArray.of(), null, - true, + /* responseType */ "text", + /* useIncrementalUpdates*/ true, 0); } verify(httpClient, times(3)).newCall(any(Request.class)); @@ -550,6 +590,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return calls[(Integer) request.tag() - 1]; } }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); NetworkingModule networkingModule = new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); @@ -561,7 +604,8 @@ public Object answer(InvocationOnMock invocation) throws Throwable { idx + 1, JavaOnlyArray.of(), null, - true, + /* responseType */ "text", + /* useIncrementalUpdates*/ true, 0); } verify(httpClient, times(3)).newCall(any(Request.class)); diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java new file mode 100644 index 00000000000000..f9d1b31e7037db --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java @@ -0,0 +1,179 @@ +/** + * 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.modules.share; + +import android.app.Activity; +import android.content.Intent; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactTestHelper; +import com.facebook.react.bridge.JavaOnlyMap; + +import javax.annotation.Nullable; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.Rule; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.internal.ShadowExtractor; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@PrepareForTest({Arguments.class}) +@RunWith(RobolectricTestRunner.class) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +public class ShareModuleTest { + + private Activity mActivity; + private ShareModule mShareModule; + + @Rule + public PowerMockRule rule = new PowerMockRule(); + + @Before + public void prepareModules() throws Exception { + PowerMockito.mockStatic(Arguments.class); + Mockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return new JavaOnlyMap(); + } + }); + + mShareModule = new ShareModule(ReactTestHelper.createCatalystContextForTest()); + } + + @After + public void cleanUp() { + mActivity = null; + mShareModule = null; + } + + @Test + public void testShareDialog() { + final String title = "Title"; + final String message = "Message"; + final String dialogTitle = "Dialog Title"; + + JavaOnlyMap content = new JavaOnlyMap(); + content.putString("title", title); + content.putString("message", message); + + final SimplePromise promise = new SimplePromise(); + + mShareModule.share(content, dialogTitle, promise); + + final Intent chooserIntent = + ((ShadowApplication)ShadowExtractor.extract(RuntimeEnvironment.application)).getNextStartedActivity(); + assertNotNull("Dialog was not displayed", chooserIntent); + assertEquals(Intent.ACTION_CHOOSER, chooserIntent.getAction()); + assertEquals(dialogTitle, chooserIntent.getExtras().get(Intent.EXTRA_TITLE)); + + final Intent contentIntent = (Intent)chooserIntent.getExtras().get(Intent.EXTRA_INTENT); + assertNotNull("Intent was not built correctly", contentIntent); + assertEquals(Intent.ACTION_SEND, contentIntent.getAction()); + assertEquals(title, contentIntent.getExtras().get(Intent.EXTRA_SUBJECT)); + assertEquals(message, contentIntent.getExtras().get(Intent.EXTRA_TEXT)); + + assertEquals(1, promise.getResolved()); + } + + @Test + public void testInvalidContent() { + final String dialogTitle = "Dialog Title"; + + final SimplePromise promise = new SimplePromise(); + + mShareModule.share(null, dialogTitle, promise); + + assertEquals(1, promise.getRejected()); + assertEquals(ShareModule.ERROR_INVALID_CONTENT, promise.getErrorCode()); + } + + final static class SimplePromise implements Promise { + private static final String DEFAULT_ERROR = "EUNSPECIFIED"; + + private int mResolved; + private int mRejected; + private Object mValue; + private String mErrorCode; + private String mErrorMessage; + + public int getResolved() { + return mResolved; + } + + public int getRejected() { + return mRejected; + } + + public Object getValue() { + return mValue; + } + + public String getErrorCode() { + return mErrorCode; + } + + public String getErrorMessage() { + return mErrorMessage; + } + + @Override + public void resolve(Object value) { + mResolved++; + mValue = value; + } + + @Override + public void reject(String code, String message) { + reject(code, message, /*Throwable*/null); + } + + @Override + @Deprecated + public void reject(String message) { + reject(DEFAULT_ERROR, message, /*Throwable*/null); + } + + @Override + public void reject(String code, Throwable e) { + reject(code, e.getMessage(), e); + } + + @Override + public void reject(Throwable e) { + reject(DEFAULT_ERROR, e.getMessage(), e); + } + + @Override + public void reject(String code, String message, @Nullable Throwable e) { + mRejected++; + mErrorCode = code; + mErrorMessage = message; + } + } + +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java index 7eebda7f1ac62b..5ca8e67e091811 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java @@ -52,8 +52,9 @@ public class TimingModuleTest { private static final long FRAME_TIME_NS = 17 * 1000 * 1000; // 17 ms private Timing mTiming; - private ReactChoreographer mChoreographerMock; + private ReactChoreographer mReactChoreographerMock; private PostFrameCallbackHandler mPostFrameCallbackHandler; + private PostFrameIdleCallbackHandler mIdlePostFrameCallbackHandler; private long mCurrentTimeNs; private JSTimersExecution mJSTimersMock; private ExecutorToken mExecutorTokenMock; @@ -73,12 +74,13 @@ public Object answer(InvocationOnMock invocation) throws Throwable { }); PowerMockito.mockStatic(SystemClock.class); + when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000); when(SystemClock.currentTimeMillis()).thenReturn(mCurrentTimeNs / 1000000); when(SystemClock.nanoTime()).thenReturn(mCurrentTimeNs); - mChoreographerMock = mock(ReactChoreographer.class); + mReactChoreographerMock = mock(ReactChoreographer.class); PowerMockito.mockStatic(ReactChoreographer.class); - when(ReactChoreographer.getInstance()).thenReturn(mChoreographerMock); + when(ReactChoreographer.getInstance()).thenReturn(mReactChoreographerMock); CatalystInstance reactInstance = mock(CatalystInstance.class); ReactApplicationContext reactContext = mock(ReactApplicationContext.class); @@ -86,26 +88,49 @@ public Object answer(InvocationOnMock invocation) throws Throwable { mCurrentTimeNs = 0; mPostFrameCallbackHandler = new PostFrameCallbackHandler(); + mIdlePostFrameCallbackHandler = new PostFrameIdleCallbackHandler(); doAnswer(mPostFrameCallbackHandler) - .when(mChoreographerMock) + .when(mReactChoreographerMock) .postFrameCallback( eq(ReactChoreographer.CallbackType.TIMERS_EVENTS), any(Choreographer.FrameCallback.class)); + doAnswer(mIdlePostFrameCallbackHandler) + .when(mReactChoreographerMock) + .postFrameCallback( + eq(ReactChoreographer.CallbackType.IDLE_EVENT), + any(Choreographer.FrameCallback.class)); + mTiming = new Timing(reactContext, mock(DevSupportManager.class)); mJSTimersMock = mock(JSTimersExecution.class); mExecutorTokenMock = mock(ExecutorToken.class); when(reactContext.getJSModule(mExecutorTokenMock, JSTimersExecution.class)).thenReturn(mJSTimersMock); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + ((Runnable)invocation.getArguments()[0]).run(); + return null; + } + }).when(reactContext).runOnJSQueueThread(any(Runnable.class)); + mTiming.initialize(); } private void stepChoreographerFrame() { Choreographer.FrameCallback callback = mPostFrameCallbackHandler.getAndResetFrameCallback(); + Choreographer.FrameCallback idleCallback = mIdlePostFrameCallbackHandler.getAndResetFrameCallback(); + mCurrentTimeNs += FRAME_TIME_NS; + when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000); if (callback != null) { callback.doFrame(mCurrentTimeNs); } + + if (idleCallback != null) { + idleCallback.doFrame(mCurrentTimeNs); + } } @Test @@ -170,6 +195,33 @@ public void testSetTimeoutZero() { verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100)); } + @Test + public void testIdleCallback() { + mTiming.onHostResume(); + mTiming.setSendIdleEvents(mExecutorTokenMock, true); + + stepChoreographerFrame(); + verify(mJSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis()); + } + + private static class PostFrameIdleCallbackHandler implements Answer<Void> { + + private Choreographer.FrameCallback mFrameCallback; + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + mFrameCallback = (Choreographer.FrameCallback) args[1]; + return null; + } + + public Choreographer.FrameCallback getAndResetFrameCallback() { + Choreographer.FrameCallback callback = mFrameCallback; + mFrameCallback = null; + return callback; + } + } + private static class PostFrameCallbackHandler implements Answer<Void> { private Choreographer.FrameCallback mFrameCallback; diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK index ccfb2bc70b74b1..d5a5c27bdb68f4 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK @@ -6,6 +6,7 @@ robolectric3_test( contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'], srcs = glob(['**/*.java']), deps = [ + CSSLAYOUT_TARGET, react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'), react_native_dep('third-party/java/fest:fest'), react_native_dep('third-party/java/jsr-305:jsr-305'), @@ -14,7 +15,6 @@ robolectric3_test( react_native_dep('third-party/java/okhttp:okhttp3'), react_native_dep('third-party/java/okio:okio'), react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), - react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/animation:animation'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/BUCK b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK index 673085e0357b0e..491648c570f146 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK @@ -6,6 +6,7 @@ robolectric3_test( contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'], srcs = glob(['**/*.java']), deps = [ + CSSLAYOUT_TARGET, react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'), react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'), react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'), @@ -17,7 +18,6 @@ robolectric3_test( react_native_dep('third-party/java/okhttp:okhttp3'), react_native_dep('third-party/java/okio:okio'), react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), - react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common:common'), react_native_target('java/com/facebook/react/touch:touch'), diff --git a/ReactCommon/cxxreact/Android.mk b/ReactCommon/cxxreact/Android.mk index 985cc035752b11..30cd232dbac745 100644 --- a/ReactCommon/cxxreact/Android.mk +++ b/ReactCommon/cxxreact/Android.mk @@ -5,6 +5,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := libreactnativefb LOCAL_SRC_FILES := \ + Executor.cpp \ Instance.cpp \ JSCExecutor.cpp \ JSCHelpers.cpp \ diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index 14a5b1e77487ba..61767e0fd91ed4 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -109,6 +109,7 @@ react_library( force_static = True, srcs = [ 'CxxMessageQueue.cpp', + 'Executor.cpp', 'Instance.cpp', 'JSCExecutor.cpp', 'JSCHelpers.cpp', @@ -116,6 +117,7 @@ react_library( 'JSCLegacyTracing.cpp', 'JSCMemory.cpp', 'JSCPerfStats.cpp', + 'JSCSamplingProfiler.cpp', 'JSCTracing.cpp', 'JSCWebWorker.cpp', 'MethodCall.cpp', @@ -129,6 +131,7 @@ react_library( 'JSCLegacyTracing.h', 'JSCMemory.h', 'JSCPerfStats.h', + 'JSCSamplingProfiler.h', 'JSCTracing.h', ], exported_headers = [ diff --git a/ReactCommon/cxxreact/Executor.cpp b/ReactCommon/cxxreact/Executor.cpp new file mode 100644 index 00000000000000..15f7730b407618 --- /dev/null +++ b/ReactCommon/cxxreact/Executor.cpp @@ -0,0 +1,77 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "Executor.h" + +#include <errno.h> +#include <fcntl.h> +#include <fstream> +#include <stdio.h> +#include <sys/stat.h> + +#include <folly/Memory.h> + +namespace facebook { +namespace react { + +void JSExecutor::loadApplicationScript(std::string bundlePath, std::string sourceURL, int flags) { + if ((flags & UNPACKED_JS_SOURCE) == 0) { + throw std::runtime_error("No unpacked js source file"); + } + return loadApplicationScript( + JSBigMmapString::fromOptimizedBundle(bundlePath), + std::move(sourceURL)); +} + +static JSBigMmapString::Encoding encodingFromByte(uint8_t byte) { + switch (byte) { + case 0: + return JSBigMmapString::Encoding::Unknown; + case 1: + return JSBigMmapString::Encoding::Ascii; + case 2: + return JSBigMmapString::Encoding::Utf8; + case 3: + return JSBigMmapString::Encoding::Utf16; + default: + throw std::invalid_argument("Unknown bundle encoding"); + } +} + +std::unique_ptr<const JSBigMmapString> JSBigMmapString::fromOptimizedBundle( + const std::string& bundlePath) { + uint8_t sha1[20]; + uint8_t encoding; + struct stat fileInfo; + int fd = -1; + SCOPE_FAIL { CHECK(fd == -1 || ::close(fd) == 0); }; + + { + auto metaPath = bundlePath + UNPACKED_META_PATH_SUFFIX; + std::ifstream metaFile; + metaFile.exceptions(std::ifstream::eofbit | std::ifstream::failbit | std::ifstream::badbit); + metaFile.open(metaPath, std::ifstream::in | std::ifstream::binary); + metaFile.read(reinterpret_cast<char*>(sha1), sizeof(sha1)); + metaFile.read(reinterpret_cast<char*>(&encoding), sizeof(encoding)); + } + + { + auto sourcePath = bundlePath + UNPACKED_JS_SOURCE_PATH_SUFFIX; + fd = ::open(sourcePath.c_str(), O_RDONLY); + if (fd == -1) { + throw std::runtime_error(std::string("could not open js bundle file: ") + ::strerror(errno)); + } + } + + if (::fstat(fd, &fileInfo)) { + throw std::runtime_error(std::string("fstat on js bundle failed: ") + strerror(errno)); + } + + return folly::make_unique<const JSBigMmapString>( + fd, + fileInfo.st_size, + sha1, + encodingFromByte(encoding)); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/cxxreact/Executor.h b/ReactCommon/cxxreact/Executor.h index e2c5a331b6bf18..62b2004c56e525 100644 --- a/ReactCommon/cxxreact/Executor.h +++ b/ReactCommon/cxxreact/Executor.h @@ -7,6 +7,8 @@ #include <string> #include <vector> +#include <sys/mman.h> + #include <folly/dynamic.h> #include "JSModulesUnbundle.h" @@ -14,6 +16,13 @@ namespace facebook { namespace react { +#define UNPACKED_JS_SOURCE_PATH_SUFFIX "/bundle.js" +#define UNPACKED_META_PATH_SUFFIX "/bundle.meta" + +enum { + UNPACKED_JS_SOURCE = (1 << 0), +}; + class JSExecutor; class MessageQueueThread; @@ -134,6 +143,70 @@ class JSBigBufferString : public facebook::react::JSBigString { size_t m_size; }; +class JSBigMmapString : public JSBigString { +public: + enum class Encoding { + Unknown, + Ascii, + Utf8, + Utf16, + }; + + + JSBigMmapString(int fd, size_t size, const uint8_t sha1[20], Encoding encoding) : + m_fd(fd), + m_size(size), + m_encoding(encoding), + m_str(nullptr) + { + memcpy(m_hash, sha1, 20); + } + + ~JSBigMmapString() { + if (m_str) { + CHECK(munmap((void *)m_str, m_size) != -1); + } + close(m_fd); + } + + bool isAscii() const override { + return m_encoding == Encoding::Ascii; + } + + const char* c_str() const override { + if (!m_str) { + m_str = (const char *)mmap(0, m_size, PROT_READ, MAP_SHARED, m_fd, 0); + CHECK(m_str != MAP_FAILED); + } + return m_str; + } + + size_t size() const override { + return m_size; + } + + int fd() const { + return m_fd; + } + + const uint8_t* hash() const { + return m_hash; + } + + Encoding encoding() const { + return m_encoding; + } + + static std::unique_ptr<const JSBigMmapString> fromOptimizedBundle(const std::string& bundlePath); + +private: + int m_fd; + size_t m_size; + uint8_t m_hash[20]; + Encoding m_encoding; + mutable const char *m_str; +}; + class JSExecutor { public: /** @@ -142,6 +215,11 @@ class JSExecutor { virtual void loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) = 0; + /** + * Execute an application script optimized bundle in the JS context. + */ + virtual void loadApplicationScript(std::string bundlePath, std::string source, int flags); + /** * Add an application "unbundle" file */ @@ -181,4 +259,6 @@ class JSExecutor { virtual ~JSExecutor() {} }; +std::unique_ptr<const JSBigMmapString> readJSBundle(const std::string& path); + } } diff --git a/ReactCommon/cxxreact/Instance.cpp b/ReactCommon/cxxreact/Instance.cpp index a179fa14f72432..eb5e31d949848a 100644 --- a/ReactCommon/cxxreact/Instance.cpp +++ b/ReactCommon/cxxreact/Instance.cpp @@ -75,6 +75,16 @@ void Instance::loadScriptFromFile(const std::string& filename, loadScriptFromString(std::move(buf), sourceURL); } +void Instance::loadScriptFromOptimizedBundle(std::string bundlePath, + std::string sourceURL, + int flags) { + SystraceSection s("reactbridge_xplat_loadScriptFromOptimizedBundle", + "bundlePath", bundlePath); + nativeToJsBridge_->loadOptimizedApplicationScript(std::move(bundlePath), + std::move(sourceURL), + flags); +} + void Instance::loadUnbundle(std::unique_ptr<JSModulesUnbundle> unbundle, std::unique_ptr<const JSBigString> startupScript, std::string startupScriptSourceURL) { @@ -101,11 +111,10 @@ void Instance::setGlobalVariable(std::string propName, nativeToJsBridge_->setGlobalVariable(std::move(propName), std::move(jsonValue)); } -void Instance::callJSFunction(ExecutorToken token, const std::string& module, const std::string& method, - folly::dynamic&& params, const std::string& tracingName) { - SystraceSection s(tracingName.c_str()); +void Instance::callJSFunction(ExecutorToken token, std::string&& module, std::string&& method, + folly::dynamic&& params) { callback_->incrementPendingJSCalls(); - nativeToJsBridge_->callFunction(token, module, method, std::move(params), tracingName); + nativeToJsBridge_->callFunction(token, std::move(module), std::move(method), std::move(params)); } void Instance::callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params) { diff --git a/ReactCommon/cxxreact/Instance.h b/ReactCommon/cxxreact/Instance.h index d5a3507eb80bc2..1e53c81f2380a6 100644 --- a/ReactCommon/cxxreact/Instance.h +++ b/ReactCommon/cxxreact/Instance.h @@ -36,6 +36,7 @@ class Instance { std::shared_ptr<ModuleRegistry> moduleRegistry); void loadScriptFromString(std::unique_ptr<const JSBigString> string, std::string sourceURL); void loadScriptFromFile(const std::string& filename, const std::string& sourceURL); + void loadScriptFromOptimizedBundle(std::string bundlePath, std::string sourceURL, int flags); void loadUnbundle( std::unique_ptr<JSModulesUnbundle> unbundle, std::unique_ptr<const JSBigString> startupScript, @@ -44,8 +45,8 @@ class Instance { void startProfiler(const std::string& title); void stopProfiler(const std::string& title, const std::string& filename); void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue); - void callJSFunction(ExecutorToken token, const std::string& module, const std::string& method, - folly::dynamic&& params, const std::string& tracingName); + void callJSFunction(ExecutorToken token, std::string&& module, std::string&& method, + folly::dynamic&& params); void callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params); MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args); diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 5bd5ba63ea0ae9..b2b50fdc6ac6cd 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -20,6 +20,8 @@ #include "SystraceSection.h" #include "Value.h" +#include "JSCSamplingProfiler.h" + #if defined(WITH_JSC_EXTRA_TRACING) || DEBUG #include "JSCTracing.h" #endif @@ -66,12 +68,7 @@ inline JSObjectCallAsFunctionCallback exceptionWrapMethod() { auto executor = static_cast<JSCExecutor*>(JSObjectGetPrivate(globalObj)); return (executor->*method)(argumentCount, arguments); } catch (...) { - try { - auto functionName = Object(ctx, function).getProperty("name").toString().str(); - *exception = translatePendingCppExceptionToJSError(ctx, functionName.c_str()); - } catch (...) { - *exception = makeJSError(ctx, "Failed to get function name while handling exception"); - } + *exception = translatePendingCppExceptionToJSError(ctx, function); return JSValueMakeUndefined(ctx); } } @@ -228,6 +225,8 @@ void JSCExecutor::initOnJSVMThread() throw(JSException) { addNativeProfilingHooks(m_context); addNativeTracingLegacyHooks(m_context); PerfLogging::installNativeHooks(m_context); + + initSamplingProfilerOnMainJSCThread(m_context); #endif #ifdef WITH_FB_MEMORY_PROFILING @@ -258,6 +257,42 @@ void JSCExecutor::terminateOnJSVMThread() { m_context = nullptr; } +#ifdef WITH_FBJSCEXTENSIONS +void JSCExecutor::loadApplicationScript( + std::string bundlePath, + std::string sourceURL, + int flags) { + SystraceSection s("JSCExecutor::loadApplicationScript", + "sourceURL", sourceURL); + + if ((flags & UNPACKED_JS_SOURCE) == 0) { + throw std::runtime_error("Optimized bundle with no unpacked js source"); + } + + auto jsScriptBigString = JSBigMmapString::fromOptimizedBundle(bundlePath); + if (jsScriptBigString->encoding() != JSBigMmapString::Encoding::Ascii) { + LOG(WARNING) << "Bundle is not ASCII encoded - falling back to the slow path"; + return loadApplicationScript(std::move(jsScriptBigString), sourceURL); + } + + String jsSourceURL(sourceURL.c_str()); + JSSourceCodeRef sourceCode = JSCreateSourceCode( + jsScriptBigString->fd(), + jsScriptBigString->size(), + jsSourceURL, + jsScriptBigString->hash(), + true); + SCOPE_EXIT { JSReleaseSourceCode(sourceCode); }; + + evaluateSourceCode(m_context, sourceCode, jsSourceURL); + + bindBridge(); + + flush(); + ReactMarker::logMarker("CREATE_REACT_CONTEXT_END"); +} +#endif + void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) throw(JSException) { SystraceSection s("JSCExecutor::loadApplicationScript", "sourceURL", sourceURL); @@ -305,42 +340,64 @@ void JSCExecutor::bindBridge() throw(JSException) { m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject(); } -void JSCExecutor::flush() throw(JSException) { +void JSCExecutor::flush() { auto result = m_flushedQueueJS->callAsFunction({}); - auto calls = Value(m_context, result).toJSONString(); - m_delegate->callNativeModules(*this, std::move(calls), true); + try { + auto calls = Value(m_context, result).toJSONString(); + m_delegate->callNativeModules(*this, std::move(calls), true); + } catch (...) { + std::string message = "Error in flush()"; + try { + message += ":" + Value(m_context, result).toString().str(); + } catch (...) { + // ignored + } + std::throw_with_nested(std::runtime_error(message)); + } } -void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) throw(JSException) { - auto result = m_callFunctionReturnFlushedQueueJS->callAsFunction({ - Value(m_context, String::createExpectingAscii(moduleId)), - Value(m_context, String::createExpectingAscii(methodId)), - Value::fromDynamic(m_context, std::move(arguments)) - }); - auto calls = Value(m_context, result).toJSONString(); - m_delegate->callNativeModules(*this, std::move(calls), true); +void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) { + try { + auto result = m_callFunctionReturnFlushedQueueJS->callAsFunction({ + Value(m_context, String::createExpectingAscii(moduleId)), + Value(m_context, String::createExpectingAscii(methodId)), + Value::fromDynamic(m_context, std::move(arguments)) + }); + auto calls = Value(m_context, result).toJSONString(); + m_delegate->callNativeModules(*this, std::move(calls), true); + } catch (...) { + std::throw_with_nested(std::runtime_error("Error calling function: " + moduleId + ":" + methodId)); + } } -void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) throw(JSException) { - auto result = m_invokeCallbackAndReturnFlushedQueueJS->callAsFunction({ - JSValueMakeNumber(m_context, callbackId), - Value::fromDynamic(m_context, std::move(arguments)) - }); - auto calls = Value(m_context, result).toJSONString(); - m_delegate->callNativeModules(*this, std::move(calls), true); +void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { + try { + auto result = m_invokeCallbackAndReturnFlushedQueueJS->callAsFunction({ + JSValueMakeNumber(m_context, callbackId), + Value::fromDynamic(m_context, std::move(arguments)) + }); + auto calls = Value(m_context, result).toJSONString(); + m_delegate->callNativeModules(*this, std::move(calls), true); + } catch (...) { + std::throw_with_nested(std::runtime_error(folly::to<std::string>("Error invoking callback.", callbackId))); + } } -void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) throw(JSException) { - SystraceSection s("JSCExecutor.setGlobalVariable", - "propName", propName); +void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) { + try { + SystraceSection s("JSCExecutor.setGlobalVariable", + "propName", propName); - auto globalObject = JSContextGetGlobalObject(m_context); - String jsPropertyName(propName.c_str()); + auto globalObject = JSContextGetGlobalObject(m_context); + String jsPropertyName(propName.c_str()); - String jsValueJSON = jsStringFromBigString(*jsonValue); - auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON); + String jsValueJSON = jsStringFromBigString(*jsonValue); + auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON); - JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL); + JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL); + } catch (...) { + std::throw_with_nested(std::runtime_error("Error setting global variable: " + propName)); + } } void* JSCExecutor::getJavaScriptContext() { @@ -561,7 +618,7 @@ JSValueRef JSCExecutor::nativeRequireModuleConfig( std::string moduleName = Value(m_context, arguments[0]).toString().str(); folly::dynamic config = m_delegate->getModuleConfig(moduleName); - return JSValueMakeString(m_context, String(folly::toJson(config).c_str())); + return Value::fromDynamic(m_context, config); } JSValueRef JSCExecutor::nativeFlushQueueImmediate( @@ -646,7 +703,7 @@ JSValueRef JSCExecutor::nativeCallSyncHook( if (result.isUndefined) { return JSValueMakeUndefined(m_context); } - return Value::fromJSON(m_context, String(folly::toJson(result.result).c_str())); + return Value::fromDynamic(m_context, result.result); } static JSValueRef nativeInjectHMRUpdate( diff --git a/ReactCommon/cxxreact/JSCExecutor.h b/ReactCommon/cxxreact/JSCExecutor.h index b1dfcdb56d523f..455fa5b9d40560 100644 --- a/ReactCommon/cxxreact/JSCExecutor.h +++ b/ReactCommon/cxxreact/JSCExecutor.h @@ -59,18 +59,24 @@ class JSCExecutor : public JSExecutor { virtual void loadApplicationScript( std::unique_ptr<const JSBigString> script, std::string sourceURL) throw(JSException) override; +#ifdef WITH_FBJSCEXTENSIONS + virtual void loadApplicationScript( + std::string bundlePath, + std::string sourceURL, + int flags) override; +#endif virtual void setJSModulesUnbundle( std::unique_ptr<JSModulesUnbundle> unbundle) override; virtual void callFunction( const std::string& moduleId, const std::string& methodId, - const folly::dynamic& arguments) throw(JSException) override; + const folly::dynamic& arguments) override; virtual void invokeCallback( const double callbackId, - const folly::dynamic& arguments) throw(JSException) override; + const folly::dynamic& arguments) override; virtual void setGlobalVariable( std::string propName, - std::unique_ptr<const JSBigString> jsonValue) throw(JSException) override; + std::unique_ptr<const JSBigString> jsonValue) override; virtual void* getJavaScriptContext() override; virtual bool supportsProfiling() override; virtual void startProfiler(const std::string &titleString) override; @@ -111,7 +117,7 @@ class JSCExecutor : public JSExecutor { void initOnJSVMThread() throw(JSException); void terminateOnJSVMThread(); void bindBridge() throw(JSException); - void flush() throw(JSException); + void flush(); void flushQueueImmediate(std::string queueJSON); void loadModule(uint32_t moduleId); diff --git a/ReactCommon/cxxreact/JSCHelpers.cpp b/ReactCommon/cxxreact/JSCHelpers.cpp index d0aa0a0eda59ef..194cc473791eac 100644 --- a/ReactCommon/cxxreact/JSCHelpers.cpp +++ b/ReactCommon/cxxreact/JSCHelpers.cpp @@ -44,47 +44,63 @@ JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef JSValueRef exn, result; result = JSEvaluateScript(context, script, NULL, source, 0, &exn); if (result == nullptr) { - Value exception = Value(context, exn); - - std::string exceptionText = exception.toString().str(); - - // The null/empty-ness of source tells us if the JS came from a - // file/resource, or was a constructed statement. The location - // info will include that source, if any. - std::string locationInfo = source != nullptr ? String::ref(source).str() : ""; - Object exObject = exception.asObject(); - auto line = exObject.getProperty("line"); - if (line != nullptr && line.isNumber()) { - if (locationInfo.empty() && line.asInteger() != 1) { - // If there is a non-trivial line number, but there was no - // location info, we include a placeholder, and the line - // number. - locationInfo = folly::to<std::string>("<unknown file>:", line.asInteger()); - } else if (!locationInfo.empty()) { - // If there is location info, we always include the line - // number, regardless of its value. - locationInfo += folly::to<std::string>(":", line.asInteger()); - } - } + formatAndThrowJSException(context, exn, source); + } + return result; +} - if (!locationInfo.empty()) { - exceptionText += " (" + locationInfo + ")"; +#if WITH_FBJSCEXTENSIONS +JSValueRef evaluateSourceCode(JSContextRef context, JSSourceCodeRef source, JSStringRef sourceURL) { + JSValueRef exn, result; + result = JSEvaluateSourceCode(context, source, NULL, &exn); + if (result == nullptr) { + formatAndThrowJSException(context, exn, sourceURL); + } + return result; +} +#endif + +void formatAndThrowJSException(JSContextRef context, JSValueRef exn, JSStringRef source) { + Value exception = Value(context, exn); + + std::string exceptionText = exception.toString().str(); + + // The null/empty-ness of source tells us if the JS came from a + // file/resource, or was a constructed statement. The location + // info will include that source, if any. + std::string locationInfo = source != nullptr ? String::ref(source).str() : ""; + Object exObject = exception.asObject(); + auto line = exObject.getProperty("line"); + if (line != nullptr && line.isNumber()) { + if (locationInfo.empty() && line.asInteger() != 1) { + // If there is a non-trivial line number, but there was no + // location info, we include a placeholder, and the line + // number. + locationInfo = folly::to<std::string>("<unknown file>:", line.asInteger()); + } else if (!locationInfo.empty()) { + // If there is location info, we always include the line + // number, regardless of its value. + locationInfo += folly::to<std::string>(":", line.asInteger()); } + } - LOG(ERROR) << "Got JS Exception: " << exceptionText; + if (!locationInfo.empty()) { + exceptionText += " (" + locationInfo + ")"; + } - Value jsStack = exObject.getProperty("stack"); - if (jsStack.isNull() || !jsStack.isString()) { - throwJSExecutionException("%s", exceptionText.c_str()); - } else { - LOG(ERROR) << "Got JS Stack: " << jsStack.toString().str(); - throwJSExecutionExceptionWithStack( + LOG(ERROR) << "Got JS Exception: " << exceptionText; + + Value jsStack = exObject.getProperty("stack"); + if (jsStack.isNull() || !jsStack.isString()) { + throwJSExecutionException("%s", exceptionText.c_str()); + } else { + LOG(ERROR) << "Got JS Stack: " << jsStack.toString().str(); + throwJSExecutionExceptionWithStack( exceptionText.c_str(), jsStack.toString().str().c_str()); - } } - return result; } + JSValueRef makeJSError(JSContextRef ctx, const char *error) { JSValueRef nestedException = nullptr; JSValueRef args[] = { Value(ctx, String(error)) }; @@ -113,4 +129,13 @@ JSValueRef translatePendingCppExceptionToJSError(JSContextRef ctx, const char *e } } +JSValueRef translatePendingCppExceptionToJSError(JSContextRef ctx, JSObjectRef jsFunctionCause) { + try { + auto functionName = Object(ctx, jsFunctionCause).getProperty("name").toString().str(); + return translatePendingCppExceptionToJSError(ctx, functionName.c_str()); + } catch (...) { + return makeJSError(ctx, "Failed to get function name while handling exception"); + } +} + } } diff --git a/ReactCommon/cxxreact/JSCHelpers.h b/ReactCommon/cxxreact/JSCHelpers.h index 0d2609a754bcdc..a55906d560c2d8 100644 --- a/ReactCommon/cxxreact/JSCHelpers.h +++ b/ReactCommon/cxxreact/JSCHelpers.h @@ -49,8 +49,48 @@ JSValueRef evaluateScript( JSStringRef script, JSStringRef sourceURL); +#if WITH_FBJSCEXTENSIONS +JSValueRef evaluateSourceCode( + JSContextRef ctx, + JSSourceCodeRef source, + JSStringRef sourceURL); +#endif + +void formatAndThrowJSException( + JSContextRef ctx, + JSValueRef exn, + JSStringRef sourceURL); + JSValueRef makeJSError(JSContextRef ctx, const char *error); JSValueRef translatePendingCppExceptionToJSError(JSContextRef ctx, const char *exceptionLocation); +JSValueRef translatePendingCppExceptionToJSError(JSContextRef ctx, JSObjectRef jsFunctionCause); + +template<JSValueRef (method)(JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception)> +inline JSObjectCallAsFunctionCallback exceptionWrapMethod() { + struct funcWrapper { + static JSValueRef call( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception) { + try { + return (*method)(ctx, function, thisObject, argumentCount, arguments, exception); + } catch (...) { + *exception = translatePendingCppExceptionToJSError(ctx, function); + return JSValueMakeUndefined(ctx); + } + } + }; + + return &funcWrapper::call; +} } } diff --git a/ReactCommon/cxxreact/JSCSamplingProfiler.cpp b/ReactCommon/cxxreact/JSCSamplingProfiler.cpp new file mode 100644 index 00000000000000..936ee41e028fe3 --- /dev/null +++ b/ReactCommon/cxxreact/JSCSamplingProfiler.cpp @@ -0,0 +1,19 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#ifdef WITH_JSC_EXTRA_TRACING + +#include "JSCSamplingProfiler.h" + +#include <JavaScriptCore/API/JSProfilerPrivate.h> + +namespace facebook { +namespace react { + +void initSamplingProfilerOnMainJSCThread(JSGlobalContextRef ctx) { + JSStartSamplingProfilingOnMainJSCThread(ctx); +} + +} +} + +#endif // WITH_JSC_EXTRA_TRACING diff --git a/ReactCommon/cxxreact/JSCSamplingProfiler.h b/ReactCommon/cxxreact/JSCSamplingProfiler.h new file mode 100644 index 00000000000000..ec8d4e8751fc0b --- /dev/null +++ b/ReactCommon/cxxreact/JSCSamplingProfiler.h @@ -0,0 +1,16 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#ifdef WITH_JSC_EXTRA_TRACING + +#include <JavaScriptCore/JSContextRef.h> + +namespace facebook { +namespace react { + +void initSamplingProfilerOnMainJSCThread(JSGlobalContextRef ctx); +} +} + +#endif // WITH_JSC_EXTRA_TRACING diff --git a/ReactCommon/cxxreact/MethodCall.cpp b/ReactCommon/cxxreact/MethodCall.cpp index d9c5e7543eac23..aa0757ca52e555 100644 --- a/ReactCommon/cxxreact/MethodCall.cpp +++ b/ReactCommon/cxxreact/MethodCall.cpp @@ -40,6 +40,11 @@ std::vector<MethodCall> parseMethodCalls(const std::string& json) throw(std::inv folly::to<std::string>("Did not get valid calls back from JS: ", json.c_str())); } + if (moduleIds.size() != methodIds.size() || moduleIds.size() != params.size()) { + throw std::invalid_argument( + folly::to<std::string>("Did not get valid calls back from JS: ", json.c_str())); + } + if (jsonData.size() > REQUEST_CALLID) { if (!jsonData[REQUEST_CALLID].isInt()) { throw std::invalid_argument( diff --git a/ReactCommon/cxxreact/NativeToJsBridge.cpp b/ReactCommon/cxxreact/NativeToJsBridge.cpp index 4a935cf5b25dca..ecddf32e230113 100644 --- a/ReactCommon/cxxreact/NativeToJsBridge.cpp +++ b/ReactCommon/cxxreact/NativeToJsBridge.cpp @@ -124,6 +124,20 @@ void NativeToJsBridge::loadApplicationScript(std::unique_ptr<const JSBigString> m_mainExecutor->loadApplicationScript(std::move(script), std::move(sourceURL)); } +void NativeToJsBridge::loadOptimizedApplicationScript( + std::string bundlePath, + std::string sourceURL, + int flags) { + runOnExecutorQueue( + m_mainExecutorToken, + [bundlePath=std::move(bundlePath), + sourceURL=std::move(sourceURL), + flags=flags] + (JSExecutor* executor) { + executor->loadApplicationScript(std::move(bundlePath), std::move(sourceURL), flags); + }); +} + void NativeToJsBridge::loadApplicationUnbundle( std::unique_ptr<JSModulesUnbundle> unbundle, std::unique_ptr<const JSBigString> startupScript, @@ -143,20 +157,24 @@ void NativeToJsBridge::loadApplicationUnbundle( void NativeToJsBridge::callFunction( ExecutorToken executorToken, - const std::string& moduleId, - const std::string& methodId, - const folly::dynamic& arguments, - const std::string& tracingName) { + std::string&& module, + std::string&& method, + folly::dynamic&& arguments) { int systraceCookie = -1; #ifdef WITH_FBSYSTRACE systraceCookie = m_systraceCookie++; + std::string tracingName = fbsystrace_is_tracing(TRACE_TAG_REACT_CXX_BRIDGE) ? + folly::to<std::string>("JSCall__", module, '_', method) : std::string(); + SystraceSection s(tracingName.c_str()); FbSystraceAsyncFlow::begin( TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str(), systraceCookie); + #else + std::string tracingName; #endif - runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) { + runOnExecutorQueue(executorToken, [module = std::move(module), method = std::move(method), arguments = std::move(arguments), tracingName = std::move(tracingName), systraceCookie] (JSExecutor* executor) { #ifdef WITH_FBSYSTRACE FbSystraceAsyncFlow::end( TRACE_TAG_REACT_CXX_BRIDGE, @@ -168,12 +186,11 @@ void NativeToJsBridge::callFunction( // This is safe because we are running on the executor's thread: it won't // destruct until after it's been unregistered (which we check above) and // that will happen on this thread - executor->callFunction(moduleId, methodId, arguments); + executor->callFunction(module, method, arguments); }); } -void NativeToJsBridge::invokeCallback(ExecutorToken executorToken, const double callbackId, - const folly::dynamic& arguments) { +void NativeToJsBridge::invokeCallback(ExecutorToken executorToken, double callbackId, folly::dynamic&& arguments) { int systraceCookie = -1; #ifdef WITH_FBSYSTRACE systraceCookie = m_systraceCookie++; @@ -183,7 +200,7 @@ void NativeToJsBridge::invokeCallback(ExecutorToken executorToken, const double systraceCookie); #endif - runOnExecutorQueue(executorToken, [callbackId, arguments, systraceCookie] (JSExecutor* executor) { + runOnExecutorQueue(executorToken, [callbackId, arguments = std::move(arguments), systraceCookie] (JSExecutor* executor) { #ifdef WITH_FBSYSTRACE FbSystraceAsyncFlow::end( TRACE_TAG_REACT_CXX_BRIDGE, diff --git a/ReactCommon/cxxreact/NativeToJsBridge.h b/ReactCommon/cxxreact/NativeToJsBridge.h index 942ee17c93607e..e953673788daeb 100644 --- a/ReactCommon/cxxreact/NativeToJsBridge.h +++ b/ReactCommon/cxxreact/NativeToJsBridge.h @@ -68,15 +68,14 @@ class NativeToJsBridge { */ void callFunction( ExecutorToken executorToken, - const std::string& moduleId, - const std::string& methodId, - const folly::dynamic& args, - const std::string& tracingName); + std::string&& module, + std::string&& method, + folly::dynamic&& args); /** * Invokes a callback with the cbID, and optional additional arguments in JS. */ - void invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& args); + void invokeCallback(ExecutorToken executorToken, double callbackId, folly::dynamic&& args); /** * Starts the JS application from an "bundle", i.e. a JavaScript file that @@ -85,6 +84,12 @@ class NativeToJsBridge { */ void loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL); + /** + * Similar to loading a "bundle", but instead of passing js source this method accepts + * path to a directory containing files prepared for particular JSExecutor. + */ + void loadOptimizedApplicationScript(std::string bundlePath, std::string sourceURL, int flags); + /** * An "unbundle" is a backend that stores and injects JavaScript modules as * individual scripts, rather than bundling all of them into a single scrupt. diff --git a/ReactCommon/cxxreact/Value.cpp b/ReactCommon/cxxreact/Value.cpp index c328701ec362d3..43b6e44131aa36 100644 --- a/ReactCommon/cxxreact/Value.cpp +++ b/ReactCommon/cxxreact/Value.cpp @@ -6,6 +6,9 @@ #include "JSCHelpers.h" +// See the comment under Value::fromDynamic() +#define USE_FAST_FOLLY_DYNAMIC_CONVERSION !defined(__APPLE__) && defined(WITH_FB_JSC_TUNING) + namespace facebook { namespace react { @@ -32,7 +35,7 @@ JSContextRef Value::context() const { return m_context; } -std::string Value::toJSONString(unsigned indent) const throw(JSException) { +std::string Value::toJSONString(unsigned indent) const { JSValueRef exn; auto stringToAdopt = JSValueCreateJSONString(m_context, m_value, indent, &exn); if (stringToAdopt == nullptr) { @@ -43,7 +46,7 @@ std::string Value::toJSONString(unsigned indent) const throw(JSException) { } /* static */ -Value Value::fromJSON(JSContextRef ctx, const String& json) throw(JSException) { +Value Value::fromJSON(JSContextRef ctx, const String& json) { auto result = JSValueMakeFromJSONString(ctx, json); if (!result) { throwJSExecutionException("Failed to create String from JSON"); @@ -51,9 +54,78 @@ Value Value::fromJSON(JSContextRef ctx, const String& json) throw(JSException) { return Value(ctx, result); } -Value Value::fromDynamic(JSContextRef ctx, folly::dynamic value) throw(JSException) { +JSValueRef Value::fromDynamic(JSContextRef ctx, const folly::dynamic& value) { +// JavaScriptCore's iOS APIs have their own version of this direct conversion. +// In addition, using this requires exposing some of JSC's private APIs, +// so it's limited to non-apple platforms and to builds that use the custom JSC. +// Otherwise, we use the old way of converting through JSON. +#if USE_FAST_FOLLY_DYNAMIC_CONVERSION + // Defer GC during the creation of the JSValue, as we don't want + // intermediate objects to be collected. + // We could use JSValueProtect(), but it will make the process much slower. + JSDeferredGCRef deferGC = JSDeferGarbageCollection(ctx); + // Set a global lock for the whole process, + // instead of re-acquiring the lock for each operation. + JSLock(ctx); + JSValueRef jsVal = Value::fromDynamicInner(ctx, value); + JSUnlock(ctx); + JSResumeGarbageCollection(ctx, deferGC); + return jsVal; +#else auto json = folly::toJson(value); return fromJSON(ctx, String(json.c_str())); +#endif +} + +JSValueRef Value::fromDynamicInner(JSContextRef ctx, const folly::dynamic& obj) { + switch (obj.type()) { + // For premitive types (and strings), just create and return an equivalent JSValue + case folly::dynamic::Type::NULLT: + return JSValueMakeNull(ctx); + + case folly::dynamic::Type::BOOL: + return JSValueMakeBoolean(ctx, obj.getBool()); + + case folly::dynamic::Type::DOUBLE: + return JSValueMakeNumber(ctx, obj.getDouble()); + + case folly::dynamic::Type::INT64: + return JSValueMakeNumber(ctx, obj.asDouble()); + + case folly::dynamic::Type::STRING: + return JSValueMakeString(ctx, String(obj.getString().c_str())); + + case folly::dynamic::Type::ARRAY: { + // Collect JSValue for every element in the array + JSValueRef vals[obj.size()]; + for (size_t i = 0; i < obj.size(); ++i) { + vals[i] = fromDynamicInner(ctx, obj[i]); + } + // Create a JSArray with the values + JSValueRef arr = JSObjectMakeArray(ctx, obj.size(), vals, nullptr); + return arr; + } + + case folly::dynamic::Type::OBJECT: { + // Create an empty object + JSObjectRef jsObj = JSObjectMake(ctx, nullptr, nullptr); + // Create a JSValue for each of the object's children and set them in the object + for (auto it = obj.items().begin(); it != obj.items().end(); ++it) { + JSObjectSetProperty( + ctx, + jsObj, + String(it->first.asString().c_str()), + fromDynamicInner(ctx, it->second), + kJSPropertyAttributeNone, + nullptr); + } + return jsObj; + } + default: + // Assert not reached + LOG(FATAL) << "Trying to convert a folly object of unsupported type."; + return JSValueMakeNull(ctx); + } } Object Value::asObject() { diff --git a/ReactCommon/cxxreact/Value.h b/ReactCommon/cxxreact/Value.h index c43a523a7fce41..8d924df15b0b61 100644 --- a/ReactCommon/cxxreact/Value.h +++ b/ReactCommon/cxxreact/Value.h @@ -191,6 +191,11 @@ class Object : public noncopyable { } } + template<typename ReturnType> + ReturnType* getPrivate() const { + return static_cast<ReturnType*>(JSObjectGetPrivate(m_obj)); + } + JSContextRef context() const { return m_context; } @@ -223,6 +228,10 @@ class Value : public noncopyable { return m_value; } + JSType getType() const { + return JSValueGetType(m_context, m_value); + } + bool isBoolean() const { return JSValueIsBoolean(context(), m_value); } @@ -273,13 +282,14 @@ class Value : public noncopyable { return String::adopt(JSValueToStringCopy(context(), m_value, nullptr)); } - std::string toJSONString(unsigned indent = 0) const throw(JSException); - static Value fromJSON(JSContextRef ctx, const String& json) throw(JSException); - static Value fromDynamic(JSContextRef ctx, folly::dynamic value) throw(JSException); + std::string toJSONString(unsigned indent = 0) const; + static Value fromJSON(JSContextRef ctx, const String& json); + static JSValueRef fromDynamic(JSContextRef ctx, const folly::dynamic& value); JSContextRef context() const; protected: JSContextRef m_context; JSValueRef m_value; + static JSValueRef fromDynamicInner(JSContextRef ctx, const folly::dynamic& obj); }; } } diff --git a/ReactCommon/cxxreact/tests/BUCK b/ReactCommon/cxxreact/tests/BUCK index c640d826e11b85..6f30d733a0d88b 100644 --- a/ReactCommon/cxxreact/tests/BUCK +++ b/ReactCommon/cxxreact/tests/BUCK @@ -7,11 +7,16 @@ jni_instrumentation_test_lib( soname = 'libxplat-bridge.so', srcs = [ 'CxxMessageQueueTest.cpp', + 'value.cpp', + 'methodcall.cpp', + 'jsclogging.cpp', + 'jscexecutor.cpp', ], compiler_flags = [ '-fexceptions', ], deps = [ + '//native/third-party/android-ndk:android', '//xplat/third-party/gmock:gtest', react_native_xplat_target('cxxreact:bridge'), ], diff --git a/ReactCommon/cxxreact/tests/jscexecutor.cpp b/ReactCommon/cxxreact/tests/jscexecutor.cpp new file mode 100644 index 00000000000000..eb1382e8dce2dd --- /dev/null +++ b/ReactCommon/cxxreact/tests/jscexecutor.cpp @@ -0,0 +1,270 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include <gtest/gtest.h> +#include <cxxreact/JSCExecutor.h> +#include <cxxreact/MessageQueueThread.h> +#include <cxxreact/MethodCall.h> + +using namespace facebook; +using namespace facebook::react; + + +// TODO(12340362): Fix these tests. And add checks for sizes. +/* + +namespace { + +std::string capturedMethodCalls; + +struct NullDelegate : ExecutorDelegate { + virtual void registerExecutor(std::unique_ptr<JSExecutor> executor, + std::shared_ptr<MessageQueueThread> queue) { + std::terminate(); + } + + virtual std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executor) { + std::terminate(); + } + + virtual std::vector<std::string> moduleNames() { + return std::vector<std::string>{}; + } + + virtual folly::dynamic getModuleConfig(const std::string& name) { + std::terminate(); + } + virtual void callNativeModules( + JSExecutor& executor, std::string callJSON, bool isEndOfBatch) { + // TODO: capture calljson + std::terminate(); + } + virtual MethodCallResult callSerializableNativeHook( + JSExecutor& executor, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args) { + std::terminate(); + } +}; + +struct FakeMessageQueue : MessageQueueThread { + virtual void runOnQueue(std::function<void()>&& runnable) { + // This is wrong, but oh well. + runnable(); + } + + virtual void runOnQueueSync(std::function<void()>&& runnable) { + runnable(); + } + + virtual void quitSynchronous() { + std::terminate(); + } +}; + +std::vector<MethodCall> executeForMethodCalls( + JSCExecutor& e, + int moduleId, + int methodId, + folly::dynamic args = folly::dynamic::array()) { + e.callFunction(folly::to<std::string>(moduleId), folly::to<std::string>(methodId), std::move(args)); + return parseMethodCalls(capturedMethodCalls); +} + +void loadApplicationScript(JSCExecutor& e, std::string jsText) { + e.loadApplicationScript(std::unique_ptr<JSBigString>(new JSBigStdString(jsText)), ""); +} + +void setGlobalVariable(JSCExecutor& e, std::string name, std::string jsonObject) { + e.setGlobalVariable(name, std::unique_ptr<JSBigString>(new JSBigStdString(jsonObject))); +} + +} + +TEST(JSCExecutor, Initialize) { + JSCExecutor executor(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); +} + +TEST(JSCExecutor, Two) { + JSCExecutor exec1(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + JSCExecutor exec2(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); +} + +TEST(JSCExecutor, CallFunction) { + auto jsText = "" + "var Bridge = {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" + " return [[module + 1], [method + 1], [args]];" + " }," + "};" + "function require() { return Bridge; }" + ""; + JSCExecutor e(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + loadApplicationScript(e, jsText); + folly::dynamic args = folly::dynamic::array(); + args.push_back(true); + args.push_back(0.4); + args.push_back("hello, world"); + args.push_back(4.0); + auto returnedCalls = executeForMethodCalls(e, 10, 9, args); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + EXPECT_EQ(11, returnedCall.moduleId); + EXPECT_EQ(10, returnedCall.methodId); + ASSERT_EQ(4, returnedCall.arguments.size()); + EXPECT_EQ(args[0], returnedCall.arguments[0]); + EXPECT_EQ(args[1], returnedCall.arguments[1]); + EXPECT_EQ(args[2], returnedCall.arguments[2]); + EXPECT_EQ(folly::dynamic(4.0), returnedCall.arguments[3]); +} + +TEST(JSCExecutor, CallFunctionWithMap) { + auto jsText = "" + "var Bridge = {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" + " var s = args[0].foo + args[0].bar + args[0].baz;" + " return [[module], [method], [[s]]];" + " }," + "};" + "function require() { return Bridge; }" + ""; + JSCExecutor e(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + loadApplicationScript(e, jsText); + folly::dynamic args = folly::dynamic::array(); + folly::dynamic map = folly::dynamic::object + ("foo", folly::dynamic("hello")) + ("bar", folly::dynamic(4.0)) + ("baz", folly::dynamic(true)) + ; + args.push_back(std::move(map)); + auto returnedCalls = executeForMethodCalls(e, 10, 9, args); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + EXPECT_EQ("hello4true", returnedCall.arguments[0].getString()); +} + +TEST(JSCExecutor, CallFunctionReturningMap) { + auto jsText = "" + "var Bridge = {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" + " var s = { foo: 4, bar: true };" + " return [[module], [method], [[s]]];" + " }," + "};" + "function require() { return Bridge; }" + ""; + JSCExecutor e(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + loadApplicationScript(e, jsText); + auto returnedCalls = executeForMethodCalls(e, 10, 9); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::OBJECT, returnedCall.arguments[0].type()); + auto& returnedMap = returnedCall.arguments[0]; + auto foo = returnedMap.at("foo"); + EXPECT_EQ(folly::dynamic(4.0), foo); + auto bar = returnedMap.at("bar"); + EXPECT_EQ(folly::dynamic(true), bar); +} + +TEST(JSCExecutor, CallFunctionWithArray) { + auto jsText = "" + "var Bridge = {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" + " var s = args[0][0]+ args[0][1] + args[0][2] + args[0].length;" + " return [[module], [method], [[s]]];" + " }," + "};" + "function require() { return Bridge; }" + ""; + JSCExecutor e(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + loadApplicationScript(e, jsText); + std::vector<folly::dynamic> args; + std::vector<folly::dynamic> array { + folly::dynamic("hello"), + folly::dynamic(4.0), + folly::dynamic(true), + }; + args.push_back(std::move(array)); + auto returnedCalls = executeForMethodCalls(e, 10, 9, args); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + EXPECT_EQ("hello4true3", returnedCall.arguments[0].getString()); +} + +TEST(JSCExecutor, CallFunctionReturningNumberArray) { + auto jsText = "" + "var Bridge = {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" + " var s = [3, 1, 4];" + " return [[module], [method], [[s]]];" + " }," + "};" + "function require() { return Bridge; }" + ""; + JSCExecutor e(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + loadApplicationScript(e, jsText); + auto returnedCalls = executeForMethodCalls(e, 10, 9); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::ARRAY, returnedCall.arguments[0].type()); + + auto& array = returnedCall.arguments[0]; + EXPECT_EQ(3, array.size()); + EXPECT_EQ(folly::dynamic(3.0), array[0]); + EXPECT_EQ(folly::dynamic(4.0), array[2]); +} + +TEST(JSCExecutor, SetSimpleGlobalVariable) { + auto jsText = "" + "var Bridge = {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" + " return [[module], [method], [[__foo]]];" + " }," + "};" + "function require() { return Bridge; }" + ""; + JSCExecutor e(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + loadApplicationScript(e, jsText); + setGlobalVariable(e, "__foo", "42"); + auto returnedCalls = executeForMethodCalls(e, 10, 9); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(42.0, returnedCall.arguments[0].getDouble()); +} + +TEST(JSCExecutor, SetObjectGlobalVariable) { + auto jsText = "" + "var Bridge = {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" + " return [[module], [method], [[__foo]]];" + " }," + "};" + "function require() { return Bridge; }" + ""; + JSCExecutor e(std::make_shared<NullDelegate>(), std::make_shared<FakeMessageQueue>(), "", folly::dynamic::object); + loadApplicationScript(e, jsText); + auto jsonObject = "" + "{" + " \"foo\": \"hello\"," + " \"bar\": 4," + " \"baz\": true" + "}" + ""; + setGlobalVariable(e, "__foo", jsonObject); + auto returnedCalls = executeForMethodCalls(e, 10, 9); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::OBJECT, returnedCall.arguments[0].type()); + auto& returnedMap = returnedCall.arguments[0]; + auto foo = returnedMap.at("foo"); + EXPECT_EQ(folly::dynamic("hello"), foo); + auto bar = returnedMap.at("bar"); + EXPECT_EQ(folly::dynamic(4.0), bar); + auto baz = returnedMap.at("baz"); + EXPECT_EQ(folly::dynamic(true), baz); +} + +*/ diff --git a/ReactAndroid/src/main/jni/react/test/jsclogging.cpp b/ReactCommon/cxxreact/tests/jsclogging.cpp similarity index 95% rename from ReactAndroid/src/main/jni/react/test/jsclogging.cpp rename to ReactCommon/cxxreact/tests/jsclogging.cpp index 56ad010bc2e831..ca325b44212ffc 100644 --- a/ReactAndroid/src/main/jni/react/test/jsclogging.cpp +++ b/ReactCommon/cxxreact/tests/jsclogging.cpp @@ -1,13 +1,12 @@ // Copyright 2004-present Facebook. All Rights Reserved. #include <gtest/gtest.h> -#include <fb/log.h> -#include <react/JSCExecutor.h> +#include <cxxreact/JSCExecutor.h> using namespace facebook; using namespace facebook::react; - +/* static const char* expectedLogMessageSubstring = NULL; static bool hasSeenExpectedLogMessage = false; @@ -42,3 +41,4 @@ TEST_F(JSCLoggingTest, LogException) { ASSERT_TRUE(hasSeenExpectedLogMessage); } +*/ diff --git a/ReactCommon/cxxreact/tests/methodcall.cpp b/ReactCommon/cxxreact/tests/methodcall.cpp new file mode 100644 index 00000000000000..233a5f6724e3db --- /dev/null +++ b/ReactCommon/cxxreact/tests/methodcall.cpp @@ -0,0 +1,137 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include <gtest/gtest.h> +#include <cxxreact/MethodCall.h> + +using namespace facebook; +using namespace facebook::react; + +TEST(parseMethodCalls, SingleReturnCallNoArgs) { + auto jsText = "[[7],[3],[\"[]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(0, returnedCall.arguments.size()); + ASSERT_EQ(7, returnedCall.moduleId); + ASSERT_EQ(3, returnedCall.methodId); +} + +TEST(parseMethodCalls, InvalidReturnFormat) { + try { + parseMethodCalls("{\"foo\":1}"); + ADD_FAILURE(); + } catch (const std::invalid_argument&) { + // ignored + } + try { + parseMethodCalls("[{\"foo\":1}]"); + ADD_FAILURE(); + } catch (const std::invalid_argument&) { + // ignored + } + try { + parseMethodCalls("[1,4,{\"foo\":2}]"); + ADD_FAILURE(); + } catch (const std::invalid_argument&) { + // ignored + } + try { + parseMethodCalls("[[1],[4],{\"foo\":2}]"); + ADD_FAILURE(); + } catch (const std::invalid_argument&) { + // ignored + } + try { + parseMethodCalls("[[1],[4],[]]"); + ADD_FAILURE(); + } catch (const std::invalid_argument&) { + // ignored + } +} + +TEST(parseMethodCalls, NumberReturn) { + auto jsText = "[[0],[0],[\"[\\\"foobar\\\"]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type()); + ASSERT_EQ("foobar", returnedCall.arguments[0].asString()); +} + +TEST(parseMethodCalls, StringReturn) { + auto jsText = "[[0],[0],[\"[42.16]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::DOUBLE, returnedCall.arguments[0].type()); + ASSERT_EQ(42.16, returnedCall.arguments[0].asDouble()); +} + +TEST(parseMethodCalls, BooleanReturn) { + auto jsText = "[[0],[0],[\"[false]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::BOOL, returnedCall.arguments[0].type()); + ASSERT_FALSE(returnedCall.arguments[0].asBool()); +} + +TEST(parseMethodCalls, NullReturn) { + auto jsText = "[[0],[0],[\"[null]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::NULLT, returnedCall.arguments[0].type()); +} + +TEST(parseMethodCalls, MapReturn) { + auto jsText = "[[0],[0],[\"[{\\\"foo\\\": \\\"hello\\\", \\\"bar\\\": 4.0, \\\"baz\\\": true}]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::OBJECT, returnedCall.arguments[0].type()); + auto& returnedMap = returnedCall.arguments[0]; + auto foo = returnedMap.at("foo"); + EXPECT_EQ(folly::dynamic("hello"), foo); + auto bar = returnedMap.at("bar"); + EXPECT_EQ(folly::dynamic(4.0), bar); + auto baz = returnedMap.at("baz"); + EXPECT_EQ(folly::dynamic(true), baz); +} + +TEST(parseMethodCalls, ArrayReturn) { + auto jsText = "[[0],[0],[\"[[\\\"foo\\\", 42.0, false]]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(1, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::ARRAY, returnedCall.arguments[0].type()); + auto& returnedArray = returnedCall.arguments[0]; + ASSERT_EQ(3, returnedArray.size()); + ASSERT_EQ(folly::dynamic("foo"), returnedArray[0]); + ASSERT_EQ(folly::dynamic(42.0), returnedArray[1]); + ASSERT_EQ(folly::dynamic(false), returnedArray[2]); +} + +TEST(parseMethodCalls, ReturnMultipleParams) { + auto jsText = "[[0],[0],[\"[\\\"foo\\\", 14, null, false]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(1, returnedCalls.size()); + auto returnedCall = returnedCalls[0]; + ASSERT_EQ(4, returnedCall.arguments.size()); + ASSERT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type()); + ASSERT_EQ(folly::dynamic::INT64, returnedCall.arguments[1].type()); + ASSERT_EQ(folly::dynamic::NULLT, returnedCall.arguments[2].type()); + ASSERT_EQ(folly::dynamic::BOOL, returnedCall.arguments[3].type()); +} + +TEST(parseMethodCalls, ParseTwoCalls) { + auto jsText = "[[0,0],[1,1],[\"[]\",\"[]\"]]"; + auto returnedCalls = parseMethodCalls(jsText); + ASSERT_EQ(2, returnedCalls.size()); +} diff --git a/ReactAndroid/src/main/jni/react/test/value.cpp b/ReactCommon/cxxreact/tests/value.cpp similarity index 80% rename from ReactAndroid/src/main/jni/react/test/value.cpp rename to ReactCommon/cxxreact/tests/value.cpp index b57b419c8d9c72..580c5a9ed74847 100644 --- a/ReactAndroid/src/main/jni/react/test/value.cpp +++ b/ReactCommon/cxxreact/tests/value.cpp @@ -2,11 +2,13 @@ #include <gtest/gtest.h> #include <folly/json.h> -#include <react/Value.h> +#include <cxxreact/Value.h> using namespace facebook; using namespace facebook::react; +// TODO(cjhopman): Fix these tests. +/* TEST(Value, Undefined) { JSContextRef ctx = JSGlobalContextCreateInGroup(nullptr, nullptr); Value v(ctx, JSValueMakeUndefined(ctx)); @@ -26,13 +28,13 @@ TEST(Value, ToJSONString) { String s("{\"a\": 4}"); Value v(Value::fromJSON(ctx, s)); folly::dynamic dyn = folly::parseJson(v.toJSONString()); - ASSERT_NE(nullptr, json); - EXPECT_TRUE(json.isObject()); - auto val = json.at("a"); + ASSERT_NE(nullptr, dyn); + EXPECT_TRUE(dyn.isObject()); + auto val = dyn.at("a"); ASSERT_NE(nullptr, val); ASSERT_TRUE(val.isInt()); - EXPECT_EQ(4, val->getInt()); - EXPECT_EQ(4.0f, val->asDouble()); - + EXPECT_EQ(4, val.getInt()); + EXPECT_EQ(4.0f, val.asDouble()); } +*/ diff --git a/Tools/FBPortForwarding/Apps/MacApp/main.m b/Tools/FBPortForwarding/Apps/MacApp/main.m deleted file mode 100644 index bfe623d2790a6a..00000000000000 --- a/Tools/FBPortForwarding/Apps/MacApp/main.m +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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. - */ - -#import <Cocoa/Cocoa.h> - -#import <FBPortForwarding-Mac/FBPortForwardingClient.h> - -int main(int argc, char *argv[]) -{ - FBPortForwardingClient *client = [FBPortForwardingClient new]; - [client forwardConnectionsToPort:8081]; - [client connectToMultiplexingChannelOnPort:8025]; - - [[NSRunLoop currentRunLoop] run]; - client = nil; - return 0; -} diff --git a/Tools/FBPortForwarding/Apps/iOSApp/Info.plist b/Tools/FBPortForwarding/Apps/iOSApp/Info.plist deleted file mode 100644 index 54a71b0241ad1b..00000000000000 --- a/Tools/FBPortForwarding/Apps/iOSApp/Info.plist +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> -<dict> - <key>CFBundleDevelopmentRegion</key> - <string>en</string> - <key>CFBundleDisplayName</key> - <string>Port Forwarding</string> - <key>CFBundleExecutable</key> - <string>${EXECUTABLE_NAME}</string> - <key>CFBundleIdentifier</key> - <string>com.facebook.example.PortForwarding</string> - <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> - <key>CFBundleName</key> - <string>${PRODUCT_NAME}</string> - <key>CFBundlePackageType</key> - <string>APPL</string> - <key>CFBundleShortVersionString</key> - <string>1.0</string> - <key>CFBundleSignature</key> - <string>????</string> - <key>CFBundleVersion</key> - <string>1.0</string> - <key>LSRequiresIPhoneOS</key> - <true/> - <key>UIRequiredDeviceCapabilities</key> - <array> - <string>armv7</string> - </array> - <key>UISupportedInterfaceOrientations</key> - <array> - <string>UIInterfaceOrientationPortrait</string> - </array> -</dict> -</plist> diff --git a/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.m b/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.m deleted file mode 100644 index edeb6e90dab47f..00000000000000 --- a/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.m +++ /dev/null @@ -1,65 +0,0 @@ -/** - * 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. - */ - -#import "PFAppDelegate.h" - -#import <FBPortForwarding-iOS/FBPortForwardingServer.h> - -@implementation PFAppDelegate -{ - FBPortForwardingServer *_portForwardingServer; -} - -@synthesize window = window_; - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - CGRect rect = [[UIScreen mainScreen] bounds]; - - UIView *view = [[UIView alloc] initWithFrame:rect]; - view.backgroundColor = [UIColor whiteColor]; - UIViewController *controller = [UIViewController new]; - controller.view = view; - - UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - [button setTitle:@"Send request" forState:UIControlStateNormal]; - [button addTarget:self action:@selector(sendRequest) forControlEvents:UIControlEventTouchUpInside]; - button.frame = CGRectMake(0, 0, 200, 50); - button.center = view.center; - [view addSubview:button]; - - self.window = [[UIWindow alloc] initWithFrame:rect]; - self.window.rootViewController = controller; - [self.window makeKeyAndVisible]; - - _portForwardingServer = [FBPortForwardingServer new]; - [_portForwardingServer forwardConnectionsFromPort:8082]; - [_portForwardingServer listenForMultiplexingChannelOnPort:8025]; - - return YES; -} - -- (void)sendRequest -{ - NSURLRequest *req = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://localhost:8082/404"]]; - [[[NSURLConnection alloc] initWithRequest:req delegate:self] start]; -} - -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data -{ - NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSLog(@"Success: %@", content); -} - -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error -{ - NSLog(@"Error: %@", error); -} - -@end diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.m b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.m deleted file mode 100644 index d9d1e97820f426..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.m +++ /dev/null @@ -1,284 +0,0 @@ -/** - * 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. - */ - -#import "FBPortForwardingClient.h" - -#import <CocoaAsyncSocket/GCDAsyncSocket.h> - -#import <Peertalk/PTChannel.h> -#import <Peertalk/PTUSBHub.h> - -#import "FBPortForwardingCommon.h" - -static const NSTimeInterval ReconnectDelay = 1.0; - -@interface FBPortForwardingClient () <GCDAsyncSocketDelegate, PTChannelDelegate> -{ - NSUInteger _destPort; - NSUInteger _channelPort; - NSNumber *_connectingToDeviceID; - NSNumber *_connectedDeviceID; - NSDictionary *_connectedDeviceProperties; - BOOL _notConnectedQueueSuspended; - PTChannel *_connectedChannel; - dispatch_queue_t _notConnectedQueue; - dispatch_queue_t _clientSocketsQueue; - NSMutableDictionary *_clientSockets; -} - -@property (atomic, readonly) NSNumber *connectedDeviceID; -@property (atomic, assign) PTChannel *connectedChannel; - -@end - -@implementation FBPortForwardingClient - -@synthesize connectedDeviceID = _connectedDeviceID; - -- (instancetype)init -{ - if (self = [super init]) { - _notConnectedQueue = dispatch_queue_create("FBPortForwarding.notConnectedQueue", DISPATCH_QUEUE_SERIAL); - _clientSocketsQueue = dispatch_queue_create("FBPortForwarding.clients", DISPATCH_QUEUE_SERIAL); - _clientSockets = [NSMutableDictionary dictionary]; - } - return self; -} - -- (void)forwardConnectionsToPort:(NSUInteger)port -{ - _destPort = port; -} - -- (void)connectToMultiplexingChannelOnPort:(NSUInteger)port -{ - _channelPort = port; - [self startListeningForDevices]; - [self enqueueConnectToLocalIPv4Port]; -} - -- (void)close -{ - [self.connectedChannel close]; -} - -- (PTChannel *)connectedChannel { - return _connectedChannel; -} - -- (void)setConnectedChannel:(PTChannel *)connectedChannel { - _connectedChannel = connectedChannel; - - if (!_connectedChannel) { - for (GCDAsyncSocket *sock in [_clientSockets objectEnumerator]) { - [sock setDelegate:nil]; - [sock disconnect]; - } - [_clientSockets removeAllObjects]; - } - - // Toggle the notConnectedQueue_ depending on if we are connected or not - if (!_connectedChannel && _notConnectedQueueSuspended) { - dispatch_resume(_notConnectedQueue); - _notConnectedQueueSuspended = NO; - } else if (_connectedChannel && !_notConnectedQueueSuspended) { - dispatch_suspend(_notConnectedQueue); - _notConnectedQueueSuspended = YES; - } - - if (!_connectedChannel && _connectingToDeviceID) { - [self enqueueConnectToUSBDevice]; - } -} - - -#pragma mark - PTChannelDelegate - -- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload { - //NSLog(@"received %@, %u, %u, %@", channel, type, tag, payload); - - if (type == FBPortForwardingFrameTypeOpenPipe) { - GCDAsyncSocket *sock = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_clientSocketsQueue]; - sock.userData = @(tag); - _clientSockets[@(tag)] = sock; - - NSError *connectError; - if (![sock connectToHost:@"localhost" onPort:_destPort error:&connectError]) { - FBPFLog(@"Failed to connect to local %lu - %@", (unsigned long)_destPort, connectError); - } - - FBPFTrace(@"open socket (%d)", tag); - } - - if (type == FBPortForwardingFrameTypeWriteToPipe) { - GCDAsyncSocket *sock = _clientSockets[@(tag)]; - [sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0]; - FBPFTrace(@"channel -> socket (%d) %zu bytes", tag, payload.length); - } - - if (type == FBPortForwardingFrameTypeClosePipe) { - GCDAsyncSocket *sock = _clientSockets[@(tag)]; - [sock disconnectAfterWriting]; - FBPFTrace(@"close socket (%d)", tag); - } -} - -- (void)ioFrameChannel:(PTChannel *)channel didEndWithError:(NSError *)error { - if (_connectedDeviceID && [_connectedDeviceID isEqualToNumber:channel.userInfo]) { - [self didDisconnectFromDevice:_connectedDeviceID]; - } - - if (_connectedChannel == channel) { - FBPFTrace(@"Disconnected from %@", channel.userInfo); - self.connectedChannel = nil; - } -} - - -#pragma mark - GCDAsyncSocketDelegate - - -- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port -{ - FBPFTrace(@"socket (%ld) connected to %@", (long)[sock.userData integerValue], host); - [sock readDataWithTimeout:-1 tag:0]; -} - -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err -{ - UInt32 tag = [sock.userData unsignedIntValue]; - [_clientSockets removeObjectForKey:@(tag)]; - FBPFTrace(@"socket (%d) disconnected", (unsigned int)tag); - - [_connectedChannel sendFrameOfType:FBPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:nil]; -} - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_ -{ - UInt32 tag = [sock.userData unsignedIntValue]; - [_connectedChannel sendFrameOfType:FBPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) { - FBPFTrace(@"channel -> socket (%d), %lu bytes", (unsigned int)tag, (unsigned long)data.length); - [sock readDataWithTimeout:-1 tag:0]; - }]; -} - -#pragma mark - Wired device connections - - -- (void)startListeningForDevices { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - - [nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { - NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; - //NSLog(@"PTUSBDeviceDidAttachNotification: %@", note.userInfo); - FBPFTrace(@"PTUSBDeviceDidAttachNotification: %@", deviceID); - - dispatch_async(_notConnectedQueue, ^{ - if (!_connectingToDeviceID || ![deviceID isEqualToNumber:_connectingToDeviceID]) { - [self disconnectFromCurrentChannel]; - _connectingToDeviceID = deviceID; - _connectedDeviceProperties = [note.userInfo objectForKey:@"Properties"]; - [self enqueueConnectToUSBDevice]; - } - }); - }]; - - [nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { - NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; - //NSLog(@"PTUSBDeviceDidDetachNotification: %@", note.userInfo); - FBPFTrace(@"PTUSBDeviceDidDetachNotification: %@", deviceID); - - if ([_connectingToDeviceID isEqualToNumber:deviceID]) { - _connectedDeviceProperties = nil; - _connectingToDeviceID = nil; - if (_connectedChannel) { - [_connectedChannel close]; - } - } - }]; -} - - -- (void)didDisconnectFromDevice:(NSNumber *)deviceID { - FBPFLog(@"Disconnected from device #%@", deviceID); - if ([_connectedDeviceID isEqualToNumber:deviceID]) { - [self willChangeValueForKey:@"connectedDeviceID"]; - _connectedDeviceID = nil; - [self didChangeValueForKey:@"connectedDeviceID"]; - } -} - - -- (void)disconnectFromCurrentChannel { - if (_connectedDeviceID && _connectedChannel) { - [_connectedChannel close]; - self.connectedChannel = nil; - } -} - -- (void)enqueueConnectToLocalIPv4Port { - dispatch_async(_notConnectedQueue, ^{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self connectToLocalIPv4Port]; - }); - }); -} - - -- (void)connectToLocalIPv4Port { - PTChannel *channel = [PTChannel channelWithDelegate:self]; - channel.userInfo = [NSString stringWithFormat:@"127.0.0.1:%lu", (unsigned long)_channelPort]; - [channel connectToPort:_channelPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, PTAddress *address) { - if (error) { - if (error.domain == NSPOSIXErrorDomain && (error.code == ECONNREFUSED || error.code == ETIMEDOUT)) { - // this is an expected state - } else { - FBPFTrace(@"Failed to connect to 127.0.0.1:%lu: %@", (unsigned long)_channelPort, error); - } - } else { - [self disconnectFromCurrentChannel]; - self.connectedChannel = channel; - channel.userInfo = address; - FBPFLog(@"Connected to %@", address); - } - [self performSelector:@selector(enqueueConnectToLocalIPv4Port) withObject:nil afterDelay:ReconnectDelay]; - }]; -} - -- (void)enqueueConnectToUSBDevice { - dispatch_async(_notConnectedQueue, ^{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self connectToUSBDevice]; - }); - }); -} - - -- (void)connectToUSBDevice { - PTChannel *channel = [PTChannel channelWithDelegate:self]; - channel.userInfo = _connectingToDeviceID; - channel.delegate = self; - - [channel connectToPort:(int)_channelPort overUSBHub:PTUSBHub.sharedHub deviceID:_connectingToDeviceID callback:^(NSError *error) { - if (error) { - FBPFTrace(@"Failed to connect to device #%@: %@", channel.userInfo, error); - if (channel.userInfo == _connectingToDeviceID) { - [self performSelector:@selector(enqueueConnectToUSBDevice) withObject:nil afterDelay:ReconnectDelay]; - } - } else { - _connectedDeviceID = _connectingToDeviceID; - self.connectedChannel = channel; - FBPFLog(@"Connected to device #%@\n%@", _connectingToDeviceID, _connectedDeviceProperties); - } - }]; -} - - - -@end diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingCommon.h b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingCommon.h deleted file mode 100644 index b240b660d1a899..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingCommon.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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. - */ - -#import <Foundation/Foundation.h> - -#define FBPFTrace(...) /*NSLog(__VA_ARGS__)*/ -#define FBPFLog(...) NSLog(__VA_ARGS__) - -enum { - FBPortForwardingFrameTypeOpenPipe = 201, - FBPortForwardingFrameTypeWriteToPipe = 202, - FBPortForwardingFrameTypeClosePipe = 203, -}; - -static dispatch_data_t NSDataToGCDData(NSData *data) { - __block NSData *retainedData = data; - return dispatch_data_create(data.bytes, data.length, nil, ^{ - retainedData = nil; - }); -} diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.h b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.h deleted file mode 100644 index 5bc082fd0213e0..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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. - */ - -#import <Foundation/Foundation.h> - -@interface FBPortForwardingServer : NSObject - -- (instancetype)init; - -- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port; -- (void)forwardConnectionsFromPort:(NSUInteger)port; -- (void)close; - -@end diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.m b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.m deleted file mode 100644 index 3bc0daec886938..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.m +++ /dev/null @@ -1,193 +0,0 @@ -/** - * 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. - */ - -#import "FBPortForwardingServer.h" - -#import <UIKit/UIKit.h> - -#import <CocoaAsyncSocket/GCDAsyncSocket.h> - -#import <Peertalk/PTChannel.h> - -#import "FBPortForwardingCommon.h" - -@interface FBPortForwardingServer () <PTChannelDelegate, GCDAsyncSocketDelegate> -{ - __weak PTChannel *_serverChannel; - __weak PTChannel *_peerChannel; - - GCDAsyncSocket *_serverSocket; - NSMutableDictionary *_clientSockets; - UInt32 _lastClientSocketTag; - dispatch_queue_t _socketQueue; - PTProtocol *_protocol; -} - -@end - -@implementation FBPortForwardingServer - -- (instancetype)init -{ - if (self = [super init]) { - _socketQueue = dispatch_queue_create("FBPortForwardingServer", DISPATCH_QUEUE_SERIAL); - _lastClientSocketTag = 0; - _clientSockets = [NSMutableDictionary dictionary]; - _protocol = [[PTProtocol alloc] initWithDispatchQueue:_socketQueue]; - } - return self; -} - -- (void)dealloc -{ - [self close]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)forwardConnectionsFromPort:(NSUInteger)port -{ - [self _forwardConnectionsFromPort:port reportError:YES]; - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { - [self _forwardConnectionsFromPort:port reportError:NO]; - }]; -} - -- (void)_forwardConnectionsFromPort:(NSUInteger)port reportError:(BOOL)shouldReportError -{ - GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue]; - NSError *listenError; - if ([serverSocket acceptOnPort:port error:&listenError]) { - _serverSocket = serverSocket; - } else { - if (shouldReportError) { - FBPFLog(@"Failed to listen: %@", listenError); - } - } -} - -- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port -{ - [self _listenForMultiplexingChannelOnPort:port reportError:YES]; - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { - [self _listenForMultiplexingChannelOnPort:port reportError:NO]; - }]; -} - -- (void)_listenForMultiplexingChannelOnPort:(NSUInteger)port reportError:(BOOL)shouldReportError -{ - PTChannel *channel = [[PTChannel alloc] initWithProtocol:_protocol delegate:self]; - [channel listenOnPort:port IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { - if (error) { - if (shouldReportError) { - FBPFLog(@"Failed to listen on 127.0.0.1:%lu: %@", (unsigned long)port, error); - } - } else { - FBPFTrace(@"Listening on 127.0.0.1:%lu", (unsigned long)port); - _serverChannel = channel; - } - }]; -} - -- (void)close -{ - if (_serverChannel) { - [_serverChannel close]; - _serverChannel = nil; - } - [_serverSocket disconnect]; -} - -#pragma mark - PTChannelDelegate - -- (void)ioFrameChannel:(PTChannel *)channel didAcceptConnection:(PTChannel *)otherChannel fromAddress:(PTAddress *)address { - // Cancel any other connection. We are FIFO, so the last connection - // established will cancel any previous connection and "take its place". - if (_peerChannel) { - [_peerChannel cancel]; - } - - // Weak pointer to current connection. Connection objects live by themselves - // (owned by its parent dispatch queue) until they are closed. - _peerChannel = otherChannel; - _peerChannel.userInfo = address; - FBPFTrace(@"Connected to %@", address); -} - -- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload { - //NSLog(@"didReceiveFrameOfType: %u, %u, %@", type, tag, payload); - if (type == FBPortForwardingFrameTypeWriteToPipe) { - GCDAsyncSocket *sock = _clientSockets[@(tag)]; - [sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0]; - FBPFTrace(@"channel -> socket (%d), %zu bytes", tag, payload.length); - } - - if (type == FBPortForwardingFrameTypeClosePipe) { - GCDAsyncSocket *sock = _clientSockets[@(tag)]; - [sock disconnectAfterWriting]; - } -} - -- (void)ioFrameChannel:(PTChannel *)channel didEndWithError:(NSError *)error { - for (GCDAsyncSocket *sock in [_clientSockets objectEnumerator]) { - [sock setDelegate:nil]; - [sock disconnect]; - } - [_clientSockets removeAllObjects]; - FBPFTrace(@"Disconnected from %@, error = %@", channel.userInfo, error); -} - - -#pragma mark - GCDAsyncSocketDelegate - -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket -{ - dispatch_block_t block = ^() { - if (!_peerChannel) { - [newSocket setDelegate:nil]; - [newSocket disconnect]; - } - - UInt32 tag = ++_lastClientSocketTag; - newSocket.userData = @(tag); - newSocket.delegate = self; - _clientSockets[@(tag)] = newSocket; - [_peerChannel sendFrameOfType:FBPortForwardingFrameTypeOpenPipe tag:_lastClientSocketTag withPayload:nil callback:^(NSError *error) { - FBPFTrace(@"open socket (%d), error = %@", (unsigned int)tag, error); - [newSocket readDataWithTimeout:-1 tag:0]; - }]; - }; - - if (_peerChannel) { - block(); - } else { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), _socketQueue, block); - } -} - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_ -{ - UInt32 tag = [[sock userData] unsignedIntValue]; - FBPFTrace(@"Incoming data on socket (%d) - %lu bytes", (unsigned int)tag, (unsigned long)data.length); - [_peerChannel sendFrameOfType:FBPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) { - FBPFTrace(@"socket (%d) -> channel %lu bytes, error = %@", (unsigned int)tag, (unsigned long)data.length, error); - [sock readDataWithTimeout:-1 tag:_]; - }]; -} - -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err -{ - UInt32 tag = [sock.userData unsignedIntValue]; - [_clientSockets removeObjectForKey:@(tag)]; - [_peerChannel sendFrameOfType:FBPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:^(NSError *error) { - FBPFTrace(@"socket (%d) disconnected, err = %@, peer error = %@", (unsigned int)tag, err, error); - }]; -} - - -@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.h b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.h deleted file mode 100644 index 1bc3f988711262..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 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. - */ - -#import <Foundation/Foundation.h> - -#import <CocoaAsyncSocket/GCDAsyncSocket.h> - -@interface PFPingClient : NSObject <GCDAsyncSocketDelegate> - -- (BOOL)connectToLocalServerOnPort:(NSUInteger)port; -- (void)sendPing:(NSData *)ping; - -@property (nonatomic, copy, readonly) NSArray *pongs; -@property (nonatomic, readonly) BOOL connected; - -@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.m deleted file mode 100644 index 36297585083ce9..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.m +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 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. - */ - -#import "PFPingClient.h" - -@implementation PFPingClient -{ - GCDAsyncSocket *_client; -} - -- (instancetype)init -{ - if (self = [super init]) { - _pongs = [NSArray array]; - _client = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; - } - return self; -} - -- (BOOL)connectToLocalServerOnPort:(NSUInteger)port -{ - return [_client connectToHost:@"localhost" onPort:port error:nil]; -} - -- (void)sendPing:(NSData *)ping -{ - [_client writeData:ping withTimeout:-1 tag:0]; -} - -- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port -{ - _connected = YES; - [_client readDataWithTimeout:-1 tag:0]; -} - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag -{ - _pongs = [_pongs arrayByAddingObject:data]; - [_client readDataWithTimeout:-1 tag:0]; -} - -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err -{ - _connected = NO; -} - -@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.m deleted file mode 100644 index 7e4e03495a27ae..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.m +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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. - */ - -#import "PFPingServer.h" - -@implementation PFPingServer -{ - GCDAsyncSocket *_server; - NSMutableArray *_clients; -} - -- (instancetype)initWithPort:(NSUInteger)port -{ - if (self = [super init]) { - _clients = [NSMutableArray array]; - _server = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; - - NSError *error; - if (![_server acceptOnPort:port error:&error]) { - NSLog(@"Failed to listen on port %lu: %@", (unsigned long)port, error); - return nil; - } - } - return self; -} - -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket -{ - [_clients addObject:newSocket]; - [newSocket readDataWithTimeout:-1 tag:0]; -} - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag -{ - [sock writeData:data withTimeout:-1 tag:0]; - [sock readDataWithTimeout:-1 tag:0]; -} - -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err -{ - [_clients removeObject:sock]; -} - -- (NSInteger)clientsCount -{ - return [_clients count]; -} - -@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.m deleted file mode 100644 index 4d1989bbaf8ab5..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.m +++ /dev/null @@ -1,51 +0,0 @@ -/** - * 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. - */ - -#import "PFSimpleHTTPServer.h" - -@implementation PFSimpleHTTPServer -{ - NSData *_response; - GCDAsyncSocket *_server; - NSMutableArray *_clients; -} - -- (instancetype)initWithPort:(NSUInteger)port response:(NSData *)data -{ - if (self = [super init]) { - _response = [data copy]; - _clients = [NSMutableArray array]; - _server = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; - if (![_server acceptOnPort:port error:nil]) { - return nil; - }; - } - return self; -} - -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket -{ - [_clients addObject:newSocket]; - [newSocket readDataToData:[NSData dataWithBytes:"\r\n\r\n" length:4] withTimeout:-1 tag:0]; -} - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag -{ - NSString *headers = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n\r\n", (unsigned long)[_response length]]; - [sock writeData:[headers dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; - [sock writeData:_response withTimeout:-1 tag:0]; - [sock disconnectAfterWriting]; -} - -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err -{ - [_clients removeObject:sock]; -} - -@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFTests.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFTests.m deleted file mode 100644 index b7bfd0f7dcea95..00000000000000 --- a/Tools/FBPortForwarding/FBPortForwardingTests/PFTests.m +++ /dev/null @@ -1,217 +0,0 @@ -/** - * 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. - */ - -#import <UIKit/UIKit.h> -#import <XCTest/XCTest.h> - -#import <FBPortForwarding-iOS/FBPortForwardingClient.h> -#import <FBPortForwarding-iOS/FBPortForwardingServer.h> - -#import <FBTest/FBTestRunLoopRunning.h> - -#import "PFPingClient.h" -#import "PFPingServer.h" -#import "PFSimpleHTTPServer.h" - -#define PFWaitForPongNumber(n) \ - XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ \ - return [client1.pongs count] == n; \ - }), @"Failed to receive pong"); - -@interface PFTests : XCTestCase <GCDAsyncSocketDelegate> - -@end - -@implementation PFTests - -- (void)simpleHTTPTestWithData:(NSData *)data -{ - FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; - [portForwardingServer forwardConnectionsFromPort:9701]; - [portForwardingServer listenForMultiplexingChannelOnPort:8055]; - - FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; - [portForwardingClient forwardConnectionsToPort:9702]; - [portForwardingClient connectToMultiplexingChannelOnPort:8055]; - - PFSimpleHTTPServer *httpServer = [[PFSimpleHTTPServer alloc] initWithPort:9702 response:data]; - XCTAssertNotNil(httpServer); - - __block BOOL finished = NO; - NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:9701/"]]; - [NSURLConnection sendAsynchronousRequest:request - queue:[NSOperationQueue mainQueue] - completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *connectionError) { - XCTAssertNil(connectionError); - XCTAssertTrue([data isEqualToData:responseData]); - finished = YES; - }]; - - XCTAssert(FBRunRunLoopWithConditionReturningPassed(&finished)); - [portForwardingServer close]; - FBRunRunLoopBarrier(); -} - -- (void)testProxiesHTTPRequests -{ - [self simpleHTTPTestWithData:[@"OK" dataUsingEncoding:NSUTF8StringEncoding]]; -} - -- (void)testLargeHTTPResponse -{ - NSMutableData *largePayload = [NSMutableData data]; - [largePayload setLength:10000000]; - char *bytes = [largePayload mutableBytes]; - for (NSUInteger i = 0; i < largePayload.length; i++) { - bytes[i] = (char)(i % 255); - } - - [self simpleHTTPTestWithData:largePayload]; -} - -- (void)testPingPong -{ - FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; - [portForwardingServer forwardConnectionsFromPort:9706]; - [portForwardingServer listenForMultiplexingChannelOnPort:8055]; - - FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; - [portForwardingClient forwardConnectionsToPort:9705]; - [portForwardingClient connectToMultiplexingChannelOnPort:8055]; - - PFPingServer *server = [[PFPingServer alloc] initWithPort:9705]; - XCTAssertNotNil(server); - - PFPingClient *client1 = [[PFPingClient alloc] init]; - [client1 connectToLocalServerOnPort:9706]; - - PFPingClient *client2 = [[PFPingClient alloc] init]; - [client2 connectToLocalServerOnPort:9706]; - - XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ - return client1.connected && client2.connected; - }), @"Failed to connect"); - - NSData *ping = [NSData dataWithBytes:"PING" length:4]; - [client1 sendPing:ping]; - - PFWaitForPongNumber(1); - - NSData *pong = client1.pongs[0]; - XCTAssert([ping isEqualToData:pong]); - - [client1 sendPing:ping]; - PFWaitForPongNumber(2); - - [client1 sendPing:ping]; - PFWaitForPongNumber(3); - - [client1 sendPing:ping]; - PFWaitForPongNumber(4); - - [client1 sendPing:ping]; - [client1 sendPing:ping]; - PFWaitForPongNumber(6); - - XCTAssertEqual(0, client2.pongs.count); - - [portForwardingServer close]; -} - -- (void)testDisconnectsWhenNoChannel -{ - FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; - [portForwardingServer forwardConnectionsFromPort:9707]; - [portForwardingServer listenForMultiplexingChannelOnPort:8056]; - - __block BOOL finished = NO; - NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:9707/"]]; - [NSURLConnection sendAsynchronousRequest:request - queue:[NSOperationQueue mainQueue] - completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *connectionError) { - XCTAssertNotNil(connectionError); - finished = YES; - }]; - - NSDate *start = [NSDate date]; - XCTAssert(FBRunRunLoopWithConditionReturningPassed(&finished)); - NSTimeInterval elapsed = -[start timeIntervalSinceNow]; - XCTAssertLessThan(elapsed, 5, @"Must disconnect - no port forwarding client"); - - [portForwardingServer close]; -} - -- (void)testWaitsForChannel -{ - FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; - [portForwardingServer forwardConnectionsFromPort:9707]; - [portForwardingServer listenForMultiplexingChannelOnPort:8056]; - - NSData *data = [@"OK" dataUsingEncoding:NSUTF8StringEncoding]; - PFSimpleHTTPServer *httpServer = [[PFSimpleHTTPServer alloc] initWithPort:9702 response:data]; - XCTAssertNotNil(httpServer); - - __block BOOL finished = NO; - NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:9707/"]]; - [NSURLConnection sendAsynchronousRequest:request - queue:[NSOperationQueue mainQueue] - completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *connectionError) { - XCTAssertNil(connectionError); - XCTAssertNotNil(responseData); - NSString *res = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; - XCTAssertEqualObjects(res, @"OK"); - finished = YES; - }]; - - NSDate *start = [NSDate date]; - FBRunRunLoopUntilBlockIsTrue(^BOOL{ - return [start timeIntervalSinceNow] < -0.5; - }); - - // NOTE: Establishing port forwarding connection *after* sending HTTP request - FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; - [portForwardingClient forwardConnectionsToPort:9702]; - [portForwardingClient connectToMultiplexingChannelOnPort:8056]; - - XCTAssert(FBRunRunLoopWithConditionReturningPassed(&finished)); - [portForwardingServer close]; -} - -- (void)testDisconnectsWhenChannelConnectionLost -{ - FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; - [portForwardingServer forwardConnectionsFromPort:9706]; - [portForwardingServer listenForMultiplexingChannelOnPort:8055]; - - FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; - [portForwardingClient forwardConnectionsToPort:9705]; - [portForwardingClient connectToMultiplexingChannelOnPort:8055]; - - PFPingServer *server = [[PFPingServer alloc] initWithPort:9705]; - XCTAssertNotNil(server); - - PFPingClient *client1 = [[PFPingClient alloc] init]; - [client1 connectToLocalServerOnPort:9706]; - - XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ - return client1.connected; - }), @"Failed to connect"); - - [portForwardingClient close]; - - XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ - return !client1.connected; - }), @"Failed to disconnect"); - - XCTAssertEqual(server.clientsCount, 0); - - [portForwardingServer close]; -} - -@end diff --git a/Tools/FBPortForwarding/README.md b/Tools/FBPortForwarding/README.md deleted file mode 100644 index b67db7a06412d5..00000000000000 --- a/Tools/FBPortForwarding/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# FBPortForwarding - -FBPortForwarding lets you expose your Mac's port to iOS device via lightning -cable. The typical usecase is connecting to a TCP server that runs on OS X -from an iPhone app without common WiFi network. - -## Benefits: - - 1. No need to be on the same WiFi, worry about firewalls (fbguest) or VPN - 2. iOS app doesn't have to know your Mac's IP address - 3. Secure - communication is possible only when connected via USB - -## How it works - -iOS provides a way to connect to device's TCP server from Mac via USBHub, but -there is no API to connect from iOS to TCP server running on Mac. FBPortForwarding -uses [Peertalk](https://github.com/rsms/peertalk) to establish communication -channel from Mac to iOS, creates a TCP server on iOS and multiplexes all -connections to that server via the peertalk channel. Helper app running on Mac -listens for commands on the peertalk channel and initializes TCP connections -to local port and forwards all communication back via the same peertalk channel. - - - | - iOS Device | Mac - | - +----------------+ +----------------+ - |Peertalk Server | connect |Peertalk Client | - | <------------+ | - | | | | - | Port 8025| | | - +----+-----------+ +---------^------+ - | | - | | - incoming +----------------+ | | +--------------+ - connections |Proxy Server | | | |Real Server | - ------------->> | | +-------------+ commands | | | - | Port 8081| | create | | stream | | Port 8081| - +-+--------------+ +---------> Peertalk <----------+ +-^------------+ - | | Channel | ^ - | +--------+ | | +--------+ | outgoing - | | | onConnect | | connect | | | connections - +---> Client +---------------> OpenPipe +---------------> Client +-----+ - | #[tag] | onRead | | write | #[tag] | - | +---------------> WriteToPipe +---------------> | - | | onDisconnect | | disconnect | | - | +---------------> ClosePipe +---------------> | - | | | | | | - | | write | | onRead | | - | <---------------+ WriteToPipe <---------------+ | - | | close | | onDisconnect | | - | <---------------+ ClosePipe <---------------+ | - | | | | | | - +--------+ | | +--------+ - +-------------+ - -First, the library on iOS device creates a TCP server on the port we want to -forward (let's say 8081) and a special Peertalk server on port 8025. Mac helper -app looks for connected iOS devices, and once it finds one it connects to its -peertalk server. Only *one* channel is created that's going to be used for -multiplexing data. - -When a socket connects to local proxy server, FBPortForwarding is going to assign -a tag to the connection and use peertalk channel to tell Mac helper app to connect -to TCP port 8081 on Mac. Now events and data on both sides of the wire are going -to be multiplexed and transferred via the peertalk channel. diff --git a/blog/2016-03-24-introducing-hot-reloading.md b/blog/2016-03-24-introducing-hot-reloading.md index fe59f919e348b1..f9121d48f6aa31 100644 --- a/blog/2016-03-24-introducing-hot-reloading.md +++ b/blog/2016-03-24-introducing-hot-reloading.md @@ -1,6 +1,7 @@ --- title: Introducing Hot Reloading author: MartĆn Bigio +authorURL: https://twitter.com/martinbigio --- React Native's goal is to give you the best possible developer experience. A big part of it is the time it takes between you save a file and be able to see the changes. Our goal is to get this feedback loop to be under 1 second, even as your app grows. diff --git a/blog/2016-07-06-toward-better-documentation.md b/blog/2016-07-06-toward-better-documentation.md index fd3e9879c44690..80dc604c5ef794 100644 --- a/blog/2016-07-06-toward-better-documentation.md +++ b/blog/2016-07-06-toward-better-documentation.md @@ -1,6 +1,7 @@ --- title: Toward Better Documentation author: Kevin Lacker +authorURL: https://twitter.com/lacker --- Part of having a great developer experience is having great documentation. A lot goes into creating good docs - the ideal documentation is concise, helpful, accurate, complete, and delightful. Recently we've been working hard to make the docs better based on your feedback, and we wanted to share some of the improvements we've made. @@ -33,7 +34,7 @@ AppRegistry.registerComponent('ScratchPad', () => ScratchPad); We think these inline examples, using the [`react-native-web-player`](https://github.com/dabbott/react-native-web-player) module with help from [Devin Abbott](https://twitter.com/devinaabbott), are a great way to learn the basics of React Native, and we have updated our [tutorial for new React Native developers](/react-native/docs/tutorial.html) to use these wherever possible. Check it out - if you have ever been curious to see what would happen if you modified just one little bit of sample code, this is a really nice way to poke around. Also, if you're building developer tools and you want to show a live React Native sample on your own site, [`react-native-web-player`](https://github.com/dabbott/react-native-web-player) can make that straightforward. -The core simulation engine is provided by the [react-native-web](https://github.com/necolas/react-native-web) library, which provides a way to display React Native components like `Text` and `View` on the web. Check out [react-native-web](https://github.com/necolas/react-native-web) if you're interested in building mobile and web experiences that share a large chunk of the codebase. +The core simulation engine is provided by [Nicolas Gallagher](https://twitter.com/necolas)'s [`react-native-web`](https://github.com/necolas/react-native-web) project, which provides a way to display React Native components like `Text` and `View` on the web. Check out [`react-native-web`](https://github.com/necolas/react-native-web) if you're interested in building mobile and web experiences that share a large chunk of the codebase. ## Better Guides diff --git a/docs/Accessibility.md b/docs/Accessibility.md index a1807b10842af7..2761176943dc26 100644 --- a/docs/Accessibility.md +++ b/docs/Accessibility.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/accessibility.html next: timers +previous: animations --- ## Native App Accessibility (iOS and Android) diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md index 9c4cdb916fbb7a..f583faaba8a2b8 100644 --- a/docs/AndroidBuildingFromSource.md +++ b/docs/AndroidBuildingFromSource.md @@ -5,6 +5,7 @@ layout: docs category: Guides (Android) permalink: docs/android-building-from-source.html next: activityindicator +previous: android-ui-performance --- You will need to build React Native from source if you want to work on a new feature/bug fix, try out the latest features which are not released yet, or maintain your own fork with patches that cannot be merged to the core. diff --git a/docs/AndroidUIPerformance.md b/docs/AndroidUIPerformance.md index 7c6f9ec699dafc..1593602470ac29 100644 --- a/docs/AndroidUIPerformance.md +++ b/docs/AndroidUIPerformance.md @@ -5,6 +5,7 @@ layout: docs category: Guides (Android) permalink: docs/android-ui-performance.html next: android-building-from-source +previous: signed-apk-android --- We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible. Remember, Android supports 10k+ different phones and is generalized to support software rendering: the framework architecture and need to generalize across many hardware targets unfortunately means you get less for free relative to iOS. But sometimes, there are things you can improve (and many times it's not native code's fault at all!). diff --git a/docs/Animations.md b/docs/Animations.md index 19629b6cb8102f..546e8b9781dd42 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/animations.html next: accessibility +previous: handling-touches --- Fluid, meaningful animations are essential to the mobile user experience. Like @@ -282,7 +283,7 @@ class App extends React.Component { this.state = { w: 100, h: 100 }; this._onPress = this._onPress.bind(this); } - + componentWillMount() { // Animate creation LayoutAnimation.spring(); diff --git a/docs/Colors.md b/docs/Colors.md index 9d863adfaac170..d7831eef1d5fed 100644 --- a/docs/Colors.md +++ b/docs/Colors.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/colors.html next: images +previous: integration-with-existing-apps --- The following formats are supported: diff --git a/docs/CommunicationIOS.md b/docs/CommunicationIOS.md index 48cc5d978c9436..c34eece8dd54e1 100644 --- a/docs/CommunicationIOS.md +++ b/docs/CommunicationIOS.md @@ -5,6 +5,7 @@ layout: docs category: Guides (iOS) permalink: docs/communication-ios.html next: native-modules-android +previous: running-on-simulator-ios --- In [Integrating with Existing Apps guide](docs/embedded-app-ios.html) and [Native UI Components guide](docs/native-components-ios.html) we learn how to embed React Native in a native component and vice versa. When we mix native and React Native components, we'll eventually find a need to communicate between these two worlds. Some ways to achieve that have been already mentioned in other guides. This article summarizes available techniques. diff --git a/docs/Debugging.md b/docs/Debugging.md index 55a940f0dcb015..9e902770236cd8 100644 --- a/docs/Debugging.md +++ b/docs/Debugging.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/debugging.html next: testing +previous: direct-manipulation --- ## Accessing the In-App Developer Menu @@ -83,7 +84,6 @@ Alternatively, select "Dev Settings" from the Developer Menu, then update the "D > If you run into any issues, it may be possible that one of your Chrome extensions is interacting in unexpected ways with the debugger. Try disabling all of your extensions and re-enabling them one-by-one until you find the problematic extension. - ### Debugging using a custom JavaScript debugger To use a custom JavaScript debugger in place of Chrome Developer Tools, set the `REACT_DEBUGGER` environment variable to a command that will start your custom debugger. You can then select "Debug JS Remotely" from the Developer Menu to start debugging. @@ -92,6 +92,46 @@ The debugger will receive a list of all project roots, separated by a space. For > Custom debugger commands executed this way should be short-lived processes, and they shouldn't produce more than 200 kilobytes of output. +### Debugging with [Stetho](http://facebook.github.io/stetho/) on Android + +1. In ```android/app/build.gradle``` , add + + ```gradle + compile 'com.facebook.stetho:stetho:1.3.1' + compile 'com.facebook.stetho:stetho-okhttp3:1.3.1' + ``` + +2. In ```android/app/src/main/java/com/{yourAppName}/MainApplication.java```, add the following imports : + + ```java + import com.facebook.react.modules.network.ReactCookieJarContainer; + import com.facebook.stetho.Stetho; + import okhttp3.OkHttpClient; + import com.facebook.react.modules.network.OkHttpClientProvider; + import com.facebook.stetho.okhttp3.StethoInterceptor; + import java.util.concurrent.TimeUnit; + ``` + +3. In ```android/app/src/main/java/com/{yourAppName}/MainApplication.java``` add the function: + ```java + public void onCreate() { + super.onCreate(); + Stetho.initializeWithDefaults(this); + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(0, TimeUnit.MILLISECONDS) + .readTimeout(0, TimeUnit.MILLISECONDS) + .writeTimeout(0, TimeUnit.MILLISECONDS) + .cookieJar(new ReactCookieJarContainer()) + .addNetworkInterceptor(new StethoInterceptor()) + .build(); + OkHttpClientProvider.replaceOkHttpClient(client); + } + ``` + +4. Run ```react-native run-android ``` + +5. In a new chrome tab, open : ```chrome://inspect```, click on 'Inspect device' (the one followed by "Powered by Stetho") + ## Performance Monitor You can enable a performance overlay to help you debug performance problems by selecting "Perf Monitor" in the Developer Menu. diff --git a/docs/DirectManipulation.md b/docs/DirectManipulation.md index 1d4f6f0416ca87..7cc795a3efdfd3 100644 --- a/docs/DirectManipulation.md +++ b/docs/DirectManipulation.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/direct-manipulation.html next: debugging +previous: timers --- It is sometimes necessary to make changes directly to a component @@ -82,11 +83,11 @@ performing continuous animations and responding to gestures, judiciously optimizing your components can improve your animations' fidelity. If you look at the implementation of `setNativeProps` in -[NativeMethodsMixin.js](https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/NativeMethodsMixin.js) +[NativeMethodsMixin.js](https://github.com/facebook/react/blob/master/src/renderers/native/NativeMethodsMixin.js) you will notice that it is a wrapper around `RCTUIManager.updateView` - this is the exact same function call that results from re-rendering - see [receiveComponent in -ReactNativeBaseComponent.js](https://github.com/facebook/react-native/blob/master/Libraries/ReactNative/ReactNativeBaseComponent.js). +ReactNativeBaseComponent.js](https://github.com/facebook/react/blob/master/src/renderers/native/ReactNativeBaseComponent.js). ## Composite components and setNativeProps diff --git a/docs/GestureResponderSystem.md b/docs/GestureResponderSystem.md index 7a61929538d6ba..08bf4b2700475b 100644 --- a/docs/GestureResponderSystem.md +++ b/docs/GestureResponderSystem.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/gesture-responder-system.html next: native-modules-ios +previous: platform-specific-code --- Gesture recognition on mobile devices is much more complicated than web. A touch can go through several phases as the app determines what the user's intention is. For example, the app needs to determine if the touch is scrolling, sliding on a widget, or tapping. This can even change during the duration of a touch. There can also be multiple simultaneous touches. diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 2532eff21a6734..da9a8645ba5b74 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -93,6 +93,8 @@ npm install -g react-native-cli If you get a permission error, try with sudo: `sudo npm install -g react-native-cli`. +If you get error `Cannot find module 'npmlog'`, try this before: `curl -0 -L http://npmjs.org/install.sh | sudo sh`. + <block class="mac ios" /> The easiest way to install Xcode is via the [Mac App Store](https://itunes.apple.com/us/app/xcode/id497799835?mt=12). diff --git a/docs/HandlingTextInput.md b/docs/HandlingTextInput.md index 76ad648c53f1da..09d428ece3b8aa 100644 --- a/docs/HandlingTextInput.md +++ b/docs/HandlingTextInput.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/handling-text-input.html next: using-a-scrollview +previous: flexbox --- [`TextInput`](/react-native/docs/textinput.html#content) is a basic component that allows the user to enter text. It has an `onChangeText` prop that takes diff --git a/docs/HandlingTouches.md b/docs/HandlingTouches.md index b042eda911bdc2..d351f69117ffb9 100644 --- a/docs/HandlingTouches.md +++ b/docs/HandlingTouches.md @@ -5,11 +5,12 @@ layout: docs category: Guides permalink: docs/handling-touches.html next: animations +previous: images --- Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a list, or zooming on a map. -React Native provides components to handle common gestures, such as taps and swipes, as well as a comprehensive [gesture responder system](/docs/gesturerespondersystem.html) to allow for more advanced gesture recognition. +React Native provides components to handle common gestures, such as taps and swipes, as well as a comprehensive [gesture responder system](/react-native/docs/gesture-responder-system.html) to allow for more advanced gesture recognition. ## Tappable Components @@ -37,13 +38,13 @@ Tappable components should provide feedback that show the user what is handling Which component you use will depend on what kind of feedback you want to provide: -- Generally, you can use [**TouchableHighlight**](/docs/touchablehighlight.html) anywhere you would use a button or link on web. The view's background will be darkened when the user presses down on the button. +- Generally, you can use [**TouchableHighlight**](/react-native/docs/touchablehighlight.html) anywhere you would use a button or link on web. The view's background will be darkened when the user presses down on the button. -- You may consider using [**TouchableNativeFeedback**](/docs/touchablenativefeedback.html) on Android to display ink surface reaction ripples that respond to the user's touch. +- You may consider using [**TouchableNativeFeedback**](/react-native/docs/touchablenativefeedback.html) on Android to display ink surface reaction ripples that respond to the user's touch. -- [**TouchableOpacity**](/docs/touchableopacity.html) can be used to provide feedback by reducing the opacity of the button, allowing the background to be seen through while the user is pressing down. +- [**TouchableOpacity**](/react-native/docs/touchableopacity.html) can be used to provide feedback by reducing the opacity of the button, allowing the background to be seen through while the user is pressing down. -- If you need to handle a tap gesture but you don't want any feedback to be displayed, use [**TouchableWithoutFeedback**](/docs/touchablewithoutfeedback.html). +- If you need to handle a tap gesture but you don't want any feedback to be displayed, use [**TouchableWithoutFeedback**](/react-native/docs/touchablewithoutfeedback.html). ### Long presses @@ -51,11 +52,11 @@ In some cases, you may want to detect when a user presses and holds a view for a ## Scrolling lists and swiping views -A common pattern to many mobile apps is the scrollable list of items. Users interact with these using panning or swiping gestures. The [ScrollView](/docs/basics-component-scrollview.html) component displays a list of items that can be scrolled using these gestures. +A common pattern to many mobile apps is the scrollable list of items. Users interact with these using panning or swiping gestures. The [ScrollView](/react-native/docs/using-a-scrollview.html) component displays a list of items that can be scrolled using these gestures. -ScrollViews can scroll vertically or horizontally, and can be configured to allow paging through views using swiping gestures by using the `pagingEnabled` props. Swiping horizontally between views can also be implemented on Android using the [ViewPagerAndroid](/docs/viewpagerandroid.html) component. +ScrollViews can scroll vertically or horizontally, and can be configured to allow paging through views using swiping gestures by using the `pagingEnabled` props. Swiping horizontally between views can also be implemented on Android using the [ViewPagerAndroid](/react-native/docs/viewpagerandroid.html) component. -A [ListView](/docs/basics-component-listview.html) is a special kind of ScrollView that is best suited for displaying long vertical lists of items. It can also display section headers and footers, similar to `UITableView`s on iOS. +A [ListView](/react-native/docs/using-a-listview.html) is a special kind of ScrollView that is best suited for displaying long vertical lists of items. It can also display section headers and footers, similar to `UITableView`s on iOS. ### Pinch-to-zoom @@ -63,4 +64,4 @@ A ScrollView with a single item can be used to allow the user to zoom content. S ## Handling additional gestures -If you want to allow a user to drag a view around the screen, or you want to implement your own custom pan/drag gesture, take a look at the [PanResponder](/docs/panresponder.html) API or the [gesture responder system docs](/docs/gesturerespondersystem.html). +If you want to allow a user to drag a view around the screen, or you want to implement your own custom pan/drag gesture, take a look at the [PanResponder](/react-native/docs/panresponder.html) API or the [gesture responder system docs](/react-native/docs/gesture-responder-system.html). diff --git a/docs/HeightAndWidth.md b/docs/HeightAndWidth.md index 228b686c36289b..0764c4a0c71d2b 100644 --- a/docs/HeightAndWidth.md +++ b/docs/HeightAndWidth.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/height-and-width.html next: flexbox +previous: style --- A component's height and width determine its size on the screen. diff --git a/docs/Images.md b/docs/Images.md index afdc8854c4b7dc..5154174e23aff1 100644 --- a/docs/Images.md +++ b/docs/Images.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/images.html next: handling-touches +previous: colors --- ## Static Image Resources diff --git a/docs/IntegrationWithExistingApps.md b/docs/IntegrationWithExistingApps.md index 05d95838e41dc8..a4e48997441457 100644 --- a/docs/IntegrationWithExistingApps.md +++ b/docs/IntegrationWithExistingApps.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/integration-with-existing-apps.html next: colors +previous: more-resources --- <div class="integration-toggler"> @@ -61,7 +62,7 @@ The keys to integrating React Native components into your iOS application are to <block class="android" /> -The keys to integrating React Native components into your iOS application are to: +The keys to integrating React Native components into your Android application are to: 1. Understand what React Native components you want to integrate. 2. Install `react-native` in your Android application root directory to create `node_modules/` directory. @@ -347,7 +348,7 @@ AppRegistry.registerComponent('RNHighScores', () => RNHighScores); ## The Magic: `RCTRootView` -Now that your React Native component is created via `index.ios.js`, you need to add that component to a new or existing `ViewController`. The easiest path is to take is to optionally create an event path to your component and then add that component to an existing `ViewController`. +Now that your React Native component is created via `index.ios.js`, you need to add that component to a new or existing `ViewController`. The easiest path to take is to optionally create an event path to your component and then add that component to an existing `ViewController`. We will tie our React Native component with a new native view in the `ViewController` that will actually host it called `RCTRootView` . diff --git a/docs/JavaScriptEnvironment.md b/docs/JavaScriptEnvironment.md index 857a3fd71a7de9..e9bce9e892ede8 100644 --- a/docs/JavaScriptEnvironment.md +++ b/docs/JavaScriptEnvironment.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/javascript-environment.html next: navigation +previous: testing --- ## JavaScript Runtime diff --git a/docs/LayoutWithFlexbox.md b/docs/LayoutWithFlexbox.md index 39d7730845b2e5..d484d78085b7f2 100644 --- a/docs/LayoutWithFlexbox.md +++ b/docs/LayoutWithFlexbox.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/flexbox.html next: handling-text-input +previous: height-and-width --- A component can specify the layout of its children using the flexbox algorithm. Flexbox is designed to provide a consistent layout on different screen sizes. @@ -76,7 +77,7 @@ Adding `alignItems` to a component's style determines the **alignment** of child import React, { Component } from 'react'; import { AppRegistry, View } from 'react-native'; -class AlignItemsBasics { +class AlignItemsBasics extends Component { render() { return ( // Try setting `alignItems` to 'flex-start' diff --git a/docs/LinkingLibraries.md b/docs/LinkingLibraries.md index b32c9bbc85a2b1..8b86331d0caa61 100644 --- a/docs/LinkingLibraries.md +++ b/docs/LinkingLibraries.md @@ -5,6 +5,7 @@ layout: docs category: Guides (iOS) permalink: docs/linking-libraries-ios.html next: running-on-device-ios +previous: native-components-ios --- Not every app uses all the native capabilities, and including the code to support diff --git a/docs/MoreResources.md b/docs/MoreResources.md index 453ea062e6b9a7..700e29f24dc93f 100644 --- a/docs/MoreResources.md +++ b/docs/MoreResources.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/more-resources.html next: integration-with-existing-apps +previous: using-navigators --- If you just read through this website, you should be able to build a pretty cool React Native app. But React Native isn't just a product made by one company - it's a community of thousands of developers. So if you're interested in React Native, here's some related stuff you might want to check out. diff --git a/docs/NativeComponentsAndroid.md b/docs/NativeComponentsAndroid.md index 1471218241631f..03d1cb66f06f26 100644 --- a/docs/NativeComponentsAndroid.md +++ b/docs/NativeComponentsAndroid.md @@ -5,6 +5,7 @@ layout: docs category: Guides (Android) permalink: docs/native-components-android.html next: running-on-device-android +previous: native-modules-android --- There are tons of native UI widgets out there ready to be used in the latest apps - some of them are part of the platform, others are available as third-party libraries, and still more might be in use in your very own portfolio. React Native has several of the most critical platform components already wrapped, like `ScrollView` and `TextInput`, but not all of them, and certainly not ones you might have written yourself for a previous app. Fortunately, it's quite easy to wrap up these existing components for seamless integration with your React Native application. diff --git a/docs/NativeComponentsIOS.md b/docs/NativeComponentsIOS.md index 49ef64e409524a..afd2febf3c378e 100644 --- a/docs/NativeComponentsIOS.md +++ b/docs/NativeComponentsIOS.md @@ -5,6 +5,7 @@ layout: docs category: Guides (iOS) permalink: docs/native-components-ios.html next: linking-libraries-ios +previous: native-modules-ios --- There are tons of native UI widgets out there ready to be used in the latest apps - some of them are part of the platform, others are available as third-party libraries, and still more might be in use in your very own portfolio. React Native has several of the most critical platform components already wrapped, like `ScrollView` and `TextInput`, but not all of them, and certainly not ones you might have written yourself for a previous app. Fortunately, it's quite easy to wrap up these existing components for seamless integration with your React Native application. diff --git a/docs/NativeModulesAndroid.md b/docs/NativeModulesAndroid.md index fcdd7902c0a520..54fefddf1a3086 100644 --- a/docs/NativeModulesAndroid.md +++ b/docs/NativeModulesAndroid.md @@ -5,6 +5,7 @@ layout: docs category: Guides (Android) permalink: docs/native-modules-android.html next: native-components-android +previous: communication-ios --- Sometimes an app needs access to a platform API that React Native doesn't have a corresponding module for yet. Maybe you want to reuse some existing Java code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions. @@ -116,7 +117,7 @@ class AnExampleReactPackage implements ReactPackage { } ``` -The package needs to be provided in the `getPackages` method of the `MainActivity.java` file. This file exists under the android folder in your react-native application directory. The path to this file is: `android/app/src/main/java/com/your-app-name/MainActivity.java`. +The package needs to be provided in the `getPackages` method of the `MainApplication.java` file. This file exists under the android folder in your react-native application directory. The path to this file is: `android/app/src/main/java/com/your-app-name/MainApplication.java`. ```java protected List<ReactPackage> getPackages() { @@ -420,16 +421,16 @@ Now you can listen to the activity's LifeCycle events by implementing the follow ```java @Override public void onHostResume() { - // Actvity `onResume` + // Activity `onResume` } @Override public void onHostPause() { - // Actvity `onPause` + // Activity `onPause` } @Override public void onHostDestroy() { - // Actvity `onDestroy` + // Activity `onDestroy` } ``` diff --git a/docs/NativeModulesIOS.md b/docs/NativeModulesIOS.md index a6739549df24c1..b769cfd4e340e8 100644 --- a/docs/NativeModulesIOS.md +++ b/docs/NativeModulesIOS.md @@ -5,6 +5,7 @@ layout: docs category: Guides (iOS) permalink: docs/native-modules-ios.html next: native-components-ios +previous: gesture-responder-system --- Sometimes an app needs access to platform API, and React Native doesn't have a corresponding module yet. Maybe you want to reuse some existing Objective-C, Swift or C++ code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions. diff --git a/docs/Navigation.md b/docs/Navigation.md index 70d89ac42d17e6..b2ce57d5594516 100644 --- a/docs/Navigation.md +++ b/docs/Navigation.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/navigation.html next: performance +previous: javascript-environment --- This guide covers the various navigation components available in React Native. If you are just getting started with navigation, you will probably want to use `Navigator`. If you are only targeting iOS and would like to stick to the native look and feel, check out `NavigatorIOS`. If you are looking for greater control over your navigation stack, you can't go wrong with `NavigationExperimental`. @@ -341,4 +342,4 @@ const styles = StyleSheet.create({ ### Homework -You are now an expert navigator. Take a look at [NavigationExperimental in UIExplorer](https://github.com/facebook/react-native/tree/master/Examples/UIExplorer/NavigationExperimental) to learn how to implement other types of navigation hierarchies, such as a tabbed application with multiple navigation stacks. +You are now an expert navigator. Take a look at [NavigationExperimental in UIExplorer](https://github.com/facebook/react-native/tree/master/Examples/UIExplorer/js/NavigationExperimental) to learn how to implement other types of navigation hierarchies, such as a tabbed application with multiple navigation stacks. diff --git a/docs/Networking.md b/docs/Networking.md index 23a82897788f4e..d62b87a6752437 100644 --- a/docs/Networking.md +++ b/docs/Networking.md @@ -4,7 +4,8 @@ title: Networking layout: docs category: The Basics permalink: docs/network.html -next: navigators +next: using-navigators +previous: using-a-listview --- Many mobile apps need to load resources from a remote URL. You may want to make a POST request to a REST API, or you may simply need to fetch a chunk of static content from another server. @@ -58,7 +59,7 @@ Networking is an inherently asynchronous operation. Fetch methods will return a } ``` -You can also use ES7's `async`/`await` syntax in React Native app: +You can also use the proposed ES2017 `async`/`await` syntax in a React Native app: ```js async getMoviesFromApi() { diff --git a/docs/Performance.md b/docs/Performance.md index cc6a9788b4e928..f22f240528f6a9 100644 --- a/docs/Performance.md +++ b/docs/Performance.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/performance.html next: upgrading +previous: navigation --- A compelling reason for using React Native instead of WebView-based @@ -81,7 +82,7 @@ but their receipt is not necessary for the scroll to occur). ### Common sources of performance problems -#### Console.log statements +#### Console.log statements When running a bundled app, these statements can cause a big bottleneck in the JavaScript thread. This includes calls from debugging libraries such as [redux-logger](https://github.com/evgenyrodionov/redux-logger), so make sure to remove them before bundling. diff --git a/docs/PlatformSpecificInformation.md b/docs/PlatformSpecificInformation.md index 0c9c5f3a6f0a4e..8dddb77d39044d 100644 --- a/docs/PlatformSpecificInformation.md +++ b/docs/PlatformSpecificInformation.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/platform-specific-code.html next: gesture-responder-system +previous: upgrading --- When building a cross-platform app, you'll want to re-use as much code as possible. Scenarios may arise where it makes sense for the code to be different, for example you may want to implement separate visual components for iOS and Android. diff --git a/docs/Props.md b/docs/Props.md index 7e5833d2bfefc5..3145fc0c5150dc 100644 --- a/docs/Props.md +++ b/docs/Props.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/props.html next: state +previous: tutorial --- Most components can be customized when they are created, with different parameters. These creation parameters are called `props`. diff --git a/docs/RunningOnDeviceAndroid.md b/docs/RunningOnDeviceAndroid.md index 0f3b89b0788014..f41b6d859b0b86 100644 --- a/docs/RunningOnDeviceAndroid.md +++ b/docs/RunningOnDeviceAndroid.md @@ -5,6 +5,7 @@ layout: docs category: Guides (Android) permalink: docs/running-on-device-android.html next: signed-apk-android +props: native-components-android --- ## Prerequisite: USB Debugging diff --git a/docs/RunningOnDeviceIOS.md b/docs/RunningOnDeviceIOS.md index 5a4af00eda5b5d..ee91cb3f4df5d1 100644 --- a/docs/RunningOnDeviceIOS.md +++ b/docs/RunningOnDeviceIOS.md @@ -5,6 +5,7 @@ layout: docs category: Guides (iOS) permalink: docs/running-on-device-ios.html next: running-on-simulator-ios +previous: linking-libraries-ios --- Running an iOS app on a device requires an [Apple Developer account](https://developer.apple.com/) and provisioning your iPhone. This guide covers only React Native specific topics. @@ -13,13 +14,11 @@ Running an iOS app on a device requires an [Apple Developer account](https://dev You can iterate quickly on device using the development server. First, ensure that you are on the same Wi-Fi network as your computer. -1. Open `ios/YourApp/AppDelegate.m` -2. Change the host in the URL from `localhost` to your laptop's IP address. On Mac, you can find the IP address in System Preferences / Network. -3. In Xcode, select your phone as build target and press "Build and run" +In Xcode, select your phone as build target and press "Build and run" > Hint > -> Shake the device to open the [developer menu](/docs/debugging.html#accessing-the-in-app-developer-menu). +> Shake the device to open the [developer menu](/react-native/docs/debugging.html#accessing-the-in-app-developer-menu). ## Building your app for production diff --git a/docs/RunningOnSimulatorIOS.md b/docs/RunningOnSimulatorIOS.md index 3746ddb5cd16be..8d97d2177d430c 100644 --- a/docs/RunningOnSimulatorIOS.md +++ b/docs/RunningOnSimulatorIOS.md @@ -5,6 +5,7 @@ layout: docs category: Guides (iOS) permalink: docs/running-on-simulator-ios.html next: communication-ios +previous: running-on-device-ios --- ## Starting the simulator diff --git a/docs/SignedAPKAndroid.md b/docs/SignedAPKAndroid.md index c271d4e7c9497c..6b17537c3bd190 100644 --- a/docs/SignedAPKAndroid.md +++ b/docs/SignedAPKAndroid.md @@ -5,6 +5,7 @@ layout: docs category: Guides (Android) permalink: docs/signed-apk-android.html next: android-ui-performance +previous: running-on-device-android --- Android requires that all apps be digitally signed with a certificate before they can be installed, so to distribute your Android application via [Google Play store](https://play.google.com/store), you'll need to generate a signed release APK. The [Signing Your Applications](https://developer.android.com/tools/publishing/app-signing.html) page on Android Developers documentation describes the topic in detail. This guide covers the process in brief, as well as lists the steps required to packaging the JavaScript bundle. diff --git a/docs/State.md b/docs/State.md index c162ed585e5b36..98459fce33ac5f 100644 --- a/docs/State.md +++ b/docs/State.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/state.html next: style +previous: props --- There are two types of data that control a component: `props` and `state`. `props` are set by the parent and they are fixed throughout the lifetime of a component. For data that is going to change, we have to use `state`. diff --git a/docs/Style.md b/docs/Style.md index 94ac2a8db4dd1d..eccb51bbb5bc28 100644 --- a/docs/Style.md +++ b/docs/Style.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/style.html next: height-and-width +previous: state --- With React Native, you don't use a special language or syntax for defining styles. You just style your application using JavaScript. All of the core components accept a prop named `style`. The style names and values usually match how CSS works on the web, except names are written like `backgroundColor` instead of like `background-color`. @@ -45,7 +46,7 @@ AppRegistry.registerComponent('LotsOfStyles', () => LotsOfStyles); ``` One common pattern is to make your component accept a `style` prop which in -turn is used to style subcomponents. You can use this to make styles "cascade" they way they do in CSS. +turn is used to style subcomponents. You can use this to make styles "cascade" the way they do in CSS. There are a lot more ways to customize text style. Check out the [Text component reference](/react-native/docs/text.html) for a complete list. diff --git a/docs/Testing.md b/docs/Testing.md index 30a5ff6e49ed86..a4c4ddf2680381 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/testing.html next: javascript-environment +previous: debugging --- ## Running Tests and Contributing diff --git a/docs/Timers.md b/docs/Timers.md index ad0fe0e06090d5..4aae9d9520d83e 100644 --- a/docs/Timers.md +++ b/docs/Timers.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/timers.html next: direct-manipulation +previous: accessibility --- Timers are an important part of an application and React Native implements the [browser timers](https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Timers). diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 5007fec876e649..0a1647f5813eaa 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/tutorial.html next: props +previous: getting-started --- React Native is like React, but it uses native components instead of web components as building blocks. So to understand the basic structure of a React Native app, you need to understand some of the basic React concepts, like JSX, components, `state`, and `props`. If you already know React, you still need to learn some React-Native-specific stuff, like the native components. This diff --git a/docs/Upgrading.md b/docs/Upgrading.md index 52865b00117dcd..c5caf428216096 100644 --- a/docs/Upgrading.md +++ b/docs/Upgrading.md @@ -5,6 +5,7 @@ layout: docs category: Guides permalink: docs/upgrading.html next: platform-specific-code +previous: performance --- Upgrading to new versions of React Native will give you access to more APIs, views, developer tools diff --git a/docs/UsingAListView.md b/docs/UsingAListView.md index 4db766ab46f888..b2c3541f972b1a 100644 --- a/docs/UsingAListView.md +++ b/docs/UsingAListView.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/using-a-listview.html next: network +previous: using-a-scrollview --- The `ListView` component displays a vertically scrolling list of changing, but similarly structured, data. diff --git a/docs/UsingAScrollView.md b/docs/UsingAScrollView.md index 8c038e9a1d86c6..48e8b03984d734 100644 --- a/docs/UsingAScrollView.md +++ b/docs/UsingAScrollView.md @@ -5,6 +5,7 @@ layout: docs category: The Basics permalink: docs/using-a-scrollview.html next: using-a-listview +previous: handling-text-input --- The [`ScrollView`](/react-native/docs/scrollview.html) is a generic scrolling container that can host multiple components and views. The scrollable items need not be homogenous, and you can scroll both vertically and horizontally (by setting the `horizontal` property). diff --git a/docs/Navigators.md b/docs/UsingNavigators.md similarity index 98% rename from docs/Navigators.md rename to docs/UsingNavigators.md index eb389f87d80f0a..5aee3bb6811dac 100644 --- a/docs/Navigators.md +++ b/docs/UsingNavigators.md @@ -1,10 +1,11 @@ --- -id: navigators +id: using-navigators title: Using Navigators layout: docs category: The Basics permalink: docs/using-navigators.html next: more-resources +previous: networking --- Mobile apps rarely consist of just one screen. As soon as you add a second screen to your app, you will have to take into consideration how the user will navigate from one screen to the other. @@ -50,7 +51,7 @@ Notice the `export default` in front of the component declaration. This will _ex ```javascript import React, { Component } from 'react'; -import { View, Text } from 'react-native'; +import { AppRegistry } from 'react-native'; import MyScene from './MyScene'; diff --git a/fbkpm.lock b/fbkpm.lock deleted file mode 100644 index 2f21d1016aae2d..00000000000000 --- a/fbkpm.lock +++ /dev/null @@ -1,6448 +0,0 @@ -lodash._isiterateecall@^3.0.0: - name lodash._isiterateecall - version "3.0.9" - resolved lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c -abab@^1.0.0: - name abab - version "1.0.3" - resolved abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d -abbrev@1.0.x abbrev@1 -absolute-path@^0.0.0: - name absolute-path - version "0.0.0" - resolved absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7 -accepts@~1.2.12: - name accepts - version "1.2.13" - resolved accepts-1.2.13.tgz#e5f1f3928c6d95fd96558c36ec3d9d0de4a6ecea - dependencies: - mime-types "~2.1.6" - negotiator "0.5.3" -accepts@~1.2.13: - name accepts - version "1.2.13" - resolved accepts-1.2.13.tgz#e5f1f3928c6d95fd96558c36ec3d9d0de4a6ecea - dependencies: - mime-types "~2.1.6" - negotiator "0.5.3" -accepts@~1.3.0: - name accepts - version "1.3.3" - resolved accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca - dependencies: - mime-types "~2.1.11" - negotiator "0.6.1" -acorn-globals@^1.0.4: - name acorn-globals - version "1.0.9" - resolved acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf - dependencies: - acorn "^2.1.0" -acorn-jsx@^3.0.0: - name acorn-jsx - version "3.0.1" - resolved acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b - dependencies: - acorn "^3.0.4" -acorn@^1.0.3: - name acorn - version "1.2.2" - resolved acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014 -acorn@^2.1.0 acorn@^2.4.0 -acorn@^2.4.0: - name acorn - version "2.7.0" - resolved acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7 -acorn@^3.0.4: - name acorn - version "3.2.0" - resolved acorn-3.2.0.tgz#7a82989ef6f063a237ababaf8df20d2965184b9f -acorn@^3.2.0: - name acorn - version "3.2.0" - resolved acorn-3.2.0.tgz#7a82989ef6f063a237ababaf8df20d2965184b9f -align-text@^0.1.1: - name align-text - version "0.1.4" - resolved align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117 - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" -align-text@^0.1.3: - name align-text - version "0.1.4" - resolved align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117 - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" -amdefine@>=0.0.4: - name amdefine - version "1.0.0" - resolved amdefine-1.0.0.tgz#fd17474700cb5cc9c2b709f0be9d23ce3c198c33 -ansi-escapes@^1.1.0: - name ansi-escapes - version "1.4.0" - resolved ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e -ansi-regex@^1.0.0 ansi-regex@^1.1.1 -ansi-regex@^1.1.1: - name ansi-regex - version "1.1.1" - resolved ansi-regex-1.1.1.tgz#41c847194646375e6a1a5d10c3ca054ef9fc980d -ansi-regex@^2.0.0: - name ansi-regex - version "2.0.0" - resolved ansi-regex-2.0.0.tgz#c5061b6e0ef8a81775e50f5d66151bf6bf371107 -ansi-styles@^2.2.1: - name ansi-styles - version "2.2.1" - resolved ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe -ansi@^0.3.0: - name ansi - version "0.3.1" - resolved ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21 -ansi@~0.3.1: - name ansi - version "0.3.1" - resolved ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21 -archive-type@^3.0.0: - name archive-type - version "3.2.0" - resolved archive-type-3.2.0.tgz#9cd9c006957ebe95fadad5bd6098942a813737f6 - dependencies: - file-type "^3.1.0" -are-we-there-yet@~1.1.2: - name are-we-there-yet - version "1.1.2" - resolved are-we-there-yet-1.1.2.tgz#80e470e95a084794fe1899262c5667c6e88de1b3 - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.0 || ^1.1.13" -argparse@^1.0.7: - name argparse - version "1.0.7" - resolved argparse-1.0.7.tgz#c289506480557810f14a8bc62d7a06f63ed7f951 - dependencies: - sprintf-js "~1.0.2" -arr-diff@^2.0.0: - name arr-diff - version "2.0.0" - resolved arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf - dependencies: - arr-flatten "^1.0.1" -arr-flatten@^1.0.1: - name arr-flatten - version "1.0.1" - resolved arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b -array-differ@^1.0.0: - name array-differ - version "1.0.0" - resolved array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031 -array-equal@^1.0.0: - name array-equal - version "1.0.0" - resolved array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93 -array-find-index@^1.0.1: - name array-find-index - version "1.0.1" - resolved array-find-index-1.0.1.tgz#0bc25ddac941ec8a496ae258fd4ac188003ef3af -array-union@^1.0.1: - name array-union - version "1.0.2" - resolved array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39 - dependencies: - array-uniq "^1.0.1" -array-uniq@^1.0.0 array-uniq@^1.0.2 -array-uniq@^1.0.1 array-uniq@^1.0.2 -array-uniq@^1.0.2: - name array-uniq - version "1.0.3" - resolved array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6 -array-unique@^0.2.1: - name array-unique - version "0.2.1" - resolved array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53 -arrify@^1.0.0: - name arrify - version "1.0.1" - resolved arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d -art@^0.10.0: - name art - version "0.10.1" - resolved art-0.10.1.tgz#38541883e399225c5e193ff246e8f157cf7b2146 -asap@~2.0.3: - name asap - version "2.0.4" - resolved asap-2.0.4.tgz#b391bf7f6bfbc65706022fec8f49c4b07fecf589 -asn1@~0.2.3: - name asn1 - version "0.2.3" - resolved asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86 -assert-plus@^0.2.0: - name assert-plus - version "0.2.0" - resolved assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234 -assert-plus@^1.0.0: - name assert-plus - version "1.0.0" - resolved assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525 -ast-query@^1.0.1: - name ast-query - version "1.2.0" - resolved ast-query-1.2.0.tgz#4b725e9f3922a5edc41dc669c87bbfbfd20171a2 - dependencies: - class-extend "^0.1.1" - escodegen "^1.6.0" - esprima "^2.0.0" - lodash "^4.6.1" - traverse "^0.6.6" -ast-types@0.8.15: - name ast-types - version "0.8.15" - resolved ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52 -async@^1.4.0 async@^1.4.2 -async@^1.4.2: - name async - version "1.5.2" - resolved async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a -async@^1.5.2 async@^1.4.2 -async@~0.2.6: - name async - version "0.2.10" - resolved async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1 -async@0.9.0: - name async - version "0.9.0" - resolved async-0.9.0.tgz#ac3613b1da9bed1b47510bb4651b8931e47146c7 -async@1.x async@^1.4.2 -aws-sign2@~0.6.0: - name aws-sign2 - version "0.6.0" - resolved aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f -aws4@^1.2.1: - name aws4 - version "1.4.1" - resolved aws4-1.4.1.tgz#fde7d5292466d230e5ee0f4e038d9dfaab08fc61 -babel-code-frame@^6.8.0: - name babel-code-frame - version "6.11.0" - resolved babel-code-frame-6.11.0.tgz#9072dd2353fb0f85b6b57d2c97f0d134d188aed8 - dependencies: - babel-runtime "^6.0.0" - chalk "^1.1.0" - esutils "^2.0.2" - js-tokens "^2.0.0" -babel-core@^6.6.4: - name babel-core - version "6.10.4" - resolved babel-core-6.10.4.tgz#283f2212bb03d4e5cd7498b9886efbf6fc2e238e - dependencies: - convert-source-map "^1.1.0" - babel-code-frame "^6.8.0" - babel-helpers "^6.8.0" - babel-messages "^6.8.0" - babel-register "^6.9.0" - babel-runtime "^6.9.1" - babel-template "^6.9.0" - babel-traverse "^6.10.4" - babel-types "^6.9.1" - babylon "^6.7.0" - babel-generator "^6.9.0" - debug "^2.1.1" - json5 "^0.4.0" - lodash "^4.2.0" - minimatch "^3.0.2" - path-exists "^1.0.0" - path-is-absolute "^1.0.0" - private "^0.1.6" - shebang-regex "^1.0.0" - slash "^1.0.0" - source-map "^0.5.0" -babel-core@^6.7.2: - name babel-core - version "6.10.4" - resolved babel-core-6.10.4.tgz#283f2212bb03d4e5cd7498b9886efbf6fc2e238e - dependencies: - convert-source-map "^1.1.0" - babel-code-frame "^6.8.0" - babel-helpers "^6.8.0" - babel-messages "^6.8.0" - babel-register "^6.9.0" - babel-runtime "^6.9.1" - babel-template "^6.9.0" - babel-traverse "^6.10.4" - babel-types "^6.9.1" - babylon "^6.7.0" - babel-generator "^6.9.0" - debug "^2.1.1" - json5 "^0.4.0" - lodash "^4.2.0" - minimatch "^3.0.2" - path-exists "^1.0.0" - path-is-absolute "^1.0.0" - private "^0.1.6" - shebang-regex "^1.0.0" - slash "^1.0.0" - source-map "^0.5.0" -babel-core@^6.9.0: - name babel-core - version "6.10.4" - resolved babel-core-6.10.4.tgz#283f2212bb03d4e5cd7498b9886efbf6fc2e238e - dependencies: - convert-source-map "^1.1.0" - babel-code-frame "^6.8.0" - babel-helpers "^6.8.0" - babel-messages "^6.8.0" - babel-register "^6.9.0" - babel-runtime "^6.9.1" - babel-template "^6.9.0" - babel-traverse "^6.10.4" - babel-types "^6.9.1" - babylon "^6.7.0" - babel-generator "^6.9.0" - debug "^2.1.1" - json5 "^0.4.0" - lodash "^4.2.0" - minimatch "^3.0.2" - path-exists "^1.0.0" - path-is-absolute "^1.0.0" - private "^0.1.6" - shebang-regex "^1.0.0" - slash "^1.0.0" - source-map "^0.5.0" -babel-eslint@^6.0.0: - name babel-eslint - version "6.1.0" - resolved babel-eslint-6.1.0.tgz#ce97bb95590f67e0af43d585126fbdad7678b8ab - dependencies: - babel-traverse "^6.0.20" - babel-types "^6.0.19" - babylon "^6.0.18" - lodash.assign "^4.0.0" - lodash.pickby "^4.0.0" -babel-generator@^6.9.0: - name babel-generator - version "6.11.0" - resolved babel-generator-6.11.0.tgz#8c5a6e3803aacc6c6da7d96cd75da6cc6e2b3275 - dependencies: - babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.10.2" - detect-indent "^3.0.1" - lodash "^4.2.0" - source-map "^0.5.0" -babel-helper-builder-react-jsx@^6.8.0: - name babel-helper-builder-react-jsx - version "6.9.0" - resolved babel-helper-builder-react-jsx-6.9.0.tgz#a633978d669c4c9dcad716cc577ee3e0bb8ae723 - dependencies: - babel-runtime "^6.9.0" - babel-types "^6.9.0" - esutils "^2.0.0" - lodash "^4.2.0" -babel-helper-call-delegate@^6.8.0: - name babel-helper-call-delegate - version "6.8.0" - resolved babel-helper-call-delegate-6.8.0.tgz#9d283e7486779b6b0481864a11b371ea5c01fa64 - dependencies: - babel-helper-hoist-variables "^6.8.0" - babel-runtime "^6.0.0" - babel-traverse "^6.8.0" - babel-types "^6.8.0" -babel-helper-define-map@^6.8.0: - name babel-helper-define-map - version "6.9.0" - resolved babel-helper-define-map-6.9.0.tgz#6629f9b2a7e58e18e8379a57d1e6fbb2969902fb - dependencies: - babel-helper-function-name "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" - lodash "^4.2.0" -babel-helper-define-map@^6.9.0: - name babel-helper-define-map - version "6.9.0" - resolved babel-helper-define-map-6.9.0.tgz#6629f9b2a7e58e18e8379a57d1e6fbb2969902fb - dependencies: - babel-helper-function-name "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" - lodash "^4.2.0" -babel-helper-function-name@^6.8.0: - name babel-helper-function-name - version "6.8.0" - resolved babel-helper-function-name-6.8.0.tgz#a0336ba14526a075cdf502fc52d3fe84b12f7a34 - dependencies: - babel-helper-get-function-arity "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" - babel-traverse "^6.8.0" - babel-types "^6.8.0" -babel-helper-get-function-arity@^6.8.0: - name babel-helper-get-function-arity - version "6.8.0" - resolved babel-helper-get-function-arity-6.8.0.tgz#88276c24bd251cdf6f61b6f89f745f486ced92af - dependencies: - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-helper-hoist-variables@^6.8.0: - name babel-helper-hoist-variables - version "6.8.0" - resolved babel-helper-hoist-variables-6.8.0.tgz#8b0766dc026ea9ea423bc2b34e665a4da7373aaf - dependencies: - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-helper-optimise-call-expression@^6.8.0: - name babel-helper-optimise-call-expression - version "6.8.0" - resolved babel-helper-optimise-call-expression-6.8.0.tgz#4175628e9c89fc36174904f27070f29d38567f06 - dependencies: - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-helper-regex@^6.8.0: - name babel-helper-regex - version "6.9.0" - resolved babel-helper-regex-6.9.0.tgz#c74265fde180ff9a16735fee05e63cadb9e0b057 - dependencies: - babel-runtime "^6.9.0" - babel-types "^6.9.0" - lodash "^4.2.0" -babel-helper-replace-supers@^6.8.0: - name babel-helper-replace-supers - version "6.8.0" - resolved babel-helper-replace-supers-6.8.0.tgz#69cb6bc4ee90164325407af1a2536a42e8fb90d5 - dependencies: - babel-helper-optimise-call-expression "^6.8.0" - babel-messages "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" - babel-traverse "^6.8.0" - babel-types "^6.8.0" -babel-helpers@^6.8.0: - name babel-helpers - version "6.8.0" - resolved babel-helpers-6.8.0.tgz#321c56f9c9cac1a297f827fdff638b27a6292503 - dependencies: - babel-runtime "^6.0.0" - babel-template "^6.8.0" -babel-messages@^6.8.0: - name babel-messages - version "6.8.0" - resolved babel-messages-6.8.0.tgz#bf504736ca967e6d65ef0adb5a2a5f947c8e0eb9 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-check-es2015-constants@^6.5.0: - name babel-plugin-check-es2015-constants - version "6.8.0" - resolved babel-plugin-check-es2015-constants-6.8.0.tgz#dbf024c32ed37bfda8dee1e76da02386a8d26fe7 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-check-es2015-constants@^6.7.2: - name babel-plugin-check-es2015-constants - version "6.8.0" - resolved babel-plugin-check-es2015-constants-6.8.0.tgz#dbf024c32ed37bfda8dee1e76da02386a8d26fe7 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-check-es2015-constants@^6.8.0: - name babel-plugin-check-es2015-constants - version "6.8.0" - resolved babel-plugin-check-es2015-constants-6.8.0.tgz#dbf024c32ed37bfda8dee1e76da02386a8d26fe7 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-external-helpers@^6.5.0: - name babel-plugin-external-helpers - version "6.8.0" - resolved babel-plugin-external-helpers-6.8.0.tgz#febfe50cec910b6dfcbc6caaabddd99f72b12697 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-react-transform@2.0.2: - name babel-plugin-react-transform - version "2.0.2" - resolved babel-plugin-react-transform-2.0.2.tgz#515bbfa996893981142d90b1f9b1635de2995109 - dependencies: - lodash "^4.6.1" -babel-plugin-syntax-async-functions@^6.5.0: - name babel-plugin-syntax-async-functions - version "6.8.0" - resolved babel-plugin-syntax-async-functions-6.8.0.tgz#d872ca350863355b7842ab47c8094aff12dbebc8 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-async-functions@^6.8.0 babel-plugin-syntax-async-functions@^6.5.0 -babel-plugin-syntax-class-properties@^6.5.0: - name babel-plugin-syntax-class-properties - version "6.8.0" - resolved babel-plugin-syntax-class-properties-6.8.0.tgz#287a7cfcc7a4884597890c1646b8015b59f2d467 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-class-properties@^6.8.0: - name babel-plugin-syntax-class-properties - version "6.8.0" - resolved babel-plugin-syntax-class-properties-6.8.0.tgz#287a7cfcc7a4884597890c1646b8015b59f2d467 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-flow@^6.5.0: - name babel-plugin-syntax-flow - version "6.8.0" - resolved babel-plugin-syntax-flow-6.8.0.tgz#708de27bf480fc2a029a3970c52a5d8a67abe058 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-flow@^6.8.0: - name babel-plugin-syntax-flow - version "6.8.0" - resolved babel-plugin-syntax-flow-6.8.0.tgz#708de27bf480fc2a029a3970c52a5d8a67abe058 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-jsx@^6.5.0: - name babel-plugin-syntax-jsx - version "6.8.0" - resolved babel-plugin-syntax-jsx-6.8.0.tgz#1c5430a488a5046a47c8da1ad631f16afe8d111a - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-jsx@^6.8.0: - name babel-plugin-syntax-jsx - version "6.8.0" - resolved babel-plugin-syntax-jsx-6.8.0.tgz#1c5430a488a5046a47c8da1ad631f16afe8d111a - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-object-rest-spread@^6.5.0: - name babel-plugin-syntax-object-rest-spread - version "6.8.0" - resolved babel-plugin-syntax-object-rest-spread-6.8.0.tgz#91b3da76cf4139b879c01b972ef7c88d7ad7a980 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-object-rest-spread@^6.8.0: - name babel-plugin-syntax-object-rest-spread - version "6.8.0" - resolved babel-plugin-syntax-object-rest-spread-6.8.0.tgz#91b3da76cf4139b879c01b972ef7c88d7ad7a980 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-trailing-function-commas@^6.5.0: - name babel-plugin-syntax-trailing-function-commas - version "6.8.0" - resolved babel-plugin-syntax-trailing-function-commas-6.8.0.tgz#c2afdf759c2037f5efe36febfabf345cd8cc7cbf - dependencies: - babel-runtime "^6.0.0" -babel-plugin-syntax-trailing-function-commas@^6.8.0 babel-plugin-syntax-trailing-function-commas@^6.5.0 -babel-plugin-transform-class-properties@^6.5.0: - name babel-plugin-transform-class-properties - version "6.10.2" - resolved babel-plugin-transform-class-properties-6.10.2.tgz#849c20334cac2917267e03fcc37b88d98bf3de0d - dependencies: - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.9.1" -babel-plugin-transform-class-properties@^6.6.0: - name babel-plugin-transform-class-properties - version "6.10.2" - resolved babel-plugin-transform-class-properties-6.10.2.tgz#849c20334cac2917267e03fcc37b88d98bf3de0d - dependencies: - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.9.1" -babel-plugin-transform-class-properties@^6.8.0: - name babel-plugin-transform-class-properties - version "6.10.2" - resolved babel-plugin-transform-class-properties-6.10.2.tgz#849c20334cac2917267e03fcc37b88d98bf3de0d - dependencies: - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.9.1" -babel-plugin-transform-es2015-arrow-functions@^6.5.0: - name babel-plugin-transform-es2015-arrow-functions - version "6.8.0" - resolved babel-plugin-transform-es2015-arrow-functions-6.8.0.tgz#5b63afc3181bdc9a8c4d481b5a4f3f7d7fef3d9d - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-arrow-functions@^6.5.2: - name babel-plugin-transform-es2015-arrow-functions - version "6.8.0" - resolved babel-plugin-transform-es2015-arrow-functions-6.8.0.tgz#5b63afc3181bdc9a8c4d481b5a4f3f7d7fef3d9d - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-arrow-functions@^6.8.0: - name babel-plugin-transform-es2015-arrow-functions - version "6.8.0" - resolved babel-plugin-transform-es2015-arrow-functions-6.8.0.tgz#5b63afc3181bdc9a8c4d481b5a4f3f7d7fef3d9d - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-block-scoped-functions@^6.6.5: - name babel-plugin-transform-es2015-block-scoped-functions - version "6.8.0" - resolved babel-plugin-transform-es2015-block-scoped-functions-6.8.0.tgz#ed95d629c4b5a71ae29682b998f70d9833eb366d - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-block-scoped-functions@^6.8.0: - name babel-plugin-transform-es2015-block-scoped-functions - version "6.8.0" - resolved babel-plugin-transform-es2015-block-scoped-functions-6.8.0.tgz#ed95d629c4b5a71ae29682b998f70d9833eb366d - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-block-scoping@^6.5.0: - name babel-plugin-transform-es2015-block-scoping - version "6.10.1" - resolved babel-plugin-transform-es2015-block-scoping-6.10.1.tgz#6ffbe42d08ed14cf889d06e27dc408080f9d5424 - dependencies: - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.10.0" - lodash "^4.2.0" -babel-plugin-transform-es2015-block-scoping@^6.7.1: - name babel-plugin-transform-es2015-block-scoping - version "6.10.1" - resolved babel-plugin-transform-es2015-block-scoping-6.10.1.tgz#6ffbe42d08ed14cf889d06e27dc408080f9d5424 - dependencies: - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.10.0" - lodash "^4.2.0" -babel-plugin-transform-es2015-block-scoping@^6.8.0: - name babel-plugin-transform-es2015-block-scoping - version "6.10.1" - resolved babel-plugin-transform-es2015-block-scoping-6.10.1.tgz#6ffbe42d08ed14cf889d06e27dc408080f9d5424 - dependencies: - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.10.0" - lodash "^4.2.0" -babel-plugin-transform-es2015-block-scoping@^6.9.0 babel-plugin-transform-es2015-block-scoping@^6.5.0 -babel-plugin-transform-es2015-classes@^6.5.0: - name babel-plugin-transform-es2015-classes - version "6.9.0" - resolved babel-plugin-transform-es2015-classes-6.9.0.tgz#2c70aadc2cbb279b99fbc7bccea87177cc8c1df2 - dependencies: - babel-helper-define-map "^6.9.0" - babel-helper-function-name "^6.8.0" - babel-helper-optimise-call-expression "^6.8.0" - babel-helper-replace-supers "^6.8.0" - babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-classes@^6.6.5: - name babel-plugin-transform-es2015-classes - version "6.9.0" - resolved babel-plugin-transform-es2015-classes-6.9.0.tgz#2c70aadc2cbb279b99fbc7bccea87177cc8c1df2 - dependencies: - babel-helper-define-map "^6.9.0" - babel-helper-function-name "^6.8.0" - babel-helper-optimise-call-expression "^6.8.0" - babel-helper-replace-supers "^6.8.0" - babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-classes@^6.8.0: - name babel-plugin-transform-es2015-classes - version "6.9.0" - resolved babel-plugin-transform-es2015-classes-6.9.0.tgz#2c70aadc2cbb279b99fbc7bccea87177cc8c1df2 - dependencies: - babel-helper-define-map "^6.9.0" - babel-helper-function-name "^6.8.0" - babel-helper-optimise-call-expression "^6.8.0" - babel-helper-replace-supers "^6.8.0" - babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-computed-properties@^6.5.0: - name babel-plugin-transform-es2015-computed-properties - version "6.8.0" - resolved babel-plugin-transform-es2015-computed-properties-6.8.0.tgz#f51010fd61b3bd7b6b60a5fdfd307bb7a5279870 - dependencies: - babel-helper-define-map "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" -babel-plugin-transform-es2015-computed-properties@^6.6.5: - name babel-plugin-transform-es2015-computed-properties - version "6.8.0" - resolved babel-plugin-transform-es2015-computed-properties-6.8.0.tgz#f51010fd61b3bd7b6b60a5fdfd307bb7a5279870 - dependencies: - babel-helper-define-map "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" -babel-plugin-transform-es2015-computed-properties@^6.8.0: - name babel-plugin-transform-es2015-computed-properties - version "6.8.0" - resolved babel-plugin-transform-es2015-computed-properties-6.8.0.tgz#f51010fd61b3bd7b6b60a5fdfd307bb7a5279870 - dependencies: - babel-helper-define-map "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" -babel-plugin-transform-es2015-destructuring@^6.5.0: - name babel-plugin-transform-es2015-destructuring - version "6.9.0" - resolved babel-plugin-transform-es2015-destructuring-6.9.0.tgz#f55747f62534866a51b4c4fdb255e6d85e8604d6 - dependencies: - babel-runtime "^6.9.0" -babel-plugin-transform-es2015-destructuring@^6.6.5: - name babel-plugin-transform-es2015-destructuring - version "6.9.0" - resolved babel-plugin-transform-es2015-destructuring-6.9.0.tgz#f55747f62534866a51b4c4fdb255e6d85e8604d6 - dependencies: - babel-runtime "^6.9.0" -babel-plugin-transform-es2015-destructuring@^6.8.0: - name babel-plugin-transform-es2015-destructuring - version "6.9.0" - resolved babel-plugin-transform-es2015-destructuring-6.9.0.tgz#f55747f62534866a51b4c4fdb255e6d85e8604d6 - dependencies: - babel-runtime "^6.9.0" -babel-plugin-transform-es2015-destructuring@6.x: - name babel-plugin-transform-es2015-destructuring - version "6.9.0" - resolved babel-plugin-transform-es2015-destructuring-6.9.0.tgz#f55747f62534866a51b4c4fdb255e6d85e8604d6 - dependencies: - babel-runtime "^6.9.0" -babel-plugin-transform-es2015-for-of@^6.5.0: - name babel-plugin-transform-es2015-for-of - version "6.8.0" - resolved babel-plugin-transform-es2015-for-of-6.8.0.tgz#82eda139ba4270dda135c3ec1b1f2813fa62f23c - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-for-of@^6.6.0: - name babel-plugin-transform-es2015-for-of - version "6.8.0" - resolved babel-plugin-transform-es2015-for-of-6.8.0.tgz#82eda139ba4270dda135c3ec1b1f2813fa62f23c - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-for-of@^6.8.0: - name babel-plugin-transform-es2015-for-of - version "6.8.0" - resolved babel-plugin-transform-es2015-for-of-6.8.0.tgz#82eda139ba4270dda135c3ec1b1f2813fa62f23c - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-function-name@^6.5.0: - name babel-plugin-transform-es2015-function-name - version "6.9.0" - resolved babel-plugin-transform-es2015-function-name-6.9.0.tgz#8c135b17dbd064e5bba56ec511baaee2fca82719 - dependencies: - babel-helper-function-name "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-function-name@^6.8.0: - name babel-plugin-transform-es2015-function-name - version "6.9.0" - resolved babel-plugin-transform-es2015-function-name-6.9.0.tgz#8c135b17dbd064e5bba56ec511baaee2fca82719 - dependencies: - babel-helper-function-name "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-function-name@6.x: - name babel-plugin-transform-es2015-function-name - version "6.9.0" - resolved babel-plugin-transform-es2015-function-name-6.9.0.tgz#8c135b17dbd064e5bba56ec511baaee2fca82719 - dependencies: - babel-helper-function-name "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-literals@^6.5.0: - name babel-plugin-transform-es2015-literals - version "6.8.0" - resolved babel-plugin-transform-es2015-literals-6.8.0.tgz#50aa2e5c7958fc2ab25d74ec117e0cc98f046468 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-literals@^6.8.0: - name babel-plugin-transform-es2015-literals - version "6.8.0" - resolved babel-plugin-transform-es2015-literals-6.8.0.tgz#50aa2e5c7958fc2ab25d74ec117e0cc98f046468 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-modules-commonjs@^6.5.0: - name babel-plugin-transform-es2015-modules-commonjs - version "6.10.3" - resolved babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz#e4993a455518ca1a3c580bfda35c074e40659c5f - dependencies: - babel-plugin-transform-strict-mode "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-modules-commonjs@^6.7.0: - name babel-plugin-transform-es2015-modules-commonjs - version "6.10.3" - resolved babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz#e4993a455518ca1a3c580bfda35c074e40659c5f - dependencies: - babel-plugin-transform-strict-mode "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-modules-commonjs@^6.8.0: - name babel-plugin-transform-es2015-modules-commonjs - version "6.10.3" - resolved babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz#e4993a455518ca1a3c580bfda35c074e40659c5f - dependencies: - babel-plugin-transform-strict-mode "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-modules-commonjs@6.x: - name babel-plugin-transform-es2015-modules-commonjs - version "6.10.3" - resolved babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz#e4993a455518ca1a3c580bfda35c074e40659c5f - dependencies: - babel-plugin-transform-strict-mode "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-object-super@^6.6.5: - name babel-plugin-transform-es2015-object-super - version "6.8.0" - resolved babel-plugin-transform-es2015-object-super-6.8.0.tgz#1b858740a5a4400887c23dcff6f4d56eea4a24c5 - dependencies: - babel-helper-replace-supers "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-object-super@^6.8.0: - name babel-plugin-transform-es2015-object-super - version "6.8.0" - resolved babel-plugin-transform-es2015-object-super-6.8.0.tgz#1b858740a5a4400887c23dcff6f4d56eea4a24c5 - dependencies: - babel-helper-replace-supers "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-parameters@^6.5.0: - name babel-plugin-transform-es2015-parameters - version "6.9.0" - resolved babel-plugin-transform-es2015-parameters-6.9.0.tgz#3a55a1c91935f39c5e2e2117d02f20370c683392 - dependencies: - babel-helper-call-delegate "^6.8.0" - babel-helper-get-function-arity "^6.8.0" - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-parameters@^6.7.0: - name babel-plugin-transform-es2015-parameters - version "6.9.0" - resolved babel-plugin-transform-es2015-parameters-6.9.0.tgz#3a55a1c91935f39c5e2e2117d02f20370c683392 - dependencies: - babel-helper-call-delegate "^6.8.0" - babel-helper-get-function-arity "^6.8.0" - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-parameters@^6.8.0: - name babel-plugin-transform-es2015-parameters - version "6.9.0" - resolved babel-plugin-transform-es2015-parameters-6.9.0.tgz#3a55a1c91935f39c5e2e2117d02f20370c683392 - dependencies: - babel-helper-call-delegate "^6.8.0" - babel-helper-get-function-arity "^6.8.0" - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-parameters@6.x: - name babel-plugin-transform-es2015-parameters - version "6.9.0" - resolved babel-plugin-transform-es2015-parameters-6.9.0.tgz#3a55a1c91935f39c5e2e2117d02f20370c683392 - dependencies: - babel-helper-call-delegate "^6.8.0" - babel-helper-get-function-arity "^6.8.0" - babel-runtime "^6.9.0" - babel-template "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" -babel-plugin-transform-es2015-shorthand-properties@^6.5.0: - name babel-plugin-transform-es2015-shorthand-properties - version "6.8.0" - resolved babel-plugin-transform-es2015-shorthand-properties-6.8.0.tgz#f0a4c5fd471630acf333c2d99c3d677bf0952149 - dependencies: - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-shorthand-properties@^6.8.0: - name babel-plugin-transform-es2015-shorthand-properties - version "6.8.0" - resolved babel-plugin-transform-es2015-shorthand-properties-6.8.0.tgz#f0a4c5fd471630acf333c2d99c3d677bf0952149 - dependencies: - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-shorthand-properties@6.x: - name babel-plugin-transform-es2015-shorthand-properties - version "6.8.0" - resolved babel-plugin-transform-es2015-shorthand-properties-6.8.0.tgz#f0a4c5fd471630acf333c2d99c3d677bf0952149 - dependencies: - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-spread@^6.5.0: - name babel-plugin-transform-es2015-spread - version "6.8.0" - resolved babel-plugin-transform-es2015-spread-6.8.0.tgz#0217f737e3b821fa5a669f187c6ed59205f05e9c - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-spread@^6.6.5: - name babel-plugin-transform-es2015-spread - version "6.8.0" - resolved babel-plugin-transform-es2015-spread-6.8.0.tgz#0217f737e3b821fa5a669f187c6ed59205f05e9c - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-spread@^6.8.0: - name babel-plugin-transform-es2015-spread - version "6.8.0" - resolved babel-plugin-transform-es2015-spread-6.8.0.tgz#0217f737e3b821fa5a669f187c6ed59205f05e9c - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-spread@6.x: - name babel-plugin-transform-es2015-spread - version "6.8.0" - resolved babel-plugin-transform-es2015-spread-6.8.0.tgz#0217f737e3b821fa5a669f187c6ed59205f05e9c - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-sticky-regex@6.x: - name babel-plugin-transform-es2015-sticky-regex - version "6.8.0" - resolved babel-plugin-transform-es2015-sticky-regex-6.8.0.tgz#e73d300a440a35d5c64f5c2a344dc236e3df47be - dependencies: - babel-helper-regex "^6.8.0" - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-plugin-transform-es2015-template-literals@^6.5.0: - name babel-plugin-transform-es2015-template-literals - version "6.8.0" - resolved babel-plugin-transform-es2015-template-literals-6.8.0.tgz#86eb876d0a2c635da4ec048b4f7de9dfc897e66b - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-template-literals@^6.6.5: - name babel-plugin-transform-es2015-template-literals - version "6.8.0" - resolved babel-plugin-transform-es2015-template-literals-6.8.0.tgz#86eb876d0a2c635da4ec048b4f7de9dfc897e66b - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-template-literals@^6.8.0: - name babel-plugin-transform-es2015-template-literals - version "6.8.0" - resolved babel-plugin-transform-es2015-template-literals-6.8.0.tgz#86eb876d0a2c635da4ec048b4f7de9dfc897e66b - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es2015-unicode-regex@6.x: - name babel-plugin-transform-es2015-unicode-regex - version "6.11.0" - resolved babel-plugin-transform-es2015-unicode-regex-6.11.0.tgz#6298ceabaad88d50a3f4f392d8de997260f6ef2c - dependencies: - babel-helper-regex "^6.8.0" - babel-runtime "^6.0.0" - regexpu-core "^2.0.0" -babel-plugin-transform-es3-member-expression-literals@^6.5.0: - name babel-plugin-transform-es3-member-expression-literals - version "6.8.0" - resolved babel-plugin-transform-es3-member-expression-literals-6.8.0.tgz#180796863e2eddc4b48561d0c228369b05b722e2 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es3-member-expression-literals@^6.8.0: - name babel-plugin-transform-es3-member-expression-literals - version "6.8.0" - resolved babel-plugin-transform-es3-member-expression-literals-6.8.0.tgz#180796863e2eddc4b48561d0c228369b05b722e2 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es3-property-literals@^6.5.0: - name babel-plugin-transform-es3-property-literals - version "6.8.0" - resolved babel-plugin-transform-es3-property-literals-6.8.0.tgz#8e7cc50cfe060b7c487ae33c501a4f659133bade - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-es3-property-literals@^6.8.0: - name babel-plugin-transform-es3-property-literals - version "6.8.0" - resolved babel-plugin-transform-es3-property-literals-6.8.0.tgz#8e7cc50cfe060b7c487ae33c501a4f659133bade - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-flow-strip-types@^6.5.0: - name babel-plugin-transform-flow-strip-types - version "6.8.0" - resolved babel-plugin-transform-flow-strip-types-6.8.0.tgz#2351d85e3a52152e1a55d3f08ae635e21ece17a0 - dependencies: - babel-plugin-syntax-flow "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-flow-strip-types@^6.6.5: - name babel-plugin-transform-flow-strip-types - version "6.8.0" - resolved babel-plugin-transform-flow-strip-types-6.8.0.tgz#2351d85e3a52152e1a55d3f08ae635e21ece17a0 - dependencies: - babel-plugin-syntax-flow "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-flow-strip-types@^6.7.0 babel-plugin-transform-flow-strip-types@^6.5.0 -babel-plugin-transform-flow-strip-types@^6.8.0: - name babel-plugin-transform-flow-strip-types - version "6.8.0" - resolved babel-plugin-transform-flow-strip-types-6.8.0.tgz#2351d85e3a52152e1a55d3f08ae635e21ece17a0 - dependencies: - babel-plugin-syntax-flow "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-object-assign@^6.5.0: - name babel-plugin-transform-object-assign - version "6.8.0" - resolved babel-plugin-transform-object-assign-6.8.0.tgz#76e17f2dc0f36f14f548b9afd7aaef58d29ebb75 - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-object-rest-spread@^6.5.0: - name babel-plugin-transform-object-rest-spread - version "6.8.0" - resolved babel-plugin-transform-object-rest-spread-6.8.0.tgz#03d1308e257a9d8e1a815ae1fd3db21bdebf08d9 - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-object-rest-spread@^6.6.5: - name babel-plugin-transform-object-rest-spread - version "6.8.0" - resolved babel-plugin-transform-object-rest-spread-6.8.0.tgz#03d1308e257a9d8e1a815ae1fd3db21bdebf08d9 - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-object-rest-spread@^6.8.0: - name babel-plugin-transform-object-rest-spread - version "6.8.0" - resolved babel-plugin-transform-object-rest-spread-6.8.0.tgz#03d1308e257a9d8e1a815ae1fd3db21bdebf08d9 - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-react-display-name@^6.5.0: - name babel-plugin-transform-react-display-name - version "6.8.0" - resolved babel-plugin-transform-react-display-name-6.8.0.tgz#f7a084977383d728bdbdc2835bba0159577f660e - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-react-display-name@^6.8.0: - name babel-plugin-transform-react-display-name - version "6.8.0" - resolved babel-plugin-transform-react-display-name-6.8.0.tgz#f7a084977383d728bdbdc2835bba0159577f660e - dependencies: - babel-runtime "^6.0.0" -babel-plugin-transform-react-jsx-source@^6.5.0: - name babel-plugin-transform-react-jsx-source - version "6.9.0" - resolved babel-plugin-transform-react-jsx-source-6.9.0.tgz#af684a05c2067a86e0957d4f343295ccf5dccf00 - dependencies: - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.9.0" -babel-plugin-transform-react-jsx@^6.5.0: - name babel-plugin-transform-react-jsx - version "6.8.0" - resolved babel-plugin-transform-react-jsx-6.8.0.tgz#94759942f70af18c617189aa7f3593f1644a71ab - dependencies: - babel-helper-builder-react-jsx "^6.8.0" - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-react-jsx@^6.8.0: - name babel-plugin-transform-react-jsx - version "6.8.0" - resolved babel-plugin-transform-react-jsx-6.8.0.tgz#94759942f70af18c617189aa7f3593f1644a71ab - dependencies: - babel-helper-builder-react-jsx "^6.8.0" - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.0.0" -babel-plugin-transform-regenerator@^6.5.0: - name babel-plugin-transform-regenerator - version "6.9.0" - resolved babel-plugin-transform-regenerator-6.9.0.tgz#2f094c905efb549e30697f85916791e33939cb70 - dependencies: - babel-core "^6.9.0" - babel-plugin-syntax-async-functions "^6.8.0" - babel-plugin-transform-es2015-block-scoping "^6.9.0" - babel-plugin-transform-es2015-for-of "^6.8.0" - babel-runtime "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" - babylon "^6.6.5" - private "~0.1.5" -babel-plugin-transform-strict-mode@^6.8.0: - name babel-plugin-transform-strict-mode - version "6.8.0" - resolved babel-plugin-transform-strict-mode-6.8.0.tgz#c0ce6620cb5f2c1fb102b20f65741155cabc444a - dependencies: - babel-runtime "^6.0.0" - babel-types "^6.8.0" -babel-polyfill@^6.6.1: - name babel-polyfill - version "6.9.1" - resolved babel-polyfill-6.9.1.tgz#6b6f2f63700c4979bbe2eeb8bdebbac6ddff75bc - dependencies: - babel-runtime "^6.9.1" - core-js "^2.4.0" - regenerator-runtime "^0.9.5" -babel-preset-es2015-node@^4.0.2: - name babel-preset-es2015-node - version "4.0.2" - resolved babel-preset-es2015-node-4.0.2.tgz#b3e2cff896f3d5c491c8da4ad5320eace152180d - dependencies: - babel-plugin-transform-es2015-destructuring "6.x" - babel-plugin-transform-es2015-function-name "6.x" - babel-plugin-transform-es2015-modules-commonjs "6.x" - babel-plugin-transform-es2015-parameters "6.x" - babel-plugin-transform-es2015-shorthand-properties "6.x" - babel-plugin-transform-es2015-spread "6.x" - babel-plugin-transform-es2015-sticky-regex "6.x" - babel-plugin-transform-es2015-unicode-regex "6.x" -babel-preset-fbjs@^1.0.0: - name babel-preset-fbjs - version "1.0.0" - resolved babel-preset-fbjs-1.0.0.tgz#c972e5c9b301d4ec9e7971f4aec3e14ac017a8b0 - dependencies: - babel-plugin-transform-es2015-literals "^6.5.0" - babel-plugin-check-es2015-constants "^6.7.2" - babel-plugin-syntax-object-rest-spread "^6.5.0" - babel-plugin-syntax-trailing-function-commas "^6.5.0" - babel-plugin-transform-class-properties "^6.6.0" - babel-plugin-transform-es2015-arrow-functions "^6.5.2" - babel-plugin-transform-es2015-block-scoped-functions "^6.6.5" - babel-plugin-transform-es2015-block-scoping "^6.7.1" - babel-plugin-transform-es2015-classes "^6.6.5" - babel-plugin-transform-es2015-computed-properties "^6.6.5" - babel-plugin-transform-es2015-destructuring "^6.6.5" - babel-plugin-transform-es2015-for-of "^6.6.0" - babel-plugin-syntax-flow "^6.5.0" - babel-plugin-transform-es2015-modules-commonjs "^6.7.0" - babel-plugin-transform-es2015-object-super "^6.6.5" - babel-plugin-transform-es2015-parameters "^6.7.0" - babel-plugin-transform-es2015-shorthand-properties "^6.5.0" - babel-plugin-transform-es2015-spread "^6.6.5" - babel-plugin-transform-es2015-template-literals "^6.6.5" - babel-plugin-transform-es3-member-expression-literals "^6.5.0" - babel-plugin-transform-es3-property-literals "^6.5.0" - babel-plugin-transform-flow-strip-types "^6.7.0" - babel-plugin-transform-object-rest-spread "^6.6.5" - object-assign "^4.0.1" -babel-preset-fbjs@^2.0.0: - name babel-preset-fbjs - version "2.0.0" - resolved babel-preset-fbjs-2.0.0.tgz#8042946d4ba2612bf52035b19206b0358eae7b46 - dependencies: - babel-plugin-transform-es2015-function-name "^6.8.0" - babel-plugin-check-es2015-constants "^6.8.0" - babel-plugin-syntax-flow "^6.8.0" - babel-plugin-syntax-jsx "^6.8.0" - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-plugin-syntax-trailing-function-commas "^6.8.0" - babel-plugin-transform-class-properties "^6.8.0" - babel-plugin-transform-es2015-arrow-functions "^6.8.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.8.0" - babel-plugin-transform-es2015-block-scoping "^6.8.0" - babel-plugin-transform-es2015-classes "^6.8.0" - babel-plugin-transform-es2015-computed-properties "^6.8.0" - babel-plugin-transform-es2015-destructuring "^6.8.0" - babel-plugin-transform-es2015-for-of "^6.8.0" - babel-plugin-syntax-class-properties "^6.8.0" - babel-plugin-transform-es2015-literals "^6.8.0" - babel-plugin-transform-es2015-modules-commonjs "^6.8.0" - babel-plugin-transform-es2015-object-super "^6.8.0" - babel-plugin-transform-es2015-parameters "^6.8.0" - babel-plugin-transform-es2015-shorthand-properties "^6.8.0" - babel-plugin-transform-es2015-spread "^6.8.0" - babel-plugin-transform-es2015-template-literals "^6.8.0" - babel-plugin-transform-es3-member-expression-literals "^6.8.0" - babel-plugin-transform-es3-property-literals "^6.8.0" - babel-plugin-transform-flow-strip-types "^6.8.0" - babel-plugin-transform-object-rest-spread "^6.8.0" - babel-plugin-transform-react-display-name "^6.8.0" - babel-plugin-transform-react-jsx "^6.8.0" -babel-preset-react-native@^1.9.0: - name babel-preset-react-native - version "1.9.0" - resolved babel-preset-react-native-1.9.0.tgz#035fc06c65f4f2a02d0336a100b2da142f36dab1 - dependencies: - babel-plugin-transform-es2015-function-name "^6.5.0" - babel-plugin-check-es2015-constants "^6.5.0" - babel-plugin-syntax-async-functions "^6.5.0" - babel-plugin-syntax-class-properties "^6.5.0" - babel-plugin-syntax-flow "^6.5.0" - babel-plugin-syntax-jsx "^6.5.0" - babel-plugin-syntax-trailing-function-commas "^6.5.0" - babel-plugin-transform-class-properties "^6.5.0" - babel-plugin-transform-es2015-arrow-functions "^6.5.0" - babel-plugin-transform-es2015-block-scoping "^6.5.0" - babel-plugin-transform-es2015-classes "^6.5.0" - babel-plugin-transform-es2015-computed-properties "^6.5.0" - babel-plugin-transform-es2015-destructuring "^6.5.0" - babel-plugin-transform-es2015-for-of "^6.5.0" - babel-plugin-react-transform "2.0.2" - babel-plugin-transform-es2015-literals "^6.5.0" - babel-plugin-transform-es2015-modules-commonjs "^6.5.0" - babel-plugin-transform-es2015-parameters "^6.5.0" - babel-plugin-transform-es2015-shorthand-properties "^6.5.0" - babel-plugin-transform-es2015-spread "^6.5.0" - babel-plugin-transform-es2015-template-literals "^6.5.0" - babel-plugin-transform-flow-strip-types "^6.5.0" - babel-plugin-transform-object-assign "^6.5.0" - babel-plugin-transform-object-rest-spread "^6.5.0" - babel-plugin-transform-react-display-name "^6.5.0" - babel-plugin-transform-react-jsx "^6.5.0" - babel-plugin-transform-react-jsx-source "^6.5.0" - babel-plugin-transform-regenerator "^6.5.0" - react-transform-hmr "^1.0.4" -babel-register@^6.6.0: - name babel-register - version "6.9.0" - resolved babel-register-6.9.0.tgz#dd5f3572ef5bd4082ca05471e942e4a91b162ff0 - dependencies: - babel-core "^6.9.0" - babel-runtime "^6.9.0" - core-js "^2.4.0" - home-or-tmp "^1.0.0" - lodash "^4.2.0" - mkdirp "^0.5.1" - path-exists "^1.0.0" - source-map-support "^0.2.10" -babel-register@^6.9.0: - name babel-register - version "6.9.0" - resolved babel-register-6.9.0.tgz#dd5f3572ef5bd4082ca05471e942e4a91b162ff0 - dependencies: - babel-core "^6.9.0" - babel-runtime "^6.9.0" - core-js "^2.4.0" - home-or-tmp "^1.0.0" - lodash "^4.2.0" - mkdirp "^0.5.1" - path-exists "^1.0.0" - source-map-support "^0.2.10" -babel-runtime@^6.0.0: - name babel-runtime - version "6.9.2" - resolved babel-runtime-6.9.2.tgz#d7fe391bc2cc29b8087c1d9b39878912e9fcfd59 - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.9.5" -babel-runtime@^6.9.0: - name babel-runtime - version "6.9.2" - resolved babel-runtime-6.9.2.tgz#d7fe391bc2cc29b8087c1d9b39878912e9fcfd59 - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.9.5" -babel-runtime@^6.9.1: - name babel-runtime - version "6.9.2" - resolved babel-runtime-6.9.2.tgz#d7fe391bc2cc29b8087c1d9b39878912e9fcfd59 - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.9.5" -babel-template@^6.8.0: - name babel-template - version "6.9.0" - resolved babel-template-6.9.0.tgz#97090fcf6bc15685b4f05be65c0a9438aa7e23e3 - dependencies: - babel-runtime "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" - babylon "^6.7.0" - lodash "^4.2.0" -babel-template@^6.9.0: - name babel-template - version "6.9.0" - resolved babel-template-6.9.0.tgz#97090fcf6bc15685b4f05be65c0a9438aa7e23e3 - dependencies: - babel-runtime "^6.9.0" - babel-traverse "^6.9.0" - babel-types "^6.9.0" - babylon "^6.7.0" - lodash "^4.2.0" -babel-traverse@^6.0.20: - name babel-traverse - version "6.10.4" - resolved babel-traverse-6.10.4.tgz#dbcf41ff1f32eb614859cead4871160f1f120d78 - dependencies: - babel-code-frame "^6.8.0" - babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" - babylon "^6.7.0" - debug "^2.2.0" - globals "^8.3.0" - invariant "^2.2.0" - lodash "^4.2.0" -babel-traverse@^6.10.4: - name babel-traverse - version "6.10.4" - resolved babel-traverse-6.10.4.tgz#dbcf41ff1f32eb614859cead4871160f1f120d78 - dependencies: - babel-code-frame "^6.8.0" - babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" - babylon "^6.7.0" - debug "^2.2.0" - globals "^8.3.0" - invariant "^2.2.0" - lodash "^4.2.0" -babel-traverse@^6.8.0 babel-traverse@^6.10.4 -babel-traverse@^6.9.0: - name babel-traverse - version "6.10.4" - resolved babel-traverse-6.10.4.tgz#dbcf41ff1f32eb614859cead4871160f1f120d78 - dependencies: - babel-code-frame "^6.8.0" - babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.9.0" - babylon "^6.7.0" - debug "^2.2.0" - globals "^8.3.0" - invariant "^2.2.0" - lodash "^4.2.0" -babel-types@^6.0.19 babel-types@^6.9.1 -babel-types@^6.10.0 babel-types@^6.9.1 -babel-types@^6.10.2 babel-types@^6.9.1 -babel-types@^6.6.4: - name babel-types - version "6.10.2" - resolved babel-types-6.10.2.tgz#9f1305e07788fa0a948c3fb364f44c8d7ee81b9a - dependencies: - babel-runtime "^6.9.1" - babel-traverse "^6.9.0" - esutils "^2.0.2" - lodash "^4.2.0" - to-fast-properties "^1.0.1" -babel-types@^6.8.0 babel-types@^6.9.1 -babel-types@^6.9.0 babel-types@^6.9.1 -babel-types@^6.9.1: - name babel-types - version "6.10.2" - resolved babel-types-6.10.2.tgz#9f1305e07788fa0a948c3fb364f44c8d7ee81b9a - dependencies: - babel-runtime "^6.9.1" - babel-traverse "^6.9.0" - esutils "^2.0.2" - lodash "^4.2.0" - to-fast-properties "^1.0.1" -babylon@^6.0.18 babylon@^6.6.4 -babylon@^6.6.4: - name babylon - version "6.8.2" - resolved babylon-6.8.2.tgz#972a172103cb2163d8a0e55356126733816df257 - dependencies: - babel-runtime "^6.0.0" -babylon@^6.6.5 babylon@^6.6.4 -babylon@^6.7.0: - name babylon - version "6.8.2" - resolved babylon-6.8.2.tgz#972a172103cb2163d8a0e55356126733816df257 - dependencies: - babel-runtime "^6.0.0" -balanced-match@^0.4.1: - name balanced-match - version "0.4.1" - resolved balanced-match-0.4.1.tgz#19053e2e0748eadb379da6c09d455cf5e1039335 -base62@^1.1.0: - name base62 - version "1.1.1" - resolved base62-1.1.1.tgz#974e82c11bd5e00816b508a7ed9c7b9086c9db6b -base64-js@^0.0.8: - name base64-js - version "0.0.8" - resolved base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978 -base64-js@0.0.8 base64-js@^0.0.8 -base64-url@1.2.1: - name base64-url - version "1.2.1" - resolved base64-url-1.2.1.tgz#199fd661702a0e7b7dcae6e0698bb089c52f6d78 -base64-url@1.2.2: - name base64-url - version "1.2.2" - resolved base64-url-1.2.2.tgz#90af26ef8b0b67bc801b05eccf943345649008b3 -basic-auth-connect@1.0.0: - name basic-auth-connect - version "1.0.0" - resolved basic-auth-connect-1.0.0.tgz#fdb0b43962ca7b40456a7c2bb48fe173da2d2122 -basic-auth@~1.0.3: - name basic-auth - version "1.0.4" - resolved basic-auth-1.0.4.tgz#030935b01de7c9b94a824b29f3fccb750d3a5290 -batch@0.5.3: - name batch - version "0.5.3" - resolved batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464 -beeper@^1.0.0: - name beeper - version "1.1.0" - resolved beeper-1.1.0.tgz#9ee6fc1ce7f54feaace7ce73588b056037866a2c -bin-check@^2.0.0: - name bin-check - version "2.0.0" - resolved bin-check-2.0.0.tgz#86f8e6f4253893df60dc316957f5af02acb05930 - dependencies: - executable "^1.0.0" -bin-version-check@^2.1.0: - name bin-version-check - version "2.1.0" - resolved bin-version-check-2.1.0.tgz#e4e5df290b9069f7d111324031efc13fdd11a5b0 - dependencies: - bin-version "^1.0.0" - minimist "^1.1.0" - semver "^4.0.3" - semver-truncate "^1.0.0" -bin-version@^1.0.0: - name bin-version - version "1.0.4" - resolved bin-version-1.0.4.tgz#9eb498ee6fd76f7ab9a7c160436f89579435d78e - dependencies: - find-versions "^1.0.0" -bin-wrapper@^3.0.2: - name bin-wrapper - version "3.0.2" - resolved bin-wrapper-3.0.2.tgz#67d3306262e4b1a5f2f88ee23464f6a655677aeb - dependencies: - bin-check "^2.0.0" - bin-version-check "^2.1.0" - download "^4.0.0" - each-async "^1.1.1" - lazy-req "^1.0.0" - os-filter-obj "^1.0.0" -binaryextensions@~1.0.0: - name binaryextensions - version "1.0.1" - resolved binaryextensions-1.0.1.tgz#1e637488b35b58bda5f4774bf96a5212a8c90755 -bindings@1.2.x: - name bindings - version "1.2.1" - resolved bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11 -bl@^1.0.0: - name bl - version "1.1.2" - resolved bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398 - dependencies: - readable-stream "~2.0.5" -bl@~1.1.2 bl@^1.0.0 -bluebird@^3.1.1: - name bluebird - version "3.4.1" - resolved bluebird-3.4.1.tgz#b731ddf48e2dd3bedac2e75e1215a11bcb91fa07 -body-parser@~1.13.3: - name body-parser - version "1.13.3" - resolved body-parser-1.13.3.tgz#c08cf330c3358e151016a05746f13f029c97fa97 - dependencies: - bytes "2.1.0" - content-type "~1.0.1" - debug "~2.2.0" - depd "~1.0.1" - http-errors "~1.3.1" - iconv-lite "0.4.11" - on-finished "~2.3.0" - qs "4.0.0" - raw-body "~2.1.2" - type-is "~1.6.6" -boolbase@~1.0.0: - name boolbase - version "1.0.0" - resolved boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e -boom@2.x.x: - name boom - version "2.10.1" - resolved boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f - dependencies: - hoek "2.x.x" -bplist-creator@0.0.4: - name bplist-creator - version "0.0.4" - resolved bplist-creator-0.0.4.tgz#4ac0496782e127a85c1d2026a4f5eb22a7aff991 - dependencies: - stream-buffers "~0.2.3" -bplist-parser@0.0.6: - name bplist-parser - version "0.0.6" - resolved bplist-parser-0.0.6.tgz#38da3471817df9d44ab3892e27707bbbd75a11b9 -brace-expansion@^1.0.0: - name brace-expansion - version "1.1.5" - resolved brace-expansion-1.1.5.tgz#f5b4ad574e2cb7ccc1eb83e6fe79b8ecadf7a526 - dependencies: - balanced-match "^0.4.1" - concat-map "0.0.1" -braces@^1.8.2: - name braces - version "1.8.5" - resolved braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7 - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" -browser-resolve@^1.7.0: - name browser-resolve - version "1.11.2" - resolved browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce - dependencies: - resolve "1.1.7" -bser@^1.0.2: - name bser - version "1.0.2" - resolved bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169 - dependencies: - node-int64 "^0.4.0" -buffer-crc32@~0.2.3: - name buffer-crc32 - version "0.2.5" - resolved buffer-crc32-0.2.5.tgz#db003ac2671e62ebd6ece78ea2c2e1b405736e91 -buffer-shims@^1.0.0: - name buffer-shims - version "1.0.0" - resolved buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51 -buffer-to-vinyl@^1.0.0: - name buffer-to-vinyl - version "1.1.0" - resolved buffer-to-vinyl-1.1.0.tgz#00f15faee3ab7a1dda2cde6d9121bffdd07b2262 - dependencies: - file-type "^3.1.0" - readable-stream "^2.0.2" - uuid "^2.0.1" - vinyl "^1.0.0" -bufferutil@1.2.x: - name bufferutil - version "1.2.1" - resolved bufferutil-1.2.1.tgz#37be5d36e1e06492221e68d474b1ac58e510cbd7 - dependencies: - bindings "1.2.x" - nan "^2.0.5" -builtin-modules@^1.0.0: - name builtin-modules - version "1.1.1" - resolved builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f -bytes@2.1.0: - name bytes - version "2.1.0" - resolved bytes-2.1.0.tgz#ac93c410e2ffc9cc7cf4b464b38289067f5e47b4 -bytes@2.4.0: - name bytes - version "2.4.0" - resolved bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339 -caller-path@^0.1.0: - name caller-path - version "0.1.0" - resolved caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f - dependencies: - callsites "^0.2.0" -callsites@^0.2.0: - name callsites - version "0.2.0" - resolved callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca -camelcase-keys@^2.0.0: - name camelcase-keys - version "2.1.0" - resolved camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7 - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" -camelcase@^1.0.2: - name camelcase - version "1.2.1" - resolved camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39 -camelcase@^2.0.0 camelcase@^2.0.1 -camelcase@^2.0.1: - name camelcase - version "2.1.1" - resolved camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f -capture-stack-trace@^1.0.0: - name capture-stack-trace - version "1.0.0" - resolved capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d -caseless@~0.11.0: - name caseless - version "0.11.0" - resolved caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7 -caw@^1.0.1: - name caw - version "1.2.0" - resolved caw-1.2.0.tgz#ffb226fe7efc547288dc62ee3e97073c212d1034 - dependencies: - get-proxy "^1.0.1" - is-obj "^1.0.0" - object-assign "^3.0.0" - tunnel-agent "^0.4.0" -center-align@^0.1.1: - name center-align - version "0.1.3" - resolved center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" -chalk@^1.0.0: - name chalk - version "1.1.3" - resolved chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98 - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" -chalk@^1.1.0 chalk@^1.0.0 -chalk@^1.1.1: - name chalk - version "1.1.3" - resolved chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98 - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" -chalk@^1.1.3 chalk@^1.0.0 -cheerio@^0.19.0: - name cheerio - version "0.19.0" - resolved cheerio-0.19.0.tgz#772e7015f2ee29965096d71ea4175b75ab354925 - dependencies: - css-select "~1.0.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "~3.8.1" - lodash "^3.2.0" -class-extend@^0.1.0: - name class-extend - version "0.1.2" - resolved class-extend-0.1.2.tgz#8057a82b00f53f82a5d62c50ef8cffdec6fabc34 - dependencies: - object-assign "^2.0.0" -class-extend@^0.1.1 class-extend@^0.1.0 -cli-cursor@^1.0.1: - name cli-cursor - version "1.0.2" - resolved cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987 - dependencies: - restore-cursor "^1.0.1" -cli-table@^0.3.1: - name cli-table - version "0.3.1" - resolved cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23 - dependencies: - colors "1.0.3" -cli-table@0.0.x: - name cli-table - version "0.0.2" - resolved cli-table-0.0.2.tgz#982867e16435325c66c208a1e71b95336ea3093b - dependencies: - colors "0.3.0" -cli-width@^1.0.1: - name cli-width - version "1.1.1" - resolved cli-width-1.1.1.tgz#a4d293ef67ebb7b88d4a4d42c0ccf00c4d1e366d -cli-width@^2.0.0: - name cli-width - version "2.1.0" - resolved cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a -cliui@^2.1.0: - name cliui - version "2.1.0" - resolved cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1 - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" -cliui@^3.0.3: - name cliui - version "3.2.0" - resolved cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" -clone-stats@^0.0.1: - name clone-stats - version "0.0.1" - resolved clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1 -clone@^0.2.0: - name clone - version "0.2.0" - resolved clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f -clone@^1.0.0: - name clone - version "1.0.2" - resolved clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149 -co@3.1.0: - name co - version "3.1.0" - resolved co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78 -code-point-at@^1.0.0: - name code-point-at - version "1.0.0" - resolved code-point-at-1.0.0.tgz#f69b192d3f7d91e382e4b71bddb77878619ab0c6 - dependencies: - number-is-nan "^1.0.0" -colors@0.3.0: - name colors - version "0.3.0" - resolved colors-0.3.0.tgz#c247d64d34db0ca4dc8e43f3ecd6da98d0af94e7 -colors@1.0.3: - name colors - version "1.0.3" - resolved colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b -combined-stream@^1.0.5: - name combined-stream - version "1.0.5" - resolved combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009 - dependencies: - delayed-stream "~1.0.0" -combined-stream@~1.0.5: - name combined-stream - version "1.0.5" - resolved combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009 - dependencies: - delayed-stream "~1.0.0" -commander@^2.5.0: - name commander - version "2.9.0" - resolved commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4 - dependencies: - graceful-readlink ">= 1.0.0" -commander@^2.9.0 commander@^2.5.0 -commander@~2.8.1: - name commander - version "2.8.1" - resolved commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4 - dependencies: - graceful-readlink ">= 1.0.0" -commondir@^1.0.1: - name commondir - version "1.0.1" - resolved commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b -commoner@^0.10.1: - name commoner - version "0.10.4" - resolved commoner-0.10.4.tgz#98f3333dd3ad399596bb2d384a783bb7213d68f8 - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.10.0" -compressible@~2.0.5: - name compressible - version "2.0.8" - resolved compressible-2.0.8.tgz#7162e6c46d3b9d200ffb45cb4e4a0f7832732503 - dependencies: - mime-db ">= 1.23.0 < 2" -compression@~1.5.2: - name compression - version "1.5.2" - resolved compression-1.5.2.tgz#b03b8d86e6f8ad29683cba8df91ddc6ffc77b395 - dependencies: - accepts "~1.2.12" - bytes "2.1.0" - compressible "~2.0.5" - debug "~2.2.0" - on-headers "~1.0.0" - vary "~1.0.1" -concat-map@0.0.1: - name concat-map - version "0.0.1" - resolved concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b -concat-stream@^1.4.6: - name concat-stream - version "1.5.1" - resolved concat-stream-1.5.1.tgz#f3b80acf9e1f48e3875c0688b41b6c31602eea1c - dependencies: - inherits "~2.0.1" - readable-stream "~2.0.0" - typedarray "~0.0.5" -concat-stream@^1.4.7: - name concat-stream - version "1.5.1" - resolved concat-stream-1.5.1.tgz#f3b80acf9e1f48e3875c0688b41b6c31602eea1c - dependencies: - inherits "~2.0.1" - readable-stream "~2.0.0" - typedarray "~0.0.5" -concat-stream@~1.4.5: - name concat-stream - version "1.4.10" - resolved concat-stream-1.4.10.tgz#acc3bbf5602cb8cc980c6ac840fa7d8603e3ef36 - dependencies: - inherits "~2.0.1" - readable-stream "~1.1.9" - typedarray "~0.0.5" -connect-timeout@~1.6.2: - name connect-timeout - version "1.6.2" - resolved connect-timeout-1.6.2.tgz#de9a5ec61e33a12b6edaab7b5f062e98c599b88e - dependencies: - debug "~2.2.0" - http-errors "~1.3.1" - ms "0.7.1" - on-headers "~1.0.0" -connect@^2.8.3: - name connect - version "2.30.2" - resolved connect-2.30.2.tgz#8da9bcbe8a054d3d318d74dfec903b5c39a1b609 - dependencies: - fresh "0.3.0" - basic-auth-connect "1.0.0" - bytes "2.1.0" - compression "~1.5.2" - connect-timeout "~1.6.2" - content-type "~1.0.1" - cookie "0.1.3" - cookie-parser "~1.3.5" - cookie-signature "1.0.6" - csurf "~1.8.3" - debug "~2.2.0" - depd "~1.0.1" - errorhandler "~1.4.2" - express-session "~1.11.3" - finalhandler "0.4.0" - body-parser "~1.13.3" - http-errors "~1.3.1" - method-override "~2.3.5" - morgan "~1.6.1" - multiparty "3.3.2" - on-headers "~1.0.0" - parseurl "~1.3.0" - pause "0.1.0" - qs "4.0.0" - response-time "~2.3.1" - serve-favicon "~2.3.0" - serve-index "~1.7.2" - serve-static "~1.10.0" - type-is "~1.6.6" - utils-merge "1.0.0" - vhost "~3.0.1" -console-stream@^0.1.1: - name console-stream - version "0.1.1" - resolved console-stream-0.1.1.tgz#a095fe07b20465955f2fafd28b5d72bccd949d44 -content-type@~1.0.1: - name content-type - version "1.0.2" - resolved content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed -convert-source-map@^1.1.0: - name convert-source-map - version "1.2.0" - resolved convert-source-map-1.2.0.tgz#44c08c2506f10fb3ca6fd888d5a3444cf8d6a669 -convert-source-map@^1.1.1 convert-source-map@^1.1.0 -cookie-parser@~1.3.5: - name cookie-parser - version "1.3.5" - resolved cookie-parser-1.3.5.tgz#9d755570fb5d17890771227a02314d9be7cf8356 - dependencies: - cookie "0.1.3" - cookie-signature "1.0.6" -cookie-signature@1.0.6: - name cookie-signature - version "1.0.6" - resolved cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c -cookie@0.1.3: - name cookie - version "0.1.3" - resolved cookie-0.1.3.tgz#e734a5c1417fce472d5aef82c381cabb64d1a435 -core-js@^1.0.0: - name core-js - version "1.2.6" - resolved core-js-1.2.6.tgz#e2351f6cae764f8c34e5d839acb6a60cef8b4a45 -core-js@^2.2.2: - name core-js - version "2.4.0" - resolved core-js-2.4.0.tgz#df408ab46d01aff91c01c3e7971935d422c54f81 -core-js@^2.4.0: - name core-js - version "2.4.0" - resolved core-js-2.4.0.tgz#df408ab46d01aff91c01c3e7971935d422c54f81 -core-util-is@~1.0.0: - name core-util-is - version "1.0.2" - resolved core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7 -cover@^0.2.9: - name cover - version "0.2.9" - resolved cover-0.2.9.tgz#262029dfb31714d48043fed91ede658593955dd4 - dependencies: - cli-table "0.0.x" - underscore "1.2.x" - underscore.string "2.0.x" - which "1.0.x" -crc@3.3.0: - name crc - version "3.3.0" - resolved crc-3.3.0.tgz#fa622e1bc388bf257309082d6b65200ce67090ba -create-error-class@^3.0.1: - name create-error-class - version "3.0.2" - resolved create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6 - dependencies: - capture-stack-trace "^1.0.0" -cross-spawn-async@^2.2.2: - name cross-spawn-async - version "2.2.4" - resolved cross-spawn-async-2.2.4.tgz#c9a8d8e9a06502c7a46296e33a1a054b5d2f1812 - dependencies: - lru-cache "^4.0.0" - which "^1.2.8" -cross-spawn@^2.0.0: - name cross-spawn - version "2.2.3" - resolved cross-spawn-2.2.3.tgz#fac56202dfd3d0dd861778f2da203bf434bb821c - dependencies: - cross-spawn-async "^2.2.2" - spawn-sync "^1.0.15" -cross-spawn@^3.0.1: - name cross-spawn - version "3.0.1" - resolved cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982 - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" -cryptiles@2.x.x: - name cryptiles - version "2.0.5" - resolved cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8 - dependencies: - boom "2.x.x" -csrf@~3.0.0: - name csrf - version "3.0.3" - resolved csrf-3.0.3.tgz#69d13220de95762808bb120f7533a994fc4293b5 - dependencies: - base64-url "1.2.2" - rndm "1.2.0" - tsscmp "1.0.5" - uid-safe "2.1.1" -css-select@~1.0.0: - name css-select - version "1.0.0" - resolved css-select-1.0.0.tgz#b1121ca51848dd264e2244d058cee254deeb44b0 - dependencies: - boolbase "~1.0.0" - css-what "1.0" - domutils "1.4" - nth-check "~1.0.0" -css-what@1.0: - name css-what - version "1.0.0" - resolved css-what-1.0.0.tgz#d7cc2df45180666f99d2b14462639469e00f736c -"cssom@>= 0.3.0 < 0.4.0": - name cssom - version "0.3.1" - resolved cssom-0.3.1.tgz#c9e37ef2490e64f6d1baa10fda852257082c25d3 -cssom@0.3.x "cssom@>= 0.3.0 < 0.4.0" -"cssstyle@>= 0.2.36 < 0.3.0": - name cssstyle - version "0.2.36" - resolved cssstyle-0.2.36.tgz#5cfed6b1a4526b58d77673d51b3520856fb6af3e - dependencies: - cssom "0.3.x" -csurf@~1.8.3: - name csurf - version "1.8.3" - resolved csurf-1.8.3.tgz#23f2a13bf1d8fce1d0c996588394442cba86a56a - dependencies: - cookie "0.1.3" - cookie-signature "1.0.6" - csrf "~3.0.0" - http-errors "~1.3.1" -currently-unhandled@^0.4.1: - name currently-unhandled - version "0.4.1" - resolved currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea - dependencies: - array-find-index "^1.0.1" -d@^0.1.1: - name d - version "0.1.1" - resolved d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309 - dependencies: - es5-ext "~0.10.2" -d@~0.1.1: - name d - version "0.1.1" - resolved d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309 - dependencies: - es5-ext "~0.10.2" -dargs@^4.0.0: - name dargs - version "4.1.0" - resolved dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17 - dependencies: - number-is-nan "^1.0.0" -dashdash@^1.12.0: - name dashdash - version "1.14.0" - resolved dashdash-1.14.0.tgz#29e486c5418bf0f356034a993d51686a33e84141 - dependencies: - assert-plus "^1.0.0" -dateformat@^1.0.11: - name dateformat - version "1.0.12" - resolved dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9 - dependencies: - get-stdin "^4.0.1" - meow "^3.3.0" -debug@^2.0.0 debug@^2.1.1 -debug@^2.1.0 debug@^2.1.1 -debug@^2.1.1: - name debug - version "2.2.0" - resolved debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da - dependencies: - ms "0.7.1" -debug@^2.2.0: - name debug - version "2.2.0" - resolved debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da - dependencies: - ms "0.7.1" -debug@~2.2.0 debug@^2.1.1 -decamelize@^1.0.0: - name decamelize - version "1.2.0" - resolved decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290 -decamelize@^1.1.1: - name decamelize - version "1.2.0" - resolved decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290 -decamelize@^1.1.2 decamelize@^1.1.1 -decompress-tar@^3.0.0: - name decompress-tar - version "3.1.0" - resolved decompress-tar-3.1.0.tgz#217c789f9b94450efaadc5c5e537978fc333c466 - dependencies: - is-tar "^1.0.0" - object-assign "^2.0.0" - strip-dirs "^1.0.0" - tar-stream "^1.1.1" - through2 "^0.6.1" - vinyl "^0.4.3" -decompress-tarbz2@^3.0.0: - name decompress-tarbz2 - version "3.1.0" - resolved decompress-tarbz2-3.1.0.tgz#8b23935681355f9f189d87256a0f8bdd96d9666d - dependencies: - is-bzip2 "^1.0.0" - object-assign "^2.0.0" - seek-bzip "^1.0.3" - strip-dirs "^1.0.0" - tar-stream "^1.1.1" - through2 "^0.6.1" - vinyl "^0.4.3" -decompress-targz@^3.0.0: - name decompress-targz - version "3.1.0" - resolved decompress-targz-3.1.0.tgz#b2c13df98166268991b715d6447f642e9696f5a0 - dependencies: - is-gzip "^1.0.0" - object-assign "^2.0.0" - strip-dirs "^1.0.0" - tar-stream "^1.1.1" - through2 "^0.6.1" - vinyl "^0.4.3" -decompress-unzip@^3.0.0: - name decompress-unzip - version "3.4.0" - resolved decompress-unzip-3.4.0.tgz#61475b4152066bbe3fee12f9d629d15fe6478eeb - dependencies: - is-zip "^1.0.0" - read-all-stream "^3.0.0" - stat-mode "^0.2.0" - strip-dirs "^1.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" - yauzl "^2.2.1" -decompress@^3.0.0: - name decompress - version "3.0.0" - resolved decompress-3.0.0.tgz#af1dd50d06e3bfc432461d37de11b38c0d991bed - dependencies: - buffer-to-vinyl "^1.0.0" - concat-stream "^1.4.6" - decompress-tar "^3.0.0" - decompress-tarbz2 "^3.0.0" - decompress-targz "^3.0.0" - decompress-unzip "^3.0.0" - stream-combiner2 "^1.1.1" - vinyl-assign "^1.0.1" - vinyl-fs "^2.2.0" -deep-extend@^0.4.0: - name deep-extend - version "0.4.1" - resolved deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253 -deep-extend@~0.4.0 deep-extend@^0.4.0 -deep-is@~0.1.3: - name deep-is - version "0.1.3" - resolved deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34 -defined@^1.0.0: - name defined - version "1.0.0" - resolved defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693 -del@^2.0.2: - name del - version "2.2.1" - resolved del-2.2.1.tgz#f6763026472209c4f0349111c5ac280868bec4fe - dependencies: - globby "^5.0.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - rimraf "^2.2.8" -delayed-stream@~1.0.0: - name delayed-stream - version "1.0.0" - resolved delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619 -delegates@^1.0.0: - name delegates - version "1.0.0" - resolved delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a -denodeify@^1.2.1: - name denodeify - version "1.2.1" - resolved denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631 -depd@~1.0.1: - name depd - version "1.0.1" - resolved depd-1.0.1.tgz#80aec64c9d6d97e65cc2a9caa93c0aa6abf73aaa -depd@~1.1.0: - name depd - version "1.1.0" - resolved depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3 -destroy@~1.0.4: - name destroy - version "1.0.4" - resolved destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80 -detect-conflict@^1.0.0: - name detect-conflict - version "1.0.0" - resolved detect-conflict-1.0.0.tgz#eaa13c2554aa015f4b471174ab7925065e9d9a02 -detect-indent@^3.0.1: - name detect-indent - version "3.0.1" - resolved detect-indent-3.0.1.tgz#9dc5e5ddbceef8325764b9451b02bc6d54084f75 - dependencies: - get-stdin "^4.0.1" - minimist "^1.1.0" - repeating "^1.1.0" -detect-newline@^1.0.3: - name detect-newline - version "1.0.3" - resolved detect-newline-1.0.3.tgz#e97b1003877d70c09af1af35bfadff168de4920d - dependencies: - get-stdin "^4.0.1" - minimist "^1.1.0" -detective@^4.0.0: - name detective - version "4.3.1" - resolved detective-4.3.1.tgz#9fb06dd1ee8f0ea4dbcc607cda39d9ce1d4f726f - dependencies: - acorn "^1.0.3" - defined "^1.0.0" -detective@^4.3.1: - name detective - version "4.3.1" - resolved detective-4.3.1.tgz#9fb06dd1ee8f0ea4dbcc607cda39d9ce1d4f726f - dependencies: - acorn "^1.0.3" - defined "^1.0.0" -diff@^2.0.2: - name diff - version "2.2.3" - resolved diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99 -diff@^2.1.1 diff@^2.1.2 -diff@^2.1.2: - name diff - version "2.2.3" - resolved diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99 -doctrine@^1.2.2: - name doctrine - version "1.2.2" - resolved doctrine-1.2.2.tgz#9e9867210149548b95ec51469dae4caad312308e - dependencies: - esutils "^1.1.6" - isarray "^1.0.0" -dom-serializer@~0.1.0: - name dom-serializer - version "0.1.0" - resolved dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82 - dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" -dom-serializer@0 dom-serializer@~0.1.0 -dom-walk@^0.1.0: - name dom-walk - version "0.1.1" - resolved dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018 -domelementtype@~1.1.1: - name domelementtype - version "1.1.3" - resolved domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b -domelementtype@1: - name domelementtype - version "1.3.0" - resolved domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2 -domhandler@2.3: - name domhandler - version "2.3.0" - resolved domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738 - dependencies: - domelementtype "1" -domutils@1.4: - name domutils - version "1.4.3" - resolved domutils-1.4.3.tgz#0865513796c6b306031850e175516baf80b72a6f - dependencies: - domelementtype "1" -domutils@1.5: - name domutils - version "1.5.1" - resolved domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf - dependencies: - dom-serializer "0" - domelementtype "1" -download@^4.0.0: - name download - version "4.4.3" - resolved download-4.4.3.tgz#aa55fdad392d95d4b68e8c2be03e0c2aa21ba9ac - dependencies: - is-url "^1.2.0" - caw "^1.0.1" - each-async "^1.0.0" - filenamify "^1.0.1" - got "^5.0.0" - gulp-decompress "^1.2.0" - gulp-rename "^1.2.0" - concat-stream "^1.4.7" - object-assign "^4.0.1" - read-all-stream "^3.0.0" - readable-stream "^2.0.2" - stream-combiner2 "^1.1.1" - vinyl "^1.0.0" - vinyl-fs "^2.2.0" - ware "^1.2.0" -download@^4.1.2: - name download - version "4.4.3" - resolved download-4.4.3.tgz#aa55fdad392d95d4b68e8c2be03e0c2aa21ba9ac - dependencies: - is-url "^1.2.0" - caw "^1.0.1" - each-async "^1.0.0" - filenamify "^1.0.1" - got "^5.0.0" - gulp-decompress "^1.2.0" - gulp-rename "^1.2.0" - concat-stream "^1.4.7" - object-assign "^4.0.1" - read-all-stream "^3.0.0" - readable-stream "^2.0.2" - stream-combiner2 "^1.1.1" - vinyl "^1.0.0" - vinyl-fs "^2.2.0" - ware "^1.2.0" -duplexer2@^0.1.4: - name duplexer2 - version "0.1.4" - resolved duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1 - dependencies: - readable-stream "^2.0.2" -duplexer2@~0.0.2 duplexer2@0.0.2 -duplexer2@~0.1.0: - name duplexer2 - version "0.1.4" - resolved duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1 - dependencies: - readable-stream "^2.0.2" -duplexer2@0.0.2: - name duplexer2 - version "0.0.2" - resolved duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db - dependencies: - readable-stream "~1.1.9" -duplexify@^3.2.0: - name duplexify - version "3.4.3" - resolved duplexify-3.4.3.tgz#af6a7b10d928b095f8ad854d072bb90998db850d - dependencies: - end-of-stream "1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" -each-async@^1.0.0: - name each-async - version "1.1.1" - resolved each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473 - dependencies: - onetime "^1.0.0" - set-immediate-shim "^1.0.0" -each-async@^1.1.1: - name each-async - version "1.1.1" - resolved each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473 - dependencies: - onetime "^1.0.0" - set-immediate-shim "^1.0.0" -ecc-jsbn@~0.1.1: - name ecc-jsbn - version "0.1.1" - resolved ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505 - dependencies: - jsbn "~0.1.0" -ee-first@1.1.1: - name ee-first - version "1.1.1" - resolved ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d -ejs@^2.3.1: - name ejs - version "2.4.2" - resolved ejs-2.4.2.tgz#7057eb4812958fb731841cd9ca353343efe597b1 -encoding@^0.1.11: - name encoding - version "0.1.12" - resolved encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb - dependencies: - iconv-lite "~0.4.13" -end-of-stream@^1.0.0: - name end-of-stream - version "1.1.0" - resolved end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07 - dependencies: - once "~1.3.0" -end-of-stream@1.0.0: - name end-of-stream - version "1.0.0" - resolved end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e - dependencies: - once "~1.3.0" -entities@~1.1.1: - name entities - version "1.1.1" - resolved entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0 -entities@1.0: - name entities - version "1.0.0" - resolved entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26 -"errno@>=0.1.1 <0.2.0-0": - name errno - version "0.1.4" - resolved errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d - dependencies: - prr "~0.0.0" -error-ex@^1.2.0: - name error-ex - version "1.3.0" - resolved error-ex-1.3.0.tgz#e67b43f3e82c96ea3a584ffee0b9fc3325d802d9 - dependencies: - is-arrayish "^0.2.1" -errorhandler@~1.4.2: - name errorhandler - version "1.4.3" - resolved errorhandler-1.4.3.tgz#b7b70ed8f359e9db88092f2d20c0f831420ad83f - dependencies: - accepts "~1.3.0" - escape-html "~1.0.3" -es5-ext@^0.10.7: - name es5-ext - version "0.10.11" - resolved es5-ext-0.10.11.tgz#8184c3e705a820948c2dbe043849379b1dbd0c45 - dependencies: - es6-iterator "2" - es6-symbol "~3.0.2" -es5-ext@^0.10.8: - name es5-ext - version "0.10.11" - resolved es5-ext-0.10.11.tgz#8184c3e705a820948c2dbe043849379b1dbd0c45 - dependencies: - es6-iterator "2" - es6-symbol "~3.0.2" -es5-ext@~0.10.10 es5-ext@~0.10.2 -es5-ext@~0.10.11: - name es5-ext - version "0.10.11" - resolved es5-ext-0.10.11.tgz#8184c3e705a820948c2dbe043849379b1dbd0c45 - dependencies: - es6-iterator "2" - es6-symbol "~3.0.2" -es5-ext@~0.10.2: - name es5-ext - version "0.10.11" - resolved es5-ext-0.10.11.tgz#8184c3e705a820948c2dbe043849379b1dbd0c45 - dependencies: - es6-iterator "2" - es6-symbol "~3.0.2" -es5-ext@~0.10.7: - name es5-ext - version "0.10.11" - resolved es5-ext-0.10.11.tgz#8184c3e705a820948c2dbe043849379b1dbd0c45 - dependencies: - es6-iterator "2" - es6-symbol "~3.0.2" -es6-iterator@2: - name es6-iterator - version "2.0.0" - resolved es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac - dependencies: - d "^0.1.1" - es5-ext "^0.10.7" - es6-symbol "3" -es6-map@^0.1.3: - name es6-map - version "0.1.4" - resolved es6-map-0.1.4.tgz#a34b147be224773a4d7da8072794cefa3632b897 - dependencies: - d "~0.1.1" - es5-ext "~0.10.11" - es6-iterator "2" - es6-set "~0.1.3" - es6-symbol "~3.1.0" - event-emitter "~0.3.4" -es6-set@~0.1.3: - name es6-set - version "0.1.4" - resolved es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8 - dependencies: - d "~0.1.1" - es5-ext "~0.10.11" - es6-iterator "2" - es6-symbol "3" - event-emitter "~0.3.4" -es6-symbol@~3.0.2: - name es6-symbol - version "3.0.2" - resolved es6-symbol-3.0.2.tgz#1e928878c6f5e63541625b4bb4df4af07d154219 - dependencies: - d "~0.1.1" - es5-ext "~0.10.10" -es6-symbol@~3.1.0: - name es6-symbol - version "3.1.0" - resolved es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa - dependencies: - d "~0.1.1" - es5-ext "~0.10.11" -es6-symbol@3 es6-symbol@~3.1.0 -es6-weak-map@^2.0.1: - name es6-weak-map - version "2.0.1" - resolved es6-weak-map-2.0.1.tgz#0d2bbd8827eb5fb4ba8f97fbfea50d43db21ea81 - dependencies: - d "^0.1.1" - es5-ext "^0.10.8" - es6-iterator "2" - es6-symbol "3" -escape-html@~1.0.3: - name escape-html - version "1.0.3" - resolved escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988 -escape-html@1.0.2: - name escape-html - version "1.0.2" - resolved escape-html-1.0.2.tgz#d77d32fa98e38c2f41ae85e9278e0e0e6ba1022c -escape-string-regexp@^1.0.2: - name escape-string-regexp - version "1.0.5" - resolved escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4 -escape-string-regexp@^1.0.5 escape-string-regexp@^1.0.2 -escodegen@^1.6.0: - name escodegen - version "1.8.0" - resolved escodegen-1.8.0.tgz#b246aae829ce73d59e2c55727359edd1c130a81b - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" -escodegen@^1.6.1 escodegen@1.8.x -escodegen@1.8.x: - name escodegen - version "1.8.0" - resolved escodegen-1.8.0.tgz#b246aae829ce73d59e2c55727359edd1c130a81b - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" -escope@^3.6.0: - name escope - version "3.6.0" - resolved escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3 - dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" - esrecurse "^4.1.0" - estraverse "^4.1.1" -eslint-plugin-flow-vars@^0.2.1: - name eslint-plugin-flow-vars - version "0.2.1" - resolved eslint-plugin-flow-vars-0.2.1.tgz#4a0fe4143054edff7fcd241c300bdda721bef160 -eslint-plugin-react@^4.2.1: - name eslint-plugin-react - version "4.3.0" - resolved eslint-plugin-react-4.3.0.tgz#c79aac8069d62de27887c13b8298d592088de378 -eslint@^2.5.3: - name eslint - version "2.13.1" - resolved eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11 - dependencies: - is-resolvable "^1.0.0" - chalk "^1.1.3" - debug "^2.1.1" - doctrine "^1.2.2" - es6-map "^0.1.3" - escope "^3.6.0" - espree "^3.1.6" - estraverse "^4.2.0" - esutils "^2.0.2" - file-entry-cache "^1.1.1" - glob "^7.0.3" - globals "^9.2.0" - ignore "^3.1.2" - imurmurhash "^0.1.4" - inquirer "^0.12.0" - is-my-json-valid "^2.10.0" - concat-stream "^1.4.6" - js-yaml "^3.5.1" - json-stable-stringify "^1.0.0" - levn "^0.3.0" - lodash "^4.0.0" - mkdirp "^0.5.0" - optionator "^0.8.1" - path-is-absolute "^1.0.0" - path-is-inside "^1.0.1" - pluralize "^1.2.1" - progress "^1.1.8" - require-uncached "^1.0.2" - shelljs "^0.6.0" - strip-json-comments "~1.0.1" - table "^3.7.8" - text-table "~0.2.0" - user-home "^2.0.0" -espree@^3.1.6: - name espree - version "3.1.6" - resolved espree-3.1.6.tgz#b26f0824de1436a0e17146e65cdcb728681e21f4 - dependencies: - acorn "^3.2.0" - acorn-jsx "^3.0.0" -esprima-fb@^15001.1.0-dev-harmony-fb: - name esprima-fb - version "15001.1.0-dev-harmony-fb" - resolved esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901 -esprima-fb@~15001.1001.0-dev-harmony-fb: - name esprima-fb - version "15001.1001.0-dev-harmony-fb" - resolved esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659 -esprima@^2.0.0: - name esprima - version "2.7.2" - resolved esprima-2.7.2.tgz#f43be543609984eae44c933ac63352a6af35f339 -esprima@^2.6.0: - name esprima - version "2.7.2" - resolved esprima-2.7.2.tgz#f43be543609984eae44c933ac63352a6af35f339 -esprima@^2.7.1 esprima@^2.6.0 -esprima@2.7.x: - name esprima - version "2.7.2" - resolved esprima-2.7.2.tgz#f43be543609984eae44c933ac63352a6af35f339 -esrecurse@^4.1.0: - name esrecurse - version "4.1.0" - resolved esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220 - dependencies: - estraverse "~4.1.0" - object-assign "^4.0.1" -estraverse@^1.9.1: - name estraverse - version "1.9.3" - resolved estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44 -estraverse@^4.1.1 estraverse@^4.2.0 -estraverse@^4.2.0: - name estraverse - version "4.2.0" - resolved estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13 -estraverse@~4.1.0: - name estraverse - version "4.1.1" - resolved estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2 -esutils@^1.1.6: - name esutils - version "1.1.6" - resolved esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375 -esutils@^2.0.0 esutils@^2.0.2 -esutils@^2.0.2: - name esutils - version "2.0.2" - resolved esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b -etag@~1.7.0: - name etag - version "1.7.0" - resolved etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8 -event-emitter@~0.3.4: - name event-emitter - version "0.3.4" - resolved event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5 - dependencies: - d "~0.1.1" - es5-ext "~0.10.7" -event-target-shim@^1.0.5: - name event-target-shim - version "1.1.1" - resolved event-target-shim-1.1.1.tgz#a86e5ee6bdaa16054475da797ccddf0c55698491 -exec-sh@^0.2.0: - name exec-sh - version "0.2.0" - resolved exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10 - dependencies: - merge "^1.1.3" -executable@^1.0.0: - name executable - version "1.1.0" - resolved executable-1.1.0.tgz#877980e9112f3391066da37265de7ad8434ab4d9 - dependencies: - meow "^3.1.0" -exit-hook@^1.0.0: - name exit-hook - version "1.1.1" - resolved exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8 -expand-brackets@^0.1.4: - name expand-brackets - version "0.1.5" - resolved expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b - dependencies: - is-posix-bracket "^0.1.0" -expand-range@^1.8.1: - name expand-range - version "1.8.2" - resolved expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337 - dependencies: - fill-range "^2.1.0" -express-session@~1.11.3: - name express-session - version "1.11.3" - resolved express-session-1.11.3.tgz#5cc98f3f5ff84ed835f91cbf0aabd0c7107400af - dependencies: - cookie "0.1.3" - cookie-signature "1.0.6" - crc "3.3.0" - debug "~2.2.0" - depd "~1.0.1" - on-headers "~1.0.0" - parseurl "~1.3.0" - uid-safe "~2.0.0" - utils-merge "1.0.0" -extend-shallow@^2.0.1: - name extend-shallow - version "2.0.1" - resolved extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f - dependencies: - is-extendable "^0.1.0" -extend@^3.0.0: - name extend - version "3.0.0" - resolved extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4 -extend@~3.0.0 extend@^3.0.0 -external-editor@^1.0.1: - name external-editor - version "1.0.3" - resolved external-editor-1.0.3.tgz#723b89cc7ea91f59db8bb19df73718f042a0a7a1 - dependencies: - extend "^3.0.0" - spawn-sync "^1.0.15" - temp "^0.8.3" -extglob@^0.3.1: - name extglob - version "0.3.2" - resolved extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1 - dependencies: - is-extglob "^1.0.0" -extsprintf@1.0.2: - name extsprintf - version "1.0.2" - resolved extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550 -fancy-log@^1.1.0: - name fancy-log - version "1.2.0" - resolved fancy-log-1.2.0.tgz#d5a51b53e9ab22ca07d558f2b67ae55fdb5fcbd8 - dependencies: - chalk "^1.1.1" - time-stamp "^1.0.0" -fast-levenshtein@^1.1.0: - name fast-levenshtein - version "1.1.3" - resolved fast-levenshtein-1.1.3.tgz#2ae7b32abc1e612da48a4e13849b888a2f61e7e9 -fb-watchman@^1.8.0: - name fb-watchman - version "1.9.0" - resolved fb-watchman-1.9.0.tgz#6f268f1f347a6b3c875d1e89da7e1ed79adfc0ec - dependencies: - bser "^1.0.2" -fb-watchman@^1.9.0 fb-watchman@^1.8.0 -fbjs-scripts@^0.7.0: - name fbjs-scripts - version "0.7.1" - resolved fbjs-scripts-0.7.1.tgz#4f115e218e243e3addbf0eddaac1e3c62f703fac - dependencies: - babel-core "^6.7.2" - babel-preset-fbjs "^1.0.0" - core-js "^1.0.0" - cross-spawn "^3.0.1" - gulp-util "^3.0.4" - object-assign "^4.0.1" - semver "^5.1.0" - through2 "^2.0.0" -fbjs@^0.8.0: - name fbjs - version "0.8.3" - resolved fbjs-0.8.3.tgz#d98a77d387fda2c33bc3d5588fce31f3553cb1c8 - dependencies: - core-js "^1.0.0" - immutable "^3.7.6" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - ua-parser-js "^0.7.9" -fbjs@^0.8.3: - name fbjs - version "0.8.3" - resolved fbjs-0.8.3.tgz#d98a77d387fda2c33bc3d5588fce31f3553cb1c8 - dependencies: - core-js "^1.0.0" - immutable "^3.7.6" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - ua-parser-js "^0.7.9" -fd-slicer@~1.0.1: - name fd-slicer - version "1.0.1" - resolved fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65 - dependencies: - pend "~1.2.0" -figures@^1.3.5: - name figures - version "1.7.0" - resolved figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" -file-entry-cache@^1.1.1: - name file-entry-cache - version "1.2.4" - resolved file-entry-cache-1.2.4.tgz#9a586072c69365a7ef7ec72a7c2b9046de091e9c - dependencies: - flat-cache "^1.0.9" - object-assign "^4.0.1" -file-type@^3.1.0: - name file-type - version "3.8.0" - resolved file-type-3.8.0.tgz#bcadf6a8f624ebe4a10e5ad26727b6b93f16d78d -filename-regex@^2.0.0: - name filename-regex - version "2.0.0" - resolved filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775 -filename-reserved-regex@^1.0.0: - name filename-reserved-regex - version "1.0.0" - resolved filename-reserved-regex-1.0.0.tgz#e61cf805f0de1c984567d0386dc5df50ee5af7e4 -filenamify@^1.0.1: - name filenamify - version "1.2.1" - resolved filenamify-1.2.1.tgz#a9f2ffd11c503bed300015029272378f1f1365a5 - dependencies: - filename-reserved-regex "^1.0.0" - strip-outer "^1.0.0" - trim-repeated "^1.0.0" -fileset@0.2.x: - name fileset - version "0.2.1" - resolved fileset-0.2.1.tgz#588ef8973c6623b2a76df465105696b96aac8067 - dependencies: - glob "5.x" - minimatch "2.x" -fill-range@^2.1.0: - name fill-range - version "2.2.3" - resolved fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723 - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^1.1.3" - repeat-element "^1.1.2" - repeat-string "^1.5.2" -finalhandler@0.4.0: - name finalhandler - version "0.4.0" - resolved finalhandler-0.4.0.tgz#965a52d9e8d05d2b857548541fb89b53a2497d9b - dependencies: - debug "~2.2.0" - escape-html "1.0.2" - on-finished "~2.3.0" - unpipe "~1.0.0" -find-up@^1.0.0: - name find-up - version "1.1.2" - resolved find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" -find-versions@^1.0.0: - name find-versions - version "1.2.1" - resolved find-versions-1.2.1.tgz#cbde9f12e38575a0af1be1b9a2c5d5fd8f186b62 - dependencies: - array-uniq "^1.0.0" - get-stdin "^4.0.1" - meow "^3.5.0" - semver-regex "^1.0.0" -findup-sync@^0.2.1: - name findup-sync - version "0.2.1" - resolved findup-sync-0.2.1.tgz#e0a90a450075c49466ee513732057514b81e878c - dependencies: - glob "~4.3.0" -first-chunk-stream@^1.0.0: - name first-chunk-stream - version "1.0.0" - resolved first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e -first-chunk-stream@^2.0.0: - name first-chunk-stream - version "2.0.0" - resolved first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70 - dependencies: - readable-stream "^2.0.2" -flat-cache@^1.0.9: - name flat-cache - version "1.0.10" - resolved flat-cache-1.0.10.tgz#73d6df4a28502160a05e059544a6aeeae8b0047a - dependencies: - del "^2.0.2" - graceful-fs "^4.1.2" - read-json-sync "^1.1.0" - write "^0.2.1" -flow-bin@^0.27.0: - name flow-bin - version "0.27.0" - resolved flow-bin-0.27.0.tgz#d849a67b1251f73ab9071e9a3398d1eb538f189b - dependencies: - bin-wrapper "^3.0.2" - logalot "^2.0.0" -for-in@^0.1.5: - name for-in - version "0.1.5" - resolved for-in-0.1.5.tgz#007374e2b6d5c67420a1479bdb75a04872b738c4 -for-own@^0.1.3: - name for-own - version "0.1.4" - resolved for-own-0.1.4.tgz#0149b41a39088c7515f51ebe1c1386d45f935072 - dependencies: - for-in "^0.1.5" -forever-agent@~0.6.1: - name forever-agent - version "0.6.1" - resolved forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91 -form-data@~1.0.0-rc3: - name form-data - version "1.0.0-rc4" - resolved form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e - dependencies: - async "^1.5.2" - combined-stream "^1.0.5" - mime-types "^2.1.10" -formatio@1.1.1: - name formatio - version "1.1.1" - resolved formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9 - dependencies: - samsam "~1.1" -fresh@0.3.0: - name fresh - version "0.3.0" - resolved fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f -fs-extra@^0.26.2: - name fs-extra - version "0.26.7" - resolved fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9 - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" -fs.realpath@^1.0.0: - name fs.realpath - version "1.0.0" - resolved fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f -gauge@~1.2.5: - name gauge - version "1.2.7" - resolved gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93 - dependencies: - ansi "^0.3.0" - has-unicode "^2.0.0" - lodash.pad "^4.1.0" - lodash.padend "^4.1.0" - lodash.padstart "^4.1.0" -generate-function@^2.0.0: - name generate-function - version "2.0.0" - resolved generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74 -generate-object-property@^1.1.0: - name generate-object-property - version "1.2.0" - resolved generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0 - dependencies: - is-property "^1.0.0" -get-proxy@^1.0.1: - name get-proxy - version "1.1.0" - resolved get-proxy-1.1.0.tgz#894854491bc591b0f147d7ae570f5c678b7256eb - dependencies: - rc "^1.1.2" -get-stdin@^4.0.1: - name get-stdin - version "4.0.1" - resolved get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe -getpass@^0.1.1: - name getpass - version "0.1.6" - resolved getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6 - dependencies: - assert-plus "^1.0.0" -gh-got@^2.2.0: - name gh-got - version "2.4.0" - resolved gh-got-2.4.0.tgz#aa51418911ca5e4f92437114cd1209383a4aa019 - dependencies: - got "^5.2.0" - object-assign "^4.0.1" - pinkie-promise "^2.0.0" -github-username@^2.0.0: - name github-username - version "2.1.0" - resolved github-username-2.1.0.tgz#200e5a104af42ba08a54096c708d4b6ec2fa256b - dependencies: - gh-got "^2.2.0" - meow "^3.5.0" -glob-base@^0.3.0: - name glob-base - version "0.3.0" - resolved glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4 - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" -glob-parent@^2.0.0: - name glob-parent - version "2.0.0" - resolved glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28 - dependencies: - is-glob "^2.0.0" -glob-stream@^5.3.2: - name glob-stream - version "5.3.2" - resolved glob-stream-5.3.2.tgz#cdfdaf7c3243cd53430a84dc934fa39d8c5da1a5 - dependencies: - extend "^3.0.0" - glob "^5.0.3" - glob-parent "^2.0.0" - micromatch "^2.3.7" - ordered-read-streams "^0.3.0" - through2 "^0.6.0" - to-absolute-glob "^0.1.1" - unique-stream "^2.0.2" -glob@^5.0.15: - name glob - version "5.0.15" - resolved glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1 - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" -glob@^5.0.3: - name glob - version "5.0.15" - resolved glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1 - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" -glob@^6.0.1: - name glob - version "6.0.4" - resolved glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22 - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" -glob@^7.0.0: - name glob - version "7.0.5" - resolved glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95 - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" -glob@^7.0.3: - name glob - version "7.0.5" - resolved glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95 - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" -glob@~4.3.0: - name glob - version "4.3.5" - resolved glob-4.3.5.tgz#80fbb08ca540f238acce5d11d1e9bc41e75173d3 - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" -glob@5.x glob@^5.0.3 -global@^4.3.0: - name global - version "4.3.0" - resolved global-4.3.0.tgz#ef7ec4beead579b454f5ebd5e7f303db54f42a2b - dependencies: - min-document "^2.6.1" - process "~0.5.1" -globals@^8.3.0: - name globals - version "8.18.0" - resolved globals-8.18.0.tgz#93d4a62bdcac38cfafafc47d6b034768cb0ffcb4 -globals@^9.2.0: - name globals - version "9.8.0" - resolved globals-9.8.0.tgz#a436ecf6214e5f73f110a400305325330a7cfd50 -globby@^4.0.0: - name globby - version "4.1.0" - resolved globby-4.1.0.tgz#080f54549ec1b82a6c60e631fc82e1211dbe95f8 - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^6.0.1" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" -globby@^5.0.0: - name globby - version "5.0.0" - resolved globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" -glogg@^1.0.0: - name glogg - version "1.0.0" - resolved glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5 - dependencies: - sparkles "^1.0.0" -got@^5.0.0: - name got - version "5.6.0" - resolved got-5.6.0.tgz#bb1d7ee163b78082bbc8eb836f3f395004ea6fbf - dependencies: - object-assign "^4.0.1" - create-error-class "^3.0.1" - is-plain-obj "^1.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - duplexer2 "^0.1.4" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^2.0.0" - unzip-response "^1.0.0" - url-parse-lax "^1.0.0" -got@^5.2.0: - name got - version "5.6.0" - resolved got-5.6.0.tgz#bb1d7ee163b78082bbc8eb836f3f395004ea6fbf - dependencies: - object-assign "^4.0.1" - create-error-class "^3.0.1" - is-plain-obj "^1.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - duplexer2 "^0.1.4" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^2.0.0" - unzip-response "^1.0.0" - url-parse-lax "^1.0.0" -graceful-fs@^4.0.0 graceful-fs@^4.1.3 -graceful-fs@^4.1.2 graceful-fs@^4.1.3 -graceful-fs@^4.1.3: - name graceful-fs - version "4.1.4" - resolved graceful-fs-4.1.4.tgz#ef089d2880f033b011823ce5c8fae798da775dbd -"graceful-readlink@>= 1.0.0": - name graceful-readlink - version "1.0.1" - resolved graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725 -grouped-queue@^0.3.0: - name grouped-queue - version "0.3.2" - resolved grouped-queue-0.3.2.tgz#1005f70ece919eccbb37a318f84af99fd6c4eb5c - dependencies: - lodash "^3.10.1" -gruntfile-editor@^1.0.0: - name gruntfile-editor - version "1.2.0" - resolved gruntfile-editor-1.2.0.tgz#169cc7ff532f0b2eb900eec351f7a2bf668302d0 - dependencies: - ast-query "^1.0.1" - lodash "^4.6.1" -gulp-decompress@^1.2.0: - name gulp-decompress - version "1.2.0" - resolved gulp-decompress-1.2.0.tgz#8eeb65a5e015f8ed8532cafe28454960626f0dc7 - dependencies: - archive-type "^3.0.0" - decompress "^3.0.0" - gulp-util "^3.0.1" - readable-stream "^2.0.2" -gulp-rename@^1.2.0: - name gulp-rename - version "1.2.2" - resolved gulp-rename-1.2.2.tgz#3ad4428763f05e2764dec1c67d868db275687817 -gulp-sourcemaps@^1.5.2: - name gulp-sourcemaps - version "1.6.0" - resolved gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c - dependencies: - convert-source-map "^1.1.1" - graceful-fs "^4.1.2" - strip-bom "^2.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" -gulp-util@^3.0.1: - name gulp-util - version "3.0.7" - resolved gulp-util-3.0.7.tgz#78925c4b8f8b49005ac01a011c557e6218941cbb - dependencies: - lodash._reevaluate "^3.0.0" - array-differ "^1.0.0" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^1.0.11" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - array-uniq "^1.0.2" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" -gulp-util@^3.0.4: - name gulp-util - version "3.0.7" - resolved gulp-util-3.0.7.tgz#78925c4b8f8b49005ac01a011c557e6218941cbb - dependencies: - lodash._reevaluate "^3.0.0" - array-differ "^1.0.0" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^1.0.11" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - array-uniq "^1.0.2" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" -gulplog@^1.0.0: - name gulplog - version "1.0.0" - resolved gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5 - dependencies: - glogg "^1.0.0" -handlebars@^4.0.1: - name handlebars - version "4.0.5" - resolved handlebars-4.0.5.tgz#92c6ed6bb164110c50d4d8d0fbddc70806c6f8e7 - dependencies: - async "^1.4.0" - optimist "^0.6.1" - source-map "^0.4.4" - optionalDependencies: - uglify-js "^2.6" -har-validator@~2.0.6: - name har-validator - version "2.0.6" - resolved har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" -has-ansi@^2.0.0: - name has-ansi - version "2.0.0" - resolved has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91 - dependencies: - ansi-regex "^2.0.0" -has-flag@^1.0.0: - name has-flag - version "1.0.0" - resolved has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa -has-gulplog@^0.1.0: - name has-gulplog - version "0.1.0" - resolved has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce - dependencies: - sparkles "^1.0.0" -has-unicode@^2.0.0: - name has-unicode - version "2.0.1" - resolved has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9 -hawk@~3.1.3: - name hawk - version "3.1.3" - resolved hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4 - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" -hoek@2.x.x: - name hoek - version "2.16.3" - resolved hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed -home-or-tmp@^1.0.0: - name home-or-tmp - version "1.0.0" - resolved home-or-tmp-1.0.0.tgz#4b9f1e40800c3e50c6c27f781676afcce71f3985 - dependencies: - os-tmpdir "^1.0.1" - user-home "^1.1.1" -hosted-git-info@^2.1.4: - name hosted-git-info - version "2.1.5" - resolved hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b -html-wiring@^1.0.0: - name html-wiring - version "1.2.0" - resolved html-wiring-1.2.0.tgz#c5f90a776e0a27241dc6df9022c37186d0270f9e - dependencies: - cheerio "^0.19.0" - detect-newline "^1.0.3" -htmlparser2@~3.8.1: - name htmlparser2 - version "3.8.3" - resolved htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068 - dependencies: - domelementtype "1" - domhandler "2.3" - domutils "1.5" - entities "1.0" - readable-stream "1.1" -http-errors@~1.3.1: - name http-errors - version "1.3.1" - resolved http-errors-1.3.1.tgz#197e22cdebd4198585e8694ef6786197b91ed942 - dependencies: - inherits "~2.0.1" - statuses "1" -http-signature@~1.1.0: - name http-signature - version "1.1.1" - resolved http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" -iconv-lite@^0.4.13 iconv-lite@~0.4.13 -iconv-lite@^0.4.5: - name iconv-lite - version "0.4.13" - resolved iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2 -iconv-lite@~0.4.13: - name iconv-lite - version "0.4.13" - resolved iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2 -iconv-lite@0.4.11: - name iconv-lite - version "0.4.11" - resolved iconv-lite-0.4.11.tgz#2ecb42fd294744922209a2e7c404dac8793d8ade -iconv-lite@0.4.13: - name iconv-lite - version "0.4.13" - resolved iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2 -ignore@^3.1.2: - name ignore - version "3.1.3" - resolved ignore-3.1.3.tgz#9e890c0652519115ae9427da47516bd54d1d6999 -image-size@^0.3.5: - name image-size - version "0.3.5" - resolved image-size-0.3.5.tgz#83240eab2fb5b00b04aab8c74b0471e9cba7ad8c -immutable@^3.7.6: - name immutable - version "3.8.1" - resolved immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2 -immutable@~3.7.6: - name immutable - version "3.7.6" - resolved immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b -imurmurhash@^0.1.4: - name imurmurhash - version "0.1.4" - resolved imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea -indent-string@^2.1.0: - name indent-string - version "2.1.0" - resolved indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80 - dependencies: - repeating "^2.0.0" -inflight@^1.0.4: - name inflight - version "1.0.5" - resolved inflight-1.0.5.tgz#db3204cd5a9de2e6cd890b85c6e2f66bcf4f620a - dependencies: - once "^1.3.0" - wrappy "1" -inherits@^2.0.1: - name inherits - version "2.0.1" - resolved inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1 -inherits@~2.0.1 inherits@2 -inherits@2: - name inherits - version "2.0.1" - resolved inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1 -inherits@2.0.1 inherits@2 -ini@~1.3.0: - name ini - version "1.3.4" - resolved ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e -inquirer@^0.12.0: - name inquirer - version "0.12.0" - resolved inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e - dependencies: - lodash "^4.3.0" - ansi-escapes "^1.1.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - figures "^1.3.5" - ansi-regex "^2.0.0" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" -inquirer@^0.8.0: - name inquirer - version "0.8.5" - resolved inquirer-0.8.5.tgz#dbd740cf6ca3b731296a63ce6f6d961851f336df - dependencies: - ansi-regex "^1.1.1" - chalk "^1.0.0" - cli-width "^1.0.1" - figures "^1.3.5" - lodash "^3.3.1" - readline2 "^0.1.1" - rx "^2.4.3" - through "^2.3.6" -inquirer@^1.0.2: - name inquirer - version "1.1.0" - resolved inquirer-1.1.0.tgz#252895668fa9dd1a1dae343178dcbd6bc112a6fb - dependencies: - mute-stream "0.0.6" - ansi-escapes "^1.1.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - external-editor "^1.0.1" - figures "^1.3.5" - lodash "^4.3.0" - chalk "^1.0.0" - pinkie-promise "^2.0.0" - run-async "^2.2.0" - rx "^4.1.0" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" -invariant@^2.2.0: - name invariant - version "2.2.1" - resolved invariant-2.2.1.tgz#b097010547668c7e337028ebe816ebe36c8a8d54 - dependencies: - loose-envify "^1.0.0" -invert-kv@^1.0.0: - name invert-kv - version "1.0.0" - resolved invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6 -is-absolute@^0.1.5: - name is-absolute - version "0.1.7" - resolved is-absolute-0.1.7.tgz#847491119fccb5fb436217cc737f7faad50f603f - dependencies: - is-relative "^0.1.0" -is-arrayish@^0.2.1: - name is-arrayish - version "0.2.1" - resolved is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d -is-buffer@^1.0.2: - name is-buffer - version "1.1.3" - resolved is-buffer-1.1.3.tgz#db897fc3f7aca2d50de94b6c8c2896a4771627af -is-builtin-module@^1.0.0: - name is-builtin-module - version "1.0.0" - resolved is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe - dependencies: - builtin-modules "^1.0.0" -is-bzip2@^1.0.0: - name is-bzip2 - version "1.0.0" - resolved is-bzip2-1.0.0.tgz#5ee58eaa5a2e9c80e21407bedf23ae5ac091b3fc -is-dotfile@^1.0.0: - name is-dotfile - version "1.0.2" - resolved is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d -is-equal-shallow@^0.1.3: - name is-equal-shallow - version "0.1.3" - resolved is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534 - dependencies: - is-primitive "^2.0.0" -is-extendable@^0.1.0: - name is-extendable - version "0.1.1" - resolved is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89 -is-extendable@^0.1.1: - name is-extendable - version "0.1.1" - resolved is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89 -is-extglob@^1.0.0: - name is-extglob - version "1.0.0" - resolved is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0 -is-finite@^1.0.0: - name is-finite - version "1.0.1" - resolved is-finite-1.0.1.tgz#6438603eaebe2793948ff4a4262ec8db3d62597b - dependencies: - number-is-nan "^1.0.0" -is-fullwidth-code-point@^1.0.0: - name is-fullwidth-code-point - version "1.0.0" - resolved is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb - dependencies: - number-is-nan "^1.0.0" -is-glob@^2.0.0: - name is-glob - version "2.0.1" - resolved is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863 - dependencies: - is-extglob "^1.0.0" -is-glob@^2.0.1: - name is-glob - version "2.0.1" - resolved is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863 - dependencies: - is-extglob "^1.0.0" -is-gzip@^1.0.0: - name is-gzip - version "1.0.0" - resolved is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83 -is-my-json-valid@^2.10.0: - name is-my-json-valid - version "2.13.1" - resolved is-my-json-valid-2.13.1.tgz#d55778a82feb6b0963ff4be111d5d1684e890707 - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - jsonpointer "2.0.0" - xtend "^4.0.0" -is-my-json-valid@^2.12.4 is-my-json-valid@^2.10.0 -is-natural-number@^2.0.0: - name is-natural-number - version "2.1.1" - resolved is-natural-number-2.1.1.tgz#7d4c5728377ef386c3e194a9911bf57c6dc335e7 -is-number@^2.0.2 is-number@^2.1.0 -is-number@^2.1.0: - name is-number - version "2.1.0" - resolved is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f - dependencies: - kind-of "^3.0.2" -is-obj@^1.0.0: - name is-obj - version "1.0.1" - resolved is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f -is-path-cwd@^1.0.0: - name is-path-cwd - version "1.0.0" - resolved is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d -is-path-in-cwd@^1.0.0: - name is-path-in-cwd - version "1.0.0" - resolved is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc - dependencies: - is-path-inside "^1.0.0" -is-path-inside@^1.0.0: - name is-path-inside - version "1.0.0" - resolved is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f - dependencies: - path-is-inside "^1.0.1" -is-plain-obj@^1.0.0: - name is-plain-obj - version "1.1.0" - resolved is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e -is-posix-bracket@^0.1.0: - name is-posix-bracket - version "0.1.1" - resolved is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4 -is-primitive@^2.0.0: - name is-primitive - version "2.0.0" - resolved is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575 -is-promise@^2.1.0: - name is-promise - version "2.1.0" - resolved is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa -is-property@^1.0.0: - name is-property - version "1.0.2" - resolved is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84 -is-redirect@^1.0.0: - name is-redirect - version "1.0.0" - resolved is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24 -is-relative@^0.1.0: - name is-relative - version "0.1.3" - resolved is-relative-0.1.3.tgz#905fee8ae86f45b3ec614bc3c15c869df0876e82 -is-resolvable@^1.0.0: - name is-resolvable - version "1.0.0" - resolved is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62 - dependencies: - tryit "^1.0.1" -is-retry-allowed@^1.0.0: - name is-retry-allowed - version "1.0.0" - resolved is-retry-allowed-1.0.0.tgz#2b2070b151823d4044a8bbbf6285b0c42b8703de -is-stream@^1.0.0 is-stream@^1.0.1 -is-stream@^1.0.1: - name is-stream - version "1.1.0" - resolved is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44 -is-tar@^1.0.0: - name is-tar - version "1.0.0" - resolved is-tar-1.0.0.tgz#2f6b2e1792c1f5bb36519acaa9d65c0d26fe853d -is-typedarray@~1.0.0: - name is-typedarray - version "1.0.0" - resolved is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a -is-url@^1.2.0: - name is-url - version "1.2.1" - resolved is-url-1.2.1.tgz#bc92ffd29b23d5f2180e253b916bce6fda711873 -is-utf8@^0.2.0: - name is-utf8 - version "0.2.1" - resolved is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72 -is-valid-glob@^0.3.0: - name is-valid-glob - version "0.3.0" - resolved is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe -is-zip@^1.0.0: - name is-zip - version "1.0.0" - resolved is-zip-1.0.0.tgz#47b0a8ff4d38a76431ccfd99a8e15a4c86ba2325 -isarray@^1.0.0: - name isarray - version "1.0.0" - resolved isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11 -isarray@~1.0.0: - name isarray - version "1.0.0" - resolved isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11 -isarray@0.0.1: - name isarray - version "0.0.1" - resolved isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf -isarray@1.0.0 isarray@^1.0.0 -isemail@1.x.x: - name isemail - version "1.2.0" - resolved isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a -isexe@^1.1.1: - name isexe - version "1.1.2" - resolved isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0 -isobject@^2.0.0: - name isobject - version "2.1.0" - resolved isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89 - dependencies: - isarray "1.0.0" -isomorphic-fetch@^2.1.1: - name isomorphic-fetch - version "2.2.1" - resolved isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9 - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" -isstream@~0.1.2: - name isstream - version "0.1.2" - resolved isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a -istanbul@^0.4.2: - name istanbul - version "0.4.4" - resolved istanbul-0.4.4.tgz#e8cf718dfedb713c8334ab9ffade35f1042d2a56 - dependencies: - mkdirp "0.5.x" - abbrev "1.0.x" - escodegen "1.8.x" - esprima "2.7.x" - fileset "0.2.x" - handlebars "^4.0.1" - js-yaml "3.x" - async "1.x" - nopt "3.x" - once "1.x" - resolve "1.1.x" - supports-color "^3.1.0" - which "^1.1.1" - wordwrap "^1.0.0" -istextorbinary@^1.0.2: - name istextorbinary - version "1.0.2" - resolved istextorbinary-1.0.2.tgz#ace19354d1a9a0173efeb1084ce0f87b0ad7decf - dependencies: - binaryextensions "~1.0.0" - textextensions "~1.0.0" -jest-changed-files@^12.1.0: - name jest-changed-files - version "12.1.0" - resolved jest-changed-files-12.1.0.tgz#7aef2c698b226f539fd4ee7eaa32dc2172e87f49 -jest-cli@^12.1.0: - name jest-cli - version "12.1.1" - resolved jest-cli-12.1.1.tgz#44436de44c64bd5f91a9141c936605db538526d6 - dependencies: - jest-jasmine2 "^12.1.0" - chalk "^1.1.1" - graceful-fs "^4.1.3" - istanbul "^0.4.2" - jest-changed-files "^12.1.0" - jest-environment-jsdom "^12.1.0" - jest-environment-node "^12.1.0" - jest-haste-map "^12.1.0" - jest-jasmine1 "^12.1.0" - cover "^0.2.9" - jest-mock "^12.1.0" - jest-resolve "^12.1.1" - jest-util "^12.1.0" - json-stable-stringify "^1.0.0" - lodash.template "^4.2.4" - optimist "^0.6.1" - sane "^1.2.0" - which "^1.1.1" - worker-farm "^1.3.1" -jest-environment-jsdom@^12.1.0: - name jest-environment-jsdom - version "12.1.0" - resolved jest-environment-jsdom-12.1.0.tgz#d630a61a900efb2a899dfb0d9233a9ae6edba51e - dependencies: - jest-util "^12.1.0" - jsdom "^9.0.0" -jest-environment-node@^12.1.0: - name jest-environment-node - version "12.1.0" - resolved jest-environment-node-12.1.0.tgz#f81890f895f5e9fc3dd7650248a35f2502c24333 - dependencies: - jest-util "^12.1.0" -jest-haste-map@^12.1.0: - name jest-haste-map - version "12.1.0" - resolved jest-haste-map-12.1.0.tgz#e26fef9b80a4391145331f64b3f962ecffb2122f - dependencies: - fb-watchman "^1.9.0" - graceful-fs "^4.1.3" - worker-farm "^1.3.1" -jest-jasmine1@^12.1.0: - name jest-jasmine1 - version "12.1.0" - resolved jest-jasmine1-12.1.0.tgz#0409044d0384ec765e8a2bc38fd78db5f6c55bff - dependencies: - graceful-fs "^4.1.3" - jest-util "^12.1.0" -jest-jasmine2@^12.1.0: - name jest-jasmine2 - version "12.1.0" - resolved jest-jasmine2-12.1.0.tgz#229c1ecc36d04d71d538bc0133c71d87077112eb - dependencies: - graceful-fs "^4.1.3" - jest-util "^12.1.0" -jest-mock@^12.1.0: - name jest-mock - version "12.1.0" - resolved jest-mock-12.1.0.tgz#472b896294f7b91e23dcddbcb6cfa3e6e4b28049 -jest-resolve@^12.1.1: - name jest-resolve - version "12.1.1" - resolved jest-resolve-12.1.1.tgz#25f42e654ae6fa48853edf2502d979d237bde1bc - dependencies: - jest-haste-map "^12.1.0" - resolve "^1.1.6" -jest-util@^12.1.0: - name jest-util - version "12.1.0" - resolved jest-util-12.1.0.tgz#3d3673e81f17e0ec967e9ddc785160a51ab9ec12 - dependencies: - chalk "^1.1.1" - diff "^2.1.1" - graceful-fs "^4.1.3" - jest-mock "^12.1.0" - mkdirp "^0.5.1" -jest@12.1.1: - name jest - version "12.1.1" - resolved jest-12.1.1.tgz#84d3aa1e3430ad515281b8a9136842a77275c4bb - dependencies: - jest-cli "^12.1.0" -jodid25519@^1.0.0: - name jodid25519 - version "1.0.2" - resolved jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967 - dependencies: - jsbn "~0.1.0" -joi@^6.6.1: - name joi - version "6.10.1" - resolved joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06 - dependencies: - hoek "2.x.x" - isemail "1.x.x" - moment "2.x.x" - topo "1.x.x" -js-tokens@^1.0.1: - name js-tokens - version "1.0.3" - resolved js-tokens-1.0.3.tgz#14e56eb68c8f1a92c43d59f5014ec29dc20f2ae1 -js-tokens@^2.0.0: - name js-tokens - version "2.0.0" - resolved js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5 -js-yaml@^3.5.1: - name js-yaml - version "3.6.1" - resolved js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30 - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" -js-yaml@3.x: - name js-yaml - version "3.6.1" - resolved js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30 - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" -jsbn@~0.1.0: - name jsbn - version "0.1.0" - resolved jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd -jsdom@^9.0.0: - name jsdom - version "9.3.0" - resolved jsdom-9.3.0.tgz#6bc89a67f26feaee48389d2464251cda8317e259 - dependencies: - nwmatcher ">= 1.3.7 < 2.0.0" - abab "^1.0.0" - acorn-globals "^1.0.4" - array-equal "^1.0.0" - cssom ">= 0.3.0 < 0.4.0" - cssstyle ">= 0.2.36 < 0.3.0" - escodegen "^1.6.1" - iconv-lite "^0.4.13" - acorn "^2.4.0" - parse5 "^1.5.1" - request "^2.55.0" - sax "^1.1.4" - symbol-tree ">= 3.1.0 < 4.0.0" - tough-cookie "^2.2.0" - webidl-conversions "^3.0.1" - whatwg-url "^3.0.0" - xml-name-validator ">= 2.0.1 < 3.0.0" -jsesc@~0.5.0: - name jsesc - version "0.5.0" - resolved jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d -json-schema@0.2.2: - name json-schema - version "0.2.2" - resolved json-schema-0.2.2.tgz#50354f19f603917c695f70b85afa77c3b0f23506 -json-stable-stringify@^1.0.0: - name json-stable-stringify - version "1.0.1" - resolved json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af - dependencies: - jsonify "~0.0.0" -json-stable-stringify@^1.0.1: - name json-stable-stringify - version "1.0.1" - resolved json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af - dependencies: - jsonify "~0.0.0" -json-stringify-safe@~5.0.1: - name json-stringify-safe - version "5.0.1" - resolved json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb -json5@^0.4.0: - name json5 - version "0.4.0" - resolved json5-0.4.0.tgz#054352e4c4c80c86c0923877d449de176a732c8d -jsonfile@^2.1.0: - name jsonfile - version "2.3.1" - resolved jsonfile-2.3.1.tgz#28bcb29c596b5b7aafd34e662a329ba62cd842fc -jsonify@~0.0.0: - name jsonify - version "0.0.0" - resolved jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73 -jsonparse@^1.1.0: - name jsonparse - version "1.2.0" - resolved jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd -jsonpointer@2.0.0: - name jsonpointer - version "2.0.0" - resolved jsonpointer-2.0.0.tgz#3af1dd20fe85463910d469a385e33017d2a030d9 -JSONStream@^1.0.3: - name JSONStream - version "1.1.2" - resolved JSONStream-1.1.2.tgz#7c01816613d7e5fe41e913edb3b7f359fddbfbf8 - dependencies: - jsonparse "^1.1.0" - through ">=2.2.7 <3" -jsprim@^1.2.2: - name jsprim - version "1.3.0" - resolved jsprim-1.3.0.tgz#ce2e1bef835204b4f3099928c602f8b6ae615650 - dependencies: - extsprintf "1.0.2" - json-schema "0.2.2" - verror "1.3.6" -jstransform@^11.0.3: - name jstransform - version "11.0.3" - resolved jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223 - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" -kind-of@^3.0.2: - name kind-of - version "3.0.3" - resolved kind-of-3.0.3.tgz#c61608747d815b0362556db3276362a7a38aded3 - dependencies: - is-buffer "^1.0.2" -klaw@^1.0.0: - name klaw - version "1.3.0" - resolved klaw-1.3.0.tgz#8857bfbc1d824badf13d3d0241d8bbe46fb12f73 -lazy-cache@^1.0.3: - name lazy-cache - version "1.0.4" - resolved lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e -lazy-req@^1.0.0: - name lazy-req - version "1.1.0" - resolved lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac -lazystream@^1.0.0: - name lazystream - version "1.0.0" - resolved lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4 - dependencies: - readable-stream "^2.0.5" -lcid@^1.0.0: - name lcid - version "1.0.0" - resolved lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835 - dependencies: - invert-kv "^1.0.0" -levn@^0.3.0: - name levn - version "0.3.0" - resolved levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" -levn@~0.3.0: - name levn - version "0.3.0" - resolved levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" -load-json-file@^1.0.0: - name load-json-file - version "1.1.0" - resolved load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0 - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" -lodash._basecopy@^3.0.0: - name lodash._basecopy - version "3.0.1" - resolved lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36 -lodash._baseiteratee@~4.7.0: - name lodash._baseiteratee - version "4.7.0" - resolved lodash._baseiteratee-4.7.0.tgz#34a9b5543572727c3db2e78edae3c0e9e66bd102 - dependencies: - lodash._stringtopath "~4.8.0" -lodash._baseslice@~4.0.0: - name lodash._baseslice - version "4.0.0" - resolved lodash._baseslice-4.0.0.tgz#f5ce1df982948ecaff63f223853415b7b9763704 -lodash._basetostring@^3.0.0: - name lodash._basetostring - version "3.0.1" - resolved lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5 -lodash._basetostring@~4.12.0: - name lodash._basetostring - version "4.12.0" - resolved lodash._basetostring-4.12.0.tgz#9327c9dc5158866b7fa4b9d42f4638e5766dd9df -lodash._basevalues@^3.0.0: - name lodash._basevalues - version "3.0.0" - resolved lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7 -lodash._getnative@^3.0.0: - name lodash._getnative - version "3.9.1" - resolved lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5 -abbrev@1: - name abbrev - version "1.0.9" - resolved abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135 -lodash._reescape@^3.0.0: - name lodash._reescape - version "3.0.0" - resolved lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a -lodash._reevaluate@^3.0.0: - name lodash._reevaluate - version "3.0.0" - resolved lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed -lodash._reinterpolate@^3.0.0: - name lodash._reinterpolate - version "3.0.0" - resolved lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d -lodash._reinterpolate@~3.0.0 lodash._reinterpolate@^3.0.0 -lodash._root@^3.0.0: - name lodash._root - version "3.0.1" - resolved lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692 -lodash._root@~3.0.0: - name lodash._root - version "3.0.1" - resolved lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692 -lodash._stringtopath@~4.8.0: - name lodash._stringtopath - version "4.8.0" - resolved lodash._stringtopath-4.8.0.tgz#941bcf0e64266e5fc1d66fed0a6959544c576824 - dependencies: - lodash._basetostring "~4.12.0" -lodash.assign@^4.0.0: - name lodash.assign - version "4.0.9" - resolved lodash.assign-4.0.9.tgz#0a0731d93590ddd9ba4589fad65aaf6ee09217e3 - dependencies: - lodash.keys "^4.0.0" - lodash.rest "^4.0.0" -lodash.assigninwith@^4.0.0: - name lodash.assigninwith - version "4.0.7" - resolved lodash.assigninwith-4.0.7.tgz#33b8558bd28774e0ac7dd9f04c0f01d9ab0bf61e - dependencies: - lodash.keysin "^4.0.0" - lodash.rest "^4.0.0" -lodash.escape@^3.0.0: - name lodash.escape - version "3.2.0" - resolved lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698 - dependencies: - lodash._root "^3.0.0" -lodash.escape@^4.0.0: - name lodash.escape - version "4.0.0" - resolved lodash.escape-4.0.0.tgz#2628f47ede967e2598306a40b3dfd89c95435dbb - dependencies: - lodash.tostring "^4.0.0" -lodash.isarguments@^3.0.0: - name lodash.isarguments - version "3.0.8" - resolved lodash.isarguments-3.0.8.tgz#5bf8da887f01f2a9e49c0a175cdaeb318a0e43dc -lodash.isarray@^3.0.0: - name lodash.isarray - version "3.0.4" - resolved lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55 -lodash.isequal@^4.0.0: - name lodash.isequal - version "4.2.0" - resolved lodash.isequal-4.2.0.tgz#43f5518f59e37463d3944e846b3a413180b0f46a - dependencies: - lodash._root "~3.0.0" - lodash.keys "^4.0.0" -lodash.keys@^3.0.0: - name lodash.keys - version "3.1.2" - resolved lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" -lodash.keys@^4.0.0: - name lodash.keys - version "4.0.7" - resolved lodash.keys-4.0.7.tgz#30e1b3bd98e54d6a0611991812685b6bc47cb63b -lodash.keysin@^4.0.0: - name lodash.keysin - version "4.1.4" - resolved lodash.keysin-4.1.4.tgz#9147d5819da927785fa1b5c994e9e2caf4a5b1d9 -lodash.pad@^4.1.0: - name lodash.pad - version "4.4.0" - resolved lodash.pad-4.4.0.tgz#faa38df26c0a69ec5086a82246c958e150dcb1ab - dependencies: - lodash._baseslice "~4.0.0" - lodash._basetostring "~4.12.0" - lodash.tostring "^4.0.0" -lodash.padend@^4.1.0: - name lodash.padend - version "4.5.0" - resolved lodash.padend-4.5.0.tgz#a289e9377ee2e6de8ba7f11f3a8eb326070b7619 - dependencies: - lodash._baseslice "~4.0.0" - lodash._basetostring "~4.12.0" - lodash.tostring "^4.0.0" -lodash.padstart@^4.1.0: - name lodash.padstart - version "4.5.0" - resolved lodash.padstart-4.5.0.tgz#3ea190f6734841c3364d279d11e056726b60a79a - dependencies: - lodash._baseslice "~4.0.0" - lodash._basetostring "~4.12.0" - lodash.tostring "^4.0.0" -lodash.pickby@^4.0.0: - name lodash.pickby - version "4.4.0" - resolved lodash.pickby-4.4.0.tgz#490d6c9c37ddd6e2d42b2bc419eba7e729129a40 - dependencies: - lodash._baseiteratee "~4.7.0" - lodash.keysin "^4.0.0" -lodash.rest@^4.0.0: - name lodash.rest - version "4.0.3" - resolved lodash.rest-4.0.3.tgz#4c1c32c40028087250fabf70d42e0151548f48c5 -lodash.restparam@^3.0.0: - name lodash.restparam - version "3.6.1" - resolved lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805 -lodash.template@^3.0.0: - name lodash.template - version "3.6.2" - resolved lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" -lodash.template@^4.2.4: - name lodash.template - version "4.2.5" - resolved lodash.template-4.2.5.tgz#857180dea78a84b90a3db3bfb1d5116bc663e77f - dependencies: - lodash._reinterpolate "~3.0.0" - lodash.assigninwith "^4.0.0" - lodash.keys "^4.0.0" - lodash.rest "^4.0.0" - lodash.templatesettings "^4.0.0" - lodash.tostring "^4.0.0" -lodash.templatesettings@^3.0.0: - name lodash.templatesettings - version "3.1.1" - resolved lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5 - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" -lodash.templatesettings@^4.0.0: - name lodash.templatesettings - version "4.0.1" - resolved lodash.templatesettings-4.0.1.tgz#6c68e40042884ff5ff789d90e30015118c833523 - dependencies: - lodash._reinterpolate "~3.0.0" - lodash.escape "^4.0.0" -lodash.tostring@^4.0.0: - name lodash.tostring - version "4.1.3" - resolved lodash.tostring-4.1.3.tgz#5697f62973f30105a76c2deb3e2d1669f04fd825 -lodash@^3.10.1: - name lodash - version "3.10.1" - resolved lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6 -lodash@^3.2.0 lodash@^3.10.1 -lodash@^3.3.1 lodash@^3.10.1 -lodash@^3.5.0: - name lodash - version "3.10.1" - resolved lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6 -lodash@^3.6.0 lodash@^3.10.1 -lodash@^4.0.0: - name lodash - version "4.13.1" - resolved lodash-4.13.1.tgz#83e4b10913f48496d4d16fec4a560af2ee744b68 -lodash@^4.11.1: - name lodash - version "4.13.1" - resolved lodash-4.13.1.tgz#83e4b10913f48496d4d16fec4a560af2ee744b68 -lodash@^4.2.0: - name lodash - version "4.13.1" - resolved lodash-4.13.1.tgz#83e4b10913f48496d4d16fec4a560af2ee744b68 -lodash@^4.3.0: - name lodash - version "4.13.1" - resolved lodash-4.13.1.tgz#83e4b10913f48496d4d16fec4a560af2ee744b68 -lodash@^4.6.1: - name lodash - version "4.13.1" - resolved lodash-4.13.1.tgz#83e4b10913f48496d4d16fec4a560af2ee744b68 -log-symbols@^1.0.1: - name log-symbols - version "1.0.2" - resolved log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18 - dependencies: - chalk "^1.0.0" -logalot@^2.0.0: - name logalot - version "2.1.0" - resolved logalot-2.1.0.tgz#5f8e8c90d304edf12530951a5554abb8c5e3f552 - dependencies: - figures "^1.3.5" - squeak "^1.0.0" -lolex@1.3.2: - name lolex - version "1.3.2" - resolved lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31 -longest@^1.0.0: - name longest - version "1.0.1" - resolved longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097 -longest@^1.0.1: - name longest - version "1.0.1" - resolved longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097 -loose-envify@^1.0.0: - name loose-envify - version "1.2.0" - resolved loose-envify-1.2.0.tgz#69a65aad3de542cf4ee0f4fe74e8e33c709ccb0f - dependencies: - js-tokens "^1.0.1" -loose-envify@^1.1.0: - name loose-envify - version "1.2.0" - resolved loose-envify-1.2.0.tgz#69a65aad3de542cf4ee0f4fe74e8e33c709ccb0f - dependencies: - js-tokens "^1.0.1" -loud-rejection@^1.0.0: - name loud-rejection - version "1.5.0" - resolved loud-rejection-1.5.0.tgz#be90d8e74d945f6d8112069967a6c4a89173308a - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" -lowercase-keys@^1.0.0: - name lowercase-keys - version "1.0.0" - resolved lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306 -lpad-align@^1.0.1: - name lpad-align - version "1.1.0" - resolved lpad-align-1.1.0.tgz#27fa786bcb695fc434ea1500723eb8d0bdc82bf4 - dependencies: - get-stdin "^4.0.1" - longest "^1.0.0" - lpad "^2.0.1" - meow "^3.3.0" -lpad@^2.0.1: - name lpad - version "2.0.1" - resolved lpad-2.0.1.tgz#28316b4e7b2015f511f6591459afc0e5944008ad -lru-cache@^4.0.0: - name lru-cache - version "4.0.1" - resolved lru-cache-4.0.1.tgz#1343955edaf2e37d9b9e7ee7241e27c4b9fb72be - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" -lru-cache@^4.0.1: - name lru-cache - version "4.0.1" - resolved lru-cache-4.0.1.tgz#1343955edaf2e37d9b9e7ee7241e27c4b9fb72be - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" -makeerror@1.0.x: - name makeerror - version "1.0.11" - resolved makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c - dependencies: - tmpl "1.0.x" -map-obj@^1.0.0: - name map-obj - version "1.0.1" - resolved map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d -map-obj@^1.0.1: - name map-obj - version "1.0.1" - resolved map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d -media-typer@0.3.0: - name media-typer - version "0.3.0" - resolved media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748 -mem-fs-editor@^2.0.0: - name mem-fs-editor - version "2.2.1" - resolved mem-fs-editor-2.2.1.tgz#d4577b67185ada93eed2f4b5ae9ac2d3e6c7fdbf - dependencies: - commondir "^1.0.1" - deep-extend "^0.4.0" - ejs "^2.3.1" - glob "^7.0.3" - globby "^4.0.0" - mkdirp "^0.5.0" - multimatch "^2.0.0" - rimraf "^2.2.8" - through2 "^2.0.0" - vinyl "^1.1.0" -mem-fs@^1.1.0: - name mem-fs - version "1.1.3" - resolved mem-fs-1.1.3.tgz#b8ae8d2e3fcb6f5d3f9165c12d4551a065d989cc - dependencies: - through2 "^2.0.0" - vinyl "^1.1.0" - vinyl-file "^2.0.0" -meow@^3.1.0: - name meow - version "3.7.0" - resolved meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" -meow@^3.3.0: - name meow - version "3.7.0" - resolved meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" -meow@^3.5.0: - name meow - version "3.7.0" - resolved meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" -merge-stream@^1.0.0: - name merge-stream - version "1.0.0" - resolved merge-stream-1.0.0.tgz#9cfd156fef35421e2b5403ce11dc6eb1962b026e - dependencies: - readable-stream "^2.0.1" -merge@^1.1.3: - name merge - version "1.2.0" - resolved merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da -method-override@~2.3.5: - name method-override - version "2.3.6" - resolved method-override-2.3.6.tgz#209261cc588d45d9d5a022ff20d7d5eb8e92179e - dependencies: - debug "~2.2.0" - methods "~1.1.2" - parseurl "~1.3.1" - vary "~1.1.0" -methods@~1.1.2: - name methods - version "1.1.2" - resolved methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee -micromatch@^2.3.7: - name micromatch - version "2.3.10" - resolved micromatch-2.3.10.tgz#f4fb3175beec62795a7b8c24d5f745c3680660ab - dependencies: - is-extglob "^1.0.0" - arr-diff "^2.0.0" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - array-unique "^0.2.1" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" -"mime-db@>= 1.23.0 < 2": - name mime-db - version "1.23.0" - resolved mime-db-1.23.0.tgz#a31b4070adaea27d732ea333740a64d0ec9a6659 -mime-db@~1.23.0: - name mime-db - version "1.23.0" - resolved mime-db-1.23.0.tgz#a31b4070adaea27d732ea333740a64d0ec9a6659 -mime-types@^2.1.10 mime-types@~2.1.9 -mime-types@~2.1.11: - name mime-types - version "2.1.11" - resolved mime-types-2.1.11.tgz#c259c471bda808a85d6cd193b430a5fae4473b3c - dependencies: - mime-db "~1.23.0" -mime-types@~2.1.6: - name mime-types - version "2.1.11" - resolved mime-types-2.1.11.tgz#c259c471bda808a85d6cd193b430a5fae4473b3c - dependencies: - mime-db "~1.23.0" -mime-types@~2.1.7 mime-types@~2.1.9 -mime-types@~2.1.9: - name mime-types - version "2.1.11" - resolved mime-types-2.1.11.tgz#c259c471bda808a85d6cd193b430a5fae4473b3c - dependencies: - mime-db "~1.23.0" -mime@^1.2.9 mime@^1.3.4 -mime@^1.3.4: - name mime - version "1.3.4" - resolved mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53 -mime@1.3.4 mime@^1.3.4 -min-document@^2.6.1: - name min-document - version "2.18.0" - resolved min-document-2.18.0.tgz#23051234b0ae8cc52af8ec6fbb8b4857e442842d - dependencies: - dom-walk "^0.1.0" -minimatch@^2.0.1: - name minimatch - version "2.0.10" - resolved minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7 - dependencies: - brace-expansion "^1.0.0" -minimatch@^3.0.0 minimatch@^3.0.2 -minimatch@^3.0.2: - name minimatch - version "3.0.2" - resolved minimatch-3.0.2.tgz#0f398a7300ea441e9c348c83d98ab8c9dbf9c40a - dependencies: - brace-expansion "^1.0.0" -"minimatch@2 || 3": - name minimatch - version "3.0.2" - resolved minimatch-3.0.2.tgz#0f398a7300ea441e9c348c83d98ab8c9dbf9c40a - dependencies: - brace-expansion "^1.0.0" -minimatch@2.x minimatch@^2.0.1 -minimist@^1.1.0: - name minimist - version "1.2.0" - resolved minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284 -minimist@^1.1.1: - name minimist - version "1.2.0" - resolved minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284 -minimist@^1.1.3 minimist@^1.1.1 -minimist@^1.2.0 minimist@^1.1.1 -minimist@~0.0.1: - name minimist - version "0.0.10" - resolved minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf -minimist@0.0.8: - name minimist - version "0.0.8" - resolved minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d -mkdirp@^0.5.0 mkdirp@0.5.x -mkdirp@^0.5.1: - name mkdirp - version "0.5.1" - resolved mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903 - dependencies: - minimist "0.0.8" -mkdirp@0.5.x: - name mkdirp - version "0.5.1" - resolved mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903 - dependencies: - minimist "0.0.8" -module-deps@^3.9.1: - name module-deps - version "3.9.1" - resolved module-deps-3.9.1.tgz#ea75caf9199090d25b0d5512b5acacb96e7f87f3 - dependencies: - parents "^1.0.0" - browser-resolve "^1.7.0" - defined "^1.0.0" - detective "^4.0.0" - duplexer2 "0.0.2" - inherits "^2.0.1" - JSONStream "^1.0.3" - concat-stream "~1.4.5" - readable-stream "^1.1.13" - resolve "^1.1.3" - stream-combiner2 "~1.0.0" - subarg "^1.0.0" - through2 "^1.0.0" - xtend "^4.0.0" -moment@2.x.x: - name moment - version "2.13.0" - resolved moment-2.13.0.tgz#24162d99521e6d40f99ae6939e806d2139eaac52 -morgan@~1.6.1: - name morgan - version "1.6.1" - resolved morgan-1.6.1.tgz#5fd818398c6819cba28a7cd6664f292fe1c0bbf2 - dependencies: - basic-auth "~1.0.3" - debug "~2.2.0" - depd "~1.0.1" - on-finished "~2.3.0" - on-headers "~1.0.0" -ms@0.7.1: - name ms - version "0.7.1" - resolved ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098 -multimatch@^2.0.0: - name multimatch - version "2.1.0" - resolved multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b - dependencies: - array-differ "^1.0.0" - array-union "^1.0.1" - arrify "^1.0.0" - minimatch "^3.0.0" -multiparty@3.3.2: - name multiparty - version "3.3.2" - resolved multiparty-3.3.2.tgz#35de6804dc19643e5249f3d3e3bdc6c8ce301d3f - dependencies: - readable-stream "~1.1.9" - stream-counter "~0.2.0" -multipipe@^0.1.2: - name multipipe - version "0.1.2" - resolved multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b - dependencies: - duplexer2 "0.0.2" -mute-stream@0.0.4: - name mute-stream - version "0.0.4" - resolved mute-stream-0.0.4.tgz#a9219960a6d5d5d046597aee51252c6655f7177e -mute-stream@0.0.5: - name mute-stream - version "0.0.5" - resolved mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0 -mute-stream@0.0.6: - name mute-stream - version "0.0.6" - resolved mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db -nan@^2.0.5: - name nan - version "2.3.5" - resolved nan-2.3.5.tgz#822a0dc266290ce4cd3a12282ca3e7e364668a08 -negotiator@0.5.3: - name negotiator - version "0.5.3" - resolved negotiator-0.5.3.tgz#269d5c476810ec92edbe7b6c2f28316384f9a7e8 -negotiator@0.6.1: - name negotiator - version "0.6.1" - resolved negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9 -node-fetch@^1.0.1: - name node-fetch - version "1.5.3" - resolved node-fetch-1.5.3.tgz#f28d8b95ca8d45b433745dd319361e36402baef0 - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" -node-fetch@^1.3.3: - name node-fetch - version "1.5.3" - resolved node-fetch-1.5.3.tgz#f28d8b95ca8d45b433745dd319361e36402baef0 - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" -node-haste@~2.12.0: - name node-haste - version "2.12.0" - resolved node-haste-2.12.0.tgz#8d4d1cb87f05adcc4207525b789d7dc717f3c1d7 - dependencies: - absolute-path "^0.0.0" - debug "^2.2.0" - denodeify "^1.2.1" - graceful-fs "^4.1.3" - json-stable-stringify "^1.0.1" - sane "^1.3.1" -node-int64@^0.4.0: - name node-int64 - version "0.4.0" - resolved node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b -node-status-codes@^1.0.0: - name node-status-codes - version "1.0.0" - resolved node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f -node-uuid@~1.4.7 node-uuid@1.4.7 -node-uuid@1.4.7: - name node-uuid - version "1.4.7" - resolved node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f -nopt@^3.0.0: - name nopt - version "3.0.6" - resolved nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9 - dependencies: - abbrev "1" -nopt@3.x nopt@^3.0.0 -normalize-package-data@^2.3.2: - name normalize-package-data - version "2.3.5" - resolved normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df - dependencies: - hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" -normalize-package-data@^2.3.4: - name normalize-package-data - version "2.3.5" - resolved normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df - dependencies: - hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" -normalize-path@^2.0.1: - name normalize-path - version "2.0.1" - resolved normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a -npmlog@^2.0.4: - name npmlog - version "2.0.4" - resolved npmlog-2.0.4.tgz#98b52530f2514ca90d09ec5b22c8846722375692 - dependencies: - ansi "~0.3.1" - are-we-there-yet "~1.1.2" - gauge "~1.2.5" -nth-check@~1.0.0: - name nth-check - version "1.0.1" - resolved nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4 - dependencies: - boolbase "~1.0.0" -number-is-nan@^1.0.0: - name number-is-nan - version "1.0.0" - resolved number-is-nan-1.0.0.tgz#c020f529c5282adfdd233d91d4b181c3d686dc4b -"nwmatcher@>= 1.3.7 < 2.0.0": - name nwmatcher - version "1.3.8" - resolved nwmatcher-1.3.8.tgz#34edb93de1aa6cb4448b573c9f2a059300241157 -oauth-sign@~0.8.1: - name oauth-sign - version "0.8.2" - resolved oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43 -object-assign@^2.0.0: - name object-assign - version "2.1.1" - resolved object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa -object-assign@^3.0.0: - name object-assign - version "3.0.0" - resolved object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2 -object-assign@^4.0.0 object-assign@^4.0.1 -object-assign@^4.0.1: - name object-assign - version "4.1.0" - resolved object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0 -object-assign@^4.1.0: - name object-assign - version "4.1.0" - resolved object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0 -object.omit@^2.0.0: - name object.omit - version "2.0.0" - resolved object.omit-2.0.0.tgz#868597333d54e60662940bb458605dd6ae12fe94 - dependencies: - for-own "^0.1.3" - is-extendable "^0.1.1" -on-finished@~2.3.0: - name on-finished - version "2.3.0" - resolved on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947 - dependencies: - ee-first "1.1.1" -on-headers@~1.0.0: - name on-headers - version "1.0.1" - resolved on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7 -once@^1.3.0: - name once - version "1.3.3" - resolved once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20 - dependencies: - wrappy "1" -once@~1.3.0 once@^1.3.0 -once@1.x once@^1.3.0 -onetime@^1.0.0: - name onetime - version "1.1.0" - resolved onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789 -opn@^3.0.2: - name opn - version "3.0.3" - resolved opn-3.0.3.tgz#b6d99e7399f78d65c3baaffef1fb288e9b85243a - dependencies: - object-assign "^4.0.1" -optimist@^0.6.1: - name optimist - version "0.6.1" - resolved optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686 - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" -optionator@^0.8.1: - name optionator - version "0.8.1" - resolved optionator-0.8.1.tgz#e31b4932cdd5fb862a8b0d10bc63d3ee1ec7d78b - dependencies: - deep-is "~0.1.3" - fast-levenshtein "^1.1.0" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" -options@>=0.0.5: - name options - version "0.0.6" - resolved options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f -ordered-read-streams@^0.3.0: - name ordered-read-streams - version "0.3.0" - resolved ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b - dependencies: - is-stream "^1.0.1" - readable-stream "^2.0.1" -os-filter-obj@^1.0.0: - name os-filter-obj - version "1.0.3" - resolved os-filter-obj-1.0.3.tgz#5915330d90eced557d2d938a31c6dd214d9c63ad -os-homedir@^1.0.0: - name os-homedir - version "1.0.1" - resolved os-homedir-1.0.1.tgz#0d62bdf44b916fd3bbdcf2cab191948fb094f007 -os-locale@^1.4.0: - name os-locale - version "1.4.0" - resolved os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9 - dependencies: - lcid "^1.0.0" -os-shim@^0.1.2: - name os-shim - version "0.1.3" - resolved os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917 -os-tmpdir@^1.0.0: - name os-tmpdir - version "1.0.1" - resolved os-tmpdir-1.0.1.tgz#e9b423a1edaf479882562e92ed71d7743a071b6e -os-tmpdir@^1.0.1: - name os-tmpdir - version "1.0.1" - resolved os-tmpdir-1.0.1.tgz#e9b423a1edaf479882562e92ed71d7743a071b6e -parents@^1.0.0: - name parents - version "1.0.1" - resolved parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751 - dependencies: - path-platform "~0.11.15" -parse-glob@^3.0.4: - name parse-glob - version "3.0.4" - resolved parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" -parse-json@^2.1.0: - name parse-json - version "2.2.0" - resolved parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9 - dependencies: - error-ex "^1.2.0" -parse-json@^2.2.0 parse-json@^2.1.0 -parse5@^1.5.1: - name parse5 - version "1.5.1" - resolved parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94 -parseurl@~1.3.0: - name parseurl - version "1.3.1" - resolved parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56 -parseurl@~1.3.1: - name parseurl - version "1.3.1" - resolved parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56 -path-exists@^1.0.0: - name path-exists - version "1.0.0" - resolved path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081 -path-exists@^2.0.0 path-exists@^2.1.0 -path-exists@^2.1.0: - name path-exists - version "2.1.0" - resolved path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b - dependencies: - pinkie-promise "^2.0.0" -path-is-absolute@^1.0.0: - name path-is-absolute - version "1.0.0" - resolved path-is-absolute-1.0.0.tgz#263dada66ab3f2fb10bf7f9d24dd8f3e570ef912 -path-is-inside@^1.0.1: - name path-is-inside - version "1.0.1" - resolved path-is-inside-1.0.1.tgz#98d8f1d030bf04bd7aeee4a1ba5485d40318fd89 -path-platform@~0.11.15: - name path-platform - version "0.11.15" - resolved path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2 -path-type@^1.0.0: - name path-type - version "1.1.0" - resolved path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441 - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" -pause@0.1.0: - name pause - version "0.1.0" - resolved pause-0.1.0.tgz#ebc8a4a8619ff0b8a81ac1513c3434ff469fdb74 -pegjs@0.9.0: - name pegjs - version "0.9.0" - resolved pegjs-0.9.0.tgz#f6aefa2e3ce56169208e52179dfe41f89141a369 -pend@~1.2.0: - name pend - version "1.2.0" - resolved pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50 -pify@^2.0.0: - name pify - version "2.3.0" - resolved pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c -pify@^2.3.0: - name pify - version "2.3.0" - resolved pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c -pinkie-promise@^2.0.0: - name pinkie-promise - version "2.0.1" - resolved pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa - dependencies: - pinkie "^2.0.0" -pinkie@^2.0.0: - name pinkie - version "2.0.4" - resolved pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870 -plist@^1.2.0: - name plist - version "1.2.0" - resolved plist-1.2.0.tgz#084b5093ddc92506e259f874b8d9b1afb8c79593 - dependencies: - base64-js "0.0.8" - util-deprecate "1.0.2" - xmlbuilder "4.0.0" - xmldom "0.1.x" -plist@1.2.0 plist@^1.2.0 -pluralize@^1.2.1: - name pluralize - version "1.2.1" - resolved pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45 -portfinder@0.4.0: - name portfinder - version "0.4.0" - resolved portfinder-0.4.0.tgz#a3ffadffafe4fb98e0601a85eda27c27ce84ca1e - dependencies: - async "0.9.0" - mkdirp "0.5.x" -prelude-ls@~1.1.2: - name prelude-ls - version "1.1.2" - resolved prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54 -prepend-http@^1.0.1: - name prepend-http - version "1.0.4" - resolved prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc -preserve@^0.2.0: - name preserve - version "0.2.0" - resolved preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b -pretty-bytes@^2.0.1: - name pretty-bytes - version "2.0.1" - resolved pretty-bytes-2.0.1.tgz#155ec4d0036f41391e7045d6dbe4963d525d264f - dependencies: - get-stdin "^4.0.1" - meow "^3.1.0" - number-is-nan "^1.0.0" -private@^0.1.6: - name private - version "0.1.6" - resolved private-0.1.6.tgz#55c6a976d0f9bafb9924851350fe47b9b5fbb7c1 -private@~0.1.5: - name private - version "0.1.6" - resolved private-0.1.6.tgz#55c6a976d0f9bafb9924851350fe47b9b5fbb7c1 -process-nextick-args@~1.0.6: - name process-nextick-args - version "1.0.7" - resolved process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3 -process@~0.5.1: - name process - version "0.5.2" - resolved process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf -progress@^1.1.8: - name progress - version "1.1.8" - resolved progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be -promise@^7.1.1: - name promise - version "7.1.1" - resolved promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf - dependencies: - asap "~2.0.3" -prr@~0.0.0: - name prr - version "0.0.0" - resolved prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a -pseudomap@^1.0.1: - name pseudomap - version "1.0.2" - resolved pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3 -q@^1.1.2: - name q - version "1.4.1" - resolved q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e -qs@~6.1.0: - name qs - version "6.1.0" - resolved qs-6.1.0.tgz#ec1d1626b24278d99f0fdf4549e524e24eceeb26 -qs@4.0.0: - name qs - version "4.0.0" - resolved qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607 -random-bytes@~1.0.0: - name random-bytes - version "1.0.0" - resolved random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b -randomatic@^1.1.3: - name randomatic - version "1.1.5" - resolved randomatic-1.1.5.tgz#5e9ef5f2d573c67bd2b8124ae90b5156e457840b - dependencies: - is-number "^2.0.2" - kind-of "^3.0.2" -range-parser@~1.0.3: - name range-parser - version "1.0.3" - resolved range-parser-1.0.3.tgz#6872823535c692e2c2a0103826afd82c2e0ff175 -raw-body@~2.1.2: - name raw-body - version "2.1.7" - resolved raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774 - dependencies: - bytes "2.4.0" - iconv-lite "0.4.13" - unpipe "1.0.0" -rc@^1.1.2: - name rc - version "1.1.6" - resolved rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9 - dependencies: - deep-extend "~0.4.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~1.0.4" -react-clone-referenced-element@^1.0.1: - name react-clone-referenced-element - version "1.0.1" - resolved react-clone-referenced-element-1.0.1.tgz#2bba8c69404c5e4a944398600bcc4c941f860682 -react-deep-force-update@^1.0.0: - name react-deep-force-update - version "1.0.1" - resolved react-deep-force-update-1.0.1.tgz#f911b5be1d2a6fe387507dd6e9a767aa2924b4c7 -react-proxy@^1.1.7: - name react-proxy - version "1.1.8" - resolved react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a - dependencies: - lodash "^4.6.1" - react-deep-force-update "^1.0.0" -react-timer-mixin@^0.13.2: - name react-timer-mixin - version "0.13.3" - resolved react-timer-mixin-0.13.3.tgz#0da8b9f807ec07dc3e854d082c737c65605b3d22 -react-transform-hmr@^1.0.4: - name react-transform-hmr - version "1.0.4" - resolved react-transform-hmr-1.0.4.tgz#e1a40bd0aaefc72e8dfd7a7cda09af85066397bb - dependencies: - global "^4.3.0" - react-proxy "^1.1.7" -react@~15.1.0: - name react - version "15.1.0" - resolved react-15.1.0.tgz#5f7a9f085a00509898efd2b24cb12ea1dfaf8b40 - dependencies: - fbjs "^0.8.0" - loose-envify "^1.1.0" - object-assign "^4.1.0" -read-all-stream@^3.0.0: - name read-all-stream - version "3.1.0" - resolved read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" -read-chunk@^1.0.1: - name read-chunk - version "1.0.1" - resolved read-chunk-1.0.1.tgz#5f68cab307e663f19993527d9b589cace4661194 -read-json-sync@^1.1.0: - name read-json-sync - version "1.1.1" - resolved read-json-sync-1.1.1.tgz#43c669ae864aae308dfbbb2721a67e295ec8fff6 - dependencies: - graceful-fs "^4.1.2" -read-pkg-up@^1.0.1: - name read-pkg-up - version "1.0.1" - resolved read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02 - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" -read-pkg@^1.0.0: - name read-pkg - version "1.1.0" - resolved read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28 - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" -readable-stream@^1.1.13: - name readable-stream - version "1.1.14" - resolved readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9 - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" -readable-stream@^2.0.0: - name readable-stream - version "2.1.4" - resolved readable-stream-2.1.4.tgz#70b9791c6fcb8480db44bd155a0f6bb58f172468 - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" -"readable-stream@^2.0.0 || ^1.1.13": - name readable-stream - version "2.1.4" - resolved readable-stream-2.1.4.tgz#70b9791c6fcb8480db44bd155a0f6bb58f172468 - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" -readable-stream@^2.0.1 readable-stream@^2.0.2 -readable-stream@^2.0.2: - name readable-stream - version "2.1.4" - resolved readable-stream-2.1.4.tgz#70b9791c6fcb8480db44bd155a0f6bb58f172468 - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" -readable-stream@^2.0.4: - name readable-stream - version "2.1.4" - resolved readable-stream-2.1.4.tgz#70b9791c6fcb8480db44bd155a0f6bb58f172468 - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" -readable-stream@^2.0.5: - name readable-stream - version "2.1.4" - resolved readable-stream-2.1.4.tgz#70b9791c6fcb8480db44bd155a0f6bb58f172468 - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" -"readable-stream@>=1.0.33-1 <1.1.0-0" readable-stream@~1.0.17 -"readable-stream@>=1.1.13-1 <1.2.0-0": - name readable-stream - version "1.1.14" - resolved readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9 - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" -readable-stream@~1.0.17: - name readable-stream - version "1.0.34" - resolved readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" -readable-stream@~1.1.8: - name readable-stream - version "1.1.14" - resolved readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9 - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" -readable-stream@~1.1.9: - name readable-stream - version "1.1.14" - resolved readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9 - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" -readable-stream@~2.0.0: - name readable-stream - version "2.0.6" - resolved readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" -readable-stream@~2.0.5 readable-stream@~2.0.0 -readable-stream@1.1: - name readable-stream - version "1.1.13" - resolved readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" -readline2@^0.1.1: - name readline2 - version "0.1.1" - resolved readline2-0.1.1.tgz#99443ba6e83b830ef3051bfd7dc241a82728d568 - dependencies: - mute-stream "0.0.4" - strip-ansi "^2.0.1" -readline2@^1.0.1: - name readline2 - version "1.0.1" - resolved readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35 - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - mute-stream "0.0.5" -rebound@^0.0.13: - name rebound - version "0.0.13" - resolved rebound-0.0.13.tgz#4a225254caf7da756797b19c5817bf7a7941fac1 -recast@^0.10.0: - name recast - version "0.10.43" - resolved recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f - dependencies: - ast-types "0.8.15" - esprima-fb "~15001.1001.0-dev-harmony-fb" - private "~0.1.5" - source-map "~0.5.0" -redent@^1.0.0: - name redent - version "1.0.0" - resolved redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" -regenerate@^1.2.1: - name regenerate - version "1.3.1" - resolved regenerate-1.3.1.tgz#0300203a5d2fdcf89116dce84275d011f5903f33 -regenerator-runtime@^0.9.5: - name regenerator-runtime - version "0.9.5" - resolved regenerator-runtime-0.9.5.tgz#403d6d40a4bdff9c330dd9392dcbb2d9a8bba1fc -regex-cache@^0.4.2: - name regex-cache - version "0.4.3" - resolved regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145 - dependencies: - is-equal-shallow "^0.1.3" - is-primitive "^2.0.0" -regexpu-core@^2.0.0: - name regexpu-core - version "2.0.0" - resolved regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240 - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" -regjsgen@^0.2.0: - name regjsgen - version "0.2.0" - resolved regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7 -regjsparser@^0.1.4: - name regjsparser - version "0.1.5" - resolved regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c - dependencies: - jsesc "~0.5.0" -repeat-element@^1.1.2: - name repeat-element - version "1.1.2" - resolved repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a -repeat-string@^1.5.2: - name repeat-string - version "1.5.4" - resolved repeat-string-1.5.4.tgz#64ec0c91e0f4b475f90d5b643651e3e6e5b6c2d5 -repeating@^1.1.0: - name repeating - version "1.1.3" - resolved repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac - dependencies: - is-finite "^1.0.0" -repeating@^2.0.0: - name repeating - version "2.0.1" - resolved repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda - dependencies: - is-finite "^1.0.0" -replace-ext@0.0.1: - name replace-ext - version "0.0.1" - resolved replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924 -request@^2.55.0: - name request - version "2.72.0" - resolved request-2.72.0.tgz#0ce3a179512620b10441f14c82e21c12c0ddb4e1 - dependencies: - http-signature "~1.1.0" - aws-sign2 "~0.6.0" - bl "~1.1.2" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~1.0.0-rc3" - har-validator "~2.0.6" - hawk "~3.1.3" - aws4 "^1.2.1" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - node-uuid "~1.4.7" - oauth-sign "~0.8.1" - qs "~6.1.0" - stringstream "~0.0.4" - tough-cookie "~2.2.0" - tunnel-agent "~0.4.1" -require-uncached@^1.0.2: - name require-uncached - version "1.0.2" - resolved require-uncached-1.0.2.tgz#67dad3b733089e77030124678a459589faf6a7ec - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" -resolve-from@^1.0.0: - name resolve-from - version "1.0.1" - resolved resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226 -resolve@^1.1.3: - name resolve - version "1.1.7" - resolved resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b -resolve@^1.1.6 resolve@^1.1.3 -resolve@1.1.7: - name resolve - version "1.1.7" - resolved resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b -resolve@1.1.x resolve@^1.1.3 -response-time@~2.3.1: - name response-time - version "2.3.1" - resolved response-time-2.3.1.tgz#2bde19181de6c81ab95e3207a28d61d965b31797 - dependencies: - depd "~1.0.1" - on-headers "~1.0.0" -restore-cursor@^1.0.1: - name restore-cursor - version "1.0.1" - resolved restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541 - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" -right-align@^0.1.1: - name right-align - version "0.1.3" - resolved right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef - dependencies: - align-text "^0.1.1" -rimraf@^2.2.0: - name rimraf - version "2.5.2" - resolved rimraf-2.5.2.tgz#62ba947fa4c0b4363839aefecd4f0fbad6059726 - dependencies: - glob "^7.0.0" -rimraf@^2.2.8: - name rimraf - version "2.5.2" - resolved rimraf-2.5.2.tgz#62ba947fa4c0b4363839aefecd4f0fbad6059726 - dependencies: - glob "^7.0.0" -rimraf@~2.2.6: - name rimraf - version "2.2.8" - resolved rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582 -rndm@1.2.0: - name rndm - version "1.2.0" - resolved rndm-1.2.0.tgz#f33fe9cfb52bbfd520aa18323bc65db110a1b76c -run-async@^0.1.0: - name run-async - version "0.1.0" - resolved run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389 - dependencies: - once "^1.3.0" -run-async@^2.2.0: - name run-async - version "2.2.0" - resolved run-async-2.2.0.tgz#8783abd83c7bb86f41ee0602fc82404b3bd6e8b9 - dependencies: - is-promise "^2.1.0" - pinkie-promise "^2.0.0" -rx-lite@^3.1.2: - name rx-lite - version "3.1.2" - resolved rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102 -rx@^2.4.3: - name rx - version "2.5.3" - resolved rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566 -rx@^4.1.0: - name rx - version "4.1.0" - resolved rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782 -samsam@~1.1: - name samsam - version "1.1.3" - resolved samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621 -samsam@1.1.2: - name samsam - version "1.1.2" - resolved samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567 -sane@^1.2.0: - name sane - version "1.3.5" - resolved sane-1.3.5.tgz#4dfc9955eb1e69008f1b023af8865e22e19f4647 - dependencies: - exec-sh "^0.2.0" - fb-watchman "^1.8.0" - minimatch "^3.0.2" - minimist "^1.1.1" - walker "~1.0.5" - watch "~0.10.0" -sane@^1.3.1: - name sane - version "1.3.5" - resolved sane-1.3.5.tgz#4dfc9955eb1e69008f1b023af8865e22e19f4647 - dependencies: - exec-sh "^0.2.0" - fb-watchman "^1.8.0" - minimatch "^3.0.2" - minimist "^1.1.1" - walker "~1.0.5" - watch "~0.10.0" -sax@^1.1.4: - name sax - version "1.2.1" - resolved sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a -sax@~1.1.1: - name sax - version "1.1.6" - resolved sax-1.1.6.tgz#5d616be8a5e607d54e114afae55b7eaf2fcc3240 -seek-bzip@^1.0.3: - name seek-bzip - version "1.0.5" - resolved seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc - dependencies: - commander "~2.8.1" -semver-regex@^1.0.0: - name semver-regex - version "1.0.0" - resolved semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9 -semver-truncate@^1.0.0: - name semver-truncate - version "1.1.0" - resolved semver-truncate-1.1.0.tgz#8904f5dc8d0091841c8df28042fd001323b130d7 - dependencies: - semver "^5.0.3" -semver@^4.0.3: - name semver - version "4.3.6" - resolved semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da -semver@^5.0.3: - name semver - version "5.1.1" - resolved semver-5.1.1.tgz#a3292a373e6f3e0798da0b20641b9a9c5bc47e19 -semver@^5.1.0: - name semver - version "5.1.1" - resolved semver-5.1.1.tgz#a3292a373e6f3e0798da0b20641b9a9c5bc47e19 -"semver@2 || 3 || 4 || 5" semver@^5.0.3 -send@0.13.2: - name send - version "0.13.2" - resolved send-0.13.2.tgz#765e7607c8055452bba6f0b052595350986036de - dependencies: - http-errors "~1.3.1" - debug "~2.2.0" - destroy "~1.0.4" - escape-html "~1.0.3" - etag "~1.7.0" - fresh "0.3.0" - depd "~1.1.0" - mime "1.3.4" - ms "0.7.1" - on-finished "~2.3.0" - range-parser "~1.0.3" - statuses "~1.2.1" -serve-favicon@~2.3.0: - name serve-favicon - version "2.3.0" - resolved serve-favicon-2.3.0.tgz#aed36cc6834069a6f189cc7222c6a1a811dc5b39 - dependencies: - etag "~1.7.0" - fresh "0.3.0" - ms "0.7.1" - parseurl "~1.3.0" -serve-index@~1.7.2: - name serve-index - version "1.7.3" - resolved serve-index-1.7.3.tgz#7a057fc6ee28dc63f64566e5fa57b111a86aecd2 - dependencies: - accepts "~1.2.13" - batch "0.5.3" - debug "~2.2.0" - escape-html "~1.0.3" - http-errors "~1.3.1" - mime-types "~2.1.9" - parseurl "~1.3.1" -serve-static@~1.10.0: - name serve-static - version "1.10.3" - resolved serve-static-1.10.3.tgz#ce5a6ecd3101fed5ec09827dac22a9c29bfb0535 - dependencies: - escape-html "~1.0.3" - parseurl "~1.3.1" - send "0.13.2" -set-immediate-shim@^1.0.0: - name set-immediate-shim - version "1.0.1" - resolved set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61 -shebang-regex@^1.0.0: - name shebang-regex - version "1.0.0" - resolved shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3 -shelljs@^0.5.0: - name shelljs - version "0.5.3" - resolved shelljs-0.5.3.tgz#c54982b996c76ef0c1e6b59fbdc5825f5b713113 -shelljs@^0.6.0 shelljs@0.6.0 -shelljs@0.6.0: - name shelljs - version "0.6.0" - resolved shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec -signal-exit@^3.0.0: - name signal-exit - version "3.0.0" - resolved signal-exit-3.0.0.tgz#3c0543b65d7b4fbc60b6cd94593d9bf436739be8 -simple-plist@0.1.4: - name simple-plist - version "0.1.4" - resolved simple-plist-0.1.4.tgz#10eb51b47e33c556eb8ec46d5ee64d64e717db5d - dependencies: - bplist-creator "0.0.4" - bplist-parser "0.0.6" - plist "1.2.0" -sinon@^1.9.1: - name sinon - version "1.17.4" - resolved sinon-1.17.4.tgz#4e4ff4d84b20adee13138f36acb132ca1cd72c83 - dependencies: - formatio "1.1.1" - lolex "1.3.2" - samsam "1.1.2" - util ">=0.10.3 <1" -slash@^1.0.0: - name slash - version "1.0.0" - resolved slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55 -slice-ansi@0.0.4: - name slice-ansi - version "0.0.4" - resolved slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35 -sntp@1.x.x: - name sntp - version "1.0.9" - resolved sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198 - dependencies: - hoek "2.x.x" -source-map-support@^0.2.10: - name source-map-support - version "0.2.10" - resolved source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc - dependencies: - source-map "0.1.32" -source-map@^0.4.2: - name source-map - version "0.4.4" - resolved source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b - dependencies: - amdefine ">=0.0.4" -source-map@^0.4.4: - name source-map - version "0.4.4" - resolved source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b - dependencies: - amdefine ">=0.0.4" -source-map@^0.5.0: - name source-map - version "0.5.6" - resolved source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412 -source-map@~0.2.0: - name source-map - version "0.2.0" - resolved source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d - dependencies: - amdefine ">=0.0.4" -source-map@~0.5.0 source-map@^0.5.0 -source-map@~0.5.1: - name source-map - version "0.5.6" - resolved source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412 -source-map@0.1.32: - name source-map - version "0.1.32" - resolved source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266 - dependencies: - amdefine ">=0.0.4" -sparkles@^1.0.0: - name sparkles - version "1.0.0" - resolved sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3 -spawn-sync@^1.0.15: - name spawn-sync - version "1.0.15" - resolved spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476 - dependencies: - concat-stream "^1.4.7" - os-shim "^0.1.2" -spdx-correct@~1.0.0: - name spdx-correct - version "1.0.2" - resolved spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40 - dependencies: - spdx-license-ids "^1.0.2" -spdx-exceptions@^1.0.4: - name spdx-exceptions - version "1.0.4" - resolved spdx-exceptions-1.0.4.tgz#220b84239119ae9045a892db81a83f4ce16f80fd -spdx-expression-parse@~1.0.0: - name spdx-expression-parse - version "1.0.2" - resolved spdx-expression-parse-1.0.2.tgz#d52b14b5e9670771440af225bcb563122ac452f6 - dependencies: - spdx-exceptions "^1.0.4" - spdx-license-ids "^1.0.0" -spdx-license-ids@^1.0.0: - name spdx-license-ids - version "1.2.1" - resolved spdx-license-ids-1.2.1.tgz#d07ea17a4d2fd9351f9d94e2ff9cec74180fe8f3 -spdx-license-ids@^1.0.2: - name spdx-license-ids - version "1.2.1" - resolved spdx-license-ids-1.2.1.tgz#d07ea17a4d2fd9351f9d94e2ff9cec74180fe8f3 -sprintf-js@^1.0.3: - name sprintf-js - version "1.0.3" - resolved sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c -sprintf-js@~1.0.2 sprintf-js@^1.0.3 -squeak@^1.0.0: - name squeak - version "1.3.0" - resolved squeak-1.3.0.tgz#33045037b64388b567674b84322a6521073916c3 - dependencies: - chalk "^1.0.0" - console-stream "^0.1.1" - lpad-align "^1.0.1" -sshpk@^1.7.0: - name sshpk - version "1.8.3" - resolved sshpk-1.8.3.tgz#890cc9d614dc5292e5cb1a543b03c9abaa5c374e - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: - ecc-jsbn "~0.1.1" - jodid25519 "^1.0.0" - jsbn "~0.1.0" - tweetnacl "~0.13.0" -stacktrace-parser@^0.1.3: - name stacktrace-parser - version "0.1.3" - resolved stacktrace-parser-0.1.3.tgz#55a99a512bef62090d9ac515920b3f96119c572e -stat-mode@^0.2.0: - name stat-mode - version "0.2.1" - resolved stat-mode-0.2.1.tgz#d714e08a4ed157089c1340f76fee54046c8242d6 -statuses@~1.2.1: - name statuses - version "1.2.1" - resolved statuses-1.2.1.tgz#dded45cc18256d51ed40aec142489d5c61026d28 -statuses@1: - name statuses - version "1.3.0" - resolved statuses-1.3.0.tgz#8e55758cb20e7682c1f4fce8dcab30bf01d1e07a -stream-buffers@~0.2.3: - name stream-buffers - version "0.2.6" - resolved stream-buffers-0.2.6.tgz#181c08d5bb3690045f69401b9ae6a7a0cf3313fc -stream-combiner2@^1.1.1: - name stream-combiner2 - version "1.1.1" - resolved stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" -stream-combiner2@~1.0.0: - name stream-combiner2 - version "1.0.2" - resolved stream-combiner2-1.0.2.tgz#ba72a6b50cbfabfa950fc8bc87604bd01eb60671 - dependencies: - duplexer2 "~0.0.2" - through2 "~0.5.1" -stream-counter@~0.2.0: - name stream-counter - version "0.2.0" - resolved stream-counter-0.2.0.tgz#ded266556319c8b0e222812b9cf3b26fa7d947de - dependencies: - readable-stream "~1.1.8" -string_decoder@~0.10.x: - name string_decoder - version "0.10.31" - resolved string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94 -string-width@^1.0.1: - name string-width - version "1.0.1" - resolved string-width-1.0.1.tgz#c92129b6f1d7f52acf9af424a26e3864a05ceb0a - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" -stringstream@~0.0.4: - name stringstream - version "0.0.5" - resolved stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878 -strip-ansi@^2.0.1: - name strip-ansi - version "2.0.1" - resolved strip-ansi-2.0.1.tgz#df62c1aa94ed2f114e1d0f21fd1d50482b79a60e - dependencies: - ansi-regex "^1.0.0" -strip-ansi@^3.0.0: - name strip-ansi - version "3.0.1" - resolved strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf - dependencies: - ansi-regex "^2.0.0" -strip-ansi@^3.0.1 strip-ansi@^3.0.0 -strip-bom-stream@^1.0.0: - name strip-bom-stream - version "1.0.0" - resolved strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee - dependencies: - first-chunk-stream "^1.0.0" - strip-bom "^2.0.0" -strip-bom-stream@^2.0.0: - name strip-bom-stream - version "2.0.0" - resolved strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca - dependencies: - first-chunk-stream "^2.0.0" - strip-bom "^2.0.0" -strip-bom@^2.0.0: - name strip-bom - version "2.0.0" - resolved strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e - dependencies: - is-utf8 "^0.2.0" -strip-dirs@^1.0.0: - name strip-dirs - version "1.1.1" - resolved strip-dirs-1.1.1.tgz#960bbd1287844f3975a4558aa103a8255e2456a0 - dependencies: - chalk "^1.0.0" - get-stdin "^4.0.1" - is-absolute "^0.1.5" - is-natural-number "^2.0.0" - minimist "^1.1.0" - sum-up "^1.0.1" -strip-indent@^1.0.1: - name strip-indent - version "1.0.1" - resolved strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2 - dependencies: - get-stdin "^4.0.1" -strip-json-comments@~1.0.1: - name strip-json-comments - version "1.0.4" - resolved strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91 -strip-json-comments@~1.0.4 strip-json-comments@~1.0.1 -strip-outer@^1.0.0: - name strip-outer - version "1.0.0" - resolved strip-outer-1.0.0.tgz#aac0ba60d2e90c5d4f275fd8869fd9a2d310ffb8 - dependencies: - escape-string-regexp "^1.0.2" -subarg@^1.0.0: - name subarg - version "1.0.0" - resolved subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2 - dependencies: - minimist "^1.1.0" -sum-up@^1.0.1: - name sum-up - version "1.0.3" - resolved sum-up-1.0.3.tgz#1c661f667057f63bcb7875aa1438bc162525156e - dependencies: - chalk "^1.0.0" -supports-color@^2.0.0: - name supports-color - version "2.0.0" - resolved supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7 -supports-color@^3.1.0: - name supports-color - version "3.1.2" - resolved supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5 - dependencies: - has-flag "^1.0.0" -"symbol-tree@>= 3.1.0 < 4.0.0": - name symbol-tree - version "3.1.4" - resolved symbol-tree-3.1.4.tgz#02b279348d337debc39694c5c95f882d448a312a -table@^3.7.8: - name table - version "3.7.8" - resolved table-3.7.8.tgz#b424433ef596851922b2fd77224a69a1951618eb - dependencies: - bluebird "^3.1.1" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^1.0.1" - strip-ansi "^3.0.0" - tv4 "^1.2.7" - xregexp "^3.0.0" -tar-stream@^1.1.1: - name tar-stream - version "1.5.2" - resolved tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf - dependencies: - bl "^1.0.0" - end-of-stream "^1.0.0" - readable-stream "^2.0.0" - xtend "^4.0.0" -temp@^0.8.3 temp@0.8.3 -temp@0.8.3: - name temp - version "0.8.3" - resolved temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59 - dependencies: - os-tmpdir "^1.0.0" - rimraf "~2.2.6" -text-table@^0.2.0: - name text-table - version "0.2.0" - resolved text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4 -text-table@~0.2.0: - name text-table - version "0.2.0" - resolved text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4 -textextensions@~1.0.0: - name textextensions - version "1.0.2" - resolved textextensions-1.0.2.tgz#65486393ee1f2bb039a60cbba05b0b68bd9501d2 -through@^2.3.6: - name through - version "2.3.8" - resolved through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5 -"through@>=2.2.7 <3" through@^2.3.6 -through2-filter@^2.0.0: - name through2-filter - version "2.0.0" - resolved through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" -through2@^0.6.0: - name through2 - version "0.6.5" - resolved through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48 - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" -through2@^0.6.1 through2@^0.6.0 -through2@^1.0.0: - name through2 - version "1.1.1" - resolved through2-1.1.1.tgz#0847cbc4449f3405574dbdccd9bb841b83ac3545 - dependencies: - readable-stream ">=1.1.13-1 <1.2.0-0" - xtend ">=4.0.0 <4.1.0-0" -through2@^2.0.0: - name through2 - version "2.0.1" - resolved through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9 - dependencies: - readable-stream "~2.0.0" - xtend "~4.0.0" -through2@~0.5.1: - name through2 - version "0.5.1" - resolved through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7 - dependencies: - readable-stream "~1.0.17" - xtend "~3.0.0" -through2@~2.0.0 through2@^2.0.0 -time-stamp@^1.0.0: - name time-stamp - version "1.0.1" - resolved time-stamp-1.0.1.tgz#9f4bd23559c9365966f3302dbba2b07c6b99b151 -timed-out@^2.0.0: - name timed-out - version "2.0.0" - resolved timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a -tmpl@1.0.x: - name tmpl - version "1.0.4" - resolved tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1 -to-absolute-glob@^0.1.1: - name to-absolute-glob - version "0.1.1" - resolved to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f - dependencies: - extend-shallow "^2.0.1" -to-fast-properties@^1.0.1: - name to-fast-properties - version "1.0.2" - resolved to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320 -topo@1.x.x: - name topo - version "1.1.0" - resolved topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5 - dependencies: - hoek "2.x.x" -tough-cookie@^2.2.0: - name tough-cookie - version "2.2.2" - resolved tough-cookie-2.2.2.tgz#c83a1830f4e5ef0b93ef2a3488e724f8de016ac7 -tough-cookie@~2.2.0 tough-cookie@^2.2.0 -tr46@~0.0.3: - name tr46 - version "0.0.3" - resolved tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a -traverse@^0.6.6: - name traverse - version "0.6.6" - resolved traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137 -trim-newlines@^1.0.0: - name trim-newlines - version "1.0.0" - resolved trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613 -trim-repeated@^1.0.0: - name trim-repeated - version "1.0.0" - resolved trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21 - dependencies: - escape-string-regexp "^1.0.2" -tryit@^1.0.1: - name tryit - version "1.0.2" - resolved tryit-1.0.2.tgz#c196b0073e6b1c595d93c9c830855b7acc32a453 -tsscmp@1.0.5: - name tsscmp - version "1.0.5" - resolved tsscmp-1.0.5.tgz#7dc4a33af71581ab4337da91d85ca5427ebd9a97 -tunnel-agent@^0.4.0: - name tunnel-agent - version "0.4.3" - resolved tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb -tunnel-agent@~0.4.1 tunnel-agent@^0.4.0 -tv4@^1.2.7: - name tv4 - version "1.2.7" - resolved tv4-1.2.7.tgz#bd29389afc73ade49ae5f48142b5d544bf68d120 -tweetnacl@~0.13.0: - name tweetnacl - version "0.13.3" - resolved tweetnacl-0.13.3.tgz#d628b56f3bcc3d5ae74ba9d4c1a704def5ab4b56 -type-check@~0.3.2: - name type-check - version "0.3.2" - resolved type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72 - dependencies: - prelude-ls "~1.1.2" -type-is@~1.6.6: - name type-is - version "1.6.13" - resolved type-is-1.6.13.tgz#6e83ba7bc30cd33a7bb0b7fb00737a2085bf9d08 - dependencies: - media-typer "0.3.0" - mime-types "~2.1.11" -typedarray@~0.0.5: - name typedarray - version "0.0.6" - resolved typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777 -ua-parser-js@^0.7.9: - name ua-parser-js - version "0.7.10" - resolved ua-parser-js-0.7.10.tgz#917559ddcce07cbc09ece7d80495e4c268f4ef9f -uglify-js@^2.6: - name uglify-js - version "2.6.4" - resolved uglify-js-2.6.4.tgz#65ea2fb3059c9394692f15fed87c2b36c16b9adf - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" -uglify-js@^2.6.2: - name uglify-js - version "2.6.4" - resolved uglify-js-2.6.4.tgz#65ea2fb3059c9394692f15fed87c2b36c16b9adf - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" -uglify-to-browserify@~1.0.0: - name uglify-to-browserify - version "1.0.2" - resolved uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7 -uid-safe@~2.0.0: - name uid-safe - version "2.0.0" - resolved uid-safe-2.0.0.tgz#a7f3c6ca64a1f6a5d04ec0ef3e4c3d5367317137 - dependencies: - base64-url "1.2.1" -uid-safe@2.1.1: - name uid-safe - version "2.1.1" - resolved uid-safe-2.1.1.tgz#3dbf9436b528be9f52882c05a6216c3763db3666 - dependencies: - base64-url "1.2.2" - random-bytes "~1.0.0" -ultron@1.0.x: - name ultron - version "1.0.2" - resolved ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa -underscore.string@^3.0.3: - name underscore.string - version "3.3.4" - resolved underscore.string-3.3.4.tgz#2c2a3f9f83e64762fdc45e6ceac65142864213db - dependencies: - sprintf-js "^1.0.3" - util-deprecate "^1.0.2" -underscore.string@2.0.x: - name underscore.string - version "2.0.0" - resolved underscore.string-2.0.0.tgz#7470858a54a0bb3560d037da56dcc67b5181e11a -underscore@1.2.x: - name underscore - version "1.2.4" - resolved underscore-1.2.4.tgz#e8da6241aa06f64df2473bb2590b8c17c84c3c7e -unique-stream@^2.0.2: - name unique-stream - version "2.2.1" - resolved unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369 - dependencies: - json-stable-stringify "^1.0.0" - through2-filter "^2.0.0" -unpipe@~1.0.0: - name unpipe - version "1.0.0" - resolved unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec -unpipe@1.0.0: - name unpipe - version "1.0.0" - resolved unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec -untildify@^2.0.0: - name untildify - version "2.1.0" - resolved untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0 - dependencies: - os-homedir "^1.0.0" -unzip-response@^1.0.0: - name unzip-response - version "1.0.0" - resolved unzip-response-1.0.0.tgz#bfda54eeec658f00c2df4d4494b9dca0ca00f3e4 -url-parse-lax@^1.0.0: - name url-parse-lax - version "1.0.0" - resolved url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73 - dependencies: - prepend-http "^1.0.1" -user-home@^1.1.1: - name user-home - version "1.1.1" - resolved user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190 -user-home@^2.0.0: - name user-home - version "2.0.0" - resolved user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f - dependencies: - os-homedir "^1.0.0" -utf-8-validate@1.2.x: - name utf-8-validate - version "1.2.1" - resolved utf-8-validate-1.2.1.tgz#44cb7c6eead73d6b40448f71f745904357b9f72c - dependencies: - bindings "1.2.x" - nan "^2.0.5" -util-deprecate@^1.0.2 util-deprecate@1.0.2 -util-deprecate@~1.0.1 util-deprecate@1.0.2 -util-deprecate@1.0.2: - name util-deprecate - version "1.0.2" - resolved util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf -"util@>=0.10.3 <1": - name util - version "0.10.3" - resolved util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9 - dependencies: - inherits "2.0.1" -utils-merge@1.0.0: - name utils-merge - version "1.0.0" - resolved utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8 -uuid@^2.0.1: - name uuid - version "2.0.2" - resolved uuid-2.0.2.tgz#48bd5698f0677e3c7901a1c46ef15b1643794726 -vali-date@^1.0.0: - name vali-date - version "1.0.0" - resolved vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6 -validate-npm-package-license@^3.0.1: - name validate-npm-package-license - version "3.0.1" - resolved validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc - dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" -vary@~1.0.1: - name vary - version "1.0.1" - resolved vary-1.0.1.tgz#99e4981566a286118dfb2b817357df7993376d10 -vary@~1.1.0: - name vary - version "1.1.0" - resolved vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140 -verror@1.3.6: - name verror - version "1.3.6" - resolved verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c - dependencies: - extsprintf "1.0.2" -vhost@~3.0.1: - name vhost - version "3.0.2" - resolved vhost-3.0.2.tgz#2fb1decd4c466aa88b0f9341af33dc1aff2478d5 -vinyl-assign@^1.0.1: - name vinyl-assign - version "1.2.1" - resolved vinyl-assign-1.2.1.tgz#4d198891b5515911d771a8cd9c5480a46a074a45 - dependencies: - object-assign "^4.0.1" - readable-stream "^2.0.0" -vinyl-file@^2.0.0: - name vinyl-file - version "2.0.0" - resolved vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a - dependencies: - graceful-fs "^4.1.2" - pify "^2.3.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - strip-bom-stream "^2.0.0" - vinyl "^1.1.0" -vinyl-fs@^2.2.0: - name vinyl-fs - version "2.4.3" - resolved vinyl-fs-2.4.3.tgz#3d97e562ebfdd4b66921dea70626b84bde9d2d07 - dependencies: - mkdirp "^0.5.0" - duplexify "^3.2.0" - graceful-fs "^4.0.0" - gulp-sourcemaps "^1.5.2" - is-valid-glob "^0.3.0" - lazystream "^1.0.0" - lodash.isequal "^4.0.0" - merge-stream "^1.0.0" - glob-stream "^5.3.2" - object-assign "^4.0.0" - readable-stream "^2.0.4" - strip-bom "^2.0.0" - strip-bom-stream "^1.0.0" - through2 "^2.0.0" - through2-filter "^2.0.0" - vali-date "^1.0.0" - vinyl "^1.0.0" -vinyl@^0.4.3: - name vinyl - version "0.4.6" - resolved vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847 - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" -vinyl@^0.5.0: - name vinyl - version "0.5.3" - resolved vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" -vinyl@^1.0.0: - name vinyl - version "1.1.1" - resolved vinyl-1.1.1.tgz#7940887eef09381eb3626ac4c0f9ab53d4b7e450 - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" -vinyl@^1.1.0: - name vinyl - version "1.1.1" - resolved vinyl-1.1.1.tgz#7940887eef09381eb3626ac4c0f9ab53d4b7e450 - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" -walker@~1.0.5: - name walker - version "1.0.7" - resolved walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb - dependencies: - makeerror "1.0.x" -ware@^1.2.0: - name ware - version "1.3.0" - resolved ware-1.3.0.tgz#d1b14f39d2e2cb4ab8c4098f756fe4b164e473d4 - dependencies: - wrap-fn "^0.1.0" -watch@~0.10.0: - name watch - version "0.10.0" - resolved watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc -webidl-conversions@^3.0.0 webidl-conversions@^3.0.1 -webidl-conversions@^3.0.1: - name webidl-conversions - version "3.0.1" - resolved webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871 -whatwg-fetch@>=0.10.0: - name whatwg-fetch - version "1.0.0" - resolved whatwg-fetch-1.0.0.tgz#01c2ac4df40e236aaa18480e3be74bd5c8eb798e -whatwg-url@^3.0.0: - name whatwg-url - version "3.0.0" - resolved whatwg-url-3.0.0.tgz#b9033c50c7ce763e91d78777ce825a6d7f56dac5 - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" -which@^1.1.1: - name which - version "1.2.10" - resolved which-1.2.10.tgz#91cd9bd0751322411b659b40f054b21de957ab2d - dependencies: - isexe "^1.1.1" -which@^1.2.8 which@^1.1.1 -which@^1.2.9: - name which - version "1.2.10" - resolved which-1.2.10.tgz#91cd9bd0751322411b659b40f054b21de957ab2d - dependencies: - isexe "^1.1.1" -which@1.0.x: - name which - version "1.0.9" - resolved which-1.0.9.tgz#460c1da0f810103d0321a9b633af9e575e64486f -window-size@^0.1.4: - name window-size - version "0.1.4" - resolved window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876 -window-size@0.1.0: - name window-size - version "0.1.0" - resolved window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d -wordwrap@^1.0.0: - name wordwrap - version "1.0.0" - resolved wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb -wordwrap@~0.0.2: - name wordwrap - version "0.0.3" - resolved wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107 -wordwrap@~1.0.0 wordwrap@^1.0.0 -wordwrap@0.0.2: - name wordwrap - version "0.0.2" - resolved wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f -worker-farm@^1.3.1: - name worker-farm - version "1.3.1" - resolved worker-farm-1.3.1.tgz#4333112bb49b17aa050b87895ca6b2cacf40e5ff - dependencies: - errno ">=0.1.1 <0.2.0-0" - xtend ">=4.0.0 <4.1.0-0" -wrap-ansi@^2.0.0: - name wrap-ansi - version "2.0.0" - resolved wrap-ansi-2.0.0.tgz#7d30f8f873f9a5bbc3a64dabc8d177e071ae426f - dependencies: - string-width "^1.0.1" -wrap-fn@^0.1.0: - name wrap-fn - version "0.1.5" - resolved wrap-fn-0.1.5.tgz#f21b6e41016ff4a7e31720dbc63a09016bdf9845 - dependencies: - co "3.1.0" -wrappy@1: - name wrappy - version "1.0.2" - resolved wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f -write@^0.2.1: - name write - version "0.2.1" - resolved write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757 - dependencies: - mkdirp "^0.5.1" -ws@^0.8.0: - name ws - version "0.8.1" - resolved ws-0.8.1.tgz#6b65273b99193c5f067a4cf5809598f777e3b759 - dependencies: - options ">=0.0.5" - ultron "1.0.x" - optionalDependencies: - bufferutil "1.2.x" - utf-8-validate "1.2.x" -xcode@^0.8.2: - name xcode - version "0.8.8" - resolved xcode-0.8.8.tgz#1330877c4200f2094009e3bf144e6932596fcca0 - dependencies: - node-uuid "1.4.7" - pegjs "0.9.0" - simple-plist "0.1.4" -xdg-basedir@^2.0.0: - name xdg-basedir - version "2.0.0" - resolved xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2 - dependencies: - os-homedir "^1.0.0" -"xml-name-validator@>= 2.0.1 < 3.0.0": - name xml-name-validator - version "2.0.1" - resolved xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635 -xmlbuilder@4.0.0: - name xmlbuilder - version "4.0.0" - resolved xmlbuilder-4.0.0.tgz#98b8f651ca30aa624036f127d11cc66dc7b907a3 - dependencies: - lodash "^3.5.0" -xmldoc@^0.4.0: - name xmldoc - version "0.4.0" - resolved xmldoc-0.4.0.tgz#d257224be8393eaacbf837ef227fd8ec25b36888 - dependencies: - sax "~1.1.1" -xmldom@0.1.x: - name xmldom - version "0.1.22" - resolved xmldom-0.1.22.tgz#10de4e5e964981f03c8cc72fadc08d14b6c3aa26 -xregexp@^3.0.0: - name xregexp - version "3.1.1" - resolved xregexp-3.1.1.tgz#8ee18d75ef5c7cb3f9967f8d29414a6ca5b1a184 -xtend@^4.0.0: - name xtend - version "4.0.1" - resolved xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af -"xtend@>=4.0.0 <4.1.0-0": - name xtend - version "4.0.1" - resolved xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af -xtend@~3.0.0: - name xtend - version "3.0.0" - resolved xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a -xtend@~4.0.0: - name xtend - version "4.0.1" - resolved xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af -y18n@^3.2.0: - name y18n - version "3.2.1" - resolved y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41 -yallist@^2.0.0: - name yallist - version "2.0.0" - resolved yallist-2.0.0.tgz#306c543835f09ee1a4cb23b7bce9ab341c91cdd4 -yargs@^3.24.0: - name yargs - version "3.32.0" - resolved yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995 - dependencies: - camelcase "^2.0.1" - cliui "^3.0.3" - decamelize "^1.1.1" - os-locale "^1.4.0" - string-width "^1.0.1" - window-size "^0.1.4" - y18n "^3.2.0" -yargs@~3.10.0: - name yargs - version "3.10.0" - resolved yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1 - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" -yauzl@^2.2.1: - name yauzl - version "2.6.0" - resolved yauzl-2.6.0.tgz#40894d4587bb125500d05df45471cd5e114a76f9 - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.0.1" -yeoman-assert@^2.0.0: - name yeoman-assert - version "2.2.1" - resolved yeoman-assert-2.2.1.tgz#524bff6b2a83d344a7a24ea825c5eb16504396f5 - dependencies: - lodash "^3.6.0" - path-exists "^2.1.0" -yeoman-environment@^1.1.0: - name yeoman-environment - version "1.6.1" - resolved yeoman-environment-1.6.1.tgz#9f80852c80084c26e988e3c85453c6151bf5522f - dependencies: - inquirer "^1.0.2" - chalk "^1.0.0" - diff "^2.1.2" - escape-string-regexp "^1.0.2" - globby "^4.0.0" - grouped-queue "^0.3.0" - debug "^2.0.0" - lodash "^4.11.1" - log-symbols "^1.0.1" - mem-fs "^1.1.0" - text-table "^0.2.0" - untildify "^2.0.0" -yeoman-environment@^1.2.7: - name yeoman-environment - version "1.6.1" - resolved yeoman-environment-1.6.1.tgz#9f80852c80084c26e988e3c85453c6151bf5522f - dependencies: - inquirer "^1.0.2" - chalk "^1.0.0" - diff "^2.1.2" - escape-string-regexp "^1.0.2" - globby "^4.0.0" - grouped-queue "^0.3.0" - debug "^2.0.0" - lodash "^4.11.1" - log-symbols "^1.0.1" - mem-fs "^1.1.0" - text-table "^0.2.0" - untildify "^2.0.0" -yeoman-generator@^0.20.3: - name yeoman-generator - version "0.20.3" - resolved yeoman-generator-0.20.3.tgz#7e6dc068ca50f79160fed1a2469ed0e79528b671 - dependencies: - mem-fs-editor "^2.0.0" - async "^1.4.2" - class-extend "^0.1.0" - cli-table "^0.3.1" - cross-spawn "^2.0.0" - dargs "^4.0.0" - dateformat "^1.0.11" - debug "^2.1.0" - detect-conflict "^1.0.0" - diff "^2.0.2" - download "^4.1.2" - findup-sync "^0.2.1" - github-username "^2.0.0" - glob "^5.0.3" - gruntfile-editor "^1.0.0" - html-wiring "^1.0.0" - inquirer "^0.8.0" - istextorbinary "^1.0.2" - lodash "^3.5.0" - chalk "^1.0.0" - mime "^1.2.9" - mkdirp "^0.5.0" - nopt "^3.0.0" - path-exists "^1.0.0" - path-is-absolute "^1.0.0" - pretty-bytes "^2.0.1" - read-chunk "^1.0.1" - rimraf "^2.2.0" - run-async "^0.1.0" - shelljs "^0.5.0" - sinon "^1.9.1" - text-table "^0.2.0" - through2 "^2.0.0" - underscore.string "^3.0.3" - user-home "^2.0.0" - xdg-basedir "^2.0.0" - yeoman-assert "^2.0.0" - yeoman-environment "^1.1.0" - yeoman-welcome "^1.0.0" -yeoman-welcome@^1.0.0: - name yeoman-welcome - version "1.0.1" - resolved yeoman-welcome-1.0.1.tgz#f6cf198fd4fba8a771672c26cdfb8a64795c84ec - dependencies: - chalk "^1.0.0" diff --git a/flow/global.js b/flow/global.js new file mode 100644 index 00000000000000..7754927262ecca --- /dev/null +++ b/flow/global.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2013-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. + */ +/* eslint-disable */ + +// Add missing flow definitions, can be removed when included in flow. +declare function requestIdleCallback(callback: any): number; +declare function cancelIdleCallback(idleCallbackId: ?number): void; diff --git a/jestSupport/preprocessor.js b/jestSupport/preprocessor.js index 7d1376b27dde52..bbc8647e66c796 100644 --- a/jestSupport/preprocessor.js +++ b/jestSupport/preprocessor.js @@ -8,16 +8,27 @@ */ 'use strict'; +const babel = require('babel-core'); +const babelRegisterOnly = require('../packager/babelRegisterOnly'); const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction'); const path = require('path'); const transformer = require('../packager/transformer.js'); +const nodeFiles = RegExp([ + '/local-cli/', + '/packager/(?!react-packager/src/Resolver/polyfills/)', +].join('|')); +const nodeOptions = babelRegisterOnly.config([nodeFiles]); + module.exports = { process(src, file) { // Don't transform node_modules, except react-tools which includes the // untransformed copy of React if (file.match(/node_modules\/(?!react-tools\/)/)) { return src; + } else if (nodeFiles.test(file)) { // node specific transforms only + return babel.transform( + src, Object.assign({filename: file}, nodeOptions)).code; } return transformer.transform(src, file, {inlineRequires: true}).code; diff --git a/local-cli/rnpm/core/src/config/android/index.js b/local-cli/rnpm/core/src/config/android/index.js index f8277307f1844f..295b3b7d318c09 100644 --- a/local-cli/rnpm/core/src/config/android/index.js +++ b/local-cli/rnpm/core/src/config/android/index.js @@ -33,9 +33,9 @@ exports.projectConfig = function projectConfigAndroid(folder, userConfig) { const packageFolder = userConfig.packageFolder || packageName.replace(/\./g, path.sep); - const mainActivityPath = path.join( + const mainFilePath = path.join( sourceDir, - userConfig.mainActivityPath || `src/main/java/${packageFolder}/MainActivity.java` + userConfig.mainFilePath || `src/main/java/${packageFolder}/MainApplication.java` ); const stringsPath = path.join( @@ -68,7 +68,7 @@ exports.projectConfig = function projectConfigAndroid(folder, userConfig) { buildGradlePath, settingsGradlePath, assetsPath, - mainActivityPath, + mainFilePath, }; }; diff --git a/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js index 687d61bd4fd6c4..deee6dfec6bc02 100644 --- a/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js +++ b/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js @@ -1,6 +1,6 @@ module.exports = function makeImportPatch(packageImportPath) { return { - pattern: 'import com.facebook.react.ReactActivity;', + pattern: 'import com.facebook.react.ReactApplication;', patch: '\n' + packageImportPath, }; }; diff --git a/local-cli/rnpm/link/src/android/registerNativeModule.js b/local-cli/rnpm/link/src/android/registerNativeModule.js index fa01c2081bd222..a5fee0dc002769 100644 --- a/local-cli/rnpm/link/src/android/registerNativeModule.js +++ b/local-cli/rnpm/link/src/android/registerNativeModule.js @@ -27,12 +27,12 @@ module.exports = function registerNativeAndroidModule( applyPatch(projectConfig.stringsPath, makeStringsPatch(params, name)); applyPatch( - projectConfig.mainActivityPath, + projectConfig.mainFilePath, makePackagePatch(androidConfig.packageInstance, params, name) ); applyPatch( - projectConfig.mainActivityPath, + projectConfig.mainFilePath, makeImportPatch(androidConfig.packageImportPath) ); }; diff --git a/local-cli/rnpm/link/src/android/unregisterNativeModule.js b/local-cli/rnpm/link/src/android/unregisterNativeModule.js index 1c5eaa2bf4ae25..9b88628330c67e 100644 --- a/local-cli/rnpm/link/src/android/unregisterNativeModule.js +++ b/local-cli/rnpm/link/src/android/unregisterNativeModule.js @@ -36,12 +36,12 @@ module.exports = function unregisterNativeAndroidModule( revokePatch(projectConfig.stringsPath, makeStringsPatch(params, name)); revokePatch( - projectConfig.mainActivityPath, + projectConfig.mainFilePath, makePackagePatch(androidConfig.packageInstance, params, name) ); revokePatch( - projectConfig.mainActivityPath, + projectConfig.mainFilePath, makeImportPatch(androidConfig.packageImportPath) ); }; diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js index d2a5f43c728ac6..ed729defc2fc25 100644 --- a/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js +++ b/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js @@ -7,7 +7,7 @@ const makeImportPatch = require('../../../../src/android/patches/0.17/makeImport const applyPatch = require('../../../../src/android/patches/applyPatch'); const projectConfig = { - mainActivityPath: 'MainActivity.java', + mainFilePath: 'MainActivity.java', }; const packageImportPath = 'import some.example.project'; diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js index 99857a5713df09..cbbb3f517c9c16 100644 --- a/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js +++ b/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js @@ -7,7 +7,7 @@ const makePackagePatch = require('../../../../src/android/patches/0.17/makePacka const applyPatch = require('../../../../src/android/patches/applyPatch'); const projectConfig = { - mainActivityPath: 'MainActivity.java', + mainFilePath: 'MainActivity.java', }; const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js index ffff9f045f60c3..0dd15c74890573 100644 --- a/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js +++ b/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js @@ -7,7 +7,7 @@ const makeImportPatch = require('../../../../src/android/patches/0.18/makeImport const applyPatch = require('../../../../src/android/patches/applyPatch'); const projectConfig = { - mainActivityPath: 'MainActivity.java', + mainFilePath: 'MainActivity.java', }; const packageImportPath = 'import some.example.project'; diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js index a2d4802f4e484b..b3f46bf5fe829c 100644 --- a/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js +++ b/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js @@ -7,7 +7,7 @@ const makePackagePatch = require('../../../../src/android/patches/0.18/makePacka const applyPatch = require('../../../../src/android/patches/applyPatch'); const projectConfig = { - mainActivityPath: 'MainActivity.java', + mainFilePath: 'MainActivity.java', }; const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js index b80fc9a577d76e..31b328261158e7 100644 --- a/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js +++ b/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js @@ -7,7 +7,7 @@ const makeImportPatch = require('../../../../src/android/patches/0.20/makeImport const applyPatch = require('../../../../src/android/patches/applyPatch'); const projectConfig = { - mainActivityPath: 'MainActivity.java', + mainFilePath: 'MainActivity.java', }; const packageImportPath = 'import some.example.project'; diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js index 87f807636e192b..fe8086ce8b3262 100644 --- a/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js +++ b/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js @@ -7,7 +7,7 @@ const makePackagePatch = require('../../../../src/android/patches/0.20/makePacka const applyPatch = require('../../../../src/android/patches/applyPatch'); const projectConfig = { - mainActivityPath: 'MainActivity.java', + mainFilePath: 'MainActivity.java', }; const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; diff --git a/package.json b/package.json index 2961e17c47c98d..b34eda1f7c0b60 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "react-native": "local-cli/wrong-react-native.js" }, "peerDependencies": { - "react": "~15.2.0" + "react": "~15.3.0-rc.2" }, "dependencies": { "absolute-path": "^0.0.0", @@ -187,8 +187,10 @@ "xcode": "^0.8.2", "xmldoc": "^0.4.0", "yargs": "^3.24.0", - "yeoman-environment": "^1.2.7", - "yeoman-generator": "^0.20.3" + "yeoman-environment": "1.5.3", + "yeoman-generator": "0.21.2", + "mime-types": "2.1.11", + "whatwg-fetch": "^1.0.0" }, "devDependencies": { "babel-eslint": "^6.0.0", @@ -196,12 +198,12 @@ "eslint-plugin-babel": "^3.2.0", "eslint-plugin-flow-vars": "^0.2.1", "eslint-plugin-react": "^4.2.1", - "flow-bin": "^0.27.0", + "flow-bin": "^0.29.0", "jest": "^13.1.0", "jest-repl": "^13.1.0", "jest-runtime": "^13.1.0", "portfinder": "0.4.0", - "react": "~15.2.0", + "react": "~15.3.0-rc.2", "shelljs": "0.6.0" } } diff --git a/packager/babelRegisterOnly.js b/packager/babelRegisterOnly.js index 703ba3385bc005..a4f96afd3586da 100644 --- a/packager/babelRegisterOnly.js +++ b/packager/babelRegisterOnly.js @@ -14,10 +14,13 @@ Object.values || require('core-js/fn/object/values'); var _only = []; -module.exports = function(onlyList) { - _only = _only.concat(onlyList); +function registerOnly(onlyList) { + require('babel-register')(config(onlyList)); +} - require('babel-register')({ +function config(onlyList) { + _only = _only.concat(onlyList); + return { presets: ['es2015-node'], plugins: [ 'transform-flow-strip-types', @@ -25,6 +28,11 @@ module.exports = function(onlyList) { 'transform-object-rest-spread', ], only: _only, + retainLines: true, sourceMaps: 'inline', - }); -}; + babelrc: false, + }; +} + +module.exports = exports = registerOnly; +exports.config = config; diff --git a/packager/react-native-xcode.sh b/packager/react-native-xcode.sh index eeedfa2818a5b9..b067ff966e3e29 100755 --- a/packager/react-native-xcode.sh +++ b/packager/react-native-xcode.sh @@ -39,6 +39,9 @@ cd .. # Define NVM_DIR and source the nvm.sh setup script [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" +# Define entry file +ENTRY_FILE=${1:-index.ios.js} + if [[ -s "$HOME/.nvm/nvm.sh" ]]; then . "$HOME/.nvm/nvm.sh" elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then @@ -47,7 +50,7 @@ fi # Set up the nodenv node version manager if present if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then - eval "$($HOME/.nodenv/bin/nodenv init -)" + eval "$("$HOME/.nodenv/bin/nodenv" init -)" fi [ -z "$NODE_BINARY" ] && export NODE_BINARY="node" @@ -72,13 +75,13 @@ if [[ "$CONFIGURATION" = "Debug" && "$PLATFORM_NAME" != "iphonesimulator" ]]; th PLISTBUDDY='/usr/libexec/PlistBuddy' PLIST=$TARGET_BUILD_DIR/$INFOPLIST_PATH IP=$(ipconfig getifaddr en0) - $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" $PLIST - $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP.xip.io:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" $PLIST + $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST" + $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP.xip.io:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST" echo "$IP.xip.io" > "$DEST/ip.txt" fi $NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \ - --entry-file index.ios.js \ + --entry-file "$ENTRY_FILE" \ --platform ios \ --dev $DEV \ --reset-cache true \ diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js index 65f1edcfc2686b..88a7bebe442dd2 100644 --- a/packager/react-packager/src/AssetServer/index.js +++ b/packager/react-packager/src/AssetServer/index.js @@ -28,9 +28,9 @@ function timeoutableDenodeify(fsFunc, timeout) { }; } -const stat = timeoutableDenodeify(fs.stat, 5000); -const readDir = timeoutableDenodeify(fs.readdir, 5000); -const readFile = timeoutableDenodeify(fs.readFile, 5000); +const stat = timeoutableDenodeify(fs.stat, 15000); +const readDir = timeoutableDenodeify(fs.readdir, 15000); +const readFile = timeoutableDenodeify(fs.readFile, 15000); const validateOpts = declareOpts({ projectRoots: { diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js index 4b6ca5a1d97392..7b7e7f177263b1 100644 --- a/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js +++ b/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js @@ -14,12 +14,14 @@ jest.mock('../extract-dependencies'); jest.mock('../inline'); jest.mock('../minify'); -const {transformCode} = require('..'); const {any, objectContaining} = jasmine; describe('code transformation worker:', () => { + let transformCode; + let extractDependencies, transform; beforeEach(() => { + ({transformCode} = require('..')); extractDependencies = require('../extract-dependencies').mockReturnValue({}); transform = jest.fn(); @@ -68,7 +70,7 @@ describe('code transformation worker:', () => { done(); }); }); - + it('removes shebang when present', done => { const shebang = '#!/usr/bin/env node'; const result = { @@ -81,7 +83,7 @@ describe('code transformation worker:', () => { done(); }); }); - + it('calls back with any error yielded by the transform', done => { const error = Error('arbitrary error'); transform.mockImplementation((_, callback) => callback(error)); diff --git a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js index 6473ee3cc7f937..c86d1853407678 100644 --- a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -11,18 +11,18 @@ jest.unmock('../'); jest.mock('path'); -const Promise = require('promise'); -const Resolver = require('../'); -const path = require('path'); - -let DependencyGraph = jest.fn(); +const DependencyGraph = jest.fn(); jest.setMock('node-haste', DependencyGraph); let Module; let Polyfill; describe('Resolver', function() { + let Resolver, path; + beforeEach(function() { + Resolver = require('../'); + path = require('path'); DependencyGraph.mockClear(); Module = jest.fn(function() { this.getName = jest.fn(); @@ -131,7 +131,14 @@ describe('Resolver', function() { ).then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies[result.dependencies.length - 1]).toBe(module); - expect(DependencyGraph.prototype.createPolyfill.mock.calls.map((call) => call[0])).toEqual([ + expect( + DependencyGraph + .prototype + .createPolyfill + .mock + .calls + .map((call) => call[0])) + .toEqual([ { id: 'polyfills/polyfills.js', file: 'polyfills/polyfills.js', dependencies: [] diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index c54e503d171e21..42e8760cba6a97 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -14,23 +14,25 @@ jest.setMock('worker-farm', function() { return () => {}; }) .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) .setMock('uglify-js') .setMock('crypto') - .setMock('source-map', { SourceMapConsumer: (fn) => {}}) + .setMock('source-map', { SourceMapConsumer: function(fn) {}}) .mock('../../Bundler') .mock('../../AssetServer') .mock('../../lib/declareOpts') .mock('node-haste') .mock('../../Activity'); -const Promise = require('promise'); -const SourceMapConsumer = require('source-map').SourceMapConsumer; - -const Bundler = require('../../Bundler'); -const Server = require('../'); -const AssetServer = require('../../AssetServer'); - let FileWatcher; describe('processRequest', () => { + let SourceMapConsumer, Bundler, Server, AssetServer, Promise; + beforeEach(() => { + SourceMapConsumer = require('source-map').SourceMapConsumer; + Bundler = require('../../Bundler'); + Server = require('../'); + AssetServer = require('../../AssetServer'); + Promise = require('promise'); + }); + let server; const options = { @@ -349,6 +351,19 @@ describe('processRequest', () => { expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios'); expect(res.end).toBeCalledWith('i am image'); }); + + it('should serve range request', () => { + const req = {url: '/assets/imgs/a.png?platform=ios', headers: {range: 'bytes=0-3'}}; + const res = {end: jest.fn(), writeHead: jest.fn()}; + const mockData = 'i am image'; + + AssetServer.prototype.get.mockImpl(() => Promise.resolve(mockData)); + + server.processRequest(req, res); + jest.runAllTimers(); + expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios'); + expect(res.end).toBeCalledWith(mockData.slice(0, 3)); + }); }); describe('buildbundle(options)', () => { diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index dadc7379c532cc..41febf63a5eb5e 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -19,6 +19,7 @@ const SourceMapConsumer = require('source-map').SourceMapConsumer; const declareOpts = require('../lib/declareOpts'); const path = require('path'); const url = require('url'); +const mime = require('mime-types'); function debounce(fn, delay) { var timeout; @@ -396,13 +397,33 @@ class Server { }); } + _rangeRequestMiddleware(req, res, data, assetPath) { + if (req.headers && req.headers.range) { + const [rangeStart, rangeEnd] = req.headers.range.replace(/bytes=/, '').split('-'); + const dataStart = parseInt(rangeStart, 10); + const dataEnd = rangeEnd ? parseInt(rangeEnd, 10) : data.length - 1; + const chunksize = (dataEnd - dataStart) + 1; + + res.writeHead(206, { + 'Accept-Ranges': 'bytes', + 'Content-Length': chunksize, + 'Content-Range': `bytes ${dataStart}-${dataEnd}/${data.length}`, + 'Content-Type': mime.lookup(path.basename(assetPath[1])) + }); + + return data.slice(dataStart, dataEnd); + } + + return data; + } + _processAssetsRequest(req, res) { const urlObj = url.parse(req.url, true); const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); const assetEvent = Activity.startEvent(`processing asset request ${assetPath[1]}`); this._assetServer.get(assetPath[1], urlObj.query.platform) .then( - data => res.end(data), + data => res.end(this._rangeRequestMiddleware(req, res, data, assetPath)), error => { console.error(error.stack); res.writeHead('404'); diff --git a/react.gradle b/react.gradle index ffa9e48c6d37ed..d34469ddce3f4b 100644 --- a/react.gradle +++ b/react.gradle @@ -31,7 +31,9 @@ gradle.projectsEvaluated { productFlavors.each { productFlavorName -> buildTypes.each { buildTypeName -> // Create variant and target names - def targetName = "${productFlavorName.capitalize()}${buildTypeName.capitalize()}" + def flavorNameCapitalized = "${productFlavorName.capitalize()}" + def buildNameCapitalized = "${buildTypeName.capitalize()}" + def targetName = "${flavorNameCapitalized}${buildNameCapitalized}" def targetPath = productFlavorName ? "${productFlavorName}/${buildTypeName}" : "${buildTypeName}" @@ -92,8 +94,8 @@ gradle.projectsEvaluated { currentBundleTask.dependsOn("merge${targetName}Resources") currentBundleTask.dependsOn("merge${targetName}Assets") - runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask) - runBefore("processX86${targetName}Resources", currentBundleTask) + runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask) + runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask) runBefore("processUniversal${targetName}Resources", currentBundleTask) runBefore("process${targetName}Resources", currentBundleTask) } diff --git a/scripts/objc-test.sh b/scripts/objc-test.sh index c4f553377379f6..ec1a43f189754f 100755 --- a/scripts/objc-test.sh +++ b/scripts/objc-test.sh @@ -16,21 +16,16 @@ function cleanup { WATCHMAN_LOGS=/usr/local/Cellar/watchman/3.1/var/run/watchman/$USER.log [ -f $WATCHMAN_LOGS ] && cat $WATCHMAN_LOGS fi - [ $SERVER_PID ] && kill -9 $SERVER_PID + # kill whatever is occupying port 8081 + lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill } trap cleanup EXIT -if [ -z "$TRAVIS" ]; then - # Run the packager process directly - node ./local-cli/cli.js start & - SERVER_PID=$! -fi - XCODE_PROJECT="Examples/UIExplorer/UIExplorer.xcodeproj" XCODE_SCHEME="UIExplorer" XCODE_SDK="iphonesimulator" if [ -z "$XCODE_DESTINATION" ]; then - XCODE_DESTINATION="platform=iOS Simulator,name=iPhone 5,OS=9.3" + XCODE_DESTINATION="platform=iOS Simulator,name=iPhone 5s,OS=9.3" fi # Support for environments without xcpretty installed diff --git a/website/core/AlgoliaDocSearch.js b/website/core/AlgoliaDocSearch.js index 16b60fd707a816..69766e5aa1c6c6 100644 --- a/website/core/AlgoliaDocSearch.js +++ b/website/core/AlgoliaDocSearch.js @@ -13,7 +13,7 @@ var AlgoliaDocSearch = React.createClass({ render: function() { return ( <div className="algolia-search-wrapper"> - <input id="algolia-doc-search" tabindex="0" type="text" placeholder="Search docs..." /> + <input id="algolia-doc-search" tabIndex="0" type="text" placeholder="Search docs..." /> </div> ); } diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index 6930bdb182998e..3540cdcfb224d6 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -63,7 +63,7 @@ function getPlatformFromPath(filepath) { } function getExamplePaths(componentName, componentPlatform) { - const componentExample = '../Examples/UIExplorer/' + componentName + 'Example.'; + const componentExample = '../Examples/UIExplorer/js/' + componentName + 'Example.'; let pathsToCheck = [ componentExample + 'js', componentExample + componentPlatform + '.js', @@ -165,6 +165,19 @@ function getNextComponent(idx) { return null; } +function getPreviousComponent(idx) { + if (all[idx - 1]) { + const previousComponentName = getNameFromPath(all[idx - 1]); + + if (shouldDisplayInSidebar(previousComponentName)) { + return slugify(previousComponentName); + } else { + return getPreviousComponent(idx - 1); + } + } + return null; +} + function componentsToMarkdown(type, json, filepath, idx, styles) { const componentName = getNameFromPath(filepath); const componentPlatform = getPlatformFromPath(filepath); @@ -189,6 +202,7 @@ function componentsToMarkdown(type, json, filepath, idx, styles) { // Put styles (e.g. Flexbox) into the API category const category = (type === 'style' ? 'apis' : type + 's'); const next = getNextComponent(idx); + const previous = getPreviousComponent(idx); const res = [ '---', @@ -199,6 +213,7 @@ function componentsToMarkdown(type, json, filepath, idx, styles) { 'permalink: docs/' + slugify(componentName) + '.html', 'platform: ' + componentPlatform, 'next: ' + next, + 'previous: ' + previous, 'sidebar: ' + shouldDisplayInSidebar(componentName), 'runnable:' + isRunnable(componentName, componentPlatform), 'path:' + json.filepath, @@ -242,19 +257,24 @@ function getTypedef(filepath, fileContent, json) { } function renderComponent(filepath) { - const fileContent = fs.readFileSync(filepath); - const json = docgen.parse( - fileContent, - docgenHelpers.findExportedOrFirst, - docgen.defaultHandlers.concat([ - docgenHelpers.stylePropTypeHandler, - docgenHelpers.deprecatedPropTypeHandler, - docgenHelpers.jsDocFormatHandler, - ]) - ); - json.typedef = getTypedef(filepath, fileContent); + try { + const fileContent = fs.readFileSync(filepath); + const json = docgen.parse( + fileContent, + docgenHelpers.findExportedOrFirst, + docgen.defaultHandlers.concat([ + docgenHelpers.stylePropTypeHandler, + docgenHelpers.deprecatedPropTypeHandler, + docgenHelpers.jsDocFormatHandler, + ]) + ); + json.typedef = getTypedef(filepath, fileContent); - return componentsToMarkdown('component', json, filepath, componentCount++, styleDocs); + return componentsToMarkdown('component', json, filepath, componentCount++, styleDocs); + } catch (e) { + console.log('error in renderComponent for', filepath); + throw e; + } } function isJsDocFormat(fileContent) { @@ -315,6 +335,9 @@ function parseAPIInferred(filepath, fileContent) { let json; try { json = jsDocs(fileContent); + if (!json) { + throw new Error('jsDocs returned falsy'); + } } catch (e) { console.error('Cannot parse file', filepath, e); json = {}; @@ -412,27 +435,32 @@ function getJsDocFormatType(entities) { } function renderAPI(filepath, type) { - const fileContent = fs.readFileSync(filepath).toString(); - let json = parseAPIInferred(filepath, fileContent); - if (isJsDocFormat(fileContent)) { - let jsonJsDoc = parseAPIJsDocFormat(filepath, fileContent); - // Combine method info with jsdoc fomatted content - const methods = json.methods; - if (methods && methods.length) { - let modMethods = methods; - methods.map((method, methodIndex) => { - modMethods[methodIndex].params = getJsDocFormatType(method.params); - modMethods[methodIndex].returns = + try { + const fileContent = fs.readFileSync(filepath).toString(); + let json = parseAPIInferred(filepath, fileContent); + if (isJsDocFormat(fileContent)) { + let jsonJsDoc = parseAPIJsDocFormat(filepath, fileContent); + // Combine method info with jsdoc formatted content + const methods = json.methods; + if (methods && methods.length) { + let modMethods = methods; + methods.map((method, methodIndex) => { + modMethods[methodIndex].params = getJsDocFormatType(method.params); + modMethods[methodIndex].returns = getJsDocFormatType(method.returntypehint); - delete modMethods[methodIndex].returntypehint; - }); - json.methods = modMethods; - // Use deep Object.assign so duplicate properties are overwritten. - deepAssign(jsonJsDoc.methods, json.methods); + delete modMethods[methodIndex].returntypehint; + }); + json.methods = modMethods; + // Use deep Object.assign so duplicate properties are overwritten. + deepAssign(jsonJsDoc.methods, json.methods); + } + json = jsonJsDoc; } - json = jsonJsDoc; + return componentsToMarkdown(type, json, filepath, componentCount++); + } catch (e) { + console.log('error in renderAPI for', filepath); + throw e; } - return componentsToMarkdown(type, json, filepath, componentCount++); } function renderStyle(filepath) { @@ -457,6 +485,7 @@ function renderStyle(filepath) { const components = [ '../Libraries/Components/ActivityIndicator/ActivityIndicator.js', + '../Libraries/Components/ActivityIndicator/ActivityIndicatorIOS.ios.js', '../Libraries/Components/DatePicker/DatePickerIOS.ios.js', '../Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js', '../Libraries/Image/Image.ios.js', @@ -465,8 +494,8 @@ const components = [ '../Libraries/Modal/Modal.js', '../Libraries/CustomComponents/Navigator/Navigator.js', '../Libraries/Components/Navigation/NavigatorIOS.ios.js', - '../Libraries/Components/Picker/PickerIOS.ios.js', '../Libraries/Components/Picker/Picker.js', + '../Libraries/Components/Picker/PickerIOS.ios.js', '../Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js', '../Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js', '../Libraries/Components/RefreshControl/RefreshControl.js', @@ -475,7 +504,10 @@ const components = [ '../Libraries/Components/Slider/Slider.js', '../Libraries/Components/SliderIOS/SliderIOS.ios.js', '../Libraries/Components/StatusBar/StatusBar.js', + '../Libraries/RCTTest/SnapshotViewIOS.ios.js', '../Libraries/Components/Switch/Switch.js', + '../Libraries/Components/SwitchAndroid/SwitchAndroid.android.js', + '../Libraries/Components/SwitchIOS/SwitchIOS.ios.js', '../Libraries/Components/TabBarIOS/TabBarIOS.ios.js', '../Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js', '../Libraries/Text/Text.js', @@ -492,6 +524,7 @@ const components = [ const apis = [ '../Libraries/ActionSheetIOS/ActionSheetIOS.js', + '../Libraries/AdSupport/AdSupportIOS.js', '../Libraries/Utilities/Alert.js', '../Libraries/Utilities/AlertIOS.js', '../Libraries/Animated/src/AnimatedImplementation.js', @@ -503,23 +536,30 @@ const apis = [ '../Libraries/Components/Clipboard/Clipboard.js', '../Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js', '../Libraries/Utilities/Dimensions.js', + '../Libraries/Animated/src/Easing.js', '../Libraries/Geolocation/Geolocation.js', + '../Libraries/Image/ImageEditor.js', + '../Libraries/CameraRoll/ImagePickerIOS.js', + '../Libraries/Image/ImageStore.js', '../Libraries/Components/Intent/IntentAndroid.android.js', '../Libraries/Interaction/InteractionManager.js', '../Libraries/LayoutAnimation/LayoutAnimation.js', '../Libraries/Linking/Linking.js', '../Libraries/CustomComponents/ListView/ListViewDataSource.js', '../node_modules/react/lib/NativeMethodsMixin.js', + '../Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js', '../Libraries/Network/NetInfo.js', '../Libraries/Interaction/PanResponder.js', '../Libraries/Utilities/PixelRatio.js', '../Libraries/PushNotificationIOS/PushNotificationIOS.js', + '../Libraries/Settings/Settings.ios.js', '../Libraries/Components/StatusBar/StatusBarIOS.ios.js', '../Libraries/StyleSheet/StyleSheet.js', + '../Libraries/Utilities/Systrace.js', '../Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js', '../Libraries/Components/ToastAndroid/ToastAndroid.android.js', - '../Libraries/Vibration/VibrationIOS.ios.js', '../Libraries/Vibration/Vibration.js', + '../Libraries/Vibration/VibrationIOS.ios.js', ]; const stylesWithPermalink = [ diff --git a/website/server/server.js b/website/server/server.js index 79e7fcede047ff..96ca60e9ac09ab 100644 --- a/website/server/server.js +++ b/website/server/server.js @@ -52,7 +52,7 @@ var app = connect() }) .use(reactMiddleware.provide(buildOptions)) .use(connect['static'](FILE_SERVE_ROOT)) - .use(connect.favicon(path.join(FILE_SERVE_ROOT, 'elements', 'favicon', 'favicon.ico'))) + .use(connect.favicon(path.join(FILE_SERVE_ROOT, 'react-native', 'img', 'favicon.png'))) .use(connect.logger()) .use(connect.compress()) .use(connect.errorHandler()); diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index f032e23cc38894..e069ffa04d0d31 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -10,14 +10,12 @@ /* Thousands of applications use React Native, so we can't list all of them in our showcase. To be useful to someone looking through the showcase, -either the app must be something that a significant number of readers would recognize, or the makers of the application must have posted something valuable technically about the making of the app. So, one of the following should hold: +either the app must be something that most readers would recognize, or the makers of the application must have posted useful technical content about the making of the app. It also must be useful considering that the majority of readers only speak English. So, each app in the showcase should link to either: -1/ The app is branded with a public company brand -2/ The app received some publicity in top-tier news -3/ The app is made by a funded startup -4/ A popular piece of developer content discusses this app +1/ An English-language news article discussing the app, built either by a funded startup or for a public company +2/ An English-language technical blog post by the app creators specifically discussing React Native -For each app in the showcase, use infoLink and infoTitle to reference content that would be relevant to a React Native developer learning about this app. +For each app in the showcase, use infoLink and infoTitle to reference this content. */ var React = require('React'); @@ -55,6 +53,13 @@ var featured = [ infoLink: 'http://makeitopen.com/tutorials/building-the-f8-app/planning/', infoTitle: 'Building the F8 App', }, + { + name: 'FanVision Bolt', + icon: 'http://a4.mzstatic.com/us/r30/Purple18/v4/94/b4/6e/94b46ee5-80e3-ff6e-513d-16da926b03a3/icon175x175.jpeg', + linkAppStore: 'https://itunes.apple.com/us/app/fanvision-bolt/id1081891275', + infoLink: 'https://www.youtube.com/watch?v=oWOcAXyDf0w', + infoTitle: 'FanVision Bolt accessory + app provide live audio/video and stats at NASCAR events', + }, { name: 'Gyroscope', icon: 'https://media.gyrosco.pe/images/magneto/180x180.png',