diff --git a/.circleci/DockerTests.md b/.circleci/DockerTests.md new file mode 100644 index 00000000000000..c65e72640e860b --- /dev/null +++ b/.circleci/DockerTests.md @@ -0,0 +1,81 @@ +# Docker Test Environment + +This is a high-level overview of the test configuration using Docker. +It explains how to run the tests locally. + +## Docker Installation + +It is required to have Docker running on your machine in order to build and run the tests in the Dockerfiles. +See for more information on how to install. + +## Convenience Scripts + +We have added a number of default run scripts to the `package.json` file to simplify building and running your tests. + +### Configuring Docker Images + +The following two scripts need to be run first before you can move on to testing: + +- `yarn run docker-setup-android`: Pulls down the React Native Community Android image that serves as a base image when building the actual test image. + +- `yarn run docker-build-android`: Builds a test image with the latest dependencies and React Native library code, including a compiled Android test app. + +### Running Tests + +Once the test image has been built, it can be used to run our Android tests. + +- `yarn run test-android-run-unit` runs the unit tests, as defined in `scripts/run-android-docker-unit-tests.sh`. +- `yarn run test-android-run-e2e` runs the end to end tests, as defined in `scripts/run-ci-e2e-tests.sh`. +- `yarn run test-android-run-instrumentation` runs the instrumentation tests, as defined in `scripts/run-android-docker-instrumentation-tests.sh`. + +#### Instrumentation Tests + +The instrumentation test script accepts the following flags in order to customize the execution of the tests: + +`--filter` - A regex that filters which instrumentation tests will be run. (Defaults to .*) + +`--package` - Name of the java package containing the instrumentation tests (Defaults to com.facebook.react.tests) + +`--path` - Path to the directory containing the instrumentation tests. (Defaults to ./ReactAndroid/src/androidTest/java/com/facebook/react/tests) + +`--retries` - Number of times to retry a failed test before declaring a failure (Defaults to 2) + +For example, if locally you only wanted to run the InitialPropsTestCase, you could do the following: +`yarn run test-android-run-instrumentation -- --filter="InitialPropsTestCase"` + +## Detailed Android Setup + +There are two Dockerfiles for use with the Android codebase. +The base image used to build `reactnativecommunity/react-native-android` is located in the https://github.com/react-native-community/docker-android GitHub repository. +It contains all the necessary prerequisites required to run the React Android tests. +It is separated out into a separate Dockerfile because these are dependencies that rarely change and also because it is quite a beastly image since it contains all the Android dependencies for running Android and the emulators (~9GB). + +The good news is you should rarely have to build or pull down the base image! +All iterative code updates happen as part of the `Dockerfile.android` image build. + +Lets break it down... + +First, you'll need to pull the base image. +You can use `docker pull` to grab the latest version of the `reactnativecommunity/react-native-android` base image. +This is what you get when you run `yarn run docker-setup-android`. + +This will take quite some time depending on your connection and you need to ensure you have ~10GB of free disk space. + +Once you have downloaded the base image, the test image can be built using `docker build -t reactnativeci/android -f ./.circleci/Dockerfiles/Dockerfile.android .`. This is what `yarn run docker-build-android` does. Note that the `-t` flag is how you tell Docker what to name this image locally. You can then use `docker run -t reactnativeci/android` to run this image. + +Now that you've built the test image, you can run unit tests using what you've learned so far: + +```bash +docker run --cap-add=SYS_ADMIN -it reactnativeci/android bash .circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh +``` + +> Note: `--cap-add=SYS_ADMIN` flag is required for the `.circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh` and `.circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh` in order to allow the remounting of `/dev/shm` as writeable so the `buck` build system may write temporary output to that location. + +Every time you make any modifications to the codebase, including changes to the test scripts inside `.circleci/Dockerfiles/scripts`, you should re-run the `docker build ...` command in order for your updates to be included in your local docker test image. + +For rapid iteration, it's useful to keep in mind that Docker can pass along arbitrary commands to an image. +For example, you can alternatively use Gradle in this manner: + +```bash +docker run --cap-add=SYS_ADMIN -it reactnativeci/android ./gradlew RNTester:android:app:assembleRelease +``` diff --git a/ContainerShip/Dockerfile.android b/.circleci/Dockerfiles/Dockerfile.android similarity index 59% rename from ContainerShip/Dockerfile.android rename to .circleci/Dockerfiles/Dockerfile.android index 28a923710d6a0d..4b3a95d8a83f24 100644 --- a/ContainerShip/Dockerfile.android +++ b/.circleci/Dockerfiles/Dockerfile.android @@ -2,41 +2,43 @@ # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. - -# React Native Android Unit Tests # -# This image builds upon the React Native Base Android Development Environment -# image. Ideally, this image would be rebuilt with any new commit to the master -# branch. Doing so will catch issues such as BUCK failing to fetch dependencies -# or run tests, as well as unit test failures. -FROM react-native-community/react-native +# This image builds upon the React Native Community Android image: +# https://github.com/react-native-community/docker-android +# +# The base image is expected to remain relatively stable, and only +# needs to be updated when major dependencies such as the Android +# SDK or NDK are updated. +# +# In this Android Test image, we download the latest dependencies +# and build a Android application that can be used to run the +# tests specified in the scripts/ directory. +# +FROM reactnativecommunity/react-native-android -LABEL Description="This image prepares and runs React Native's Android tests." +LABEL Description="React Native Android Test Image" LABEL maintainer="HΓ©ctor Ramos " # set default environment variables ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\"" ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8" -# add ReactAndroid directory ADD .buckconfig /app/.buckconfig ADD .buckjavaargs /app/.buckjavaargs +ADD tools /app/tools ADD ReactAndroid /app/ReactAndroid ADD ReactCommon /app/ReactCommon -ADD ReactNative /app/ReactNative +ADD React /app/React ADD keystores /app/keystores -# set workdir WORKDIR /app -# run buck fetches RUN buck fetch ReactAndroid/src/test/java/com/facebook/react/modules RUN buck fetch ReactAndroid/src/main/java/com/facebook/react RUN buck fetch ReactAndroid/src/main/java/com/facebook/react/shell RUN buck fetch ReactAndroid/src/test/... RUN buck fetch ReactAndroid/src/androidTest/... -# build app RUN buck build ReactAndroid/src/main/java/com/facebook/react RUN buck build ReactAndroid/src/main/java/com/facebook/react/shell @@ -46,22 +48,10 @@ ADD settings.gradle /app/settings.gradle ADD build.gradle /app/build.gradle ADD react.gradle /app/react.gradle -# run gradle downloads RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSC -# compile native libs with Gradle script, we need bridge for unit and integration tests RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1 -# add all react-native code ADD . /app -WORKDIR /app -# build node dependencies -RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - -RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list -RUN apt-get install apt-transport-https -RUN apt-get update -RUN apt-get install yarn RUN yarn - -WORKDIR /app diff --git a/ContainerShip/scripts/run-android-ci-instrumentation-tests.js b/.circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js similarity index 96% rename from ContainerShip/scripts/run-android-ci-instrumentation-tests.js rename to .circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js index a788bf19b80a26..6f8a3a7aa7d5aa 100644 --- a/ContainerShip/scripts/run-android-ci-instrumentation-tests.js +++ b/.circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js @@ -86,7 +86,7 @@ return async.mapSeries(testClasses, (clazz, callback) => { } return async.retry(test_opts.RETRIES, (retryCb) => { - const test_process = child_process.spawn('./ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh', [test_opts.PACKAGE, clazz], { + const test_process = child_process.spawn('./.circleci/Dockerfiles/scripts/run-instrumentation-tests-via-adb-shell.sh', [test_opts.PACKAGE, clazz], { stdio: 'inherit', }); diff --git a/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh b/.circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh similarity index 94% rename from ContainerShip/scripts/run-android-docker-instrumentation-tests.sh rename to .circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh index 2062b547e2bbc4..ef6911e0f6765a 100644 --- a/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh +++ b/.circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh @@ -38,5 +38,5 @@ node cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/a source ./scripts/android-setup.sh && NO_BUCKD=1 retry3 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1 # run installed apk with tests -node ./ContainerShip/scripts/run-android-ci-instrumentation-tests.js "$*" +node ./.circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js "$*" exit $? diff --git a/ContainerShip/scripts/run-android-docker-unit-tests.sh b/.circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh similarity index 100% rename from ContainerShip/scripts/run-android-docker-unit-tests.sh rename to .circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh diff --git a/ContainerShip/scripts/run-ci-e2e-tests.sh b/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh similarity index 100% rename from ContainerShip/scripts/run-ci-e2e-tests.sh rename to .circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh diff --git a/ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh b/.circleci/Dockerfiles/scripts/run-instrumentation-tests-via-adb-shell.sh similarity index 100% rename from ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh rename to .circleci/Dockerfiles/scripts/run-instrumentation-tests-via-adb-shell.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index edd327fff96de7..0aaa34da3e8842 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,104 +1,56 @@ +# ------------------------- +# ALIASES +# ------------------------- aliases: - # Cache Management + # ------------------------- + # ALIASES: Caches + # ------------------------- - &restore-yarn-cache keys: - - v1-yarn-cache-{{ arch }}-{{ checksum "package.json" }} + - v1-yarn-cache-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} - v1-yarn-cache-{{ arch }} + - &save-yarn-cache paths: - ~/.cache/yarn - key: v1-yarn-cache-{{ arch }}-{{ checksum "package.json" }} + key: v1-yarn-cache-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} - - &restore-node-modules + - &restore-brew-cache keys: - - v2-node-modules-{{ arch }}-{{ checksum "package.json" }} - - &save-node-modules - paths: - - node_modules - key: v2-node-modules-{{ arch }}-{{ checksum "package.json" }} + - v1-brew - - &restore-cache-analysis - keys: - - v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} - - &save-cache-analysis + - &save-brew-cache paths: - - bots/node_modules - - node_modules - key: v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} - - - &restore-cache-gradle - keys: - - v1-gradle-{{ .Branch }}-{{ checksum "build.gradle" }}-{{ checksum "ReactAndroid/build.gradle" }} - # Fallback in case checksum fails - - v1-gradle-{{ .Branch }}-{{ checksum "build.gradle" }}- - - v1-gradle-{{ .Branch }}- - # Fallback in case this is a first-time run on a fork - - v1-gradle-master- - - &save-cache-gradle - paths: - - ~/.gradle - key: v1-gradle-{{ .Branch }}-{{ checksum "build.gradle" }}-{{ checksum "ReactAndroid/build.gradle" }} + - /usr/local/Homebrew + - ~/Library/Caches/Homebrew + key: v1-brew - - &restore-cache-downloads-buck + # Android + - &restore-buck-downloads-cache keys: - v3-buck-v2019.01.10.01-{{ checksum "scripts/circleci/buck_fetch.sh" }}} - v3-buck-v2019.01.10.01- - - &save-cache-downloads-buck + - &save-buck-downloads-cache paths: - ~/buck - ~/okbuck key: v3-buck-v2019.01.10.01-{{ checksum "scripts/circleci/buck_fetch.sh" }} - - &restore-cache-watchman - keys: - - v1-watchman-{{ arch }}-v4.9.0 - - &save-cache-watchman - paths: - - ~/watchman - key: v1-watchman-{{ arch }}-v4.9.0 - - - &restore-cache-downloads-gradle + - &restore-gradle-downloads-cache keys: - v1-gradle-{{ checksum "ReactAndroid/build.gradle" }}-{{ checksum "scripts/circleci/gradle_download_deps.sh" }} - v1-gradle- - - &save-cache-downloads-gradle + + - &save-gradle-downloads-cache paths: - ~/.gradle - ReactAndroid/build/downloads - ReactAndroid/build/third-party-ndk key: v1-gradle-{{ checksum "ReactAndroid/build.gradle" }}-{{ checksum "scripts/circleci/gradle_download_deps.sh" }} - - &restore-cache-homebrew - keys: - - v1-homebrew - - &save-cache-homebrew - paths: - - /usr/local/Homebrew - key: v1-homebrew - - # Branch Filtering - - &filter-only-master-stable - branches: - only: - - /.*-stable/ - - master - - - &filter-only-stable - branches: - only: - - /.*-stable/ - - - &filter-ignore-gh-pages - branches: - ignore: gh-pages - - - &filter-ignore-master-stable - branches: - ignore: - - master - - /.*-stable/ - - gh-pages - + # ------------------------- + # ALIASES: Shared Commands + # ------------------------- - &yarn name: Run Yarn command: | @@ -108,117 +60,6 @@ aliases: yarn install --non-interactive --cache-folder ~/.cache/yarn fi - - &install-buck - name: Install BUCK - command: | - buck --version - # Install related tooling - if [[ ! -e ~/okbuck ]]; then - git clone https://github.com/uber/okbuck.git ~/okbuck --depth=1 - fi - mkdir -p ~/react-native/tooling/junit - cp -R ~/okbuck/tooling/junit/* ~/react-native/tooling/junit/. - - &validate-android-sdk - name: Validate Android SDK Install - command: ./scripts/validate-android-sdk.sh - - - &validate-android-test-env - name: Validate Android Test Environment - command: ./scripts/validate-android-test-env.sh - - # Test Definitions - - &run-js-tests - name: JavaScript Test Suite - command: yarn test-ci - - # eslint sometimes runs into trouble generating the reports - - &run-lint-checks - name: Lint code - command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ~/react-native/reports/junit/eslint/results.xml - - - &run-flow-checks-ios - name: Check for errors in code using Flow (iOS) - command: yarn flow-check-ios - - - &run-flow-checks-android - name: Check for errors in code using Flow (Android) - command: yarn flow-check-android - - - &run-sanity-checks - name: Sanity checks - command: | - ./scripts/circleci/check_license.sh - ./scripts/circleci/validate_yarn_lockfile.sh - when: always - - - &js-coverage - name: Test coverage - command: | - yarn test --coverage --maxWorkers=2 - cat ./coverage/lcov.info | ./node_modules/.bin/coveralls - when: always - - - &download-dependencies-gradle - name: Download Dependencies Using Gradle - command: ./scripts/circleci/gradle_download_deps.sh - - - &download-dependencies-buck - name: Download Dependencies Using Buck - command: ./scripts/circleci/buck_fetch.sh - - - &build-android-app - name: Build Android App - command: | - buck build ReactAndroid/src/main/java/com/facebook/react - buck build ReactAndroid/src/main/java/com/facebook/react/shell - - - &create-avd - name: Create Android Virtual Device - command: source scripts/android-setup.sh && createAVD - - - &launch-avd - name: Launch Android Virtual Device in Background - command: source scripts/android-setup.sh && launchAVD - background: true - - - &wait-for-avd - name: Wait for Android Virtual Device - command: source scripts/android-setup.sh && waitForAVD - - - &build-js-bundle - name: Build JavaScript Bundle - command: node cli.js bundle --max-workers 2 --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js - - - &compile-native-libs - name: Compile Native Libs for Unit and Integration Tests - command: ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=$BUILD_THREADS - no_output_timeout: 6m - - - &run-android-unit-tests - name: Run Unit Tests - command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ~/react-native/reports/buck/all-results-raw.xml - - - &run-android-instrumentation-tests - name: Run Instrumentation Tests - command: | - if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then - echo "JavaScript bundle missing, cannot run instrumentation tests. Verify build-js-bundle step completed successfully."; exit 1; - fi - source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS - - - &build-android-rntester-app - name: Build Android RNTester App - command: ./gradlew RNTester:android:app:assembleRelease - - - &collect-android-test-results - name: Collect Test Results - command: | - find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/react-native/reports/build/ \; - find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/react-native/reports/outputs/ \; - find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ~/react-native/reports/buck/ \; - ./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/all-results-junit.xml - when: always - - &setup-artifacts name: Initial Setup command: | @@ -227,29 +68,23 @@ aliases: mkdir -p ~/react-native/reports/junit/ mkdir -p ~/react-native/reports/outputs/ - - &brew-install-watchman - name: Install Watchman - command: | - brew install watchman - touch .watchmanconfig - - - &boot-simulator-iphone - name: Boot iPhone Simulator - command: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true - - - &run-objc-ios-tests - name: iOS Test Suite - command: ./scripts/objc-test-ios.sh test + # Android + - &download-dependencies-buck + name: Download Dependencies Using Buck + command: ./scripts/circleci/buck_fetch.sh - - &run-objc-ios-e2e-tests - name: iOS End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --ios --retries 3; + - &download-dependencies-gradle + name: Download Dependencies Using Gradle + command: ./scripts/circleci/gradle_download_deps.sh - - &run-js-e2e-tests - name: JavaScript End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --js --retries 3; + # JavaScript + - &run-js-tests + name: JavaScript Test Suite + command: yarn test-ci - # DISABLED TESTS + # ------------------------- + # ALIASES: Disabled Tests + # ------------------------- - &run-podspec-tests name: Test CocoaPods command: ./scripts/process-podspecs.sh @@ -260,16 +95,65 @@ aliases: name: Android End-to-End Test Suite command: node ./scripts/run-ci-e2e-tests.js --android --retries 3; + + # ------------------------- + # ALIASES: Branch Filters + # ------------------------- + - &filter-only-master + branches: + only: master + + - &filter-only-master-stable + branches: + only: + - /.*-stable/ + - master + + - &filter-only-stable + branches: + only: + - /.*-stable/ + + - &filter-ignore-gh-pages + branches: + ignore: gh-pages + + - &filter-only-version-tags + # Both of the following conditions must be included! + # Ignore any commit on any branch by default. + branches: + ignore: /.*/ + # Only act on version tags. + tags: + only: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/ + + - &filter-only-forked-pull-requests + branches: + only: /^pull\/.*$/ + + # ------------------------- + # ALIASES: Workflows + # ------------------------- + - &run-after-checkout + filters: *filter-ignore-gh-pages + requires: + - checkout_code + +# ------------------------- +# DEFAULTS +# ------------------------- defaults: &defaults working_directory: ~/react-native environment: - GIT_COMMIT_DESC: git log --format=oneline -n 1 $CIRCLE_SHA1 +# JavaScript js_defaults: &js_defaults <<: *defaults docker: - image: node:8 +# Android android_defaults: &android_defaults <<: *defaults docker: @@ -282,11 +166,15 @@ android_defaults: &android_defaults - GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError"' - BUILD_THREADS: 2 +# iOS macos_defaults: &macos_defaults <<: *defaults macos: xcode: "10.1.0" +# ------------------------- +# JOBS +# ------------------------- version: 2 jobs: # Set up a Node environment for downstream jobs @@ -300,30 +188,110 @@ jobs: - run: *yarn - save-cache: *save-yarn-cache - # Basic checks against the checkout, cache... - - run: *run-sanity-checks - - persist_to_workspace: root: . paths: . - # Runs JavaScript lint and flow checks. - # Currently will fail a PR if lint/flow raises issues. + # ------------------------- + # JOBS: Analyze PR + # ------------------------- + # Analyze pull request and raise any lint/flow issues. + # Issues will be posted to the PR itself via GitHub bots. + # This workflow should only fail if the bots fail to run. + analyze_pr: + <<: *defaults + docker: + - image: node:lts + # The public github tokens are publicly visible by design + environment: + - PUBLIC_PULLBOT_GITHUB_TOKEN_A: "a6edf8e8d40ce4e8b11a" + - PUBLIC_PULLBOT_GITHUB_TOKEN_B: "150e1341f4dd9c944d2a" + - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: "78a72af35445ca3f8180" + - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: "b1a98e0bbd56ff1ccba1" + + steps: + - checkout + - run: *setup-artifacts + + - restore-cache: *restore-yarn-cache + - run: *yarn + + - run: + name: Analyze Shell Scripts + command: | + echo -e "\\x1B[36mInstalling additional dependencies\\x1B[0m" + apt update && apt install -y shellcheck jq + yarn add @octokit/rest@15.18.0 + echo -e "\\x1B[36mAnalyzing shell scripts\\x1B[0m"; \ + GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" \ + GITHUB_OWNER="$CIRCLE_PROJECT_USERNAME" \ + GITHUB_REPO="$CIRCLE_PROJECT_REPONAME" \ + GITHUB_PR_NUMBER="$CIRCLE_PR_NUMBER" \ + ./scripts/circleci/analyze_scripts.sh + when: always + + - run: + name: Analyze Code + command: | + echo -e "\\x1B[36mInstalling additional dependencies\\x1B[0m"; yarn add @octokit/rest@15.18.0 + echo -e "\\x1B[36mAnalyzing code\\x1B[0m"; \ + GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" \ + GITHUB_OWNER="$CIRCLE_PROJECT_USERNAME" \ + GITHUB_REPO="$CIRCLE_PROJECT_REPONAME" \ + GITHUB_PR_NUMBER="$CIRCLE_PR_NUMBER" \ + ./scripts/circleci/analyze_code.sh + when: always + + - run: + name: Analyze Pull Request + command: | + echo -e "\\x1B[36mInstalling additional dependencies\\x1B[0m" + cd bots + yarn install --non-interactive --cache-folder ~/.cache/yarn + echo -e "\\x1B[36mAnalyzing pull request\\x1B[0m"; \ + DANGER_GITHUB_API_TOKEN="$PUBLIC_PULLBOT_GITHUB_TOKEN_A""$PUBLIC_PULLBOT_GITHUB_TOKEN_B" \ + yarn danger ci --use-github-checks + when: always + - save-cache: *save-yarn-cache + + # ------------------------- + # JOBS: Analyze Code + # ------------------------- analyze: <<: *js_defaults steps: - attach_workspace: at: ~/react-native - - run: *run-lint-checks - - run: *run-flow-checks-ios - - run: *run-flow-checks-android + - run: + name: Lint code + command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ~/react-native/reports/junit/eslint/results.xml + + - run: + name: Check for errors in code using Flow (iOS) + command: yarn flow-check-ios + when: always + + - run: + name: Check for errors in code using Flow (Android) + command: yarn flow-check-android + when: always + + - run: + name: Sanity checks + command: | + ./scripts/circleci/check_license.sh + ./scripts/circleci/validate_yarn_lockfile.sh + when: always - store_test_results: path: ~/react-native/reports/junit - store_artifacts: path: ~/react-native/yarn.lock + # ------------------------- + # JOBS: Test JavaScript + # ------------------------- # Runs JavaScript tests on Node 8 test_javascript: <<: *js_defaults @@ -332,7 +300,10 @@ jobs: at: ~/react-native - run: *run-js-tests - - run: *run-js-e2e-tests + + - run: + name: JavaScript End-to-End Test Suite + command: node ./scripts/run-ci-e2e-tests.js --js --retries 3; - store_test_results: path: ~/react-native/reports/junit @@ -345,12 +316,19 @@ jobs: steps: - checkout - run: *setup-artifacts + - run: *yarn + - run: *run-js-tests + - run: yarn run format-check + - store_test_results: path: ~/react-native/reports/junit + # ------------------------- + # JOBS: Test iOS + # ------------------------- # Runs unit tests on iOS devices test_ios: <<: *macos_defaults @@ -358,13 +336,25 @@ jobs: - attach_workspace: at: ~/react-native - - run: *boot-simulator-iphone + - run: + name: Boot iPhone Simulator + command: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true - - restore-cache: *restore-cache-homebrew - - run: *brew-install-watchman - - save-cache: *save-cache-homebrew - - run: *run-objc-ios-e2e-tests - - run: *run-objc-ios-tests + - restore-cache: *restore-brew-cache + - run: + name: Install Watchman + command: | + HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman >/dev/null + touch .watchmanconfig + - save-cache: *save-brew-cache + + - run: + name: iOS Test Suite + command: ./scripts/objc-test-ios.sh test + + - run: + name: iOS End-to-End Test Suite + command: node ./scripts/run-ci-e2e-tests.js --ios --retries 3; - store_test_results: path: ~/react-native/reports/junit @@ -375,30 +365,49 @@ jobs: steps: - attach_workspace: at: ~/react-native - - run: xcrun simctl boot "iPhone 5s" || true + + - run: + name: Start iPhone 5s simulator + background: true + command: xcrun simctl boot "iPhone 5s" || true + - run: name: Configure Environment Variables command: | echo 'export PATH=/usr/local/opt/node@8/bin:$PATH' >> $BASH_ENV source $BASH_ENV + + # Brew + - restore-cache: *restore-brew-cache - run: - name: Install Node 8 + name: Configure Detox Environment command: | - brew install node@8 - brew link node@8 - brew tap wix/brew - brew install applesimutils + HOMEBREW_NO_AUTO_UPDATE=1 brew install node@8 >/dev/null + HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null + HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null + touch .watchmanconfig node -v + - save-cache: *save-brew-cache + + # Yarn install + - restore-cache: *restore-yarn-cache - run: *yarn + - save-cache: *save-yarn-cache + # Xcode build - run: name: Build iOS app for simulator command: yarn run build-ios-e2e + + # Test - run: name: Run Detox Tests command: yarn run test-ios-e2e - # Set up an Android environment for downstream jobs + # ------------------------- + # JOBS: Test Android + # ------------------------- + # Run Android tests test_android: <<: *android_defaults steps: @@ -406,110 +415,104 @@ jobs: at: ~/react-native # Validate Android SDK installation and packages - - run: *validate-android-sdk + - run: + name: Validate Android SDK Install + command: ./scripts/validate-android-sdk.sh # Starting emulator in advance as it takes some time to boot. - - run: *create-avd - - run: *launch-avd + - run: + name: Create Android Virtual Device + command: source scripts/android-setup.sh && createAVD + - run: + name: Launch Android Virtual Device in Background + command: source scripts/android-setup.sh && launchAVD + background: true # Keep configuring Android dependencies while AVD boots up # Install Buck - - restore-cache: *restore-cache-downloads-buck - - run: *install-buck - - save-cache: *save-cache-downloads-buck + - restore-cache: *restore-buck-downloads-cache + - run: + name: Install BUCK + command: | + buck --version + # Install related tooling + if [[ ! -e ~/okbuck ]]; then + git clone https://github.com/uber/okbuck.git ~/okbuck --depth=1 + fi + mkdir -p ~/react-native/tooling/junit + cp -R ~/okbuck/tooling/junit/* ~/react-native/tooling/junit/. + - save-cache: *save-buck-downloads-cache # Validate Android test environment (including Buck) - - run: *validate-android-test-env + - run: + name: Validate Android Test Environment + command: ./scripts/validate-android-test-env.sh # Download dependencies using Buck - run: *download-dependencies-buck # Download dependencies using Gradle - - restore-cache: *restore-cache-downloads-gradle + - restore-cache: *restore-gradle-downloads-cache - run: *download-dependencies-gradle - - save-cache: *save-cache-downloads-gradle + - save-cache: *save-gradle-downloads-cache # Build and compile - - run: *build-android-app - - run: *compile-native-libs + - run: + name: Build Android App + command: | + buck build ReactAndroid/src/main/java/com/facebook/react + buck build ReactAndroid/src/main/java/com/facebook/react/shell + - run: + name: Compile Native Libs for Unit and Integration Tests + command: ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=$BUILD_THREADS + no_output_timeout: 6m # Build JavaScript Bundle for instrumentation tests - - run: *build-js-bundle + - run: + name: Build JavaScript Bundle + command: node cli.js bundle --max-workers 2 --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js + # Wait for AVD to finish booting before running tests - - run: *wait-for-avd + - run: + name: Wait for Android Virtual Device + command: source scripts/android-setup.sh && waitForAVD # Test Suite - - run: *run-android-unit-tests - - run: *run-android-instrumentation-tests - - run: *build-android-rntester-app - - # Collect Results - - run: *collect-android-test-results - - store_test_results: - path: ~/react-native/reports/junit - - # Analyze pull request and raise any lint/flow issues. - # Issues will be posted to the PR itself via GitHub bots. - # This workflow should only fail if the bots fail to run. - # The public github tokens are publicly visible by design - analyze_pr: - <<: *defaults - docker: - - image: node:lts - environment: - - PUBLIC_PULLBOT_GITHUB_TOKEN_A: "a6edf8e8d40ce4e8b11a" - - PUBLIC_PULLBOT_GITHUB_TOKEN_B: "150e1341f4dd9c944d2a" - - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: "78a72af35445ca3f8180" - - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: "b1a98e0bbd56ff1ccba1" - - steps: - - checkout - - run: *setup-artifacts - - - restore-cache: *restore-cache-analysis - - run: *yarn - - run: - name: Analyze Shell Scripts - command: | - echo -e "\\x1B[36mInstalling additional dependencies\\x1B[0m" - apt update && apt install -y shellcheck - yarn add @octokit/rest@15.18.0 - echo -e "\\x1B[36mAnalyzing shell scripts\\x1B[0m"; \ - GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" \ - GITHUB_OWNER="$CIRCLE_PROJECT_USERNAME" \ - GITHUB_REPO="$CIRCLE_PROJECT_REPONAME" \ - GITHUB_PR_NUMBER="$CIRCLE_PR_NUMBER" \ - ./scripts/circleci/analyze_scripts.sh - when: always + name: Run Unit Tests + command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ~/react-native/reports/buck/all-results-raw.xml - run: - name: Analyze Code + name: Run Instrumentation Tests command: | - echo -e "\\x1B[36mInstalling additional dependencies\\x1B[0m"; yarn add @octokit/rest@15.18.0 - echo -e "\\x1B[36mAnalyzing code\\x1B[0m"; \ - GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" \ - GITHUB_OWNER="$CIRCLE_PROJECT_USERNAME" \ - GITHUB_REPO="$CIRCLE_PROJECT_REPONAME" \ - GITHUB_PR_NUMBER="$CIRCLE_PR_NUMBER" \ - ./scripts/circleci/analyze_code.sh - when: always + if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then + echo "JavaScript bundle missing, cannot run instrumentation tests. Verify Build JavaScript Bundle step completed successfully."; exit 1; + fi + source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS + + - run: + name: Build Android RNTester App + command: ./gradlew RNTester:android:app:assembleRelease + # Collect Results - run: - name: Analyze Pull Request + name: Collect Test Results command: | - echo -e "\\x1B[36mInstalling additional dependencies\\x1B[0m" - cd bots - yarn install --non-interactive --cache-folder ~/.cache/yarn - echo -e "\\x1B[36mAnalyzing pull request\\x1B[0m"; \ - DANGER_GITHUB_API_TOKEN="$PUBLIC_PULLBOT_GITHUB_TOKEN_A""$PUBLIC_PULLBOT_GITHUB_TOKEN_B" \ - yarn danger ci --use-github-checks + find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/react-native/reports/build/ \; + find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/react-native/reports/outputs/ \; + find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ~/react-native/reports/buck/ \; + ./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/all-results-junit.xml when: always - - save-cache: *save-cache-analysis - - # Test Coverage + + - store_test_results: + path: ~/react-native/reports/junit + + # ------------------------- + # JOBS: Coverage + # ------------------------- + # Collect JavaScript test coverage js_coverage: <<: *js_defaults environment: @@ -521,10 +524,18 @@ jobs: - checkout - restore-cache: *restore-yarn-cache - run: *yarn - - run: *js-coverage + - run: + name: Test coverage + command: | + yarn test --coverage --maxWorkers=2 + cat ./coverage/lcov.info | ./node_modules/.bin/coveralls + when: always - store_artifacts: path: ~/react-native/coverage/ + # ------------------------- + # JOBS: Releases + # ------------------------- # Publishes new version onto npm # Only works on stable branches when a properly tagged commit is pushed publish_npm_package: @@ -536,11 +547,11 @@ jobs: - run: *yarn # Fetch dependencies using Buck - - restore-cache: *restore-cache-downloads-buck + - restore-cache: *restore-buck-downloads-cache - run: *download-dependencies-buck # Fetch dependencies using Gradle - - restore-cache: *restore-cache-downloads-gradle + - restore-cache: *restore-gradle-downloads-cache - run: *download-dependencies-gradle - restore-cache: *restore-yarn-cache @@ -557,75 +568,42 @@ jobs: git config --global user.name "npm Deployment Script" echo "machine github.com login react-native-bot password $GITHUB_TOKEN" > ~/.netrc + # Build and publish release. Requires an Android environment. - run: name: Publish React Native Package command: node ./scripts/publish-npm.js -# Workflows enables us to run multiple jobs in parallel +# ------------------------- +# WORK FLOWS +# ------------------------- workflows: version: 2 tests: jobs: - # Checkout repo and run Yarn - - checkout_code: - filters: *filter-ignore-gh-pages - - # Run lint, flow, and other checks - - analyze: - filters: *filter-ignore-gh-pages - requires: - - checkout_code - - # Test JavaScript - - test_javascript: - filters: *filter-ignore-gh-pages - requires: - - checkout_code - - # Test Android - - test_android: - filters: *filter-ignore-gh-pages - requires: - - checkout_code - - # Test iOS - - test_ios: + - test_node_lts: filters: *filter-ignore-gh-pages - requires: - - checkout_code - - test_detox_end_to_end: + - checkout_code: filters: *filter-ignore-gh-pages - requires: - - checkout_code - # Tooling Compatibility Checks - - test_node_lts: - filters: *filter-ignore-gh-pages + - analyze: *run-after-checkout + - test_javascript: *run-after-checkout + - test_android: *run-after-checkout + - test_ios: *run-after-checkout + - test_detox_end_to_end: *run-after-checkout releases: jobs: - # Only runs on vX.X.X tags if all tests are green - publish_npm_package: - filters: - # ignore any commit on any branch by default - branches: - ignore: /.*/ - # only act on version tags - tags: - only: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/ + filters: *filter-only-version-tags analysis: jobs: # Run code checks on PRs from forks - analyze_pr: - filters: - branches: - only: /^pull\/.*$/ + filters: *filter-only-forked-pull-requests # Gather coverage on master - js_coverage: - filters: - branches: - only: master + filters: *filter-only-master diff --git a/.eslintrc b/.eslintrc index 6fef2c2c1d1538..d18f7eb8286127 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,272 +1,11 @@ { "root": true, - "parser": "babel-eslint", - - "env": { - "es6": true, - }, - - "plugins": [ - "eslint-comments", - "flowtype", - "prettier", - "react", - "react-hooks", - "react-native", - "jest", + "extends": [ + "./packages/eslint-config-react-native-community/index.js" ], - // Map from global var to bool specifying if it can be redefined - "globals": { - "__DEV__": true, - "__dirname": false, - "__fbBatchedBridgeConfig": false, - "alert": false, - "cancelAnimationFrame": false, - "cancelIdleCallback": false, - "clearImmediate": true, - "clearInterval": false, - "clearTimeout": false, - "console": false, - "document": false, - "escape": false, - "Event": false, - "EventTarget": false, - "exports": false, - "fetch": false, - "FormData": false, - "global": false, - "Map": true, - "module": false, - "navigator": false, - "process": false, - "Promise": true, - "requestAnimationFrame": true, - "requestIdleCallback": true, - "require": false, - "Set": true, - "setImmediate": true, - "setInterval": false, - "setTimeout": false, - "window": false, - "XMLHttpRequest": false, - }, - - "rules": { - // General - "comma-dangle": [1, "always-multiline"], // allow or disallow trailing commas - "no-cond-assign": 1, // disallow assignment in conditional expressions - "no-console": 0, // disallow use of console (off by default in the node environment) - "no-const-assign": 2, // disallow assignment to const-declared variables - "no-constant-condition": 0, // disallow use of constant expressions in conditions - "no-control-regex": 1, // disallow control characters in regular expressions - "no-debugger": 1, // disallow use of debugger - "no-dupe-class-members": 2, // Disallow duplicate name in class members - "no-dupe-keys": 2, // disallow duplicate keys when creating object literals - "no-empty": 0, // disallow empty statements - "no-ex-assign": 1, // disallow assigning to the exception in a catch block - "no-extra-boolean-cast": 1, // disallow double-negation boolean casts in a boolean context - "no-extra-parens": 0, // disallow unnecessary parentheses (off by default) - "no-extra-semi": 1, // disallow unnecessary semicolons - "no-func-assign": 1, // disallow overwriting functions written as function declarations - "no-inner-declarations": 0, // disallow function or variable declarations in nested blocks - "no-invalid-regexp": 1, // disallow invalid regular expression strings in the RegExp constructor - "no-negated-in-lhs": 1, // disallow negation of the left operand of an in expression - "no-obj-calls": 1, // disallow the use of object properties of the global object (Math and JSON) as functions - "no-regex-spaces": 1, // disallow multiple spaces in a regular expression literal - "no-reserved-keys": 0, // disallow reserved words being used as object literal keys (off by default) - "no-sparse-arrays": 1, // disallow sparse arrays - "no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement - "use-isnan": 1, // disallow comparisons with the value NaN - "valid-jsdoc": 0, // Ensure JSDoc comments are valid (off by default) - "valid-typeof": 1, // Ensure that the results of typeof are compared against a valid string - - // Best Practices - // These are rules designed to prevent you from making mistakes. They either prescribe a better way of doing something or help you avoid footguns. - - "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default) - "complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) - "consistent-return": 0, // require return statements to either always or never specify values - "curly": 1, // specify curly brace conventions for all control statements - "default-case": 0, // require default case in switch statements (off by default) - "dot-notation": 1, // encourages use of dot notation whenever possible - "eqeqeq": [1, "allow-null"], // require the use of === and !== - "guard-for-in": 0, // make sure for-in loops have an if statement (off by default) - "no-alert": 1, // disallow the use of alert, confirm, and prompt - "no-caller": 1, // disallow use of arguments.caller or arguments.callee - "no-div-regex": 1, // disallow division operators explicitly at beginning of regular expression (off by default) - "no-else-return": 0, // disallow else after a return in an if (off by default) - "no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default) - "no-eval": 2, // disallow use of eval() - "no-extend-native": 1, // disallow adding to native types - "no-extra-bind": 1, // disallow unnecessary function binding - "no-fallthrough": 1, // disallow fallthrough of case statements - "no-floating-decimal": 1, // disallow the use of leading or trailing decimal points in numeric literals (off by default) - "no-implied-eval": 1, // disallow use of eval()-like methods - "no-labels": 1, // disallow use of labeled statements - "no-iterator": 1, // disallow usage of __iterator__ property - "no-lone-blocks": 1, // disallow unnecessary nested blocks - "no-loop-func": 0, // disallow creation of functions within loops - "no-multi-str": 0, // disallow use of multiline strings - "no-native-reassign": 0, // disallow reassignments of native objects - "no-new": 1, // disallow use of new operator when not part of the assignment or comparison - "no-new-func": 2, // disallow use of new operator for Function object - "no-new-wrappers": 1, // disallows creating new instances of String,Number, and Boolean - "no-octal": 1, // disallow use of octal literals - "no-octal-escape": 1, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; - "no-proto": 1, // disallow usage of __proto__ property - "no-redeclare": 0, // disallow declaring the same variable more then once - "no-return-assign": 1, // disallow use of assignment in return statement - "no-script-url": 1, // disallow use of javascript: urls. - "no-self-compare": 1, // disallow comparisons where both sides are exactly the same (off by default) - "no-sequences": 1, // disallow use of comma operator - "no-unused-expressions": 0, // disallow usage of expressions in statement position - "no-void": 1, // disallow use of void operator (off by default) - "no-warning-comments": 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default) - "no-with": 1, // disallow use of the with statement - "radix": 1, // require use of the second argument for parseInt() (off by default) - "semi-spacing": 1, // require a space after a semi-colon - "vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default) - "wrap-iife": 0, // require immediate function invocation to be wrapped in parentheses (off by default) - "yoda": 1, // require or disallow Yoda conditions - - // Variables - // These rules have to do with variable declarations. - - "no-catch-shadow": 1, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) - "no-delete-var": 1, // disallow deletion of variables - "no-label-var": 1, // disallow labels that share a name with a variable - "no-shadow": 1, // disallow declaration of variables already declared in the outer scope - "no-shadow-restricted-names": 1, // disallow shadowing of names such as arguments - "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block - "no-undefined": 0, // disallow use of undefined variable (off by default) - "no-undef-init": 1, // disallow use of undefined when initializing variables - "no-unused-vars": [1, {"vars": "all", "args": "none", ignoreRestSiblings: true}], // disallow declaration of variables that are not used in the code - "no-use-before-define": 0, // disallow use of variables before they are defined - - // Node.js - // These rules are specific to JavaScript running on Node.js. - - "handle-callback-err": 1, // enforces error handling in callbacks (off by default) (on by default in the node environment) - "no-mixed-requires": 1, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment) - "no-new-require": 1, // disallow use of new operator with the require function (off by default) (on by default in the node environment) - "no-path-concat": 1, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment) - "no-process-exit": 0, // disallow process.exit() (on by default in the node environment) - "no-restricted-modules": 1, // restrict usage of specified node modules (off by default) - "no-sync": 0, // disallow use of synchronous methods (off by default) - - // ESLint Comments Plugin - // The following rules are made available via `eslint-plugin-eslint-comments` - "eslint-comments/no-aggregating-enable": 1, // disallows eslint-enable comments for multiple eslint-disable comments - "eslint-comments/no-unlimited-disable": 1, // disallows eslint-disable comments without rule names - "eslint-comments/no-unused-disable": 1, // disallow disables that don't cover any errors - "eslint-comments/no-unused-enable": 1, // // disallow enables that don't enable anything or enable rules that weren't disabled - - // Flow Plugin - // The following rules are made available via `eslint-plugin-flowtype` - "flowtype/define-flow-type": 1, - "flowtype/use-flow-type": 1, - - // Prettier Plugin - // https://github.com/prettier/eslint-plugin-prettier - "prettier/prettier": [2, "fb", "@format"], - - // Stylistic Issues - // These rules are purely matters of style and are quite subjective. - - "key-spacing": 0, - "keyword-spacing": 1, // enforce spacing before and after keywords - "jsx-quotes": [1, "prefer-double"], // enforces the usage of double quotes for all JSX attribute values which doesn’t contain a double quote - "comma-spacing": 0, - "no-multi-spaces": 0, - "brace-style": 0, // enforce one true brace style (off by default) - "camelcase": 0, // require camel case names - "consistent-this": 1, // enforces consistent naming when capturing the current execution context (off by default) - "eol-last": 1, // enforce newline at the end of file, with no multiple empty lines - "func-names": 0, // require function expressions to have a name (off by default) - "func-style": 0, // enforces use of function declarations or expressions (off by default) - "new-cap": 0, // require a capital letter for constructors - "new-parens": 1, // disallow the omission of parentheses when invoking a constructor with no arguments - "no-nested-ternary": 0, // disallow nested ternary expressions (off by default) - "no-array-constructor": 1, // disallow use of the Array constructor - "no-empty-character-class": 1, // disallow the use of empty character classes in regular expressions - "no-lonely-if": 0, // disallow if as the only statement in an else block (off by default) - "no-new-object": 1, // disallow use of the Object constructor - "no-spaced-func": 1, // disallow space between function identifier and application - "no-ternary": 0, // disallow the use of ternary operators (off by default) - "no-trailing-spaces": 1, // disallow trailing whitespace at the end of lines - "no-underscore-dangle": 0, // disallow dangling underscores in identifiers - "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation - "quotes": [1, "single", "avoid-escape"], // specify whether double or single quotes should be used - "quote-props": 0, // require quotes around object literal property names (off by default) - "semi": 1, // require or disallow use of semicolons instead of ASI - "sort-vars": 0, // sort variables within the same declaration block (off by default) - "space-in-brackets": 0, // require or disallow spaces inside brackets (off by default) - "space-in-parens": 0, // require or disallow spaces inside parentheses (off by default) - "space-infix-ops": 1, // require spaces around operators - "space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default) - "max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default) - "one-var": 0, // allow just one var statement per function (off by default) - "wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default) - - // Legacy - // The following rules are included for compatibility with JSHint and JSLint. While the names of the rules may not match up with the JSHint/JSLint counterpart, the functionality is the same. - - "max-depth": 0, // specify the maximum depth that blocks can be nested (off by default) - "max-len": 0, // specify the maximum length of a line in your program (off by default) - "max-params": 0, // limits the number of parameters that can be used in the function declaration. (off by default) - "max-statements": 0, // specify the maximum number of statement allowed in a function (off by default) - "no-bitwise": 1, // disallow use of bitwise operators (off by default) - "no-plusplus": 0, // disallow use of unary operators, ++ and -- (off by default) - - // React Plugin - // The following rules are made available via `eslint-plugin-react`. - - "react/display-name": 0, - "react/jsx-boolean-value": 0, - "react/jsx-no-comment-textnodes": 1, - "react/jsx-no-duplicate-props": 2, - "react/jsx-no-undef": 2, - "react/jsx-sort-props": 0, - "react/jsx-uses-react": 1, - "react/jsx-uses-vars": 1, - "react/no-did-mount-set-state": 1, - "react/no-did-update-set-state": 1, - "react/no-multi-comp": 0, - "react/no-string-refs": 1, - "react/no-unknown-property": 0, - "react/prop-types": 0, - "react/react-in-jsx-scope": 1, - "react/self-closing-comp": 1, - "react/wrap-multilines": 0, - - // React-Hooks Plugin - // The following rules are made available via `eslint-plugin-react-hooks` - "react-hooks/rules-of-hooks": "error", - - // React-Native Plugin - // The following rules are made available via `eslint-plugin-react-native` - - "react-native/no-inline-styles": 1, - - // Jest Plugin - // The following rules are made available via `eslint-plugin-jest`. - "jest/no-disabled-tests": 1, - "jest/no-focused-tests": 1, - "jest/no-identical-title": 1, - "jest/valid-expect": 1, - }, - "overrides": [ - { - "files": [ - "packages/react-native-symbolicate/**/*.js", - ], - "env": { - "node": true, - }, - }, { "files": [ "**/__fixtures__/**/*.js", diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6a98f2155a0da2..c67c2346df1961 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,11 +1,37 @@ -Libraries/Animated/* @janicduplessis -Libraries/NativeAnimation/* @janicduplessis -Libraries/Image/* @shergin -Libraries/Text/* @shergin +# See https://help.github.com/en/articles/about-code-owners +# to learn more about code owners. +# Order is important; the last matching pattern takes the most +# precedence. You may specify either a GitHub username, or an +# email address if you prefer, as the code owner. + +# Any Markdown file anywhere in the repository +**/*.md @hramos @cpojer + +# GitHub Settings, Bots +/.github/ @hramos +/bots @hramos + +# Continuous Integration +/.circleci/ @hramos +/.circleci/Dockerfiles @gengjiawen +/.appveyor/ @gengjiawen + +# Internals React/Base/* @shergin React/Views/* @shergin React/Modules/* @shergin React/CxxBridge/* @mhorowitz + +# Components and APIs ReactAndroid/src/main/java/com/facebook/react/animated/* @janicduplessis -**/*.md @hramos @cpojer -package.json @hramos @cpojer +Libraries/Animated/* @janicduplessis +Libraries/NativeAnimation/* @janicduplessis +Libraries/Image/* @shergin +Libraries/Text/* @shergin + +# Modifications to package.json typically require +# additional effort from a Facebook employee to land +/package.json @hramos @cpojer + +# These should not be modified through a GitHub PR +LICENSE* @hramos @cpojer @yungsters diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 85d815179a6ce3..e1f15bbd0807eb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,27 +1,29 @@ --- -name: πŸ› Bug Report +name: "πŸ› Bug Report" about: You want to report a reproducible bug or regression in React Native. -labels: "Type: Bug Report" +title: '' +labels: 'Type: Bug Report' + --- ## πŸ› Bug Report - ## To Reproduce - ## Expected Behavior - ## Code Example - ## Environment - diff --git a/.github/ISSUE_TEMPLATE/discussion.md b/.github/ISSUE_TEMPLATE/discussion.md index 2e6be6c030d610..9bed944dab781a 100644 --- a/.github/ISSUE_TEMPLATE/discussion.md +++ b/.github/ISSUE_TEMPLATE/discussion.md @@ -1,7 +1,9 @@ --- -name: πŸš€ Discussion +name: "πŸš€ Discussion" about: You have an idea that could make React Native better, or you want to discuss some aspect of the framework. -labels: "Type: Discussion" +title: 'Discussion: ' +labels: 'Type: Discussion' + --- If you want to participate in casual discussions about the use of React Native, consider participating in one of the following forums: @@ -15,4 +17,4 @@ For a full list of community resources: If you'd like to discuss topics related to the future of React Native, please check out the discussions and proposals repo: - https://github.com/react-native-community/discussions-and-proposals -### Please note that discussions opened as issues in the core React Native repository will be closed. \ No newline at end of file +### Please note that discussions opened as issues in the core React Native repository will be closed. diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md index acc4af60f9d47b..cd0c0424362550 100644 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -1,7 +1,9 @@ --- -name: πŸ“ƒ Documentation Bug +name: "πŸ“ƒ Documentation Bug" about: You want to report something that is wrong or missing from the documentation. -labels: "Type: Docs" +title: 'Docs:' +labels: 'Type: Docs' + --- The React Native website is hosted on a separate repository. You may let the @@ -9,4 +11,4 @@ team know about any issues with the documentation by opening an issue there: - https://github.com/facebook/react-native-website - https://github.com/facebook/react-native-website/issues -### Please do not open a documentation issue in the core React Native repository. \ No newline at end of file +### Please do not open a documentation issue in the core React Native repository. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index b5fc4bdbf0d3f0..8ee57b5d621a4a 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,15 +1,26 @@ --- -name: πŸ’¬ Questions and Help +name: "πŸ€” Questions and Help" about: You need help writing your React Native app. -labels: "Type: Question" +title: 'Question: ' +labels: 'Type: Question' + --- -GitHub Issues in the `facebook/react-native` repository are used exclusively for tracking bugs in the React Native framework. Please do not submit support requests through GitHub. +We use GitHub Issues exclusively to track bugs in React Native. As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek. Please take a look at the following resources. + + +## Coding Questions + +### https://stackoverflow.com/questions/tagged/react-native + +If you have a coding question related to React Native, it might be better suited for Stack Overflow. It's a great place to browse through frequent questions about using React Native, as well as ask for help with specific questions. + + +## Talk to other React Native developers + +### https://www.reactiflux.com/ -For questions or help, please see: -- The React Native help page: http://facebook.github.io/react-native/help -- The React Native channel in Reactiflux: https://discord.gg/0ZcbPKXt5bZjGY5n -- The react-native tag on Stack Overflow: http://stackoverflow.com/questions/tagged/react-native +Reactiflux is an active community of React and React Native developers. If you are looking for immediate assistance or have a general question about React Native, the #react-native channel is a good place to start. -### Please note that this issue tracker is not a help forum and requests for help will be closed. \ No newline at end of file +> For a full list of community resources, check out React Native's Community page at https://facebook.github.io/react-native/help. diff --git a/.github/ISSUE_TEMPLATE/regression.md b/.github/ISSUE_TEMPLATE/regression.md index b54db1473b7791..efbfe4dcc3e180 100644 --- a/.github/ISSUE_TEMPLATE/regression.md +++ b/.github/ISSUE_TEMPLATE/regression.md @@ -1,12 +1,15 @@ --- -name: πŸ’₯ Regression Report +name: "πŸ’₯ Regression Report" about: You want to report unexpected behavior that worked in previous releases. -labels: "Type: Bug Report", "Impact: Regression" +title: 'Regression: ' +labels: 'Type: Bug Report, Impact: Regression' + --- ## πŸ’₯ Regression Report - ## Last working version @@ -23,12 +26,12 @@ Stopped working in version: ## Expected Behavior - ## Code Example - ## Environment - diff --git a/.github/SUPPORT b/.github/SUPPORT new file mode 100644 index 00000000000000..d229644aa40f9d --- /dev/null +++ b/.github/SUPPORT @@ -0,0 +1,35 @@ +Thanks for using React Native! If you need help with your React Native app, the right place to go depends on the type of help that you need. + + +## πŸ€” I have a question or need help with my React Native app. + +If you have a coding question related to React Native, it might be better suited for Stack Overflow. It's a great place to browse through [frequent questions about using React Native](https://stackoverflow.com/questions/tagged/react-native?sort=frequent&pageSize=15), as well as [ask for help with specific questions](https://stackoverflow.com/questions/tagged/react-native). + +[Reactiflux](https://www.reactiflux.com/) is an active community of React and React Native developers. If you are looking for immediate assistance or have a general question about React Native, the #react-native channel is a good place to start. + + +## πŸ“ƒ I found something that seems wrong in the documentation. + +The React Native website is hosted on a [separate repository](https://github.com/facebook/react-native-website). If you want to report something that is wrong or missing from the documentation, [please open a new issue there](https://github.com/facebook/react-native-website/issues). + + +## πŸ› I found a bug in React Native. + +If you want to report a reproducible bug or regression in the React Native library, you can [create a new issue](https://github.com/facebook/react-native/issues/new?labels=Type%3A+Bug+Report&template=bug_report.md). It's a good idea to look through [open issues](https://github.com/facebook/react-native/issues) before doing so, as someone else may have reported a similar issue. + + +## πŸš€ I want to discuss the future of React Native. + +If you'd like to discuss topics related to the future of React Native, please check out the [React Native Community Discussions and Proposals](https://github.com/react-native-community/discussions-and-proposals) repository. + + +## πŸ’¬ I want to talk to other React Native developers. + +If you want to participate in casual discussions about the use of React Native, consider participating in one of the following forums: + +- [Reactiflux Discord Server](https://www.reactiflux) +- [Spectrum Chat](https://spectrum.chat/react-native) +- [React Native Community Facebook Group](https://www.facebook.com/groups/react.native.community) + + +> For a full list of community resources, check out [React Native's Community page](https://facebook.github.io/react-native/help). diff --git a/DockerTests.md b/DockerTests.md deleted file mode 100644 index 682a902e0148d5..00000000000000 --- a/DockerTests.md +++ /dev/null @@ -1,96 +0,0 @@ -# Dockerfile Tests - -This is a high-level overview of the test configuration using Docker. It explains how to run the tests locally -and how they integrate with the Jenkins Pipeline script to run the automated tests on ContainerShip . - -## Docker Installation - -It is required to have Docker running on your machine in order to build and run the tests in the Dockerfiles. -See for more information on how to install. - -## Convenience NPM Run Scripts - -We have added a number of default run scripts to the `package.json` file to simplify building and running your tests. - -`npm run docker-setup-android` - Pulls down the base android docker image used for running the tests - -`npm run docker-build-android` - Builds the docker image used to run the tests - -`npm run test-android-run-unit` - Runs all the unit tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this if the image does not exist it will fail) - -`npm run test-android-run-instrumentation` - Runs all the instrumentation tests that have been built in the latest react/android docker image. If the image does not exist, run `test-android-build` before. You can also pass additional flags to filter which tests instrumentation tests are run. Ex: `npm run test-android-run-instrumentation -- --filter=TestIdTestCase` to only run the TestIdTestCase instrumentation test. See below for more information -on the instrumentation test flags. - -`npm run test-android-run-e2e` - Runs all the end to end tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this if the image does not exist it will fail) - -`npm run test-android-unit` - Builds and runs the Android unit tests. - -`npm run test-android-instrumentation` - Builds and runs the Android instrumentation tests. - -`npm run test-android-e2e` - Builds and runs the Android end to end tests. - -## Detailed Android Setup - -There are two Dockerfiles for use with the Android codebase. - -The `Dockerfile.android-base` contains all the necessary prerequisites required to run the React Android tests. It is -separated out into a separate Dockerfile because these are dependencies that rarely change and also because it is quite -a beastly image since it contains all the Android dependencies for running Android and the emulators (~9GB). - -The good news is you should rarely have to build or pull down the base image! All iterative code updates happen as -part of the `Dockerfile.android` image build. - -So step one... - -`docker pull containership/android-base:latest` - -This will take quite some time depending on your connection and you need to ensure you have ~10GB of free disk space. - -Once this is done, you can run tests locally by executing two simple commands: - -1. `docker build -t react/android -f ./ContainerShip/Dockerfile.android .` -2. `docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-unit-tests.sh` - -> Note: `--cap-add=SYS_ADMIN` flag is required for the `ContainerShip/scripts/run-android-docker-unit-tests.sh` and -`ContainerShip/scripts/run-android-docker-instrumentation-tests.sh` in order to allow the remounting of `/dev/shm` as writeable -so the `buck` build system may write temporary output to that location - -Every time you make any modifications to the codebase, you should re-run the `docker build ...` command in order for your -updates to be included in your local docker image. - -The following shell scripts have been provided for android testing: - -`ContainerShip/scripts/run-android-docker-unit-tests.sh` - Runs the standard android unit tests - -`ContainerShip/scripts/run-android-docker-instrumentation-tests.sh` - Runs the Android instrumentation tests on the emulator. *Note* that these -tests take quite some time to run so there are various flags you can pass in order to filter which tests are run (see below) - -`ContainerShip/scripts/run-ci-e2e-tests.sh` - Runs the android end to end tests - -#### ContainerShip/scripts/run-android-docker-instrumentation-tests.sh - -The instrumentation test script accepts the following flags in order to customize the execution of the tests: - -`--filter` - A regex that filters which instrumentation tests will be run. (Defaults to .*) - -`--package` - Name of the java package containing the instrumentation tests (Defaults to com.facebook.react.tests) - -`--path` - Path to the directory containing the instrumentation tests. (Defaults to ./ReactAndroid/src/androidTest/java/com/facebook/react/tests) - -`--retries` - Number of times to retry a failed test before declaring a failure (Defaults to 2) - -For example, if locally you only wanted to run the InitialPropsTestCase, you could do the following: - -`docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-instrumentation-tests.sh --filter="InitialPropsTestCase"` - -# Javascript Setup - -There is a single Dockerfile for use with the javascript codebase. - -The `Dockerfile.javascript` base requires all the necessary dependencies for running Javascript tests. - -Any time you make an update to the codebase, you can build and run the javascript tests with the following three commands: - -1. `docker build -t react/js -f ./ContainerShip/Dockerfile.javascript .` -2. `docker run -it react/js yarn test --maxWorkers=4` -3. `docker run -it react/js yarn run flow -- check` diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index e5918c01da7009..00000000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,206 +0,0 @@ -import groovy.json.JsonSlurperClassic - -def runPipeline() { - try { - ansiColor('xterm') { - runStages(); - } - } catch(err) { - echo "Error: ${err}" - currentBuild.result = "FAILED" - } -} - -def pullDockerImage(imageName) { - def result = sh(script: "docker pull ${imageName}", returnStatus: true) - - if (result != 0) { - throw new Exception("Failed to pull image[${imageName}]") - } -} - -def buildDockerfile(dockerfilePath = "Dockerfile", imageName) { - def buildCmd = "docker build -f ${dockerfilePath} -t ${imageName} ." - echo "${buildCmd}" - - def result = sh(script: buildCmd, returnStatus: true) - - if (result != 0) { - throw new Exception("Failed to build image[${imageName}] from '${dockerfilePath}'") - } -} - -def runCmdOnDockerImage(imageName, cmd, run_opts = '') { - def result = sh(script: "docker run ${run_opts} -i ${imageName} sh -c '${cmd}'", returnStatus: true) - - if(result != 0) { - throw new Exception("Failed to run cmd[${cmd}] on image[${imageName}]") - } -} - -def calculateGithubInfo() { - return [ - branch: env.BRANCH_NAME, - sha: sh(returnStdout: true, script: 'git rev-parse HEAD').trim(), - tag: null, - isPR: "${env.CHANGE_URL}".contains('/pull/') - ] -} - -def getParallelInstrumentationTests(testDir, parallelCount, imageName) { - def integrationTests = [:] - def testCount = sh(script: "ls ${testDir} | wc -l", returnStdout: true).trim().toInteger() - def testPerParallel = testCount.intdiv(parallelCount) + 1 - - def ignoredTests = 'CatalystNativeJavaToJSReturnValuesTestCase|CatalystUIManagerTestCase|CatalystMeasureLayoutTest|CatalystNativeJavaToJSArgumentsTestCase|CatalystNativeJSToJavaParametersTestCase|ReactScrollViewTestCase|ReactHorizontalScrollViewTestCase|ViewRenderingTestCase'; - - for (def x = 0; (x*testPerParallel) < testCount; x++) { - def offset = x - integrationTests["android integration tests: ${offset}"] = { - run: { - runCmdOnDockerImage(imageName, "bash /app/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh --offset=${offset} --count=${testPerParallel} --ignore=\"${ignoredTests}\"", '--privileged --rm') - } - } - } - - return integrationTests -} - -def runStages() { - def buildInfo = [ - image: [ - name: "facebook/react-native", - tag: null - ], - scm: [ - branch: null, - sha: null, - tag: null, - isPR: false - ] - ] - - node { - def jsDockerBuild, androidDockerBuild - def jsTag, androidTag, jsImageName, androidImageName, parallelInstrumentationTests - - try { - stage('Setup') { - parallel( - 'pull images': { - pullDockerImage('containership/android-base:latest') - } - ) - } - - stage('Build') { - checkout scm - - def githubInfo = calculateGithubInfo() - buildInfo.scm.branch = githubInfo.branch - buildInfo.scm.sha = githubInfo.sha - buildInfo.scm.tag = githubInfo.tag - buildInfo.scm.isPR = githubInfo.isPR - buildInfo.image.tag = "${buildInfo.scm.sha}-${env.BUILD_TAG.replace(" ", "-").replace("/", "-").replace("%2F", "-")}" - - jsTag = "${buildInfo.image.tag}" - androidTag = "${buildInfo.image.tag}" - jsImageName = "${buildInfo.image.name}-js:${jsTag}" - androidImageName = "${buildInfo.image.name}-android:${androidTag}" - - parallelInstrumentationTests = getParallelInstrumentationTests('./ReactAndroid/src/androidTest/java/com/facebook/react/tests', 3, androidImageName) - - parallel( - 'javascript build': { - jsDockerBuild = docker.build("${jsImageName}", "-f ContainerShip/Dockerfile.javascript .") - }, - 'android build': { - androidDockerBuild = docker.build("${androidImageName}", "-f ContainerShip/Dockerfile.android .") - } - ) - - } - - stage('Tests JS') { - try { - parallel( - 'javascript flow': { - runCmdOnDockerImage(jsImageName, 'yarn run flow -- check', '--rm') - }, - 'javascript tests': { - runCmdOnDockerImage(jsImageName, 'yarn test --maxWorkers=4', '--rm') - }, - 'documentation tests': { - runCmdOnDockerImage(jsImageName, 'cd website && yarn test', '--rm') - }, - 'documentation generation': { - runCmdOnDockerImage(jsImageName, 'cd website && node ./server/generate.js', '--rm') - } - ) - } catch(e) { - currentBuild.result = "FAILED" - echo "Test JS Stage Error: ${e}" - } - } - - stage('Tests Android') { - try { - parallel( - 'android unit tests': { - runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-android-docker-unit-tests.sh', '--privileged --rm') - }, - 'android e2e tests': { - runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-ci-e2e-tests.sh --android --js', '--privileged --rm') - } - ) - } catch(e) { - currentBuild.result = "FAILED" - echo "Tests Android Stage Error: ${e}" - } - } - - stage('Tests Android Instrumentation') { - // run all tests in parallel - try { - parallel(parallelInstrumentationTests) - } catch(e) { - currentBuild.result = "FAILED" - echo "Tests Android Instrumentation Stage Error: ${e}" - } - } - - stage('Cleanup') { - cleanupImage(jsDockerBuild) - cleanupImage(androidDockerBuild) - } - } catch(err) { - cleanupImage(jsDockerBuild) - cleanupImage(androidDockerBuild) - - throw err - } - } - -} - -def isMasterBranch() { - return env.GIT_BRANCH == 'master' -} - -def gitCommit() { - return sh(returnStdout: true, script: 'git rev-parse HEAD').trim() -} - -def cleanupImage(image) { - if (image) { - try { - sh "docker ps -a | awk '{ print \$1,\$2 }' | grep ${image.id} | awk '{print \$1 }' | xargs -I {} docker rm {}" - sh "docker rmi -f ${image.id}" - } catch(e) { - echo "Error cleaning up ${image.id}" - echo "${e}" - } - } -} - -runPipeline() diff --git a/Libraries/Animated/release/package.json b/Libraries/Animated/release/package.json index cccaf09d35f896..ec7fec05fd599f 100644 --- a/Libraries/Animated/release/package.json +++ b/Libraries/Animated/release/package.json @@ -19,7 +19,7 @@ "babel-core": "^5.8.25", "babel-loader": "^5.3.2", "del": "^1.2.0", - "fbjs-scripts": "^1.0.0", + "fbjs-scripts": "^1.1.0", "gulp": "^3.9.0", "gulp-babel": "^5.1.0", "gulp-derequire": "^2.1.0", diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m index 245546a8f12564..ad84741253e38d 100644 --- a/Libraries/CameraRoll/RCTCameraRollManager.m +++ b/Libraries/CameraRoll/RCTCameraRollManager.m @@ -27,7 +27,7 @@ @implementation RCTConvert (PHAssetCollectionSubtype) RCT_ENUM_CONVERTER(PHAssetCollectionSubtype, (@{ @"album": @(PHAssetCollectionSubtypeAny), - @"all": @(PHAssetCollectionSubtypeAny), + @"all": @(PHAssetCollectionSubtypeSmartAlbumUserLibrary), @"event": @(PHAssetCollectionSubtypeAlbumSyncedEvent), @"faces": @(PHAssetCollectionSubtypeAlbumSyncedFaces), @"library": @(PHAssetCollectionSubtypeSmartAlbumUserLibrary), diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.js index 7d8b9cb77ce863..67d5b8a5d31045 100644 --- a/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -70,15 +70,18 @@ type Props = $ReadOnly<{| * See http://facebook.github.io/react-native/docs/activityindicator.html */ const ActivityIndicator = (props: Props, forwardedRef?: any) => { - const {onLayout, style, ...restProps} = props; + const {onLayout, style, size, ...restProps} = props; let sizeStyle; + let sizeProp; - switch (props.size) { + switch (size) { case 'small': sizeStyle = styles.sizeSmall; + sizeProp = 'small'; break; case 'large': sizeStyle = styles.sizeLarge; + sizeProp = 'large'; break; default: sizeStyle = {height: props.size, width: props.size}; @@ -89,6 +92,7 @@ const ActivityIndicator = (props: Props, forwardedRef?: any) => { ...restProps, ref: forwardedRef, style: sizeStyle, + size: sizeProp, styleAttr: 'Normal', indeterminate: true, }; diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicatorSchema.js b/Libraries/Components/ActivityIndicator/ActivityIndicatorSchema.js new file mode 100644 index 00000000000000..7da1cbc1c27f0c --- /dev/null +++ b/Libraries/Components/ActivityIndicator/ActivityIndicatorSchema.js @@ -0,0 +1,91 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +import type {SchemaType} from '../../../packages/react-native-codegen/src/CodegenSchema.js'; + +const SwitchSchema: SchemaType = { + modules: { + ActivityIndicatorSchema: { + components: { + ActivityIndicatorView: { + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [], + props: [ + { + name: 'hidesWhenStopped', + optional: true, + typeAnnotation: { + type: 'BooleanTypeAnnotation', + default: false, + }, + }, + { + name: 'animating', + optional: true, + typeAnnotation: { + type: 'BooleanTypeAnnotation', + default: false, + }, + }, + { + name: 'styleAttr', + optional: true, + typeAnnotation: { + type: 'StringTypeAnnotation', + default: '', + }, + }, + { + name: 'color', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ColorPrimitive', + }, + }, + { + name: 'size', + optional: true, + typeAnnotation: { + type: 'StringEnumTypeAnnotation', + default: 'small', + options: [ + { + name: 'small', + }, + { + name: 'large', + }, + ], + }, + }, + { + name: 'intermediate', + optional: true, + typeAnnotation: { + type: 'BooleanTypeAnnotation', + default: false, + }, + }, + ], + }, + }, + }, + }, +}; + +module.exports = SwitchSchema; diff --git a/Libraries/Components/ActivityIndicator/RCTActivityIndicatorViewNativeComponent.js b/Libraries/Components/ActivityIndicator/RCTActivityIndicatorViewNativeComponent.js index 461ee0e51d3392..77992f4fbafd21 100644 --- a/Libraries/Components/ActivityIndicator/RCTActivityIndicatorViewNativeComponent.js +++ b/Libraries/Components/ActivityIndicator/RCTActivityIndicatorViewNativeComponent.js @@ -46,7 +46,7 @@ type NativeProps = $ReadOnly<{| * * See http://facebook.github.io/react-native/docs/activityindicator.html#size */ - size?: ?(number | 'small' | 'large'), + size?: ?('small' | 'large'), style?: ?ViewStyleProp, styleAttr?: ?string, diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js index 2c1e970ca5fe9c..3f7866b5e9279f 100644 --- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js @@ -288,7 +288,10 @@ class DrawerLayoutAndroid extends React.Component { /** * Closing and opening example - * Note: To access the drawer you have to give it a ref. Refs do not work on stateless components + * Note: To access the drawer you have to give it a ref + * + * Class component: + * * render () { * this.openDrawer = () => { * this.refs.DRAWER.openDrawer() @@ -298,9 +301,25 @@ class DrawerLayoutAndroid extends React.Component { * } * return ( * + * {children} * * ) * } + * + * Function component: + * + * const drawerRef = useRef() + * const openDrawer = () => { + * drawerRef.current.openDrawer() + * } + * const closeDrawer = () => { + * drawerRef.current.closeDrawer() + * } + * return ( + * + * {children} + * + * ) */ _getDrawerLayoutHandle() { return ReactNative.findNodeHandle(this._nativeRef.current); diff --git a/Libraries/Components/Picker/PickerAndroid.android.js b/Libraries/Components/Picker/PickerAndroid.android.js index af72f74459230b..375e6e8f94f352 100644 --- a/Libraries/Components/Picker/PickerAndroid.android.js +++ b/Libraries/Components/Picker/PickerAndroid.android.js @@ -64,6 +64,9 @@ class PickerAndroid extends React.Component< ): PickerAndroidState { let selectedIndex = 0; const items = React.Children.map(props.children, (child, index) => { + if (child === null) { + return; + } if (child.props.value === props.selectedValue) { selectedIndex = index; } diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 6b37dadda3b1bc..a077f7b97f8d19 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -141,6 +141,10 @@ const ScrollResponderMixin = { * Invoke this from an `onScroll` event. */ scrollResponderHandleScrollShouldSetResponder: function(): boolean { + // Allow any event touch pass through if the default pan responder is disabled + if (this.props.disableScrollViewPanResponder === true) { + return false; + } return this.state.isTouching; }, @@ -172,6 +176,11 @@ const ScrollResponderMixin = { scrollResponderHandleStartShouldSetResponder: function( e: PressEvent, ): boolean { + // Allow any event touch pass through if the default pan responder is disabled + if (this.props.disableScrollViewPanResponder === true) { + return false; + } + const currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); if ( @@ -204,6 +213,11 @@ const ScrollResponderMixin = { return true; } + // Allow any event touch pass through if the default pan responder is disabled + if (this.props.disableScrollViewPanResponder === true) { + return false; + } + // * the keyboard is up, keyboardShouldPersistTaps is 'never' (the default), // and a new touch starts with a non-textinput target (in which case the // first tap should be sent to the scroll view and dismiss the keyboard, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index c237e0a6a4a96d..dc166d41d54aed 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -95,6 +95,13 @@ type IOSProps = $ReadOnly<{| * @platform ios */ bounces?: ?boolean, + /** + * By default, ScrollView has an active pan responder that hijacks panresponders + * deeper in the render tree in order to prevent accidental touches while scrolling. + * However, in certain occasions (such as when using snapToInterval) in a vertical scrollview + * You may want to disable this behavior in order to prevent the ScrollView from blocking touches + */ + disableScrollViewPanResponder?: ?boolean, /** * When true, gestures can drive zoom past min/max and the zoom will animate * to the min/max value at gesture end, otherwise the zoom will not exceed @@ -651,6 +658,18 @@ class ScrollView extends React.Component { this._headerLayoutYs = new Map(); } + UNSAFE_componentWillReceiveProps(nextProps: Props) { + const currentContentInsetTop = this.props.contentInset + ? this.props.contentInset.top + : 0; + const nextContentInsetTop = nextProps.contentInset + ? nextProps.contentInset.top + : 0; + if (currentContentInsetTop !== nextContentInsetTop) { + this._scrollAnimatedValue.setOffset(nextContentInsetTop || 0); + } + } + componentDidMount() { this._updateAnimatedNodeAttachment(); } diff --git a/Libraries/Components/Slider/RCTSliderNativeComponent.js b/Libraries/Components/Slider/RCTSliderNativeComponent.js index c718c67ee2e2d9..245218f7967fe4 100644 --- a/Libraries/Components/Slider/RCTSliderNativeComponent.js +++ b/Libraries/Components/Slider/RCTSliderNativeComponent.js @@ -17,7 +17,6 @@ import type {ImageSource} from 'ImageSource'; import type {NativeComponent} from 'ReactNative'; import type {SyntheticEvent} from 'CoreEventTypes'; import type {ViewProps} from 'ViewPropTypes'; -import type {ViewStyleProp} from 'StyleSheet'; type Event = SyntheticEvent< $ReadOnly<{| diff --git a/Libraries/Components/Slider/SliderSchema.js b/Libraries/Components/Slider/SliderSchema.js new file mode 100644 index 00000000000000..0785b9f55c8550 --- /dev/null +++ b/Libraries/Components/Slider/SliderSchema.js @@ -0,0 +1,218 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +import type {SchemaType} from '../../../packages/react-native-codegen/src/CodegenSchema.js'; + +const SliderSchema: SchemaType = { + modules: { + SliderSchema: { + components: { + Slider: { + interfaceOnly: true, + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [ + { + name: 'onChange', + optional: true, + bubblingType: 'bubble', + typeAnnotation: { + type: 'EventTypeAnnotation', + argument: { + type: 'ObjectTypeAnnotation', + properties: [ + { + type: 'FloatTypeAnnotation', + name: 'value', + optional: false, + }, + { + type: 'BooleanTypeAnnotation', + name: 'fromUser', + optional: false, + }, + ], + }, + }, + }, + { + name: 'onSlidingComplete', + optional: true, + bubblingType: 'bubble', + typeAnnotation: { + type: 'EventTypeAnnotation', + argument: { + type: 'ObjectTypeAnnotation', + properties: [ + { + type: 'FloatTypeAnnotation', + name: 'value', + optional: false, + }, + { + type: 'BooleanTypeAnnotation', + name: 'fromUser', + optional: false, + }, + ], + }, + }, + }, + { + name: 'onValueChange', + optional: true, + bubblingType: 'bubble', + typeAnnotation: { + type: 'EventTypeAnnotation', + argument: { + type: 'ObjectTypeAnnotation', + properties: [ + { + type: 'FloatTypeAnnotation', + name: 'value', + optional: false, + }, + { + type: 'BooleanTypeAnnotation', + name: 'fromUser', + optional: false, + }, + ], + }, + }, + }, + ], + props: [ + { + name: 'disabled', + optional: true, + typeAnnotation: { + type: 'BooleanTypeAnnotation', + default: false, + }, + }, + { + name: 'enabled', + optional: true, + typeAnnotation: { + type: 'BooleanTypeAnnotation', + default: false, + }, + }, + { + name: 'maximumTrackImage', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ImageSourcePrimitive', + }, + }, + { + name: 'maximumTrackTintColor', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ColorPrimitive', + }, + }, + { + name: 'maximumValue', + optional: true, + typeAnnotation: { + type: 'FloatTypeAnnotation', + default: 1, + }, + }, + { + name: 'minimumTrackImage', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ImageSourcePrimitive', + }, + }, + { + name: 'minimumTrackTintColor', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ColorPrimitive', + }, + }, + { + name: 'minimumValue', + optional: true, + typeAnnotation: { + type: 'FloatTypeAnnotation', + default: 0, + }, + }, + { + name: 'step', + optional: true, + typeAnnotation: { + type: 'FloatTypeAnnotation', + default: 0, + }, + }, + { + name: 'testID', + optional: true, + typeAnnotation: { + type: 'StringTypeAnnotation', + default: '', + }, + }, + { + name: 'thumbImage', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ImageSourcePrimitive', + }, + }, + { + name: 'trackImage', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ImageSourcePrimitive', + }, + }, + { + name: 'thumbTintColor', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ColorPrimitive', + }, + }, + { + name: 'value', + optional: true, + typeAnnotation: { + type: 'FloatTypeAnnotation', + default: 0, + }, + }, + ], + }, + }, + }, + }, +}; + +module.exports = SliderSchema; diff --git a/Libraries/Core/InitializeCore.js b/Libraries/Core/InitializeCore.js index 2a2ca6479b5399..69fa9c87089875 100644 --- a/Libraries/Core/InitializeCore.js +++ b/Libraries/Core/InitializeCore.js @@ -44,7 +44,7 @@ if (__DEV__) { require('setUpDeveloperTools'); } -const PerformanceLogger = require('PerformanceLogger'); +const PerformanceLogger = require('GlobalPerformanceLogger'); // We could just call PerformanceLogger.markPoint at the top of the file, // but then we'd be excluding the time it took to require PerformanceLogger. // Instead, we just use Date.now and backdate the timestamp. diff --git a/Libraries/Core/setUpBatchedBridge.js b/Libraries/Core/setUpBatchedBridge.js index fd936bf50426a3..be0335629a2829 100644 --- a/Libraries/Core/setUpBatchedBridge.js +++ b/Libraries/Core/setUpBatchedBridge.js @@ -31,7 +31,7 @@ BatchedBridge.registerLazyCallableModule('RCTNativeAppEventEmitter', () => require('RCTNativeAppEventEmitter'), ); BatchedBridge.registerLazyCallableModule('PerformanceLogger', () => - require('PerformanceLogger'), + require('GlobalPerformanceLogger'), ); BatchedBridge.registerLazyCallableModule('JSDevSupportModule', () => require('JSDevSupportModule'), diff --git a/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js b/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js index 071ee6cdf6e3f5..5bbe37f0a0b643 100644 --- a/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js +++ b/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js @@ -46,12 +46,10 @@ type State = {| * A container component that renders multiple SwipeableRow's in a FlatList * implementation. This is designed to be a drop-in replacement for the * standard React Native `FlatList`, so use it as if it were a FlatList, but - * with extra props, i.e. - * - * + * with extra props. * * SwipeableRow can be used independently of this component, but the main - * benefit of using this component is + * benefits of using this component are: * * - It ensures that at most 1 row is swiped open (auto closes others) * - It can bounce the 1st row of the list so users know it's swipeable diff --git a/Libraries/Experimental/SwipeableRow/SwipeableListView.js b/Libraries/Experimental/SwipeableRow/SwipeableListView.js deleted file mode 100644 index 3189b728a7962a..00000000000000 --- a/Libraries/Experimental/SwipeableRow/SwipeableListView.js +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -const ListView = require('ListView'); -const React = require('React'); -const SwipeableListViewDataSource = require('SwipeableListViewDataSource'); -const SwipeableRow = require('SwipeableRow'); - -type ListViewProps = React.ElementConfig; - -type Props = $ReadOnly<{| - ...ListViewProps, - - /** - * To alert the user that swiping is possible, the first row can bounce - * on component mount. - */ - bounceFirstRowOnMount: boolean, - /** - * Use `SwipeableListView.getNewDataSource()` to get a data source to use, - * then use it just like you would a normal ListView data source - */ - dataSource: SwipeableListViewDataSource, - /** - * Maximum distance to open to after a swipe - */ - maxSwipeDistance: - | number - | ((rowData: Object, sectionID: string, rowID: string) => number), - onScroll?: ?Function, - /** - * Callback method to render the swipeable view - */ - renderRow: ( - rowData: Object, - sectionID: string, - rowID: string, - ) => React.Element, - /** - * Callback method to render the view that will be unveiled on swipe - */ - renderQuickActions: ( - rowData: Object, - sectionID: string, - rowID: string, - ) => ?React.Element, -|}>; - -type State = {| - dataSource: Object, -|}; - -/** - * A container component that renders multiple SwipeableRow's in a ListView - * implementation. This is designed to be a drop-in replacement for the - * standard React Native `ListView`, so use it as if it were a ListView, but - * with extra props, i.e. - * - * let ds = SwipeableListView.getNewDataSource(); - * ds.cloneWithRowsAndSections(dataBlob, ?sectionIDs, ?rowIDs); - * // .. - * - * - * SwipeableRow can be used independently of this component, but the main - * benefit of using this component is - * - * - It ensures that at most 1 row is swiped open (auto closes others) - * - It can bounce the 1st row of the list so users know it's swipeable - * - More to come - */ -class SwipeableListView extends React.Component { - props: Props; - state: State; - - _listViewRef: ?React.Element = null; - _shouldBounceFirstRowOnMount: boolean = false; - - static getNewDataSource(): Object { - return new SwipeableListViewDataSource({ - getRowData: (data, sectionID, rowID) => data[sectionID][rowID], - getSectionHeaderData: (data, sectionID) => data[sectionID], - rowHasChanged: (row1, row2) => row1 !== row2, - sectionHeaderHasChanged: (s1, s2) => s1 !== s2, - }); - } - - static defaultProps = { - bounceFirstRowOnMount: false, - renderQuickActions: () => null, - }; - - constructor(props: Props, context: any): void { - super(props, context); - - this._shouldBounceFirstRowOnMount = this.props.bounceFirstRowOnMount; - this.state = { - dataSource: this.props.dataSource, - }; - } - - UNSAFE_componentWillReceiveProps(nextProps: Props): void { - if ( - this.state.dataSource.getDataSource() !== - nextProps.dataSource.getDataSource() - ) { - this.setState({ - dataSource: nextProps.dataSource, - }); - } - } - - render(): React.Node { - return ( - // $FlowFixMe Found when typing ListView - { - // $FlowFixMe Found when typing ListView - this._listViewRef = ref; - }} - dataSource={this.state.dataSource.getDataSource()} - onScroll={this._onScroll} - renderRow={this._renderRow} - /> - ); - } - - _onScroll = (e): void => { - // Close any opens rows on ListView scroll - if (this.props.dataSource.getOpenRowID()) { - this.setState({ - dataSource: this.state.dataSource.setOpenRowID(null), - }); - } - this.props.onScroll && this.props.onScroll(e); - }; - - /** - * This is a work-around to lock vertical `ListView` scrolling on iOS and - * mimic Android behaviour. Locking vertical scrolling when horizontal - * scrolling is active allows us to significantly improve framerates - * (from high 20s to almost consistently 60 fps) - */ - _setListViewScrollable(value: boolean): void { - if ( - this._listViewRef && - /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.68 was deployed. To see the error delete this - * comment and run Flow. */ - typeof this._listViewRef.setNativeProps === 'function' - ) { - this._listViewRef.setNativeProps({ - scrollEnabled: value, - }); - } - } - - // Passing through ListView's getScrollResponder() function - getScrollResponder(): ?Object { - if ( - this._listViewRef && - /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.68 was deployed. To see the error delete this - * comment and run Flow. */ - typeof this._listViewRef.getScrollResponder === 'function' - ) { - return this._listViewRef.getScrollResponder(); - } - } - - // This enables rows having variable width slideoutView. - _getMaxSwipeDistance( - rowData: Object, - sectionID: string, - rowID: string, - ): number { - if (typeof this.props.maxSwipeDistance === 'function') { - return this.props.maxSwipeDistance(rowData, sectionID, rowID); - } - - return this.props.maxSwipeDistance; - } - - _renderRow = ( - rowData: Object, - sectionID: string, - rowID: string, - ): React.Element => { - const slideoutView = this.props.renderQuickActions( - rowData, - sectionID, - rowID, - ); - - // If renderQuickActions is unspecified or returns falsey, don't allow swipe - if (!slideoutView) { - return this.props.renderRow(rowData, sectionID, rowID); - } - - let shouldBounceOnMount = false; - if (this._shouldBounceFirstRowOnMount) { - this._shouldBounceFirstRowOnMount = false; - shouldBounceOnMount = rowID === this.props.dataSource.getFirstRowID(); - } - - return ( - this._onOpen(rowData.id)} - onClose={() => this._onClose(rowData.id)} - onSwipeEnd={() => this._setListViewScrollable(true)} - onSwipeStart={() => this._setListViewScrollable(false)} - shouldBounceOnMount={shouldBounceOnMount}> - {this.props.renderRow(rowData, sectionID, rowID)} - - ); - }; - - _onOpen(rowID: string): void { - this.setState({ - dataSource: this.state.dataSource.setOpenRowID(rowID), - }); - } - - _onClose(rowID: string): void { - this.setState({ - dataSource: this.state.dataSource.setOpenRowID(null), - }); - } -} - -module.exports = SwipeableListView; diff --git a/Libraries/Experimental/SwipeableRow/SwipeableListViewDataSource.js b/Libraries/Experimental/SwipeableRow/SwipeableListViewDataSource.js deleted file mode 100644 index 38e03c66140d0f..00000000000000 --- a/Libraries/Experimental/SwipeableRow/SwipeableListViewDataSource.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -const ListViewDataSource = require('ListViewDataSource'); - -/** - * Data source wrapper around ListViewDataSource to allow for tracking of - * which row is swiped open and close opened row(s) when another row is swiped - * open. - * - * See https://github.com/facebook/react-native/pull/5602 for why - * ListViewDataSource is not subclassed. - */ -class SwipeableListViewDataSource { - _previousOpenRowID: string; - _openRowID: string; - - _dataBlob: any; - _dataSource: ListViewDataSource; - - rowIdentities: Array>; - sectionIdentities: Array; - - constructor(params: Object) { - this._dataSource = new ListViewDataSource({ - getRowData: params.getRowData, - getSectionHeaderData: params.getSectionHeaderData, - rowHasChanged: (row1, row2) => { - /** - * Row needs to be re-rendered if its swiped open/close status is - * changed, or its data blob changed. - */ - return ( - (row1.id !== this._previousOpenRowID && - row2.id === this._openRowID) || - (row1.id === this._previousOpenRowID && - row2.id !== this._openRowID) || - params.rowHasChanged(row1, row2) - ); - }, - sectionHeaderHasChanged: params.sectionHeaderHasChanged, - }); - } - - cloneWithRowsAndSections( - dataBlob: any, - sectionIdentities: ?Array, - rowIdentities: ?Array>, - ): SwipeableListViewDataSource { - this._dataSource = this._dataSource.cloneWithRowsAndSections( - dataBlob, - sectionIdentities, - rowIdentities, - ); - - this._dataBlob = dataBlob; - this.rowIdentities = this._dataSource.rowIdentities; - this.sectionIdentities = this._dataSource.sectionIdentities; - - return this; - } - - // For the actual ListView to use - getDataSource(): ListViewDataSource { - return this._dataSource; - } - - getOpenRowID(): ?string { - return this._openRowID; - } - - getFirstRowID(): ?string { - /** - * If rowIdentities is specified, find the first data row from there since - * we don't want to attempt to bounce section headers. If unspecified, find - * the first data row from _dataBlob. - */ - if (this.rowIdentities) { - return this.rowIdentities[0] && this.rowIdentities[0][0]; - } - return Object.keys(this._dataBlob)[0]; - } - - getLastRowID(): ?string { - if (this.rowIdentities && this.rowIdentities.length) { - const lastSection = this.rowIdentities[this.rowIdentities.length - 1]; - if (lastSection && lastSection.length) { - return lastSection[lastSection.length - 1]; - } - } - return Object.keys(this._dataBlob)[this._dataBlob.length - 1]; - } - - setOpenRowID(rowID: string): SwipeableListViewDataSource { - this._previousOpenRowID = this._openRowID; - this._openRowID = rowID; - - this._dataSource = this._dataSource.cloneWithRowsAndSections( - this._dataBlob, - this.sectionIdentities, - this.rowIdentities, - ); - - return this; - } -} - -module.exports = SwipeableListViewDataSource; diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index d5d2c2f21aa212..f3b2209a39afc9 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -151,12 +151,14 @@ - (dispatch_queue_t)methodQueue #pragma mark - Private API -- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy distanceFilter:(CLLocationDistance)distanceFilter useSignificantChanges:(BOOL)useSignificantChanges +- (void)_beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy + distanceFilter:(CLLocationDistance)distanceFilter + useSignificantChanges:(BOOL)useSignificantChanges { if (!_locationConfiguration.skipPermissionRequests) { [self requestAuthorization]; } - + if (!_locationManager) { _locationManager = [CLLocationManager new]; _locationManager.delegate = self; @@ -172,6 +174,32 @@ - (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccur [_locationManager startUpdatingLocation]; } +- (void)_stopUpdatingIfIdle { + if (_pendingRequests.count == 0 && !_observingLocation) { + _usingSignificantChanges ? + [_locationManager stopMonitoringSignificantLocationChanges] : + [_locationManager stopUpdatingLocation]; + } +} + +#pragma mark - Static Helpers + +static BOOL locationEventValid(NSDictionary *event, RCTLocationOptions options) { + return [NSDate date].timeIntervalSince1970 - [RCTConvert NSTimeInterval:event[@"timestamp"]] < options.maximumAge && + [event[@"coords"][@"accuracy"] doubleValue] <= options.accuracy; +} + +static void checkLocationConfig() +{ +#if RCT_DEV + if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] || + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] || + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"])) { + RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription key must be present in Info.plist to use geolocation."); + } +#endif +} + #pragma mark - Timeout handler - (void)timeout:(NSTimer *)timer @@ -181,12 +209,8 @@ - (void)timeout:(NSTimer *)timer request.errorBlock(@[RCTPositionError(RCTPositionErrorTimeout, message)]); [_pendingRequests removeObject:request]; - // Stop updating if no pending requests - if (_pendingRequests.count == 0 && !_observingLocation) { - _usingSignificantChanges ? - [_locationManager stopMonitoringSignificantLocationChanges] : - [_locationManager stopUpdatingLocation]; - } + // Stop updating if not observing and no pending requests + [self _stopUpdatingIfIdle]; } #pragma mark - Public API @@ -229,9 +253,9 @@ - (void)timeout:(NSTimer *)timer _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); } - [self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy - distanceFilter:_observerOptions.distanceFilter - useSignificantChanges:_observerOptions.useSignificantChanges]; + [self _beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy + distanceFilter:_observerOptions.distanceFilter + useSignificantChanges:_observerOptions.useSignificantChanges]; _observingLocation = YES; } @@ -241,11 +265,8 @@ - (void)timeout:(NSTimer *)timer _observingLocation = NO; // Stop updating if no pending requests - if (_pendingRequests.count == 0) { - _usingSignificantChanges ? - [_locationManager stopMonitoringSignificantLocationChanges] : - [_locationManager stopUpdatingLocation]; - } + [self _stopUpdatingIfIdle]; + } RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options @@ -278,9 +299,7 @@ - (void)timeout:(NSTimer *)timer } // Check if previous recorded location exists and is good enough - if (_lastLocationEvent && - [NSDate date].timeIntervalSince1970 - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge && - [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] <= options.accuracy) { + if (_lastLocationEvent && locationEventValid(_lastLocationEvent, options)) { // Call success block with most recent known location successBlock(@[_lastLocationEvent]); @@ -307,9 +326,9 @@ - (void)timeout:(NSTimer *)timer if (_locationManager) { accuracy = MIN(_locationManager.desiredAccuracy, accuracy); } - [self beginLocationUpdatesWithDesiredAccuracy:accuracy - distanceFilter:options.distanceFilter - useSignificantChanges:options.useSignificantChanges]; + [self _beginLocationUpdatesWithDesiredAccuracy:accuracy + distanceFilter:options.distanceFilter + useSignificantChanges:options.useSignificantChanges]; } #pragma mark - CLLocationManagerDelegate @@ -337,20 +356,17 @@ - (void)locationManager:(CLLocationManager *)manager [self sendEventWithName:@"geolocationDidChange" body:_lastLocationEvent]; } - // Fire all queued callbacks - for (RCTLocationRequest *request in _pendingRequests) { - request.successBlock(@[_lastLocationEvent]); - [request.timeoutTimer invalidate]; - } - [_pendingRequests removeAllObjects]; - - // Stop updating if not observing - if (!_observingLocation) { - _usingSignificantChanges ? - [_locationManager stopMonitoringSignificantLocationChanges] : - [_locationManager stopUpdatingLocation]; + // Fire off queued callbacks that pass maximumAge and accuracy filters + for (RCTLocationRequest *request in [_pendingRequests copy]) { + if (locationEventValid(_lastLocationEvent, request.options)) { + request.successBlock(@[_lastLocationEvent]); + [request.timeoutTimer invalidate]; + [_pendingRequests removeObject:request]; + } } + // Stop updating if not observing and no pending requests + [self _stopUpdatingIfIdle]; } - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error @@ -384,15 +400,4 @@ - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError * } -static void checkLocationConfig() -{ -#if RCT_DEV - if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] || - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] || - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"])) { - RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription key must be present in Info.plist to use geolocation."); - } -#endif -} - @end diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index a4269be70360a2..422aeda2d7e77c 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -157,7 +157,7 @@ function abortPrefetch(requestId: number) { */ async function queryCache( urls: Array, -): Promise> { +): Promise<{[string]: 'memory' | 'disk' | 'disk/memory'}> { return await ImageLoader.queryCache(urls); } diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 7475caa0cf5b01..7fb0462bcf6af2 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -48,7 +48,7 @@ function prefetch(url: string) { async function queryCache( urls: Array, -): Promise> { +): Promise<{[string]: 'memory' | 'disk' | 'disk/memory'}> { return await ImageViewManager.queryCache(urls); } diff --git a/Libraries/Image/ImageProps.js b/Libraries/Image/ImageProps.js index 99591c48f604b9..f9a9e5786f2b5a 100644 --- a/Libraries/Image/ImageProps.js +++ b/Libraries/Image/ImageProps.js @@ -94,7 +94,13 @@ export type ImageProps = {| * * See https://facebook.github.io/react-native/docs/image.html#onerror */ - onError?: ?(event: SyntheticEvent<$ReadOnly<{||}>>) => void, + onError?: ?( + event: SyntheticEvent< + $ReadOnly<{| + error: string, + |}>, + >, + ) => void, /** * Invoked on mount and layout changes with diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index b9276f00f86eaf..76393a562ed108 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -35,12 +35,12 @@ @implementation UIImage (React) - (CAKeyframeAnimation *)reactKeyframeAnimation { - return objc_getAssociatedObject(self, _cmd); + return objc_getAssociatedObject(self, _cmd); } - (void)setReactKeyframeAnimation:(CAKeyframeAnimation *)reactKeyframeAnimation { - objc_setAssociatedObject(self, @selector(reactKeyframeAnimation), reactKeyframeAnimation, OBJC_ASSOCIATION_COPY_NONATOMIC); + objc_setAssociatedObject(self, @selector(reactKeyframeAnimation), reactKeyframeAnimation, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSInteger)reactDecodedImageBytes @@ -61,16 +61,16 @@ - (void)setReactDecodedImageBytes:(NSInteger)bytes @implementation RCTImageLoader { - NSArray> *_loaders; - NSArray> *_decoders; - NSOperationQueue *_imageDecodeQueue; - dispatch_queue_t _URLRequestQueue; - id _imageCache; - NSMutableArray *_pendingTasks; - NSInteger _activeTasks; - NSMutableArray *_pendingDecodes; - NSInteger _scheduledDecodes; - NSUInteger _activeBytes; + NSArray> *_loaders; + NSArray> *_decoders; + NSOperationQueue *_imageDecodeQueue; + dispatch_queue_t _URLRequestQueue; + id _imageCache; + NSMutableArray *_pendingTasks; + NSInteger _activeTasks; + NSMutableArray *_pendingDecodes; + NSInteger _scheduledDecodes; + NSUInteger _activeBytes; __weak id _redirectDelegate; } @@ -85,162 +85,162 @@ - (instancetype)init + (BOOL)requiresMainQueueSetup { - return NO; + return NO; } - (instancetype)initWithRedirectDelegate:(id)redirectDelegate { - if (self = [super init]) { - _redirectDelegate = redirectDelegate; - } - return self; + if (self = [super init]) { + _redirectDelegate = redirectDelegate; + } + return self; } - (void)setUp { - // Set defaults - _maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4; - _maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2; - _maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB - - _URLRequestQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLRequestQueue", DISPATCH_QUEUE_SERIAL); + // Set defaults + _maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4; + _maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2; + _maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB + + _URLRequestQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLRequestQueue", DISPATCH_QUEUE_SERIAL); } - (float)handlerPriority { - return 2; + return 2; } - (id)imageCache { - if (!_imageCache) { - //set up with default cache - _imageCache = [RCTImageCache new]; - } - return _imageCache; + if (!_imageCache) { + //set up with default cache + _imageCache = [RCTImageCache new]; + } + return _imageCache; } - (void)setImageCache:(id)cache { - if (_imageCache) { - RCTLogWarn(@"RCTImageCache was already set and has now been overriden."); - } - _imageCache = cache; + if (_imageCache) { + RCTLogWarn(@"RCTImageCache was already set and has now been overriden."); + } + _imageCache = cache; } - (id)imageURLLoaderForURL:(NSURL *)URL { - if (!_maxConcurrentLoadingTasks) { - [self setUp]; - } - - if (!_loaders) { - // Get loaders, sorted in reverse priority order (highest priority first) - RCTAssert(_bridge, @"Bridge not set"); - _loaders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { - float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0; - float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0; - if (priorityA > priorityB) { - return NSOrderedAscending; - } else if (priorityA < priorityB) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; - } - - if (RCT_DEBUG) { - // Check for handler conflicts - float previousPriority = 0; - id previousLoader = nil; - for (id loader in _loaders) { - float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0; - if (previousLoader && priority < previousPriority) { - return previousLoader; - } - if ([loader canLoadImageURL:URL]) { - if (previousLoader) { - if (priority == previousPriority) { - RCTLogError(@"The RCTImageURLLoaders %@ and %@ both reported that" - " they can load the URL %@, and have equal priority" - " (%g). This could result in non-deterministic behavior.", - loader, previousLoader, URL, priority); - } - } else { - previousLoader = loader; - previousPriority = priority; - } - } - } - return previousLoader; - } - - // Normal code path + if (!_maxConcurrentLoadingTasks) { + [self setUp]; + } + + if (!_loaders) { + // Get loaders, sorted in reverse priority order (highest priority first) + RCTAssert(_bridge, @"Bridge not set"); + _loaders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { + float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0; + float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0; + if (priorityA > priorityB) { + return NSOrderedAscending; + } else if (priorityA < priorityB) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; + } + + if (RCT_DEBUG) { + // Check for handler conflicts + float previousPriority = 0; + id previousLoader = nil; for (id loader in _loaders) { - if ([loader canLoadImageURL:URL]) { - return loader; + float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0; + if (previousLoader && priority < previousPriority) { + return previousLoader; + } + if ([loader canLoadImageURL:URL]) { + if (previousLoader) { + if (priority == previousPriority) { + RCTLogError(@"The RCTImageURLLoaders %@ and %@ both reported that" + " they can load the URL %@, and have equal priority" + " (%g). This could result in non-deterministic behavior.", + loader, previousLoader, URL, priority); + } + } else { + previousLoader = loader; + previousPriority = priority; } + } + } + return previousLoader; + } + + // Normal code path + for (id loader in _loaders) { + if ([loader canLoadImageURL:URL]) { + return loader; } - return nil; + } + return nil; } - (id)imageDataDecoderForData:(NSData *)data { - if (!_maxConcurrentLoadingTasks) { - [self setUp]; - } - - if (!_decoders) { - // Get decoders, sorted in reverse priority order (highest priority first) - RCTAssert(_bridge, @"Bridge not set"); - _decoders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { - float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0; - float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0; - if (priorityA > priorityB) { - return NSOrderedAscending; - } else if (priorityA < priorityB) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; - } - - if (RCT_DEBUG) { - // Check for handler conflicts - float previousPriority = 0; - id previousDecoder = nil; - for (id decoder in _decoders) { - float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0; - if (previousDecoder && priority < previousPriority) { - return previousDecoder; - } - if ([decoder canDecodeImageData:data]) { - if (previousDecoder) { - if (priority == previousPriority) { - RCTLogError(@"The RCTImageDataDecoders %@ and %@ both reported that" - " they can decode the data , and" - " have equal priority (%g). This could result in" - " non-deterministic behavior.", - decoder, previousDecoder, data, data.length, priority); - } - } else { - previousDecoder = decoder; - previousPriority = priority; - } - } - } - return previousDecoder; - } - - // Normal code path + if (!_maxConcurrentLoadingTasks) { + [self setUp]; + } + + if (!_decoders) { + // Get decoders, sorted in reverse priority order (highest priority first) + RCTAssert(_bridge, @"Bridge not set"); + _decoders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { + float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0; + float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0; + if (priorityA > priorityB) { + return NSOrderedAscending; + } else if (priorityA < priorityB) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; + } + + if (RCT_DEBUG) { + // Check for handler conflicts + float previousPriority = 0; + id previousDecoder = nil; for (id decoder in _decoders) { - if ([decoder canDecodeImageData:data]) { - return decoder; + float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0; + if (previousDecoder && priority < previousPriority) { + return previousDecoder; + } + if ([decoder canDecodeImageData:data]) { + if (previousDecoder) { + if (priority == previousPriority) { + RCTLogError(@"The RCTImageDataDecoders %@ and %@ both reported that" + " they can decode the data , and" + " have equal priority (%g). This could result in" + " non-deterministic behavior.", + decoder, previousDecoder, data, data.length, priority); + } + } else { + previousDecoder = decoder; + previousPriority = priority; } + } } - return nil; + return previousDecoder; + } + + // Normal code path + for (id decoder in _decoders) { + if ([decoder canDecodeImageData:data]) { + return decoder; + } + } + return nil; } static UIImage *RCTResizeImageIfNeeded(UIImage *image, @@ -248,91 +248,91 @@ - (void)setImageCache:(id)cache CGFloat scale, RCTResizeMode resizeMode) { - if (CGSizeEqualToSize(size, CGSizeZero) || - CGSizeEqualToSize(image.size, CGSizeZero) || - CGSizeEqualToSize(image.size, size)) { - return image; - } - CAKeyframeAnimation *animation = image.reactKeyframeAnimation; - CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode); - CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize); - image = RCTTransformImage(image, size, scale, transform); - image.reactKeyframeAnimation = animation; + if (CGSizeEqualToSize(size, CGSizeZero) || + CGSizeEqualToSize(image.size, CGSizeZero) || + CGSizeEqualToSize(image.size, size)) { return image; + } + CAKeyframeAnimation *animation = image.reactKeyframeAnimation; + CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode); + CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize); + image = RCTTransformImage(image, size, scale, transform); + image.reactKeyframeAnimation = animation; + return image; } - (RCTImageLoaderCancellationBlock) loadImageWithURLRequest:(NSURLRequest *)imageURLRequest - callback:(RCTImageLoaderCompletionBlock)callback + callback:(RCTImageLoaderCompletionBlock)callback { - return [self loadImageWithURLRequest:imageURLRequest - size:CGSizeZero - scale:1 - clipped:YES - resizeMode:RCTResizeModeStretch - progressBlock:nil - partialLoadBlock:nil - completionBlock:callback]; + return [self loadImageWithURLRequest:imageURLRequest + size:CGSizeZero + scale:1 + clipped:YES + resizeMode:RCTResizeModeStretch + progressBlock:nil + partialLoadBlock:nil + completionBlock:callback]; } - (void)dequeueTasks { - dispatch_async(_URLRequestQueue, ^{ - // Remove completed tasks - NSMutableArray *tasksToRemove = nil; - for (RCTNetworkTask *task in self->_pendingTasks.reverseObjectEnumerator) { - switch (task.status) { - case RCTNetworkTaskFinished: - if (!tasksToRemove) { - tasksToRemove = [NSMutableArray new]; - } - [tasksToRemove addObject:task]; - self->_activeTasks--; - break; - case RCTNetworkTaskPending: - break; - case RCTNetworkTaskInProgress: - // Check task isn't "stuck" - if (task.requestToken == nil) { - RCTLogWarn(@"Task orphaned for request %@", task.request); - if (!tasksToRemove) { - tasksToRemove = [NSMutableArray new]; - } - [tasksToRemove addObject:task]; - self->_activeTasks--; - [task cancel]; - } - break; - } - } - - if (tasksToRemove) { - [self->_pendingTasks removeObjectsInArray:tasksToRemove]; - } - - // Start queued decode - 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) { - [self->_pendingDecodes removeObjectAtIndex:0]; - decodeBlock(); - } else { - break; + dispatch_async(_URLRequestQueue, ^{ + // Remove completed tasks + NSMutableArray *tasksToRemove = nil; + for (RCTNetworkTask *task in self->_pendingTasks.reverseObjectEnumerator) { + switch (task.status) { + case RCTNetworkTaskFinished: + if (!tasksToRemove) { + tasksToRemove = [NSMutableArray new]; + } + [tasksToRemove addObject:task]; + self->_activeTasks--; + break; + case RCTNetworkTaskPending: + break; + case RCTNetworkTaskInProgress: + // Check task isn't "stuck" + if (task.requestToken == nil) { + RCTLogWarn(@"Task orphaned for request %@", task.request); + if (!tasksToRemove) { + tasksToRemove = [NSMutableArray new]; } - } - - // Start queued tasks - for (RCTNetworkTask *task in self->_pendingTasks) { - if (MAX(self->_activeTasks, self->_scheduledDecodes) >= self->_maxConcurrentLoadingTasks) { - break; - } - if (task.status == RCTNetworkTaskPending) { - [task start]; - self->_activeTasks++; - } - } - }); + [tasksToRemove addObject:task]; + self->_activeTasks--; + [task cancel]; + } + break; + } + } + + if (tasksToRemove) { + [self->_pendingTasks removeObjectsInArray:tasksToRemove]; + } + + // Start queued decode + 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) { + [self->_pendingDecodes removeObjectAtIndex:0]; + decodeBlock(); + } else { + break; + } + } + + // Start queued tasks + for (RCTNetworkTask *task in self->_pendingTasks) { + if (MAX(self->_activeTasks, self->_scheduledDecodes) >= self->_maxConcurrentLoadingTasks) { + break; + } + if (task.status == RCTNetworkTaskPending) { + [task start]; + self->_activeTasks++; + } + } + }); } /** @@ -348,229 +348,239 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response))completionBlock { - { - 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 = [request.URL URLByAppendingPathExtension:@"png"]; - } - if (_redirectDelegate != nil) { - mutableRequest.URL = [_redirectDelegate redirectAssetsURL:mutableRequest.URL]; - } - request = mutableRequest; - } - - // Find suitable image URL loader - id loadHandler = [self imageURLLoaderForURL:request.URL]; - BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ? - [loadHandler requiresScheduling] : YES; - - BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? - [loadHandler shouldCacheLoadedImages] : YES; - - __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); - // TODO: Protect this variable shared between threads. - __block dispatch_block_t cancelLoad = nil; - void (^completionHandler)(NSError *, id, NSURLResponse *) = ^(NSError *error, id imageOrData, NSURLResponse *response) { - cancelLoad = nil; - - // If we've received an image, we should try to set it synchronously, - // if it's data, do decoding on a background thread. - 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 (!atomic_load(&cancelled)) { - completionBlock(error, imageOrData, cacheResult, response); - } - }); - } else if (!atomic_load(&cancelled)) { - completionBlock(error, imageOrData, cacheResult, response); - } - }; - - // 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 - partialLoadHandler:partialLoadHandler - completionHandler:^(NSError *error, UIImage *image){ - completionHandler(error, image, nil); - }]; - } - - // All access to URL cache must be serialized - if (!_URLRequestQueue) { - [self setUp]; + { + 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 = [request.URL URLByAppendingPathExtension:@"png"]; } - - __weak RCTImageLoader *weakSelf = self; - dispatch_async(_URLRequestQueue, ^{ - __typeof(self) strongSelf = weakSelf; - if (atomic_load(&cancelled) || !strongSelf) { - return; - } - - if (loadHandler) { - cancelLoad = [loadHandler loadImageForURL:request.URL - size:size - scale:scale - resizeMode:resizeMode - progressHandler:progressHandler - partialLoadHandler:partialLoadHandler - completionHandler:^(NSError *error, UIImage *image) { - completionHandler(error, image, nil); - }]; - } else { - UIImage *image; - if (cacheResult) { - image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString - size:size - scale:scale - resizeMode:resizeMode]; - } - - if (image) { - completionHandler(nil, image, nil); - } else { - // Use networking module to load image - cancelLoad = [strongSelf _loadURLRequest:request - progressBlock:progressHandler - completionBlock:completionHandler]; - } - } - }); - - return ^{ - BOOL alreadyCancelled = atomic_fetch_or(&cancelled, 1); - if (alreadyCancelled) { - return; - } - dispatch_block_t cancelLoadLocal = cancelLoad; - cancelLoad = nil; - if (cancelLoadLocal) { - cancelLoadLocal(); + if (_redirectDelegate != nil) { + mutableRequest.URL = [_redirectDelegate redirectAssetsURL:mutableRequest.URL]; + } + request = mutableRequest; + } + + // Find suitable image URL loader + id loadHandler = [self imageURLLoaderForURL:request.URL]; + BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ? + [loadHandler requiresScheduling] : YES; + + BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? + [loadHandler shouldCacheLoadedImages] : YES; + + __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); + __block dispatch_block_t cancelLoad = nil; + __block NSLock *cancelLoadLock = [NSLock new]; + void (^completionHandler)(NSError *, id, NSURLResponse *) = ^(NSError *error, id imageOrData, NSURLResponse *response) { + [cancelLoadLock lock]; + cancelLoad = nil; + [cancelLoadLock unlock]; + + // If we've received an image, we should try to set it synchronously, + // if it's data, do decoding on a background thread. + 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 (!atomic_load(&cancelled)) { + completionBlock(error, imageOrData, cacheResult, response); } - }; + }); + } else if (!atomic_load(&cancelled)) { + completionBlock(error, imageOrData, cacheResult, response); + } + }; + + // 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 + partialLoadHandler:partialLoadHandler + completionHandler:^(NSError *error, UIImage *image){ + completionHandler(error, image, nil); + }]; + } + + // All access to URL cache must be serialized + if (!_URLRequestQueue) { + [self setUp]; + } + + __weak RCTImageLoader *weakSelf = self; + dispatch_async(_URLRequestQueue, ^{ + __typeof(self) strongSelf = weakSelf; + if (atomic_load(&cancelled) || !strongSelf) { + return; + } + + if (loadHandler) { + dispatch_block_t cancelLoadLocal = [loadHandler loadImageForURL:request.URL + size:size + scale:scale + resizeMode:resizeMode + progressHandler:progressHandler + partialLoadHandler:partialLoadHandler + completionHandler:^(NSError *error, UIImage *image) { + completionHandler(error, image, nil); + }]; + [cancelLoadLock lock]; + cancelLoad = cancelLoadLocal; + [cancelLoadLock unlock]; + } else { + UIImage *image; + if (cacheResult) { + image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString + size:size + scale:scale + resizeMode:resizeMode]; + } + + if (image) { + completionHandler(nil, image, nil); + } else { + // Use networking module to load image + dispatch_block_t cancelLoadLocal = [strongSelf _loadURLRequest:request + progressBlock:progressHandler + completionBlock:completionHandler]; + [cancelLoadLock lock]; + cancelLoad = cancelLoadLocal; + [cancelLoadLock unlock]; + } + } + }); + + return ^{ + BOOL alreadyCancelled = atomic_fetch_or(&cancelled, 1); + if (alreadyCancelled) { + return; + } + [cancelLoadLock lock]; + dispatch_block_t cancelLoadLocal = cancelLoad; + cancelLoad = nil; + [cancelLoadLock unlock]; + if (cancelLoadLocal) { + cancelLoadLocal(); + } + }; } - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request progressBlock:(RCTImageLoaderProgressBlock)progressHandler completionBlock:(void (^)(NSError *error, id imageOrData, NSURLResponse *response))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 && ![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, response); - return; - } else if (!response) { - completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, response); - return; - } else if (!data) { - completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, response); - return; - } - - // Check for http errors - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; - if (statusCode != 200) { - NSString *errorMessage = [NSString stringWithFormat:@"Failed to load %@", response.URL]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage}; - completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain - code:statusCode - userInfo:userInfo], nil, response); - return; - } - } - - // Call handler - completionHandler(nil, data, response); - }; - - // Download image - __weak __typeof(self) weakSelf = self; - __block RCTNetworkTask *task = - [networking networkTaskWithRequest:request - completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { - __typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (error || !response || !data) { - NSError *someError = nil; - if (error) { - someError = error; - } else if (!response) { - someError = RCTErrorWithMessage(@"Response metadata error"); - } else { - someError = RCTErrorWithMessage(@"Unknown image download error"); - } - completionHandler(someError, nil, response); - [strongSelf dequeueTasks]; - return; - } - - dispatch_async(strongSelf->_URLRequestQueue, ^{ - // Process image data - processResponse(response, data, nil); - - // Prepare for next task - [strongSelf dequeueTasks]; - }); - }]; - - task.downloadProgressBlock = ^(int64_t progress, int64_t total) { - if (progressHandler) { - progressHandler(progress, total); - } - }; - - if (task) { - if (!_pendingTasks) { - _pendingTasks = [NSMutableArray new]; - } - [_pendingTasks addObject:task]; - [self dequeueTasks]; + // 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 && ![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, response); + return; + } else if (!response) { + completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, response); + return; + } else if (!data) { + completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, response); + return; } - - return ^{ - __typeof(self) strongSelf = weakSelf; - if (!strongSelf || !task) { - return; - } - dispatch_async(strongSelf->_URLRequestQueue, ^{ - [task cancel]; - task = nil; - }); - [strongSelf dequeueTasks]; - }; + + // Check for http errors + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + if (statusCode != 200) { + NSString *errorMessage = [NSString stringWithFormat:@"Failed to load %@", response.URL]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage}; + completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain + code:statusCode + userInfo:userInfo], nil, response); + return; + } + } + + // Call handler + completionHandler(nil, data, response); + }; + + // Download image + __weak __typeof(self) weakSelf = self; + __block RCTNetworkTask *task = + [networking networkTaskWithRequest:request + completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { + __typeof(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (error || !response || !data) { + NSError *someError = nil; + if (error) { + someError = error; + } else if (!response) { + someError = RCTErrorWithMessage(@"Response metadata error"); + } else { + someError = RCTErrorWithMessage(@"Unknown image download error"); + } + completionHandler(someError, nil, response); + [strongSelf dequeueTasks]; + return; + } + + dispatch_async(strongSelf->_URLRequestQueue, ^{ + // Process image data + processResponse(response, data, nil); + + // Prepare for next task + [strongSelf dequeueTasks]; + }); + }]; + + task.downloadProgressBlock = ^(int64_t progress, int64_t total) { + if (progressHandler) { + progressHandler(progress, total); + } + }; + + if (task) { + if (!_pendingTasks) { + _pendingTasks = [NSMutableArray new]; + } + [_pendingTasks addObject:task]; + [self dequeueTasks]; + } + + return ^{ + __typeof(self) strongSelf = weakSelf; + if (!strongSelf || !task) { + return; + } + dispatch_async(strongSelf->_URLRequestQueue, ^{ + [task cancel]; + task = nil; + }); + [strongSelf dequeueTasks]; + }; } - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest @@ -582,65 +592,72 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock completionBlock:(RCTImageLoaderCompletionBlock)completionBlock { - __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); - // TODO: Protect this variable shared between threads. - __block dispatch_block_t cancelLoad = nil; - dispatch_block_t cancellationBlock = ^{ - BOOL alreadyCancelled = atomic_fetch_or(&cancelled, 1); - if (alreadyCancelled) { - return; - } - dispatch_block_t cancelLoadLocal = cancelLoad; - cancelLoad = nil; - if (cancelLoadLocal) { - cancelLoadLocal(); - } - }; - - __weak RCTImageLoader *weakSelf = self; - void (^completionHandler)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) { - __typeof(self) strongSelf = weakSelf; - if (atomic_load(&cancelled) || !strongSelf) { - return; - } - - if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { - cancelLoad = nil; - completionBlock(error, imageOrData); - return; - } - - RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) { - if (cacheResult && image) { - // Store decoded image in cache - [[strongSelf imageCache] addImageToCache:image - URL:imageURLRequest.URL.absoluteString - size:size - scale:scale - resizeMode:resizeMode - response:response]; - } - - cancelLoad = nil; - completionBlock(error_, image); - }; - - cancelLoad = [strongSelf decodeImageData:imageOrData + __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); + __block dispatch_block_t cancelLoad = nil; + __block NSLock *cancelLoadLock = [NSLock new]; + dispatch_block_t cancellationBlock = ^{ + BOOL alreadyCancelled = atomic_fetch_or(&cancelled, 1); + if (alreadyCancelled) { + return; + } + [cancelLoadLock lock]; + dispatch_block_t cancelLoadLocal = cancelLoad; + cancelLoad = nil; + [cancelLoadLock unlock]; + if (cancelLoadLocal) { + cancelLoadLocal(); + } + }; + + __weak RCTImageLoader *weakSelf = self; + void (^completionHandler)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) { + __typeof(self) strongSelf = weakSelf; + if (atomic_load(&cancelled) || !strongSelf) { + return; + } + + if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { + [cancelLoadLock lock]; + cancelLoad = nil; + [cancelLoadLock unlock]; + completionBlock(error, imageOrData); + return; + } + + RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) { + if (cacheResult && image) { + // Store decoded image in cache + [[strongSelf imageCache] addImageToCache:image + URL:imageURLRequest.URL.absoluteString size:size scale:scale - clipped:clipped resizeMode:resizeMode - completionBlock:decodeCompletionHandler]; + response:response]; + } + [cancelLoadLock lock]; + cancelLoad = nil; + [cancelLoadLock unlock]; + completionBlock(error_, image); }; - - cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest - size:size - scale:scale - resizeMode:resizeMode - progressBlock:progressBlock - partialLoadBlock:partialLoadBlock - completionBlock:completionHandler]; - return cancellationBlock; + dispatch_block_t cancelLoadLocal = [strongSelf decodeImageData:imageOrData + size:size + scale:scale + clipped:clipped + resizeMode:resizeMode + completionBlock:decodeCompletionHandler]; + [cancelLoadLock lock]; + cancelLoad = cancelLoadLocal; + [cancelLoadLock unlock]; + }; + + cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest + size:size + scale:scale + resizeMode:resizeMode + progressBlock:progressBlock + partialLoadBlock:partialLoadBlock + completionBlock:completionHandler]; + return cancellationBlock; } - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data @@ -650,153 +667,153 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data resizeMode:(RCTResizeMode)resizeMode completionBlock:(RCTImageLoaderCompletionBlock)completionBlock { - if (data.length == 0) { - completionBlock(RCTErrorWithMessage(@"No image data"), nil); - return ^{}; - } - - __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); - void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) { - if (RCTIsMainQueue()) { - // 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 (!atomic_load(&cancelled)) { - completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image); - } - }); - } else if (!atomic_load(&cancelled)) { - completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image); + if (data.length == 0) { + completionBlock(RCTErrorWithMessage(@"No image data"), nil); + return ^{}; + } + + __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); + void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) { + if (RCTIsMainQueue()) { + // 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 (!atomic_load(&cancelled)) { + completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image); } - }; - - id imageDecoder = [self imageDataDecoderForData:data]; - if (imageDecoder) { - return [imageDecoder decodeImageData:data - size:size - scale:scale - 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; - - // Mark these bytes as in-use - self->_activeBytes += decodedImageBytes; - - // Do actual decompression on a concurrent background queue - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (!atomic_load(&cancelled)) { - - // Decompress the image data (this may be CPU and memory intensive) - UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode); - + }); + } else if (!atomic_load(&cancelled)) { + completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image); + } + }; + + id imageDecoder = [self imageDataDecoderForData:data]; + if (imageDecoder) { + return [imageDecoder decodeImageData:data + size:size + scale:scale + 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; + + // Mark these bytes as in-use + self->_activeBytes += decodedImageBytes; + + // Do actual decompression on a concurrent background queue + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + if (!atomic_load(&cancelled)) { + + // 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 ", 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(self->_URLRequestQueue, ^{ - self->_scheduledDecodes--; - self->_activeBytes -= decodedImageBytes; - [self dequeueTasks]; - }); - }); - }; - - if (!_URLRequestQueue) { - [self setUp]; + + if (image) { + completionHandler(nil, image); + } else { + NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data ", data, data.length]; + NSError *finalError = RCTErrorWithMessage(errorMessage); + completionHandler(finalError, nil); + } } - 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. - self->_scheduledDecodes++; - - if (!self->_pendingDecodes) { - self->_pendingDecodes = [NSMutableArray new]; - } - NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count - 1; - if (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes && - activeDecodes <= self->_maxConcurrentDecodingTasks)) { - decodeBlock(); - } else { - [self->_pendingDecodes addObject:decodeBlock]; - } + + // 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]; }); - - return ^{ - atomic_store(&cancelled, YES); - }; + }); + }; + + 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. + self->_scheduledDecodes++; + + if (!self->_pendingDecodes) { + self->_pendingDecodes = [NSMutableArray new]; + } + NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count - 1; + if (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes && + activeDecodes <= self->_maxConcurrentDecodingTasks)) { + decodeBlock(); + } else { + [self->_pendingDecodes addObject:decodeBlock]; + } + }); + + return ^{ + atomic_store(&cancelled, YES); + }; + } } - (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest block:(void(^)(NSError *error, CGSize size))callback { - void (^completion)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) { - CGSize size; - if ([imageOrData isKindOfClass:[NSData class]]) { - NSDictionary *meta = RCTGetImageMetadata(imageOrData); - - NSInteger imageOrientation = [meta[(id)kCGImagePropertyOrientation] integerValue]; - switch (imageOrientation) { - case kCGImagePropertyOrientationLeft: - case kCGImagePropertyOrientationRight: - case kCGImagePropertyOrientationLeftMirrored: - case kCGImagePropertyOrientationRightMirrored: - // swap width and height - size = (CGSize){ - [meta[(id)kCGImagePropertyPixelHeight] doubleValue], - [meta[(id)kCGImagePropertyPixelWidth] doubleValue], - }; - break; - case kCGImagePropertyOrientationUp: - case kCGImagePropertyOrientationDown: - case kCGImagePropertyOrientationUpMirrored: - case kCGImagePropertyOrientationDownMirrored: - default: - size = (CGSize){ - [meta[(id)kCGImagePropertyPixelWidth] doubleValue], - [meta[(id)kCGImagePropertyPixelHeight] doubleValue], - }; - break; - } - } 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 - partialLoadBlock:NULL - completionBlock:completion]; + void (^completion)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) { + CGSize size; + if ([imageOrData isKindOfClass:[NSData class]]) { + NSDictionary *meta = RCTGetImageMetadata(imageOrData); + + NSInteger imageOrientation = [meta[(id)kCGImagePropertyOrientation] integerValue]; + switch (imageOrientation) { + case kCGImagePropertyOrientationLeft: + case kCGImagePropertyOrientationRight: + case kCGImagePropertyOrientationLeftMirrored: + case kCGImagePropertyOrientationRightMirrored: + // swap width and height + size = (CGSize){ + [meta[(id)kCGImagePropertyPixelHeight] doubleValue], + [meta[(id)kCGImagePropertyPixelWidth] doubleValue], + }; + break; + case kCGImagePropertyOrientationUp: + case kCGImagePropertyOrientationDown: + case kCGImagePropertyOrientationUpMirrored: + case kCGImagePropertyOrientationDownMirrored: + default: + size = (CGSize){ + [meta[(id)kCGImagePropertyPixelWidth] doubleValue], + [meta[(id)kCGImagePropertyPixelHeight] doubleValue], + }; + break; + } + } 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 + partialLoadBlock:NULL + completionBlock:completion]; } - (NSDictionary *)getImageCacheStatus:(NSArray *)requests @@ -808,9 +825,13 @@ - (NSDictionary *)getImageCacheStatus:(NSArray *)requests NSCachedURLResponse *cachedResponse = [NSURLCache.sharedURLCache cachedResponseForRequest:urlRequest]; if (cachedResponse) { if (cachedResponse.storagePolicy == NSURLCacheStorageAllowedInMemoryOnly) { - [results setObject:@"memory" forKey:urlRequest.URL.absoluteString]; + results[urlRequest.URL.absoluteString] = @"memory"; + } else if (NSURLCache.sharedURLCache.currentMemoryUsage == 0) { + // We can't check whether the file is cached on disk or memory. + // However, if currentMemoryUsage is disabled, it must be read from disk. + results[urlRequest.URL.absoluteString] = @"disk"; } else { - [results setObject:@"disk" forKey:urlRequest.URL.absoluteString]; + results[urlRequest.URL.absoluteString] = @"disk/memory"; } } } @@ -823,7 +844,7 @@ - (NSDictionary *)getImageCacheStatus:(NSArray *)requests - (BOOL)canHandleRequest:(NSURLRequest *)request { NSURL *requestURL = request.URL; - + // If the data being loaded is a video, return NO // Even better may be to implement this on the RCTImageURLLoader that would try to load it, // but we'd have to run the logic both in RCTPhotoLibraryImageLoader and @@ -840,67 +861,67 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request RCTLogError(@"%@", error); } }); - + NSString *query = requestURL.query; if ( - query != nil && - [videoRegex firstMatchInString:query - options:0 - range:NSMakeRange(0, query.length)] - ) { + query != nil && + [videoRegex firstMatchInString:query + options:0 + range:NSMakeRange(0, query.length)] + ) { return NO; } - + for (id loader in _loaders) { // Don't use RCTImageURLLoader protocol for modules that already conform to // RCTURLRequestHandler as it's inefficient to decode an image and then // convert it back into data if (![loader conformsToProtocol:@protocol(RCTURLRequestHandler)] && - [loader canLoadImageURL:requestURL]) { + [loader canLoadImageURL:requestURL]) { return YES; } } - + return NO; } - (id)sendRequest:(NSURLRequest *)request withDelegate:(id)delegate { - __block RCTImageLoaderCancellationBlock requestToken; - requestToken = [self loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) { - if (error) { - [delegate URLRequest:requestToken didCompleteWithError:error]; - return; - } - - NSString *mimeType = nil; - NSData *imageData = nil; - if (RCTImageHasAlpha(image.CGImage)) { - mimeType = @"image/png"; - imageData = UIImagePNGRepresentation(image); - } else { - mimeType = @"image/jpeg"; - imageData = UIImageJPEGRepresentation(image, 1.0); - } - - NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL - MIMEType:mimeType - expectedContentLength:imageData.length - textEncodingName:nil]; - - [delegate URLRequest:requestToken didReceiveResponse:response]; - [delegate URLRequest:requestToken didReceiveData:imageData]; - [delegate URLRequest:requestToken didCompleteWithError:nil]; - }]; - - return requestToken; + __block RCTImageLoaderCancellationBlock requestToken; + requestToken = [self loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) { + if (error) { + [delegate URLRequest:requestToken didCompleteWithError:error]; + return; + } + + NSString *mimeType = nil; + NSData *imageData = nil; + if (RCTImageHasAlpha(image.CGImage)) { + mimeType = @"image/png"; + imageData = UIImagePNGRepresentation(image); + } else { + mimeType = @"image/jpeg"; + imageData = UIImageJPEGRepresentation(image, 1.0); + } + + NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL + MIMEType:mimeType + expectedContentLength:imageData.length + textEncodingName:nil]; + + [delegate URLRequest:requestToken didReceiveResponse:response]; + [delegate URLRequest:requestToken didReceiveData:imageData]; + [delegate URLRequest:requestToken didCompleteWithError:nil]; + }]; + + return requestToken; } - (void)cancelRequest:(id)requestToken { - if (requestToken) { - ((RCTImageLoaderCancellationBlock)requestToken)(); - } + if (requestToken) { + ((RCTImageLoaderCancellationBlock)requestToken)(); + } } @end @@ -909,7 +930,7 @@ @implementation RCTBridge (RCTImageLoader) - (RCTImageLoader *)imageLoader { - return [self moduleForClass:[RCTImageLoader class]]; + return [self moduleForClass:[RCTImageLoader class]]; } @end diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 3392a54c56ed47..3b3d0ea709c994 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -412,8 +412,8 @@ - (void)reactSetFrame:(CGRect)frame return; } - // Don't reload if the current image size is the maximum size of the image source - CGSize imageSourceSize = _imageSource.size; + // Don't reload if the current image size is the maximum size of either the pending image source or image source + CGSize imageSourceSize = (_imageSource ? _imageSource : _pendingImageSource).size; if (imageSize.width * imageScale == imageSourceSize.width * _imageSource.scale && imageSize.height * imageScale == imageSourceSize.height * _imageSource.scale) { return; diff --git a/Libraries/Inspector/PerformanceOverlay.js b/Libraries/Inspector/PerformanceOverlay.js index 2384769bc5475b..c87b46820dbb0d 100644 --- a/Libraries/Inspector/PerformanceOverlay.js +++ b/Libraries/Inspector/PerformanceOverlay.js @@ -10,7 +10,7 @@ 'use strict'; -const PerformanceLogger = require('PerformanceLogger'); +const PerformanceLogger = require('GlobalPerformanceLogger'); const React = require('React'); const StyleSheet = require('StyleSheet'); const Text = require('Text'); diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index bf1f575e8f60b7..392e1620616922 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -88,12 +88,23 @@ - (void)handleOpenURLNotification:(NSNotification *)notification resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - BOOL opened = [RCTSharedApplication() openURL:URL]; - if (opened) { - resolve(nil); + if (@available(iOS 10.0, *)) { + [RCTSharedApplication() openURL:URL options:@{} completionHandler:^(BOOL success) { + if (success) { + resolve(nil); + } else { + reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@", URL], nil); + } + }]; } else { - reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@", URL], nil); + BOOL opened = [RCTSharedApplication() openURL:URL]; + if (opened) { + resolve(nil); + } else { + reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@", URL], nil); + } } + } RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL @@ -109,20 +120,20 @@ - (void)handleOpenURLNotification:(NSNotification *)notification // This can be expensive, so we deliberately don't call on main thread BOOL canOpen = [RCTSharedApplication() canOpenURL:URL]; - NSString *scheme = [URL scheme]; - - // On iOS 9 and above canOpenURL returns NO without a helpful error. - // Check if a custom scheme is being used, and if it exists in LSApplicationQueriesSchemes - if (![[scheme lowercaseString] hasPrefix:@"http"] && ![[scheme lowercaseString] hasPrefix:@"https"]) { + if (canOpen) { + resolve(@YES); + } else if (![[scheme lowercaseString] hasPrefix:@"http"] && ![[scheme lowercaseString] hasPrefix:@"https"]) { + // On iOS 9 and above canOpenURL returns NO without a helpful error. + // Check if a custom scheme is being used, and if it exists in LSApplicationQueriesSchemes NSArray *querySchemes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSApplicationQueriesSchemes"]; if (querySchemes != nil && ([querySchemes containsObject:scheme] || [querySchemes containsObject:[scheme lowercaseString]])) { - resolve(@(canOpen)); + resolve(@NO); } else { reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unable to open URL: %@. Add %@ to LSApplicationQueriesSchemes in your Info.plist.", URL, scheme], nil); } } else { - resolve(@(canOpen)); + resolve(@NO); } } diff --git a/Libraries/Lists/ListView/InternalListViewType.js b/Libraries/Lists/ListView/InternalListViewType.js deleted file mode 100644 index c8eaddf392f5f5..00000000000000 --- a/Libraries/Lists/ListView/InternalListViewType.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -const React = require('React'); -const ListViewDataSource = require('ListViewDataSource'); - -// This class is purely a facsimile of ListView so that we can -// properly type it with Flow before migrating ListView off of -// createReactClass. If there are things missing here that are in -// ListView, that is unintentional. -class InternalListViewType extends React.Component { - static DataSource = ListViewDataSource; - setNativeProps(props: Object) {} - flashScrollIndicators() {} - getScrollResponder(): any {} - getScrollableNode(): any {} - getMetrics(): Object {} - scrollTo(...args: Array) {} - scrollToEnd(options?: ?{animated?: ?boolean}) {} -} - -module.exports = InternalListViewType; diff --git a/Libraries/Lists/ListView/ListView.js b/Libraries/Lists/ListView/ListView.js deleted file mode 100644 index 193ca055d631b7..00000000000000 --- a/Libraries/Lists/ListView/ListView.js +++ /dev/null @@ -1,766 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ -'use strict'; - -const InternalListViewType = require('InternalListViewType'); -const ListViewDataSource = require('ListViewDataSource'); -const Platform = require('Platform'); -const React = require('React'); -const ReactNative = require('ReactNative'); -const RCTScrollViewManager = require('NativeModules').ScrollViewManager; -const ScrollView = require('ScrollView'); -const ScrollResponder = require('ScrollResponder'); -const StaticRenderer = require('StaticRenderer'); -const View = require('View'); -const cloneReferencedElement = require('react-clone-referenced-element'); -const createReactClass = require('create-react-class'); -const isEmpty = require('isEmpty'); -const merge = require('merge'); - -import type {Props as ScrollViewProps} from 'ScrollView'; - -const DEFAULT_PAGE_SIZE = 1; -const DEFAULT_INITIAL_ROWS = 10; -const DEFAULT_SCROLL_RENDER_AHEAD = 1000; -const DEFAULT_END_REACHED_THRESHOLD = 1000; -const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; - -type Props = $ReadOnly<{| - ...ScrollViewProps, - - /** - * An instance of [ListView.DataSource](docs/listviewdatasource.html) to use - */ - dataSource: ListViewDataSource, - /** - * (sectionID, rowID, adjacentRowHighlighted) => renderable - * - * If provided, a renderable component to be rendered as the separator - * below each row but not the last row if there is a section header below. - * Take a sectionID and rowID of the row above and whether its adjacent row - * is highlighted. - */ - renderSeparator?: ?Function, - /** - * (rowData, sectionID, rowID, highlightRow) => renderable - * - * Takes a data entry from the data source and its ids and should return - * a renderable component to be rendered as the row. By default the data - * is exactly what was put into the data source, but it's also possible to - * provide custom extractors. ListView can be notified when a row is - * being highlighted by calling `highlightRow(sectionID, rowID)`. This - * sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you - * to control the separators above and below the highlighted row. The highlighted - * state of a row can be reset by calling highlightRow(null). - */ - renderRow: Function, - /** - * How many rows to render on initial component mount. Use this to make - * it so that the first screen worth of data appears at one time instead of - * over the course of multiple frames. - */ - initialListSize?: ?number, - /** - * Called when all rows have been rendered and the list has been scrolled - * to within onEndReachedThreshold of the bottom. The native scroll - * event is provided. - */ - onEndReached?: ?Function, - /** - * Threshold in pixels (virtual, not physical) for calling onEndReached. - */ - onEndReachedThreshold?: ?number, - /** - * Number of rows to render per event loop. Note: if your 'rows' are actually - * cells, i.e. they don't span the full width of your view (as in the - * ListViewGridLayoutExample), you should set the pageSize to be a multiple - * of the number of cells per row, otherwise you're likely to see gaps at - * the edge of the ListView as new pages are loaded. - */ - pageSize?: ?number, - /** - * () => renderable - * - * The header and footer are always rendered (if these props are provided) - * on every render pass. If they are expensive to re-render, wrap them - * in StaticContainer or other mechanism as appropriate. Footer is always - * at the bottom of the list, and header at the top, on every render pass. - * In a horizontal ListView, the header is rendered on the left and the - * footer on the right. - */ - renderFooter?: ?Function, - renderHeader?: ?Function, - /** - * (sectionData, sectionID) => renderable - * - * If provided, a header is rendered for this section. - */ - renderSectionHeader?: ?Function, - /** - * (props) => renderable - * - * A function that returns the scrollable component in which the list rows - * are rendered. Defaults to returning a ScrollView with the given props. - */ - renderScrollComponent?: ?Function, - /** - * How early to start rendering rows before they come on screen, in - * pixels. - */ - scrollRenderAheadDistance?: ?number, - /** - * (visibleRows, changedRows) => void - * - * Called when the set of visible rows changes. `visibleRows` maps - * { sectionID: { rowID: true }} for all the visible rows, and - * `changedRows` maps { sectionID: { rowID: true | false }} for the rows - * that have changed their visibility, with true indicating visible, and - * false indicating the view has moved out of view. - */ - onChangeVisibleRows?: ?Function, - /** - * A performance optimization for improving scroll perf of - * large lists, used in conjunction with overflow: 'hidden' on the row - * containers. This is enabled by default. - */ - removeClippedSubviews?: ?boolean, - /** - * Makes the sections headers sticky. The sticky behavior means that it - * will scroll with the content at the top of the section until it reaches - * the top of the screen, at which point it will stick to the top until it - * is pushed off the screen by the next section header. This property is - * not supported in conjunction with `horizontal={true}`. Only enabled by - * default on iOS because of typical platform standards. - */ - stickySectionHeadersEnabled?: ?boolean, - /** - * An array of child indices determining which children get docked to the - * top of the screen when scrolling. For example, passing - * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the - * top of the scroll view. This property is not supported in conjunction - * with `horizontal={true}`. - */ - stickyHeaderIndices?: ?$ReadOnlyArray, - /** - * Flag indicating whether empty section headers should be rendered. In the future release - * empty section headers will be rendered by default, and the flag will be deprecated. - * If empty sections are not desired to be rendered their indices should be excluded from sectionID object. - */ - enableEmptySections?: ?boolean, -|}>; - -/** - * DEPRECATED - use one of the new list components, such as [`FlatList`](docs/flatlist.html) - * or [`SectionList`](docs/sectionlist.html) for bounded memory use, fewer bugs, - * better performance, an easier to use API, and more features. Check out this - * [blog post](https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html) - * for more details. - * - * ListView - A core component designed for efficient display of vertically - * scrolling lists of changing data. The minimal API is to create a - * [`ListView.DataSource`](docs/listviewdatasource.html), populate it with a simple - * array of data blobs, and instantiate a `ListView` component with that data - * source and a `renderRow` callback which takes a blob from the data array and - * returns a renderable component. - * - * Minimal example: - * - * ``` - * class MyComponent extends Component { - * constructor() { - * super(); - * const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); - * this.state = { - * dataSource: ds.cloneWithRows(['row 1', 'row 2']), - * }; - * } - * - * render() { - * return ( - * {rowData}} - * /> - * ); - * } - * } - * ``` - * - * ListView also supports more advanced features, including sections with sticky - * section headers, header and footer support, callbacks on reaching the end of - * the available data (`onEndReached`) and on the set of rows that are visible - * in the device viewport change (`onChangeVisibleRows`), and several - * performance optimizations. - * - * There are a few performance operations designed to make ListView scroll - * smoothly while dynamically loading potentially very large (or conceptually - * infinite) data sets: - * - * * Only re-render changed rows - the rowHasChanged function provided to the - * data source tells the ListView if it needs to re-render a row because the - * source data has changed - see ListViewDataSource for more details. - * - * * Rate-limited row rendering - By default, only one row is rendered per - * event-loop (customizable with the `pageSize` prop). This breaks up the - * work into smaller chunks to reduce the chance of dropping frames while - * rendering rows. - */ - -const ListView = createReactClass({ - displayName: 'ListView', - _rafIds: ([]: Array), - _childFrames: ([]: Array), - _sentEndForContentLength: (null: ?number), - _scrollComponent: (null: ?React.ElementRef), - _prevRenderedRowsCount: 0, - _visibleRows: ({}: Object), - scrollProperties: ({}: Object), - - mixins: [ScrollResponder.Mixin], - - statics: { - DataSource: ListViewDataSource, - }, - - /** - * Exports some data, e.g. for perf investigations or analytics. - */ - getMetrics: function() { - return { - contentLength: this.scrollProperties.contentLength, - totalRows: this.props.enableEmptySections - ? this.props.dataSource.getRowAndSectionCount() - : this.props.dataSource.getRowCount(), - renderedRows: this.state.curRenderedRowsCount, - visibleRows: Object.keys(this._visibleRows).length, - }; - }, - - /** - * Provides a handle to the underlying scroll responder. - * Note that `this._scrollComponent` might not be a `ScrollView`, so we - * need to check that it responds to `getScrollResponder` before calling it. - */ - getScrollResponder: function() { - if (this._scrollComponent && this._scrollComponent.getScrollResponder) { - return this._scrollComponent.getScrollResponder(); - } - }, - - getScrollableNode: function() { - if (this._scrollComponent && this._scrollComponent.getScrollableNode) { - return this._scrollComponent.getScrollableNode(); - } else { - return ReactNative.findNodeHandle(this._scrollComponent); - } - }, - - /** - * Scrolls to a given x, y offset, either immediately or with a smooth animation. - * - * See `ScrollView#scrollTo`. - */ - scrollTo: function(...args: any) { - if (this._scrollComponent && this._scrollComponent.scrollTo) { - this._scrollComponent.scrollTo(...args); - } - }, - - /** - * If this is a vertical ListView scrolls to the bottom. - * If this is a horizontal ListView scrolls to the right. - * - * Use `scrollToEnd({animated: true})` for smooth animated scrolling, - * `scrollToEnd({animated: false})` for immediate scrolling. - * If no options are passed, `animated` defaults to true. - * - * See `ScrollView#scrollToEnd`. - */ - scrollToEnd: function(options?: ?{animated?: boolean}) { - if (this._scrollComponent) { - if (this._scrollComponent.scrollToEnd) { - this._scrollComponent.scrollToEnd(options); - } else { - console.warn( - 'The scroll component used by the ListView does not support ' + - 'scrollToEnd. Check the renderScrollComponent prop of your ListView.', - ); - } - } - }, - - /** - * Displays the scroll indicators momentarily. - * - * @platform ios - */ - flashScrollIndicators: function() { - if (this._scrollComponent && this._scrollComponent.flashScrollIndicators) { - this._scrollComponent.flashScrollIndicators(); - } - }, - - setNativeProps: function(props: Object) { - if (this._scrollComponent) { - this._scrollComponent.setNativeProps(props); - } - }, - - /** - * React life cycle hooks. - */ - - getDefaultProps: function() { - return { - initialListSize: DEFAULT_INITIAL_ROWS, - pageSize: DEFAULT_PAGE_SIZE, - renderScrollComponent: props => , - scrollRenderAheadDistance: DEFAULT_SCROLL_RENDER_AHEAD, - onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD, - stickySectionHeadersEnabled: Platform.OS === 'ios', - stickyHeaderIndices: [], - }; - }, - - getInitialState: function() { - return { - curRenderedRowsCount: this.props.initialListSize, - highlightedRow: ({}: Object), - }; - }, - - getInnerViewNode: function() { - return this._scrollComponent && this._scrollComponent.getInnerViewNode(); - }, - - UNSAFE_componentWillMount: function() { - // this data should never trigger a render pass, so don't put in state - this.scrollProperties = { - visibleLength: null, - contentLength: null, - offset: 0, - }; - - this._rafIds = []; - this._childFrames = []; - this._visibleRows = {}; - this._prevRenderedRowsCount = 0; - this._sentEndForContentLength = null; - }, - - componentWillUnmount: function() { - this._rafIds.forEach(cancelAnimationFrame); - this._rafIds = []; - }, - - componentDidMount: function() { - // do this in animation frame until componentDidMount actually runs after - // the component is laid out - this._requestAnimationFrame(() => { - this._measureAndUpdateScrollProps(); - }); - }, - - UNSAFE_componentWillReceiveProps: function(nextProps: Object) { - if ( - this.props.dataSource !== nextProps.dataSource || - this.props.initialListSize !== nextProps.initialListSize - ) { - this.setState( - (state, props) => { - this._prevRenderedRowsCount = 0; - return { - curRenderedRowsCount: Math.min( - Math.max(state.curRenderedRowsCount, props.initialListSize), - props.enableEmptySections - ? props.dataSource.getRowAndSectionCount() - : props.dataSource.getRowCount(), - ), - }; - }, - () => this._renderMoreRowsIfNeeded(), - ); - } - }, - - componentDidUpdate: function() { - this._requestAnimationFrame(() => { - this._measureAndUpdateScrollProps(); - }); - }, - - _onRowHighlighted: function(sectionID: string, rowID: string) { - this.setState({highlightedRow: {sectionID, rowID}}); - }, - - render: function() { - const bodyComponents = []; - - const dataSource = this.props.dataSource; - const allRowIDs = dataSource.rowIdentities; - let rowCount = 0; - const stickySectionHeaderIndices = []; - - const {renderSectionHeader} = this.props; - - const header = this.props.renderHeader && this.props.renderHeader(); - const footer = this.props.renderFooter && this.props.renderFooter(); - let totalIndex = header ? 1 : 0; - - for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - const sectionID = dataSource.sectionIdentities[sectionIdx]; - const rowIDs = allRowIDs[sectionIdx]; - if (rowIDs.length === 0) { - if (this.props.enableEmptySections === undefined) { - const warning = require('fbjs/lib/warning'); - warning( - false, - 'In next release empty section headers will be rendered.' + - " In this release you can use 'enableEmptySections' flag to render empty section headers.", - ); - continue; - } else { - const invariant = require('invariant'); - invariant( - this.props.enableEmptySections, - "In next release 'enableEmptySections' flag will be deprecated, empty section headers will always be rendered." + - ' If empty section headers are not desirable their indices should be excluded from sectionIDs object.' + - " In this release 'enableEmptySections' may only have value 'true' to allow empty section headers rendering.", - ); - } - } - - if (renderSectionHeader) { - const element = renderSectionHeader( - dataSource.getSectionHeaderData(sectionIdx), - sectionID, - ); - if (element) { - bodyComponents.push( - React.cloneElement(element, {key: 's_' + sectionID}), - ); - if (this.props.stickySectionHeadersEnabled) { - stickySectionHeaderIndices.push(totalIndex); - } - totalIndex++; - } - } - - for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { - const rowID = rowIDs[rowIdx]; - const comboID = sectionID + '_' + rowID; - const shouldUpdateRow = - rowCount >= this._prevRenderedRowsCount && - dataSource.rowShouldUpdate(sectionIdx, rowIdx); - const row = ( - - ); - bodyComponents.push(row); - totalIndex++; - - if ( - this.props.renderSeparator && - (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1) - ) { - const adjacentRowHighlighted = - this.state.highlightedRow.sectionID === sectionID && - (this.state.highlightedRow.rowID === rowID || - this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]); - const separator = this.props.renderSeparator( - sectionID, - rowID, - adjacentRowHighlighted, - ); - if (separator) { - bodyComponents.push({separator}); - totalIndex++; - } - } - if (++rowCount === this.state.curRenderedRowsCount) { - break; - } - } - if (rowCount >= this.state.curRenderedRowsCount) { - break; - } - } - - const {renderScrollComponent, ...props} = this.props; - if (!props.scrollEventThrottle) { - props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE; - } - if (props.removeClippedSubviews === undefined) { - props.removeClippedSubviews = true; - } - Object.assign(props, { - onScroll: this._onScroll, - stickyHeaderIndices: this.props.stickyHeaderIndices.concat( - stickySectionHeaderIndices, - ), - - // Do not pass these events downstream to ScrollView since they will be - // registered in ListView's own ScrollResponder.Mixin - onKeyboardWillShow: undefined, - onKeyboardWillHide: undefined, - onKeyboardDidShow: undefined, - onKeyboardDidHide: undefined, - }); - - return cloneReferencedElement( - renderScrollComponent(props), - { - ref: this._setScrollComponentRef, - onContentSizeChange: this._onContentSizeChange, - onLayout: this._onLayout, - DEPRECATED_sendUpdatedChildFrames: - typeof props.onChangeVisibleRows !== undefined, - }, - header, - bodyComponents, - footer, - ); - }, - - /** - * Private methods - */ - - _requestAnimationFrame: function(fn: () => void): void { - const rafId = requestAnimationFrame(() => { - this._rafIds = this._rafIds.filter(id => id !== rafId); - fn(); - }); - this._rafIds.push(rafId); - }, - - _measureAndUpdateScrollProps: function() { - const scrollComponent = this.getScrollResponder(); - if (!scrollComponent || !scrollComponent.getInnerViewNode) { - return; - } - - // RCTScrollViewManager.calculateChildFrames is not available on - // every platform - RCTScrollViewManager && - RCTScrollViewManager.calculateChildFrames && - RCTScrollViewManager.calculateChildFrames( - ReactNative.findNodeHandle(scrollComponent), - this._updateVisibleRows, - ); - }, - - _setScrollComponentRef: function(scrollComponent) { - this._scrollComponent = scrollComponent; - }, - - _onContentSizeChange: function(width: number, height: number) { - const contentLength = !this.props.horizontal ? height : width; - if (contentLength !== this.scrollProperties.contentLength) { - this.scrollProperties.contentLength = contentLength; - this._updateVisibleRows(); - this._renderMoreRowsIfNeeded(); - } - this.props.onContentSizeChange && - this.props.onContentSizeChange(width, height); - }, - - _onLayout: function(event: Object) { - const {width, height} = event.nativeEvent.layout; - const visibleLength = !this.props.horizontal ? height : width; - if (visibleLength !== this.scrollProperties.visibleLength) { - this.scrollProperties.visibleLength = visibleLength; - this._updateVisibleRows(); - this._renderMoreRowsIfNeeded(); - } - this.props.onLayout && this.props.onLayout(event); - }, - - _maybeCallOnEndReached: function(event?: Object) { - if ( - this.props.onEndReached && - this.scrollProperties.contentLength !== this._sentEndForContentLength && - this._getDistanceFromEnd(this.scrollProperties) < - this.props.onEndReachedThreshold && - this.state.curRenderedRowsCount === - (this.props.enableEmptySections - ? this.props.dataSource.getRowAndSectionCount() - : this.props.dataSource.getRowCount()) - ) { - this._sentEndForContentLength = this.scrollProperties.contentLength; - this.props.onEndReached(event); - return true; - } - return false; - }, - - _renderMoreRowsIfNeeded: function() { - if ( - this.scrollProperties.contentLength === null || - this.scrollProperties.visibleLength === null || - this.state.curRenderedRowsCount === - (this.props.enableEmptySections - ? this.props.dataSource.getRowAndSectionCount() - : this.props.dataSource.getRowCount()) - ) { - this._maybeCallOnEndReached(); - return; - } - - const distanceFromEnd = this._getDistanceFromEnd(this.scrollProperties); - if (distanceFromEnd < this.props.scrollRenderAheadDistance) { - this._pageInNewRows(); - } - }, - - _pageInNewRows: function() { - this.setState( - (state, props) => { - const rowsToRender = Math.min( - state.curRenderedRowsCount + props.pageSize, - props.enableEmptySections - ? props.dataSource.getRowAndSectionCount() - : props.dataSource.getRowCount(), - ); - this._prevRenderedRowsCount = state.curRenderedRowsCount; - return { - curRenderedRowsCount: rowsToRender, - }; - }, - () => { - this._measureAndUpdateScrollProps(); - this._prevRenderedRowsCount = this.state.curRenderedRowsCount; - }, - ); - }, - - _getDistanceFromEnd: function(scrollProperties: Object) { - return ( - scrollProperties.contentLength - - scrollProperties.visibleLength - - scrollProperties.offset - ); - }, - - _updateVisibleRows: function(updatedFrames?: Array) { - if (!this.props.onChangeVisibleRows) { - return; // No need to compute visible rows if there is no callback - } - if (updatedFrames) { - updatedFrames.forEach(newFrame => { - this._childFrames[newFrame.index] = merge(newFrame); - }); - } - const isVertical = !this.props.horizontal; - const dataSource = this.props.dataSource; - const visibleMin = this.scrollProperties.offset; - const visibleMax = visibleMin + this.scrollProperties.visibleLength; - const allRowIDs = dataSource.rowIdentities; - - const header = this.props.renderHeader && this.props.renderHeader(); - let totalIndex = header ? 1 : 0; - let visibilityChanged = false; - const changedRows = {}; - for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - const rowIDs = allRowIDs[sectionIdx]; - if (rowIDs.length === 0) { - continue; - } - const sectionID = dataSource.sectionIdentities[sectionIdx]; - if (this.props.renderSectionHeader) { - totalIndex++; - } - let visibleSection = this._visibleRows[sectionID]; - if (!visibleSection) { - visibleSection = {}; - } - for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { - const rowID = rowIDs[rowIdx]; - const frame = this._childFrames[totalIndex]; - totalIndex++; - if ( - this.props.renderSeparator && - (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1) - ) { - totalIndex++; - } - if (!frame) { - break; - } - const rowVisible = visibleSection[rowID]; - const min = isVertical ? frame.y : frame.x; - const max = min + (isVertical ? frame.height : frame.width); - if ((!min && !max) || min === max) { - break; - } - if (min > visibleMax || max < visibleMin) { - if (rowVisible) { - visibilityChanged = true; - delete visibleSection[rowID]; - if (!changedRows[sectionID]) { - changedRows[sectionID] = {}; - } - changedRows[sectionID][rowID] = false; - } - } else if (!rowVisible) { - visibilityChanged = true; - visibleSection[rowID] = true; - if (!changedRows[sectionID]) { - changedRows[sectionID] = {}; - } - changedRows[sectionID][rowID] = true; - } - } - if (!isEmpty(visibleSection)) { - this._visibleRows[sectionID] = visibleSection; - } else if (this._visibleRows[sectionID]) { - delete this._visibleRows[sectionID]; - } - } - visibilityChanged && - this.props.onChangeVisibleRows(this._visibleRows, changedRows); - }, - - _onScroll: function(e: Object) { - const isVertical = !this.props.horizontal; - this.scrollProperties.visibleLength = - e.nativeEvent.layoutMeasurement[isVertical ? 'height' : 'width']; - this.scrollProperties.contentLength = - e.nativeEvent.contentSize[isVertical ? 'height' : 'width']; - this.scrollProperties.offset = - e.nativeEvent.contentOffset[isVertical ? 'y' : 'x']; - this._updateVisibleRows(e.nativeEvent.updatedChildFrames); - if (!this._maybeCallOnEndReached(e)) { - this._renderMoreRowsIfNeeded(); - } - - if ( - this.props.onEndReached && - this._getDistanceFromEnd(this.scrollProperties) > - this.props.onEndReachedThreshold - ) { - // Scrolled out of the end zone, so it should be able to trigger again. - this._sentEndForContentLength = null; - } - - this.props.onScroll && this.props.onScroll(e); - }, -}); - -module.exports = ((ListView: any): Class>); diff --git a/Libraries/Lists/ListView/ListViewDataSource.js b/Libraries/Lists/ListView/ListViewDataSource.js deleted file mode 100644 index e1b872c4e7aa5a..00000000000000 --- a/Libraries/Lists/ListView/ListViewDataSource.js +++ /dev/null @@ -1,428 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ -'use strict'; - -const invariant = require('invariant'); -const isEmpty = require('isEmpty'); -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ -const warning = require('fbjs/lib/warning'); - -function defaultGetRowData( - dataBlob: any, - sectionID: number | string, - rowID: number | string, -): any { - return dataBlob[sectionID][rowID]; -} - -function defaultGetSectionHeaderData( - dataBlob: any, - sectionID: number | string, -): any { - return dataBlob[sectionID]; -} - -type differType = (data1: any, data2: any) => boolean; - -type ParamType = { - rowHasChanged: differType, - getRowData?: ?typeof defaultGetRowData, - sectionHeaderHasChanged?: ?differType, - getSectionHeaderData?: ?typeof defaultGetSectionHeaderData, -}; - -/** - * Provides efficient data processing and access to the - * `ListView` component. A `ListViewDataSource` is created with functions for - * extracting data from the input blob, and comparing elements (with default - * implementations for convenience). The input blob can be as simple as an - * array of strings, or an object with rows nested inside section objects. - * - * To update the data in the datasource, use `cloneWithRows` (or - * `cloneWithRowsAndSections` if you care about sections). The data in the - * data source is immutable, so you can't modify it directly. The clone methods - * suck in the new data and compute a diff for each row so ListView knows - * whether to re-render it or not. - * - * In this example, a component receives data in chunks, handled by - * `_onDataArrived`, which concats the new data onto the old data and updates the - * data source. We use `concat` to create a new array - mutating `this._data`, - * e.g. with `this._data.push(newRowData)`, would be an error. `_rowHasChanged` - * understands the shape of the row data and knows how to efficiently compare - * it. - * - * ``` - * getInitialState: function() { - * const ds = new ListView.DataSource({rowHasChanged: this._rowHasChanged}); - * return {ds}; - * }, - * _onDataArrived(newData) { - * this._data = this._data.concat(newData); - * this.setState({ - * ds: this.state.ds.cloneWithRows(this._data) - * }); - * } - * ``` - */ - -class ListViewDataSource { - /** - * You can provide custom extraction and `hasChanged` functions for section - * headers and rows. If absent, data will be extracted with the - * `defaultGetRowData` and `defaultGetSectionHeaderData` functions. - * - * The default extractor expects data of one of the following forms: - * - * { sectionID_1: { rowID_1: , ... }, ... } - * - * or - * - * { sectionID_1: [ , , ... ], ... } - * - * or - * - * [ [ , , ... ], ... ] - * - * The constructor takes in a params argument that can contain any of the - * following: - * - * - getRowData(dataBlob, sectionID, rowID); - * - getSectionHeaderData(dataBlob, sectionID); - * - rowHasChanged(prevRowData, nextRowData); - * - sectionHeaderHasChanged(prevSectionData, nextSectionData); - */ - constructor(params: ParamType) { - invariant( - params && typeof params.rowHasChanged === 'function', - 'Must provide a rowHasChanged function.', - ); - this._rowHasChanged = params.rowHasChanged; - this._getRowData = params.getRowData || defaultGetRowData; - this._sectionHeaderHasChanged = params.sectionHeaderHasChanged; - this._getSectionHeaderData = - params.getSectionHeaderData || defaultGetSectionHeaderData; - - this._dataBlob = null; - this._dirtyRows = []; - this._dirtySections = []; - this._cachedRowCount = 0; - - // These two private variables are accessed by outsiders because ListView - // uses them to iterate over the data in this class. - this.rowIdentities = []; - this.sectionIdentities = []; - } - - /** - * Clones this `ListViewDataSource` with the specified `dataBlob` and - * `rowIdentities`. The `dataBlob` is just an arbitrary blob of data. At - * construction an extractor to get the interesting information was defined - * (or the default was used). - * - * The `rowIdentities` is a 2D array of identifiers for rows. - * ie. [['a1', 'a2'], ['b1', 'b2', 'b3'], ...]. If not provided, it's - * assumed that the keys of the section data are the row identities. - * - * Note: This function does NOT clone the data in this data source. It simply - * passes the functions defined at construction to a new data source with - * the data specified. If you wish to maintain the existing data you must - * handle merging of old and new data separately and then pass that into - * this function as the `dataBlob`. - */ - cloneWithRows( - dataBlob: $ReadOnlyArray | {+[key: string]: any}, - rowIdentities: ?$ReadOnlyArray, - ): ListViewDataSource { - const rowIds = rowIdentities ? [[...rowIdentities]] : null; - if (!this._sectionHeaderHasChanged) { - this._sectionHeaderHasChanged = () => false; - } - return this.cloneWithRowsAndSections({s1: dataBlob}, ['s1'], rowIds); - } - - /** - * This performs the same function as the `cloneWithRows` function but here - * you also specify what your `sectionIdentities` are. If you don't care - * about sections you should safely be able to use `cloneWithRows`. - * - * `sectionIdentities` is an array of identifiers for sections. - * ie. ['s1', 's2', ...]. The identifiers should correspond to the keys or array indexes - * of the data you wish to include. If not provided, it's assumed that the - * keys of dataBlob are the section identities. - * - * Note: this returns a new object! - * - * ``` - * const dataSource = ds.cloneWithRowsAndSections({ - * addresses: ['row 1', 'row 2'], - * phone_numbers: ['data 1', 'data 2'], - * }, ['phone_numbers']); - * ``` - */ - cloneWithRowsAndSections( - dataBlob: any, - sectionIdentities: ?Array, - rowIdentities: ?Array>, - ): ListViewDataSource { - invariant( - typeof this._sectionHeaderHasChanged === 'function', - 'Must provide a sectionHeaderHasChanged function with section data.', - ); - invariant( - !sectionIdentities || - !rowIdentities || - sectionIdentities.length === rowIdentities.length, - 'row and section ids lengths must be the same', - ); - - const newSource = new ListViewDataSource({ - getRowData: this._getRowData, - getSectionHeaderData: this._getSectionHeaderData, - rowHasChanged: this._rowHasChanged, - sectionHeaderHasChanged: this._sectionHeaderHasChanged, - }); - newSource._dataBlob = dataBlob; - if (sectionIdentities) { - newSource.sectionIdentities = sectionIdentities; - } else { - newSource.sectionIdentities = Object.keys(dataBlob); - } - if (rowIdentities) { - newSource.rowIdentities = rowIdentities; - } else { - newSource.rowIdentities = []; - newSource.sectionIdentities.forEach(sectionID => { - newSource.rowIdentities.push(Object.keys(dataBlob[sectionID])); - }); - } - newSource._cachedRowCount = countRows(newSource.rowIdentities); - - newSource._calculateDirtyArrays( - this._dataBlob, - this.sectionIdentities, - this.rowIdentities, - ); - - return newSource; - } - - /** - * Returns the total number of rows in the data source. - * - * If you are specifying the rowIdentities or sectionIdentities, then `getRowCount` will return the number of rows in the filtered data source. - */ - getRowCount(): number { - return this._cachedRowCount; - } - - /** - * Returns the total number of rows in the data source (see `getRowCount` for how this is calculated) plus the number of sections in the data. - * - * If you are specifying the rowIdentities or sectionIdentities, then `getRowAndSectionCount` will return the number of rows & sections in the filtered data source. - */ - getRowAndSectionCount(): number { - return this._cachedRowCount + this.sectionIdentities.length; - } - - /** - * Returns if the row is dirtied and needs to be rerendered - */ - rowShouldUpdate(sectionIndex: number, rowIndex: number): boolean { - const needsUpdate = this._dirtyRows[sectionIndex][rowIndex]; - warning( - needsUpdate !== undefined, - 'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex, - ); - return needsUpdate; - } - - /** - * Gets the data required to render the row. - */ - getRowData(sectionIndex: number, rowIndex: number): any { - const sectionID = this.sectionIdentities[sectionIndex]; - const rowID = this.rowIdentities[sectionIndex][rowIndex]; - warning( - sectionID !== undefined && rowID !== undefined, - 'rendering invalid section, row: ' + sectionIndex + ', ' + rowIndex, - ); - return this._getRowData(this._dataBlob, sectionID, rowID); - } - - /** - * Gets the rowID at index provided if the dataSource arrays were flattened, - * or null of out of range indexes. - */ - getRowIDForFlatIndex(index: number): ?string { - let accessIndex = index; - for (let ii = 0; ii < this.sectionIdentities.length; ii++) { - if (accessIndex >= this.rowIdentities[ii].length) { - accessIndex -= this.rowIdentities[ii].length; - } else { - return this.rowIdentities[ii][accessIndex]; - } - } - return null; - } - - /** - * Gets the sectionID at index provided if the dataSource arrays were flattened, - * or null for out of range indexes. - */ - getSectionIDForFlatIndex(index: number): ?string { - let accessIndex = index; - for (let ii = 0; ii < this.sectionIdentities.length; ii++) { - if (accessIndex >= this.rowIdentities[ii].length) { - accessIndex -= this.rowIdentities[ii].length; - } else { - return this.sectionIdentities[ii]; - } - } - return null; - } - - /** - * Returns an array containing the number of rows in each section - */ - getSectionLengths(): Array { - const results = []; - for (let ii = 0; ii < this.sectionIdentities.length; ii++) { - results.push(this.rowIdentities[ii].length); - } - return results; - } - - /** - * Returns if the section header is dirtied and needs to be rerendered - */ - sectionHeaderShouldUpdate(sectionIndex: number): boolean { - const needsUpdate = this._dirtySections[sectionIndex]; - warning( - needsUpdate !== undefined, - 'missing dirtyBit for section: ' + sectionIndex, - ); - return needsUpdate; - } - - /** - * Gets the data required to render the section header - */ - getSectionHeaderData(sectionIndex: number): any { - if (!this._getSectionHeaderData) { - return null; - } - const sectionID = this.sectionIdentities[sectionIndex]; - warning( - sectionID !== undefined, - 'renderSection called on invalid section: ' + sectionIndex, - ); - return this._getSectionHeaderData(this._dataBlob, sectionID); - } - - /** - * Private members and methods. - */ - - _getRowData: typeof defaultGetRowData; - _getSectionHeaderData: typeof defaultGetSectionHeaderData; - _rowHasChanged: differType; - _sectionHeaderHasChanged: ?differType; - - _dataBlob: any; - _dirtyRows: Array>; - _dirtySections: Array; - _cachedRowCount: number; - - // These two 'protected' variables are accessed by ListView to iterate over - // the data in this class. - rowIdentities: Array>; - sectionIdentities: Array; - - _calculateDirtyArrays( - prevDataBlob: any, - prevSectionIDs: Array, - prevRowIDs: Array>, - ): void { - // construct a hashmap of the existing (old) id arrays - const prevSectionsHash = keyedDictionaryFromArray(prevSectionIDs); - const prevRowsHash = {}; - for (let ii = 0; ii < prevRowIDs.length; ii++) { - const sectionID = prevSectionIDs[ii]; - warning( - !prevRowsHash[sectionID], - 'SectionID appears more than once: ' + sectionID, - ); - prevRowsHash[sectionID] = keyedDictionaryFromArray(prevRowIDs[ii]); - } - - // compare the 2 identity array and get the dirtied rows - this._dirtySections = []; - this._dirtyRows = []; - - let dirty; - for (let sIndex = 0; sIndex < this.sectionIdentities.length; sIndex++) { - const sectionID = this.sectionIdentities[sIndex]; - // dirty if the sectionHeader is new or _sectionHasChanged is true - dirty = !prevSectionsHash[sectionID]; - const sectionHeaderHasChanged = this._sectionHeaderHasChanged; - if (!dirty && sectionHeaderHasChanged) { - dirty = sectionHeaderHasChanged( - this._getSectionHeaderData(prevDataBlob, sectionID), - this._getSectionHeaderData(this._dataBlob, sectionID), - ); - } - this._dirtySections.push(!!dirty); - - this._dirtyRows[sIndex] = []; - for ( - let rIndex = 0; - rIndex < this.rowIdentities[sIndex].length; - rIndex++ - ) { - const rowID = this.rowIdentities[sIndex][rIndex]; - // dirty if the section is new, row is new or _rowHasChanged is true - dirty = - !prevSectionsHash[sectionID] || - !prevRowsHash[sectionID][rowID] || - this._rowHasChanged( - this._getRowData(prevDataBlob, sectionID, rowID), - this._getRowData(this._dataBlob, sectionID, rowID), - ); - this._dirtyRows[sIndex].push(!!dirty); - } - } - } -} - -function countRows(allRowIDs) { - let totalRows = 0; - for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - const rowIDs = allRowIDs[sectionIdx]; - totalRows += rowIDs.length; - } - return totalRows; -} - -function keyedDictionaryFromArray(arr) { - if (isEmpty(arr)) { - return {}; - } - const result = {}; - for (let ii = 0; ii < arr.length; ii++) { - const key = arr[ii]; - warning(!result[key], 'Value appears more than once in array: ' + key); - result[key] = true; - } - return result; -} - -module.exports = ListViewDataSource; diff --git a/Libraries/Lists/ListView/__mocks__/ListViewMock.js b/Libraries/Lists/ListView/__mocks__/ListViewMock.js deleted file mode 100644 index 4813e7a428a066..00000000000000 --- a/Libraries/Lists/ListView/__mocks__/ListViewMock.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ -'use strict'; - -const ListViewDataSource = require('ListViewDataSource'); -const React = require('React'); -const ScrollView = require('ScrollView'); -const StaticRenderer = require('StaticRenderer'); - -class ListViewMock extends React.Component<$FlowFixMeProps> { - static latestRef: ?ListViewMock; - static defaultProps = { - /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an - * error caught by Flow 0.59 which was not caught before. Most likely, this - * error is because an exported function parameter is missing an - * annotation. Without an annotation, these parameters are uncovered by - * Flow. */ - renderScrollComponent: props => , - }; - - componentDidMount() { - ListViewMock.latestRef = this; - } - - render() { - const {dataSource, renderFooter, renderHeader} = this.props; - let rows = [ - renderHeader && ( - - ), - ]; - - const dataSourceRows = dataSource.rowIdentities.map( - (rowIdentity, rowIdentityIndex) => { - const sectionID = dataSource.sectionIdentities[rowIdentityIndex]; - return rowIdentity.map((row, rowIndex) => ( - - )); - }, - ); - - rows = [...rows, ...dataSourceRows]; - renderFooter && - rows.push( - , - ); - - return this.props.renderScrollComponent({...this.props, children: rows}); - } - static DataSource = ListViewDataSource; -} - -module.exports = ListViewMock; diff --git a/Libraries/Lists/VirtualizedSectionList.js b/Libraries/Lists/VirtualizedSectionList.js index e314871c4ee13a..7cb2af4dff0439 100644 --- a/Libraries/Lists/VirtualizedSectionList.js +++ b/Libraries/Lists/VirtualizedSectionList.js @@ -221,9 +221,9 @@ class VirtualizedSectionList extends React.PureComponent< trailingSection?: ?SectionT, } { let itemIndex = index; - const defaultKeyExtractor = this.props.keyExtractor; - for (let ii = 0; ii < this.props.sections.length; ii++) { - const section = this.props.sections[ii]; + const {sections} = this.props; + for (let ii = 0; ii < sections.length; ii++) { + const section = sections[ii]; const key = section.key || String(ii); itemIndex -= 1; // The section adds an item for the header if (itemIndex >= section.data.length + 1) { @@ -234,7 +234,7 @@ class VirtualizedSectionList extends React.PureComponent< key: key + ':header', index: null, header: true, - trailingSection: this.props.sections[ii + 1], + trailingSection: sections[ii + 1], }; } else if (itemIndex === section.data.length) { return { @@ -242,18 +242,21 @@ class VirtualizedSectionList extends React.PureComponent< key: key + ':footer', index: null, header: false, - trailingSection: this.props.sections[ii + 1], + trailingSection: sections[ii + 1], }; } else { - const keyExtractor = section.keyExtractor || defaultKeyExtractor; + const keyExtractor = section.keyExtractor || this.props.keyExtractor; return { section, key: key + ':' + keyExtractor(section.data[itemIndex], itemIndex), index: itemIndex, leadingItem: section.data[itemIndex - 1], - leadingSection: this.props.sections[ii - 1], - trailingItem: section.data[itemIndex + 1], - trailingSection: this.props.sections[ii + 1], + leadingSection: sections[ii - 1], + trailingItem: + section.data.length > itemIndex + 1 + ? section.data[itemIndex + 1] + : undefined, + trailingSection: sections[ii + 1], }; } } diff --git a/Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h b/Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h index 915606c2d2a993..1a13699da445fd 100644 --- a/Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h +++ b/Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h @@ -10,7 +10,7 @@ #import -static CGFloat RCTSingleFrameInterval = 1.0 / 60.0; +static CGFloat RCTSingleFrameInterval = (CGFloat)(1.0 / 60.0); @class RCTValueAnimatedNode; diff --git a/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m index b7e784b6ce04ac..179a187877e368 100644 --- a/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m +++ b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m @@ -113,14 +113,12 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime } // calculate delta time - NSTimeInterval deltaTime; if(_animationStartTime == -1) { _t = 0.0; _animationStartTime = currentTime; - deltaTime = 0.0; } else { // Handle frame drops, and only advance dt by a max of MAX_DELTA_TIME - deltaTime = MIN(MAX_DELTA_TIME, currentTime - _animationCurrentTime); + NSTimeInterval deltaTime = MIN(MAX_DELTA_TIME, currentTime - _animationCurrentTime); _t = _t + deltaTime / RCTAnimationDragCoefficient(); } diff --git a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h index 4bf39fe3e00726..a8a5218a5e50ab 100644 --- a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h +++ b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h @@ -7,14 +7,14 @@ #import "RCTAnimatedNode.h" -@class RCTUIManager; +@class RCTBridge; @class RCTViewPropertyMapper; @interface RCTPropsAnimatedNode : RCTAnimatedNode - (void)connectToView:(NSNumber *)viewTag viewName:(NSString *)viewName - uiManager:(RCTUIManager *)uiManager; + bridge:(RCTBridge *)bridge; - (void)disconnectFromView:(NSNumber *)viewTag; diff --git a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m index 473a921ba3534c..8677d2da1ee3ec 100644 --- a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m @@ -7,6 +7,8 @@ #import "RCTPropsAnimatedNode.h" +#import + #import #import @@ -14,12 +16,33 @@ #import "RCTStyleAnimatedNode.h" #import "RCTValueAnimatedNode.h" +// TODO: Eventually we should just include RCTSurfacePresenter.h, but that pulls in all of fabric +// which doesn't compile in open source yet, so we mirror the protocol and duplicate the category +// here for now. + +@protocol SyncViewUpdater + +- (BOOL)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag props:(NSDictionary *)props; + +@end + +@implementation RCTBridge (SurfacePresenterShadow) + +- (id)surfacePresenter +{ + return objc_getAssociatedObject(self, @selector(surfacePresenter)); +} + +@end + + @implementation RCTPropsAnimatedNode { NSNumber *_connectedViewTag; + NSNumber *_rootTag; NSString *_connectedViewName; - __weak RCTUIManager *_uiManager; - NSMutableDictionary *_propsDictionary; + __weak RCTBridge *_bridge; + NSMutableDictionary *_propsDictionary; // TODO: use RawProps or folly::dynamic directly } - (instancetype)initWithTag:(NSNumber *)tag @@ -33,18 +56,32 @@ - (instancetype)initWithTag:(NSNumber *)tag - (void)connectToView:(NSNumber *)viewTag viewName:(NSString *)viewName - uiManager:(RCTUIManager *)uiManager + bridge:(RCTBridge *)bridge { _connectedViewTag = viewTag; _connectedViewName = viewName; - _uiManager = uiManager; + _bridge = bridge; + _rootTag = nil; } - (void)disconnectFromView:(NSNumber *)viewTag { _connectedViewTag = nil; _connectedViewName = nil; - _uiManager = nil; + _bridge = nil; + _rootTag = nil; +} + +- (void)updateView +{ + BOOL fabricUpdateSuccess = [_bridge.surfacePresenter synchronouslyUpdateViewOnUIThread:_connectedViewTag + props:_propsDictionary]; + if (fabricUpdateSuccess) { + return; + } + [_bridge.uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag + viewName:_connectedViewName + props:_propsDictionary]; } - (void)restoreDefaultValues @@ -55,9 +92,7 @@ - (void)restoreDefaultValues } if (_propsDictionary.count) { - [_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag - viewName:_connectedViewName - props:_propsDictionary]; + [self updateView]; } } @@ -83,12 +118,12 @@ - (void)performUpdate if (!_connectedViewTag) { return; } - + for (NSNumber *parentTag in self.parentNodes.keyEnumerator) { RCTAnimatedNode *parentNode = [self.parentNodes objectForKey:parentTag]; if ([parentNode isKindOfClass:[RCTStyleAnimatedNode class]]) { [self->_propsDictionary addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]]; - + } else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) { NSString *property = [self propertyNameForParentTag:parentTag]; CGFloat value = [(RCTValueAnimatedNode *)parentNode value]; @@ -97,9 +132,7 @@ - (void)performUpdate } if (_propsDictionary.count) { - [_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag - viewName:_connectedViewName - props:_propsDictionary]; + [self updateView]; } } diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 57f08034e177a1..b00d95ff513a67 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -41,12 +41,12 @@ - (void)setBridge:(RCTBridge *)bridge { [super setBridge:bridge]; - _nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithUIManager:self.bridge.uiManager]; + _nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithBridge:self.bridge]; _operations = [NSMutableArray new]; _preOperations = [NSMutableArray new]; [bridge.eventDispatcher addDispatchObserver:self]; - [bridge.uiManager.observerCoordinator addObserver:self]; + [bridge.uiManager.observerCoordinator addObserver:self]; // TODO: add fabric equivalent? } #pragma mark -- API @@ -196,7 +196,7 @@ - (void)addPreOperationBlock:(AnimatedOperation)operation #pragma mark - RCTUIManagerObserver -- (void)uiManagerWillPerformMounting:(RCTUIManager *)uiManager +- (void)uiManagerWillPerformMounting:(RCTUIManager *)uiManager // TODO: need fabric equivalent { if (_preOperations.count == 0 && _operations.count == 0) { return; diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h index 6f30f96fabe22d..1222050583c4ef 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h @@ -13,7 +13,7 @@ @interface RCTNativeAnimatedNodesManager : NSObject -- (nonnull instancetype)initWithUIManager:(nonnull RCTUIManager *)uiManager; +- (nonnull instancetype)initWithBridge:(nonnull RCTBridge *)bridge; - (void)updateAnimations; diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m index 30ec0e42dafc05..03bded86af55da 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m @@ -30,7 +30,7 @@ @implementation RCTNativeAnimatedNodesManager { - __weak RCTUIManager *_uiManager; + __weak RCTBridge *_bridge; NSMutableDictionary *_animationNodes; // Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time // there will be only one driver per mapping so all code code should be optimized around that. @@ -39,10 +39,10 @@ @implementation RCTNativeAnimatedNodesManager CADisplayLink *_displayLink; } -- (instancetype)initWithUIManager:(nonnull RCTUIManager *)uiManager +- (instancetype)initWithBridge:(nonnull RCTBridge *)bridge { if ((self = [super init])) { - _uiManager = uiManager; + _bridge = bridge; _animationNodes = [NSMutableDictionary new]; _eventDrivers = [NSMutableDictionary new]; _activeAnimations = [NSMutableSet new]; @@ -124,7 +124,7 @@ - (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { - [(RCTPropsAnimatedNode *)node connectToView:viewTag viewName:viewName uiManager:_uiManager]; + [(RCTPropsAnimatedNode *)node connectToView:viewTag viewName:viewName bridge:_bridge]; } [node setNeedsUpdate]; } diff --git a/Libraries/ReactNative/AppRegistry.js b/Libraries/ReactNative/AppRegistry.js index 19483c98ad411b..c401dec043f7d3 100644 --- a/Libraries/ReactNative/AppRegistry.js +++ b/Libraries/ReactNative/AppRegistry.js @@ -18,6 +18,8 @@ const SceneTracker = require('SceneTracker'); const infoLog = require('infoLog'); const invariant = require('invariant'); const renderApplication = require('renderApplication'); +const createPerformanceLogger = require('createPerformanceLogger'); +import type {IPerformanceLogger} from 'createPerformanceLogger'; type Task = (taskData: any) => Promise; type TaskProvider = () => Task; @@ -30,6 +32,7 @@ type TaskCancelProvider = () => TaskCanceller; export type ComponentProvider = () => React$ComponentType; export type ComponentProviderInstrumentationHook = ( component: ComponentProvider, + scopedPerformanceLogger: IPerformanceLogger, ) => React$ComponentType; export type AppConfig = { appKey: string, @@ -101,15 +104,21 @@ const AppRegistry = { componentProvider: ComponentProvider, section?: boolean, ): string { + let scopedPerformanceLogger = createPerformanceLogger(); runnables[appKey] = { componentProvider, run: appParameters => { renderApplication( - componentProviderInstrumentationHook(componentProvider), + componentProviderInstrumentationHook( + componentProvider, + scopedPerformanceLogger, + ), appParameters.initialProps, appParameters.rootTag, wrapperComponentProvider && wrapperComponentProvider(appParameters), appParameters.fabric, + false, + scopedPerformanceLogger, ); }, }; diff --git a/Libraries/ReactNative/FabricUIManager.js b/Libraries/ReactNative/FabricUIManager.js index b467ed23c846c6..927d93d8fb1613 100644 --- a/Libraries/ReactNative/FabricUIManager.js +++ b/Libraries/ReactNative/FabricUIManager.js @@ -30,6 +30,7 @@ type Spec = {| +appendChild: (parentNode: Node, child: Node) => Node, +appendChildToSet: (childSet: NodeSet, child: Node) => void, +completeRoot: (rootTag: number, childSet: NodeSet) => void, + +setNativeProps: (node: Node, nativeProps: NodeProps) => void, |}; const FabricUIManager: ?Spec = global.nativeFabricUIManager; diff --git a/Libraries/ReactNative/renderApplication.js b/Libraries/ReactNative/renderApplication.js index 694567726bd280..3fa9deff700ec5 100644 --- a/Libraries/ReactNative/renderApplication.js +++ b/Libraries/ReactNative/renderApplication.js @@ -11,7 +11,9 @@ 'use strict'; const AppContainer = require('AppContainer'); -import PerformanceLogger from 'PerformanceLogger'; +import GlobalPerformanceLogger from 'GlobalPerformanceLogger'; +import type {IPerformanceLogger} from 'createPerformanceLogger'; +import PerformanceLoggerContext from 'PerformanceLoggerContext'; const React = require('React'); const ReactFabricIndicator = require('ReactFabricIndicator'); @@ -27,16 +29,20 @@ function renderApplication( WrapperComponent?: ?React.ComponentType<*>, fabric?: boolean, showFabricIndicator?: boolean, + scopedPerformanceLogger?: IPerformanceLogger, ) { invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag); let renderable = ( - - - {fabric === true && showFabricIndicator === true ? ( - - ) : null} - + + + + {fabric === true && showFabricIndicator === true ? ( + + ) : null} + + ); // If the root component is async, the user probably wants the initial render @@ -54,13 +60,13 @@ function renderApplication( renderable = {renderable}; } - PerformanceLogger.startTimespan('renderApplication_React_render'); + GlobalPerformanceLogger.startTimespan('renderApplication_React_render'); if (fabric) { require('ReactFabric').render(renderable, rootTag); } else { require('ReactNative').render(renderable, rootTag); } - PerformanceLogger.stopTimespan('renderApplication_React_render'); + GlobalPerformanceLogger.stopTimespan('renderApplication_React_render'); } module.exports = renderApplication; diff --git a/Libraries/Renderer/REVISION b/Libraries/Renderer/REVISION index afe4ebf1265bea..b51f74404e8fb0 100644 --- a/Libraries/Renderer/REVISION +++ b/Libraries/Renderer/REVISION @@ -1 +1 @@ -f24a0da6e0f59484e5aafd0825bb1a6ed27d7182 \ No newline at end of file +8e25ed20bd27d126f670d04680db85209f779056 \ No newline at end of file diff --git a/Libraries/Renderer/oss/ReactFabric-dev.js b/Libraries/Renderer/oss/ReactFabric-dev.js index 147df8bf432166..3dbff8b085202f 100644 --- a/Libraries/Renderer/oss/ReactFabric-dev.js +++ b/Libraries/Renderer/oss/ReactFabric-dev.js @@ -1090,6 +1090,7 @@ var MemoComponent = 14; var SimpleMemoComponent = 15; var LazyComponent = 16; var IncompleteClassComponent = 17; +var DehydratedSuspenseComponent = 18; function getParent(inst) { do { @@ -3503,6 +3504,19 @@ function shouldYield$1() { return frameDeadline <= now$1(); } +var debugRenderPhaseSideEffects = false; +var debugRenderPhaseSideEffectsForStrictMode = false; +var enableUserTimingAPI = true; +var replayFailedUnitOfWorkWithInvokeGuardedCallback = true; +var warnAboutDeprecatedLifecycles = false; +var enableProfilerTimer = true; +var enableSchedulerTracing = true; +var enableSuspenseServerRenderer = false; + +var warnAboutDeprecatedSetNativeProps = false; + +// Only used in www builds. + // Use to restore controlled state after a change event has fired. var restoreImpl = null; @@ -3651,21 +3665,31 @@ function shim$1() { } // Hydration (when unsupported) + var supportsHydration = false; var canHydrateInstance = shim$1; var canHydrateTextInstance = shim$1; +var canHydrateSuspenseInstance = shim$1; +var isSuspenseInstancePending = shim$1; +var isSuspenseInstanceFallback = shim$1; +var registerSuspenseInstanceRetry = shim$1; var getNextHydratableSibling = shim$1; var getFirstHydratableChild = shim$1; var hydrateInstance = shim$1; var hydrateTextInstance = shim$1; +var getNextHydratableInstanceAfterSuspenseInstance = shim$1; +var clearSuspenseBoundary = shim$1; +var clearSuspenseBoundaryFromContainer = shim$1; var didNotMatchHydratedContainerTextInstance = shim$1; var didNotMatchHydratedTextInstance = shim$1; var didNotHydrateContainerInstance = shim$1; var didNotHydrateInstance = shim$1; var didNotFindHydratableContainerInstance = shim$1; var didNotFindHydratableContainerTextInstance = shim$1; +var didNotFindHydratableContainerSuspenseInstance = shim$1; var didNotFindHydratableInstance = shim$1; var didNotFindHydratableTextInstance = shim$1; +var didNotFindHydratableSuspenseInstance = shim$1; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { @@ -3742,6 +3766,15 @@ var ReactFabricHostComponent = (function() { nativeProps ) { { + if (warnAboutDeprecatedSetNativeProps) { + warningWithoutStack$1( + false, + "Warning: Calling ref.setNativeProps(nativeProps) " + + "is deprecated and will be removed in a future release. " + + "Use the setNativeProps export from the react-native package instead." + + "\n\timport {setNativeProps} from 'react-native';\n\tsetNativeProps(ref, nativeProps);\n" + ); + } warnForStyleProps(nativeProps, this.viewConfig.validAttributes); } @@ -4121,16 +4154,6 @@ function setCurrentPhase(lifeCyclePhase) { } } -var debugRenderPhaseSideEffects = false; -var debugRenderPhaseSideEffectsForStrictMode = false; -var enableUserTimingAPI = true; -var replayFailedUnitOfWorkWithInvokeGuardedCallback = true; -var warnAboutDeprecatedLifecycles = false; -var enableProfilerTimer = true; -var enableSchedulerTracing = true; - -// Only used in www builds. - // Prefix measurements so that it's possible to filter them. // Longer prefixes are hard to read in DevTools. var reactEmoji = "\u269B"; @@ -4402,7 +4425,8 @@ function stopFailedWorkTimer(fiber) { } fiber._debugIsCurrentlyTiming = false; var warning = - fiber.tag === SuspenseComponent + fiber.tag === SuspenseComponent || + fiber.tag === DehydratedSuspenseComponent ? "Rendering was suspended" : "An error was thrown inside this error boundary"; endFiberMark(fiber, null, warning); @@ -9277,17 +9301,6 @@ function mountWorkInProgressHook() { { hook._debugType = currentHookNameInDev; - if ( - currentlyRenderingFiber$1 !== null && - currentlyRenderingFiber$1.alternate !== null - ) { - warning$1( - false, - "%s: Rendered more hooks than during the previous render. This is " + - "not currently supported and may lead to unexpected behavior.", - getComponentName(currentlyRenderingFiber$1.type) - ); - } } if (workInProgressHook === null) { // This is the first hook in the list @@ -9431,7 +9444,6 @@ function updateReducer(reducer, initialArg, init) { } hook.memoizedState = newState; - // Don't persist the state accumlated from the render phase updates to // the base state unless the queue is empty. // TODO: Not sure if this is the desired semantics, but it's what we @@ -9440,6 +9452,9 @@ function updateReducer(reducer, initialArg, init) { hook.baseState = newState; } + queue.eagerReducer = reducer; + queue.eagerState = newState; + return [newState, _dispatch]; } } @@ -10302,6 +10317,18 @@ function enterHydrationState(fiber) { return true; } +function reenterHydrationStateFromDehydratedSuspenseInstance(fiber) { + if (!supportsHydration) { + return false; + } + + var suspenseInstance = fiber.stateNode; + nextHydratableInstance = getNextHydratableSibling(suspenseInstance); + popToNextHostParent(fiber); + isHydrating = true; + return true; +} + function deleteHydratableInstance(returnFiber, instance) { { switch (returnFiber.tag) { @@ -10356,6 +10383,9 @@ function insertNonHydratedInstance(returnFiber, fiber) { var text = fiber.pendingProps; didNotFindHydratableContainerTextInstance(parentContainer, text); break; + case SuspenseComponent: + didNotFindHydratableContainerSuspenseInstance(parentContainer); + break; } break; } @@ -10384,6 +10414,13 @@ function insertNonHydratedInstance(returnFiber, fiber) { _text ); break; + case SuspenseComponent: + didNotFindHydratableSuspenseInstance( + parentType, + parentProps, + parentInstance + ); + break; } break; } @@ -10414,6 +10451,18 @@ function tryHydrate(fiber, nextInstance) { } return false; } + case SuspenseComponent: { + if (enableSuspenseServerRenderer) { + var suspenseInstance = canHydrateSuspenseInstance(nextInstance); + if (suspenseInstance !== null) { + // Downgrade the tag to a dehydrated component until we've hydrated it. + fiber.tag = DehydratedSuspenseComponent; + fiber.stateNode = suspenseInstance; + return true; + } + } + return false; + } default: return false; } @@ -10534,12 +10583,32 @@ function prepareToHydrateHostTextInstance(fiber) { return shouldUpdate; } +function skipPastDehydratedSuspenseInstance(fiber) { + if (!supportsHydration) { + invariant( + false, + "Expected skipPastDehydratedSuspenseInstance() to never be called. " + + "This error is likely caused by a bug in React. Please file an issue." + ); + } + var suspenseInstance = fiber.stateNode; + invariant( + suspenseInstance, + "Expected to have a hydrated suspense instance. " + + "This error is likely caused by a bug in React. Please file an issue." + ); + nextHydratableInstance = getNextHydratableInstanceAfterSuspenseInstance( + suspenseInstance + ); +} + function popToNextHostParent(fiber) { var parent = fiber.return; while ( parent !== null && parent.tag !== HostComponent && - parent.tag !== HostRoot + parent.tag !== HostRoot && + parent.tag !== DehydratedSuspenseComponent ) { parent = parent.return; } @@ -10691,6 +10760,10 @@ function updateForwardRef( nextProps, renderExpirationTime ) { + // TODO: current can be non-null here even if the component + // hasn't yet mounted. This happens after the first render suspends. + // We'll need to figure out if this is fine or can cause issues. + { if (workInProgress.type !== workInProgress.elementType) { // Lazy component props can't be validated in createElement @@ -10880,6 +10953,10 @@ function updateSimpleMemoComponent( updateExpirationTime, renderExpirationTime ) { + // TODO: current can be non-null here even if the component + // hasn't yet mounted. This happens when the inner render suspends. + // We'll need to figure out if this is fine or can cause issues. + { if (workInProgress.type !== workInProgress.elementType) { // Lazy component props can't be validated in createElement @@ -11833,6 +11910,22 @@ function updateSuspenseComponent( // children -- we skip over the primary children entirely. var next = void 0; if (current$$1 === null) { + if (enableSuspenseServerRenderer) { + // If we're currently hydrating, try to hydrate this boundary. + // But only if this has a fallback. + if (nextProps.fallback !== undefined) { + tryToClaimNextHydratableInstance(workInProgress); + // This could've changed the tag if this was a dehydrated suspense component. + if (workInProgress.tag === DehydratedSuspenseComponent) { + return updateDehydratedSuspenseComponent( + null, + workInProgress, + renderExpirationTime + ); + } + } + } + // This is the initial mount. This branch is pretty simple because there's // no previous state that needs to be preserved. if (nextDidTimeout) { @@ -12035,6 +12128,107 @@ function updateSuspenseComponent( return next; } +function updateDehydratedSuspenseComponent( + current$$1, + workInProgress, + renderExpirationTime +) { + if (current$$1 === null) { + // During the first pass, we'll bail out and not drill into the children. + // Instead, we'll leave the content in place and try to hydrate it later. + workInProgress.expirationTime = Never; + return null; + } + if ((workInProgress.effectTag & DidCapture) !== NoEffect) { + // Something suspended. Leave the existing children in place. + // TODO: In non-concurrent mode, should we commit the nodes we have hydrated so far? + workInProgress.child = null; + return null; + } + // We use childExpirationTime to indicate that a child might depend on context, so if + // any context has changed, we need to treat is as if the input might have changed. + var hasContextChanged$$1 = + current$$1.childExpirationTime >= renderExpirationTime; + var suspenseInstance = current$$1.stateNode; + if ( + didReceiveUpdate || + hasContextChanged$$1 || + isSuspenseInstanceFallback(suspenseInstance) + ) { + // This boundary has changed since the first render. This means that we are now unable to + // hydrate it. We might still be able to hydrate it using an earlier expiration time but + // during this render we can't. Instead, we're going to delete the whole subtree and + // instead inject a new real Suspense boundary to take its place, which may render content + // or fallback. The real Suspense boundary will suspend for a while so we have some time + // to ensure it can produce real content, but all state and pending events will be lost. + + // Alternatively, this boundary is in a permanent fallback state. In this case, we'll never + // get an update and we'll never be able to hydrate the final content. Let's just try the + // client side render instead. + + // Detach from the current dehydrated boundary. + current$$1.alternate = null; + workInProgress.alternate = null; + + // Insert a deletion in the effect list. + var returnFiber = workInProgress.return; + invariant( + returnFiber !== null, + "Suspense boundaries are never on the root. " + + "This is probably a bug in React." + ); + var last = returnFiber.lastEffect; + if (last !== null) { + last.nextEffect = current$$1; + returnFiber.lastEffect = current$$1; + } else { + returnFiber.firstEffect = returnFiber.lastEffect = current$$1; + } + current$$1.nextEffect = null; + current$$1.effectTag = Deletion; + + // Upgrade this work in progress to a real Suspense component. + workInProgress.tag = SuspenseComponent; + workInProgress.stateNode = null; + workInProgress.memoizedState = null; + // This is now an insertion. + workInProgress.effectTag |= Placement; + // Retry as a real Suspense component. + return updateSuspenseComponent(null, workInProgress, renderExpirationTime); + } else if (isSuspenseInstancePending(suspenseInstance)) { + // This component is still pending more data from the server, so we can't hydrate its + // content. We treat it as if this component suspended itself. It might seem as if + // we could just try to render it client-side instead. However, this will perform a + // lot of unnecessary work and is unlikely to complete since it often will suspend + // on missing data anyway. Additionally, the server might be able to render more + // than we can on the client yet. In that case we'd end up with more fallback states + // on the client than if we just leave it alone. If the server times out or errors + // these should update this boundary to the permanent Fallback state instead. + // Mark it as having captured (i.e. suspended). + workInProgress.effectTag |= DidCapture; + // Leave the children in place. I.e. empty. + workInProgress.child = null; + // Register a callback to retry this boundary once the server has sent the result. + registerSuspenseInstanceRetry( + suspenseInstance, + retryTimedOutBoundary.bind(null, current$$1) + ); + return null; + } else { + // This is the first attempt. + reenterHydrationStateFromDehydratedSuspenseInstance(workInProgress); + var nextProps = workInProgress.pendingProps; + var nextChildren = nextProps.children; + workInProgress.child = mountChildFibers( + workInProgress, + null, + nextChildren, + renderExpirationTime + ); + return workInProgress.child; + } +} + function updatePortalComponent( current$$1, workInProgress, @@ -12321,6 +12515,15 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { } break; } + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + // We know that this component will suspend again because if it has + // been unsuspended it has committed as a regular Suspense component. + // If it needs to be retried, it should have work scheduled on it. + workInProgress.effectTag |= DidCapture; + break; + } + } } return bailoutOnAlreadyFinishedWork( current$$1, @@ -12494,13 +12697,22 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { renderExpirationTime ); } - default: - invariant( - false, - "Unknown unit of work tag. This error is likely caused by a bug in " + - "React. Please file an issue." - ); + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + return updateDehydratedSuspenseComponent( + current$$1, + workInProgress, + renderExpirationTime + ); + } + break; + } } + invariant( + false, + "Unknown unit of work tag. This error is likely caused by a bug in " + + "React. Please file an issue." + ); } var valueCursor = createCursor(null); @@ -12619,6 +12831,34 @@ function calculateChangedBits(context, newValue, oldValue) { } } +function scheduleWorkOnParentPath(parent, renderExpirationTime) { + // Update the child expiration time of all the ancestors, including + // the alternates. + var node = parent; + while (node !== null) { + var alternate = node.alternate; + if (node.childExpirationTime < renderExpirationTime) { + node.childExpirationTime = renderExpirationTime; + if ( + alternate !== null && + alternate.childExpirationTime < renderExpirationTime + ) { + alternate.childExpirationTime = renderExpirationTime; + } + } else if ( + alternate !== null && + alternate.childExpirationTime < renderExpirationTime + ) { + alternate.childExpirationTime = renderExpirationTime; + } else { + // Neither alternate was updated, which means the rest of the + // ancestor path already has sufficient priority. + break; + } + node = node.return; + } +} + function propagateContextChange( workInProgress, context, @@ -12668,31 +12908,8 @@ function propagateContextChange( ) { alternate.expirationTime = renderExpirationTime; } - // Update the child expiration time of all the ancestors, including - // the alternates. - var node = fiber.return; - while (node !== null) { - alternate = node.alternate; - if (node.childExpirationTime < renderExpirationTime) { - node.childExpirationTime = renderExpirationTime; - if ( - alternate !== null && - alternate.childExpirationTime < renderExpirationTime - ) { - alternate.childExpirationTime = renderExpirationTime; - } - } else if ( - alternate !== null && - alternate.childExpirationTime < renderExpirationTime - ) { - alternate.childExpirationTime = renderExpirationTime; - } else { - // Neither alternate was updated, which means the rest of the - // ancestor path already has sufficient priority. - break; - } - node = node.return; - } + + scheduleWorkOnParentPath(fiber.return, renderExpirationTime); // Mark the expiration time on the list, too. if (list.expirationTime < renderExpirationTime) { @@ -12708,6 +12925,29 @@ function propagateContextChange( } else if (fiber.tag === ContextProvider) { // Don't scan deeper if this is a matching provider nextFiber = fiber.type === workInProgress.type ? null : fiber.child; + } else if ( + enableSuspenseServerRenderer && + fiber.tag === DehydratedSuspenseComponent + ) { + // If a dehydrated suspense component is in this subtree, we don't know + // if it will have any context consumers in it. The best we can do is + // mark it as having updates on its children. + if (fiber.expirationTime < renderExpirationTime) { + fiber.expirationTime = renderExpirationTime; + } + var _alternate = fiber.alternate; + if ( + _alternate !== null && + _alternate.expirationTime < renderExpirationTime + ) { + _alternate.expirationTime = renderExpirationTime; + } + // This is intentionally passing this fiber as the parent + // because we want to schedule this fiber as having work + // on its children. We'll use the childExpirationTime on + // this fiber to indicate that a context has changed. + scheduleWorkOnParentPath(fiber, renderExpirationTime); + nextFiber = fiber.sibling; } else { // Traverse down. nextFiber = fiber.child; @@ -13984,7 +14224,12 @@ function completeWork(current, workInProgress, renderExpirationTime) { var nextDidTimeout = nextState !== null; var prevDidTimeout = current !== null && current.memoizedState !== null; - if (current !== null && !nextDidTimeout && prevDidTimeout) { + if (current === null) { + // In cases where we didn't find a suitable hydration boundary we never + // downgraded this to a DehydratedSuspenseComponent, but we still need to + // pop the hydration state since we might be inside the insertion tree. + popHydrationState(workInProgress); + } else if (!nextDidTimeout && prevDidTimeout) { // We just switched from the fallback to the normal children. Delete // the fallback. // TODO: Would it be better to store the fallback fragment on @@ -14038,6 +14283,29 @@ function completeWork(current, workInProgress, renderExpirationTime) { } break; } + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + if (current === null) { + var _wasHydrated2 = popHydrationState(workInProgress); + invariant( + _wasHydrated2, + "A dehydrated suspense component was completed without a hydrated node. " + + "This is probably a bug in React." + ); + skipPastDehydratedSuspenseInstance(workInProgress); + } else if ((workInProgress.effectTag & DidCapture) === NoEffect) { + // This boundary did not suspend so it's now hydrated. + // To handle any future suspense cases, we're going to now upgrade it + // to a Suspense component. We detach it from the existing current fiber. + current.alternate = null; + workInProgress.alternate = null; + workInProgress.tag = SuspenseComponent; + workInProgress.memoizedState = null; + workInProgress.stateNode = null; + } + } + break; + } default: invariant( false, @@ -14181,7 +14449,7 @@ var didWarnAboutUndefinedSnapshotBeforeUpdate = null; didWarnAboutUndefinedSnapshotBeforeUpdate = new Set(); } -var PossiblyWeakSet = typeof WeakSet === "function" ? WeakSet : Set; +var PossiblyWeakSet$1 = typeof WeakSet === "function" ? WeakSet : Set; function logError(boundary, errorInfo) { var source = errorInfo.source; @@ -14938,7 +15206,11 @@ function getHostSibling(fiber) { } node.sibling.return = node.return; node = node.sibling; - while (node.tag !== HostComponent && node.tag !== HostText) { + while ( + node.tag !== HostComponent && + node.tag !== HostText && + node.tag !== DehydratedSuspenseComponent + ) { // If it is not host node and, we might have a host node inside it. // Try to search down until we find one. if (node.effectTag & Placement) { @@ -15093,6 +15365,16 @@ function unmountHostComponents(current$$1) { removeChild(currentParent, node.stateNode); } // Don't visit children because we already visited them. + } else if ( + enableSuspenseServerRenderer && + node.tag === DehydratedSuspenseComponent + ) { + // Delete the dehydrated suspense boundary and all of its content. + if (currentParentIsContainer) { + clearSuspenseBoundaryFromContainer(currentParent, node.stateNode); + } else { + clearSuspenseBoundary(currentParent, node.stateNode); + } } else if (node.tag === HostPortal) { if (node.child !== null) { // When we go into a portal, it becomes the parent to remove from. @@ -15253,11 +15535,11 @@ function commitWork(current$$1, finishedWork) { finishedWork.updateQueue = null; var retryCache = finishedWork.stateNode; if (retryCache === null) { - retryCache = finishedWork.stateNode = new PossiblyWeakSet(); + retryCache = finishedWork.stateNode = new PossiblyWeakSet$1(); } thenables.forEach(function(thenable) { // Memoize using the boundary fiber to prevent redundant listeners. - var retry = retryTimedOutBoundary.bind(null, finishedWork, thenable); + var retry = resolveRetryThenable.bind(null, finishedWork, thenable); if (enableSchedulerTracing) { retry = tracing.unstable_wrap(retry); } @@ -15290,6 +15572,7 @@ function commitResetTextContent(current$$1) { resetTextContent(current$$1.stateNode); } +var PossiblyWeakSet = typeof WeakSet === "function" ? WeakSet : Set; var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map; function createRootErrorUpdate(fiber, errorInfo, expirationTime) { @@ -15355,6 +15638,39 @@ function createClassErrorUpdate(fiber, errorInfo, expirationTime) { return update; } +function attachPingListener(root, renderExpirationTime, thenable) { + // Attach a listener to the promise to "ping" the root and retry. But + // only if one does not already exist for the current render expiration + // time (which acts like a "thread ID" here). + var pingCache = root.pingCache; + var threadIDs = void 0; + if (pingCache === null) { + pingCache = root.pingCache = new PossiblyWeakMap(); + threadIDs = new Set(); + pingCache.set(thenable, threadIDs); + } else { + threadIDs = pingCache.get(thenable); + if (threadIDs === undefined) { + threadIDs = new Set(); + pingCache.set(thenable, threadIDs); + } + } + if (!threadIDs.has(renderExpirationTime)) { + // Memoize using the thread ID to prevent redundant listeners. + threadIDs.add(renderExpirationTime); + var ping = pingSuspendedRoot.bind( + null, + root, + thenable, + renderExpirationTime + ); + if (enableSchedulerTracing) { + ping = tracing.unstable_wrap(ping); + } + thenable.then(ping, ping); + } +} + function throwException( root, returnFiber, @@ -15409,6 +15725,9 @@ function throwException( } } } + // If there is a DehydratedSuspenseComponent we don't have to do anything because + // if something suspends inside it, we will simply leave that as dehydrated. It + // will never timeout. _workInProgress = _workInProgress.return; } while (_workInProgress !== null); @@ -15475,36 +15794,7 @@ function throwException( // Confirmed that the boundary is in a concurrent mode tree. Continue // with the normal suspend path. - // Attach a listener to the promise to "ping" the root and retry. But - // only if one does not already exist for the current render expiration - // time (which acts like a "thread ID" here). - var pingCache = root.pingCache; - var threadIDs = void 0; - if (pingCache === null) { - pingCache = root.pingCache = new PossiblyWeakMap(); - threadIDs = new Set(); - pingCache.set(thenable, threadIDs); - } else { - threadIDs = pingCache.get(thenable); - if (threadIDs === undefined) { - threadIDs = new Set(); - pingCache.set(thenable, threadIDs); - } - } - if (!threadIDs.has(renderExpirationTime)) { - // Memoize using the thread ID to prevent redundant listeners. - threadIDs.add(renderExpirationTime); - var ping = pingSuspendedRoot.bind( - null, - root, - thenable, - renderExpirationTime - ); - if (enableSchedulerTracing) { - ping = tracing.unstable_wrap(ping); - } - thenable.then(ping, ping); - } + attachPingListener(root, renderExpirationTime, thenable); var absoluteTimeoutMs = void 0; if (earliestTimeoutMs === -1) { @@ -15541,6 +15831,40 @@ function throwException( // whole tree. renderDidSuspend(root, absoluteTimeoutMs, renderExpirationTime); + _workInProgress.effectTag |= ShouldCapture; + _workInProgress.expirationTime = renderExpirationTime; + return; + } else if ( + enableSuspenseServerRenderer && + _workInProgress.tag === DehydratedSuspenseComponent + ) { + attachPingListener(root, renderExpirationTime, thenable); + + // Since we already have a current fiber, we can eagerly add a retry listener. + var retryCache = _workInProgress.memoizedState; + if (retryCache === null) { + retryCache = _workInProgress.memoizedState = new PossiblyWeakSet(); + var _current = _workInProgress.alternate; + invariant( + _current, + "A dehydrated suspense boundary must commit before trying to render. " + + "This is probably a bug in React." + ); + _current.memoizedState = retryCache; + } + // Memoize using the boundary fiber to prevent redundant listeners. + if (!retryCache.has(thenable)) { + retryCache.add(thenable); + var retry = resolveRetryThenable.bind( + null, + _workInProgress, + thenable + ); + if (enableSchedulerTracing) { + retry = tracing.unstable_wrap(retry); + } + thenable.then(retry, retry); + } _workInProgress.effectTag |= ShouldCapture; _workInProgress.expirationTime = renderExpirationTime; return; @@ -15639,6 +15963,7 @@ function unwindWork(workInProgress, renderExpirationTime) { return workInProgress; } case HostComponent: { + // TODO: popHydrationState popHostContext(workInProgress); return null; } @@ -15651,6 +15976,19 @@ function unwindWork(workInProgress, renderExpirationTime) { } return null; } + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + // TODO: popHydrationState + var _effectTag3 = workInProgress.effectTag; + if (_effectTag3 & ShouldCapture) { + workInProgress.effectTag = + (_effectTag3 & ~ShouldCapture) | DidCapture; + // Captured a suspense effect. Re-render the boundary. + return workInProgress; + } + } + return null; + } case HostPortal: popHostContainer(workInProgress); return null; @@ -16070,6 +16408,10 @@ function commitPassiveEffects(root, firstEffect) { if (rootExpirationTime !== NoWork) { requestWork(root, rootExpirationTime); } + // Flush any sync work that was scheduled by effects + if (!isBatchingUpdates && !isRendering) { + performSyncWork(); + } } function isAlreadyFailedLegacyErrorBoundary(instance) { @@ -16685,7 +17027,7 @@ function workLoop(isYieldy) { } } else { // Flush asynchronous work until there's a higher priority event - while (nextUnitOfWork !== null && !shouldYieldToRenderer()) { + while (nextUnitOfWork !== null && !shouldYield$$1()) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } } @@ -17144,17 +17486,7 @@ function pingSuspendedRoot(root, thenable, pingTime) { } } -function retryTimedOutBoundary(boundaryFiber, thenable) { - // The boundary fiber (a Suspense component) previously timed out and was - // rendered in its fallback state. One of the promises that suspended it has - // resolved, which means at least part of the tree was likely unblocked. Try - var retryCache = boundaryFiber.stateNode; - if (retryCache !== null) { - // The thenable resolved, so we no longer need to memoize, because it will - // never be thrown again. - retryCache.delete(thenable); - } - +function retryTimedOutBoundary(boundaryFiber) { var currentTime = requestCurrentTime(); var retryTime = computeExpirationForFiber(currentTime, boundaryFiber); var root = scheduleWorkToRoot(boundaryFiber, retryTime); @@ -17167,6 +17499,38 @@ function retryTimedOutBoundary(boundaryFiber, thenable) { } } +function resolveRetryThenable(boundaryFiber, thenable) { + // The boundary fiber (a Suspense component) previously timed out and was + // rendered in its fallback state. One of the promises that suspended it has + // resolved, which means at least part of the tree was likely unblocked. Try + var retryCache = void 0; + if (enableSuspenseServerRenderer) { + switch (boundaryFiber.tag) { + case SuspenseComponent: + retryCache = boundaryFiber.stateNode; + break; + case DehydratedSuspenseComponent: + retryCache = boundaryFiber.memoizedState; + break; + default: + invariant( + false, + "Pinged unknown suspense boundary type. " + + "This is probably a bug in React." + ); + } + } else { + retryCache = boundaryFiber.stateNode; + } + if (retryCache !== null) { + // The thenable resolved, so we no longer need to memoize, because it will + // never be thrown again. + retryCache.delete(thenable); + } + + retryTimedOutBoundary(boundaryFiber); +} + function scheduleWorkToRoot(fiber, expirationTime) { recordScheduleUpdate(); @@ -17265,8 +17629,10 @@ function warnIfNotCurrentlyBatchingInDev(fiber) { "});\n" + "/* assert on the output */\n\n" + "This ensures that you're testing the behavior the user would see in the browser." + - " Learn more at https://fb.me/react-wrap-tests-with-act", - getComponentName(fiber.type) + " Learn more at https://fb.me/react-wrap-tests-with-act" + + "%s", + getComponentName(fiber.type), + getStackByFiberInDevAndProd(fiber) ); } } @@ -17408,7 +17774,7 @@ function onSuspend( msUntilTimeout ) { root.expirationTime = rootExpirationTime; - if (msUntilTimeout === 0 && !shouldYieldToRenderer()) { + if (msUntilTimeout === 0 && !shouldYield$$1()) { // Don't wait an additional tick. Commit the tree immediately. root.pendingCommitExpirationTime = suspendedExpirationTime; root.finishedWork = finishedWork; @@ -17605,43 +17971,24 @@ function findHighestPriorityRoot() { nextFlushedExpirationTime = highestPriorityWork; } -// TODO: This wrapper exists because many of the older tests (the ones that use -// flushDeferredPri) rely on the number of times `shouldYield` is called. We -// should get rid of it. -var didYield = false; -function shouldYieldToRenderer() { - if (didYield) { - return true; - } - if (shouldYield$$1()) { - didYield = true; - return true; - } - return false; -} - -function performAsyncWork() { - try { - if (!shouldYieldToRenderer()) { - // The callback timed out. That means at least one update has expired. - // Iterate through the root schedule. If they contain expired work, set - // the next render expiration time to the current time. This has the effect - // of flushing all expired work in a single batch, instead of flushing each - // level one at a time. - if (firstScheduledRoot !== null) { - recomputeCurrentRendererTime(); - var root = firstScheduledRoot; - do { - didExpireAtExpirationTime(root, currentRendererTime); - // The root schedule is circular, so this is never null. - root = root.nextScheduledRoot; - } while (root !== firstScheduledRoot); - } +function performAsyncWork(didTimeout) { + if (didTimeout) { + // The callback timed out. That means at least one update has expired. + // Iterate through the root schedule. If they contain expired work, set + // the next render expiration time to the current time. This has the effect + // of flushing all expired work in a single batch, instead of flushing each + // level one at a time. + if (firstScheduledRoot !== null) { + recomputeCurrentRendererTime(); + var root = firstScheduledRoot; + do { + didExpireAtExpirationTime(root, currentRendererTime); + // The root schedule is circular, so this is never null. + root = root.nextScheduledRoot; + } while (root !== firstScheduledRoot); } - performWork(NoWork, true); - } finally { - didYield = false; } + performWork(NoWork, true); } function performSyncWork() { @@ -17667,7 +18014,7 @@ function performWork(minExpirationTime, isYieldy) { nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime && - !(didYield && currentRendererTime > nextFlushedExpirationTime) + !(shouldYield$$1() && currentRendererTime > nextFlushedExpirationTime) ) { performWorkOnRoot( nextFlushedRoot, @@ -17811,7 +18158,7 @@ function performWorkOnRoot(root, expirationTime, isYieldy) { if (_finishedWork !== null) { // We've completed the root. Check the if we should yield one more time // before committing. - if (!shouldYieldToRenderer()) { + if (!shouldYield$$1()) { // Still time left. Commit the root. completeRoot$1(root, _finishedWork, expirationTime); } else { @@ -18226,7 +18573,7 @@ function createPortal( // TODO: this is special because it gets imported during build. -var ReactVersion = "16.8.1"; +var ReactVersion = "16.8.3"; // Modules provided by RN: var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { @@ -18320,6 +18667,17 @@ var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { * Manipulation](docs/direct-manipulation.html)). */ setNativeProps: function(nativeProps) { + { + if (warnAboutDeprecatedSetNativeProps) { + warningWithoutStack$1( + false, + "Warning: Calling ref.setNativeProps(nativeProps) " + + "is deprecated and will be removed in a future release. " + + "Use the setNativeProps export from the react-native package instead." + + "\n\timport {setNativeProps} from 'react-native';\n\tsetNativeProps(ref, nativeProps);\n" + ); + } + } // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. @@ -18341,7 +18699,10 @@ var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { return; } - var viewConfig = maybeInstance.viewConfig; + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + var viewConfig = + maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; { warnForStyleProps(nativeProps, viewConfig.validAttributes); @@ -18354,7 +18715,7 @@ var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { // view invalidation for certain components (eg RCTTextInput) on iOS. if (updatePayload != null) { UIManager.updateView( - maybeInstance._nativeTag, + nativeTag, viewConfig.uiViewClassName, updatePayload ); @@ -18575,6 +18936,18 @@ var ReactNativeComponent = function(findNodeHandle, findHostInstance) { ReactNativeComponent.prototype.setNativeProps = function setNativeProps( nativeProps ) { + { + if (warnAboutDeprecatedSetNativeProps) { + warningWithoutStack$1( + false, + "Warning: Calling ref.setNativeProps(nativeProps) " + + "is deprecated and will be removed in a future release. " + + "Use the setNativeProps export from the react-native package instead." + + "\n\timport {setNativeProps} from 'react-native';\n\tsetNativeProps(ref, nativeProps);\n" + ); + } + } + // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. @@ -18596,6 +18969,8 @@ var ReactNativeComponent = function(findNodeHandle, findHostInstance) { return; } + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; var viewConfig = maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; @@ -18606,7 +18981,7 @@ var ReactNativeComponent = function(findNodeHandle, findHostInstance) { // view invalidation for certain components (eg RCTTextInput) on iOS. if (updatePayload != null) { UIManager.updateView( - maybeInstance._nativeTag, + nativeTag, viewConfig.uiViewClassName, updatePayload ); @@ -18734,6 +19109,36 @@ var getInspectorDataForViewTag = void 0; }; } +// Module provided by RN: +function setNativeProps(handle, nativeProps) { + if (handle._nativeTag == null) { + !(handle._nativeTag != null) + ? warningWithoutStack$1( + false, + "setNativeProps was called with a ref that isn't a " + + "native component. Use React.forwardRef to get access to the underlying native component" + ) + : void 0; + return; + } + + { + warnForStyleProps(nativeProps, handle.viewConfig.validAttributes); + } + + var updatePayload = create(nativeProps, handle.viewConfig.validAttributes); + // Avoid the overhead of bridge calls if there's no update. + // This is an expensive no-op for Android, and causes an unnecessary + // view invalidation for certain components (eg RCTTextInput) on iOS. + if (updatePayload != null) { + UIManager.updateView( + handle._nativeTag, + handle.viewConfig.uiViewClassName, + updatePayload + ); + } +} + var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; function findNodeHandle(componentOrHandle) { @@ -18801,6 +19206,8 @@ var ReactFabric = { findNodeHandle: findNodeHandle, + setNativeProps: setNativeProps, + render: function(element, containerTag, callback) { var root = roots.get(containerTag); diff --git a/Libraries/Renderer/oss/ReactFabric-prod.js b/Libraries/Renderer/oss/ReactFabric-prod.js index 6a2913b0b564c7..f77404ffb913fc 100644 --- a/Libraries/Renderer/oss/ReactFabric-prod.js +++ b/Libraries/Renderer/oss/ReactFabric-prod.js @@ -3188,6 +3188,8 @@ function updateReducer(reducer) { is(newState, hook.memoizedState) || (didReceiveUpdate = !0); hook.memoizedState = newState; hook.baseUpdate === queue.last && (hook.baseState = newState); + queue.eagerReducer = reducer; + queue.eagerState = newState; return [newState, _dispatch]; } } @@ -3508,6 +3510,8 @@ function tryHydrate(fiber, nextInstance) { (nextInstance = shim$1(nextInstance, fiber.pendingProps)), null !== nextInstance ? ((fiber.stateNode = nextInstance), !0) : !1 ); + case 13: + return !1; default: return !1; } @@ -4571,19 +4575,19 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { null !== dependency && dependency.expirationTime < renderExpirationTime && (dependency.expirationTime = renderExpirationTime); + dependency = renderExpirationTime; for (var node = oldValue.return; null !== node; ) { - dependency = node.alternate; - if (node.childExpirationTime < renderExpirationTime) - (node.childExpirationTime = renderExpirationTime), - null !== dependency && - dependency.childExpirationTime < - renderExpirationTime && - (dependency.childExpirationTime = renderExpirationTime); + var alternate = node.alternate; + if (node.childExpirationTime < dependency) + (node.childExpirationTime = dependency), + null !== alternate && + alternate.childExpirationTime < dependency && + (alternate.childExpirationTime = dependency); else if ( - null !== dependency && - dependency.childExpirationTime < renderExpirationTime + null !== alternate && + alternate.childExpirationTime < dependency ) - dependency.childExpirationTime = renderExpirationTime; + alternate.childExpirationTime = dependency; else break; node = node.return; } @@ -4713,12 +4717,11 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { renderExpirationTime ) ); - default: - invariant( - !1, - "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." - ); } + invariant( + !1, + "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." + ); } var valueCursor = { current: null }, currentlyRenderingFiber = null, @@ -5361,6 +5364,8 @@ function unwindWork(workInProgress) { workInProgress) : null ); + case 18: + return null; case 4: return popHostContainer(workInProgress), null; case 10: @@ -5686,6 +5691,7 @@ function commitPassiveEffects(root, firstEffect) { isRendering = previousIsRendering; previousIsRendering = root.expirationTime; 0 !== previousIsRendering && requestWork(root, previousIsRendering); + isBatchingUpdates || isRendering || performWork(1073741823, !1); } function flushPassiveEffects() { if (null !== passiveEffectCallbackHandle) { @@ -5960,6 +5966,8 @@ function completeUnitOfWork(workInProgress) { case 17: isContextProvider(current$$1.type) && popContext(current$$1); break; + case 18: + break; default: invariant( !1, @@ -6050,7 +6058,7 @@ function renderRoot(root$jscomp$0, isYieldy) { do { try { if (isYieldy) - for (; null !== nextUnitOfWork && !shouldYieldToRenderer(); ) + for (; null !== nextUnitOfWork && !(frameDeadline <= now$1()); ) nextUnitOfWork = performUnitOfWork(nextUnitOfWork); else for (; null !== nextUnitOfWork; ) @@ -6136,27 +6144,24 @@ function renderRoot(root$jscomp$0, isYieldy) { sourceFiber$jscomp$0.expirationTime = 1073741823; break a; } - sourceFiber$jscomp$0 = root.pingCache; - null === sourceFiber$jscomp$0 - ? ((sourceFiber$jscomp$0 = root.pingCache = new PossiblyWeakMap()), - (returnFiber$jscomp$0 = new Set()), - sourceFiber$jscomp$0.set(thenable, returnFiber$jscomp$0)) - : ((returnFiber$jscomp$0 = sourceFiber$jscomp$0.get( - thenable - )), - void 0 === returnFiber$jscomp$0 && - ((returnFiber$jscomp$0 = new Set()), - sourceFiber$jscomp$0.set( - thenable, - returnFiber$jscomp$0 - ))); - returnFiber$jscomp$0.has(returnFiber) || - (returnFiber$jscomp$0.add(returnFiber), + sourceFiber$jscomp$0 = root; + returnFiber$jscomp$0 = returnFiber; + var pingCache = sourceFiber$jscomp$0.pingCache; + null === pingCache + ? ((pingCache = sourceFiber$jscomp$0.pingCache = new PossiblyWeakMap()), + (current$$1 = new Set()), + pingCache.set(thenable, current$$1)) + : ((current$$1 = pingCache.get(thenable)), + void 0 === current$$1 && + ((current$$1 = new Set()), + pingCache.set(thenable, current$$1))); + current$$1.has(returnFiber$jscomp$0) || + (current$$1.add(returnFiber$jscomp$0), (sourceFiber$jscomp$0 = pingSuspendedRoot.bind( null, - root, + sourceFiber$jscomp$0, thenable, - returnFiber + returnFiber$jscomp$0 )), thenable.then(sourceFiber$jscomp$0, sourceFiber$jscomp$0)); -1 === earliestTimeoutMs @@ -6200,24 +6205,25 @@ function renderRoot(root$jscomp$0, isYieldy) { break a; case 1: if ( - ((thenable = value), - (earliestTimeoutMs = root.type), - (startTimeMs = root.stateNode), + ((earliestTimeoutMs = value), + (startTimeMs = root.type), + (sourceFiber$jscomp$0 = root.stateNode), 0 === (root.effectTag & 64) && ("function" === - typeof earliestTimeoutMs.getDerivedStateFromError || - (null !== startTimeMs && - "function" === typeof startTimeMs.componentDidCatch && + typeof startTimeMs.getDerivedStateFromError || + (null !== sourceFiber$jscomp$0 && + "function" === + typeof sourceFiber$jscomp$0.componentDidCatch && (null === legacyErrorBoundariesThatAlreadyFailed || !legacyErrorBoundariesThatAlreadyFailed.has( - startTimeMs + sourceFiber$jscomp$0 ))))) ) { root.effectTag |= 2048; root.expirationTime = returnFiber; returnFiber = createClassErrorUpdate( root, - thenable, + earliestTimeoutMs, returnFiber ); enqueueCapturedUpdate(root, returnFiber); @@ -6477,7 +6483,7 @@ function onSuspend( msUntilTimeout ) { root.expirationTime = rootExpirationTime; - 0 !== msUntilTimeout || shouldYieldToRenderer() + 0 !== msUntilTimeout || frameDeadline <= now$1() ? 0 < msUntilTimeout && (root.timeoutHandle = scheduleTimeout( onTimeout.bind(null, root, finishedWork, suspendedExpirationTime), @@ -6577,27 +6583,19 @@ function findHighestPriorityRoot() { nextFlushedRoot = highestPriorityRoot; nextFlushedExpirationTime = highestPriorityWork; } -var didYield = !1; -function shouldYieldToRenderer() { - return didYield ? !0 : frameDeadline <= now$1() ? (didYield = !0) : !1; -} -function performAsyncWork() { - try { - if (!shouldYieldToRenderer() && null !== firstScheduledRoot) { - recomputeCurrentRendererTime(); - var root = firstScheduledRoot; - do { - var expirationTime = root.expirationTime; - 0 !== expirationTime && - currentRendererTime <= expirationTime && - (root.nextExpirationTimeToWorkOn = currentRendererTime); - root = root.nextScheduledRoot; - } while (root !== firstScheduledRoot); - } - performWork(0, !0); - } finally { - didYield = !1; +function performAsyncWork(didTimeout) { + if (didTimeout && null !== firstScheduledRoot) { + recomputeCurrentRendererTime(); + didTimeout = firstScheduledRoot; + do { + var expirationTime = didTimeout.expirationTime; + 0 !== expirationTime && + currentRendererTime <= expirationTime && + (didTimeout.nextExpirationTimeToWorkOn = currentRendererTime); + didTimeout = didTimeout.nextScheduledRoot; + } while (didTimeout !== firstScheduledRoot); } + performWork(0, !0); } function performWork(minExpirationTime, isYieldy) { findHighestPriorityRoot(); @@ -6608,7 +6606,10 @@ function performWork(minExpirationTime, isYieldy) { null !== nextFlushedRoot && 0 !== nextFlushedExpirationTime && minExpirationTime <= nextFlushedExpirationTime && - !(didYield && currentRendererTime > nextFlushedExpirationTime); + !( + frameDeadline <= now$1() && + currentRendererTime > nextFlushedExpirationTime + ); ) performWorkOnRoot( @@ -6676,7 +6677,7 @@ function performWorkOnRoot(root, expirationTime, isYieldy) { renderRoot(root, isYieldy), (_finishedWork = root.finishedWork), null !== _finishedWork && - (shouldYieldToRenderer() + (frameDeadline <= now$1() ? (root.finishedWork = _finishedWork) : completeRoot$1(root, _finishedWork, expirationTime))); } else @@ -6917,18 +6918,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -6937,6 +6940,21 @@ var roots = new Map(), })(React.Component); })(findNodeHandle, findHostInstance), findNodeHandle: findNodeHandle, + setNativeProps: function(handle, nativeProps) { + null != handle._nativeTag && + ((nativeProps = diffProperties( + null, + emptyObject, + nativeProps, + handle.viewConfig.validAttributes + )), + null != nativeProps && + UIManager.updateView( + handle._nativeTag, + handle.viewConfig.uiViewClassName, + nativeProps + )); + }, render: function(element, containerTag, callback) { var root = roots.get(containerTag); if (!root) { @@ -7022,17 +7040,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = maybeInstance.viewConfig; + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = + maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -7068,7 +7089,7 @@ var roots = new Map(), findFiberByHostInstance: getInstanceFromInstance, getInspectorDataForViewTag: getInspectorDataForViewTag, bundleType: 0, - version: "16.8.1", + version: "16.8.3", rendererPackageName: "react-native-renderer" }); var ReactFabric$2 = { default: ReactFabric }, diff --git a/Libraries/Renderer/oss/ReactFabric-profiling.js b/Libraries/Renderer/oss/ReactFabric-profiling.js index 4e42e56b1bb6fc..ab75b165f51128 100644 --- a/Libraries/Renderer/oss/ReactFabric-profiling.js +++ b/Libraries/Renderer/oss/ReactFabric-profiling.js @@ -3208,6 +3208,8 @@ function updateReducer(reducer) { is(newState, hook.memoizedState) || (didReceiveUpdate = !0); hook.memoizedState = newState; hook.baseUpdate === queue.last && (hook.baseState = newState); + queue.eagerReducer = reducer; + queue.eagerState = newState; return [newState, _dispatch]; } } @@ -3538,6 +3540,8 @@ function tryHydrate(fiber, nextInstance) { (nextInstance = shim$1(nextInstance, fiber.pendingProps)), null !== nextInstance ? ((fiber.stateNode = nextInstance), !0) : !1 ); + case 13: + return !1; default: return !1; } @@ -4644,19 +4648,19 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { null !== dependency && dependency.expirationTime < renderExpirationTime && (dependency.expirationTime = renderExpirationTime); + dependency = renderExpirationTime; for (var node = oldValue.return; null !== node; ) { - dependency = node.alternate; - if (node.childExpirationTime < renderExpirationTime) - (node.childExpirationTime = renderExpirationTime), - null !== dependency && - dependency.childExpirationTime < - renderExpirationTime && - (dependency.childExpirationTime = renderExpirationTime); + var alternate = node.alternate; + if (node.childExpirationTime < dependency) + (node.childExpirationTime = dependency), + null !== alternate && + alternate.childExpirationTime < dependency && + (alternate.childExpirationTime = dependency); else if ( - null !== dependency && - dependency.childExpirationTime < renderExpirationTime + null !== alternate && + alternate.childExpirationTime < dependency ) - dependency.childExpirationTime = renderExpirationTime; + alternate.childExpirationTime = dependency; else break; node = node.return; } @@ -4786,12 +4790,11 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { renderExpirationTime ) ); - default: - invariant( - !1, - "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." - ); } + invariant( + !1, + "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." + ); } var valueCursor = { current: null }, currentlyRenderingFiber = null, @@ -5465,22 +5468,23 @@ function throwException( sourceFiber.expirationTime = 1073741823; return; } - sourceFiber = root.pingCache; - null === sourceFiber - ? ((sourceFiber = root.pingCache = new PossiblyWeakMap()), - (returnFiber = new Set()), - sourceFiber.set(thenable, returnFiber)) - : ((returnFiber = sourceFiber.get(thenable)), - void 0 === returnFiber && - ((returnFiber = new Set()), - sourceFiber.set(thenable, returnFiber))); - returnFiber.has(renderExpirationTime) || - (returnFiber.add(renderExpirationTime), + sourceFiber = root; + returnFiber = renderExpirationTime; + var pingCache = sourceFiber.pingCache; + null === pingCache + ? ((pingCache = sourceFiber.pingCache = new PossiblyWeakMap()), + (current$$1 = new Set()), + pingCache.set(thenable, current$$1)) + : ((current$$1 = pingCache.get(thenable)), + void 0 === current$$1 && + ((current$$1 = new Set()), pingCache.set(thenable, current$$1))); + current$$1.has(returnFiber) || + (current$$1.add(returnFiber), (sourceFiber = pingSuspendedRoot.bind( null, - root, + sourceFiber, thenable, - renderExpirationTime + returnFiber )), (sourceFiber = tracing.unstable_wrap(sourceFiber)), thenable.then(sourceFiber, sourceFiber)); @@ -5528,21 +5532,21 @@ function throwException( return; case 1: if ( - ((thenable = value), - (earliestTimeoutMs = root.type), - (startTimeMs = root.stateNode), + ((earliestTimeoutMs = value), + (startTimeMs = root.type), + (sourceFiber = root.stateNode), 0 === (root.effectTag & 64) && - ("function" === typeof earliestTimeoutMs.getDerivedStateFromError || - (null !== startTimeMs && - "function" === typeof startTimeMs.componentDidCatch && + ("function" === typeof startTimeMs.getDerivedStateFromError || + (null !== sourceFiber && + "function" === typeof sourceFiber.componentDidCatch && (null === legacyErrorBoundariesThatAlreadyFailed || - !legacyErrorBoundariesThatAlreadyFailed.has(startTimeMs))))) + !legacyErrorBoundariesThatAlreadyFailed.has(sourceFiber))))) ) { root.effectTag |= 2048; root.expirationTime = renderExpirationTime; renderExpirationTime = createClassErrorUpdate( root, - thenable, + earliestTimeoutMs, renderExpirationTime ); enqueueCapturedUpdate(root, renderExpirationTime); @@ -5583,6 +5587,8 @@ function unwindWork(workInProgress) { workInProgress) : null ); + case 18: + return null; case 4: return popHostContainer(workInProgress), null; case 10: @@ -5927,6 +5933,7 @@ function commitPassiveEffects(root, firstEffect) { isRendering = previousIsRendering; previousIsRendering = root.expirationTime; 0 !== previousIsRendering && requestWork(root, previousIsRendering); + isBatchingUpdates || isRendering || performWork(1073741823, !1); } function flushPassiveEffects() { if (null !== passiveEffectCallbackHandle) { @@ -6241,6 +6248,8 @@ function completeUnitOfWork(workInProgress) { case 17: isContextProvider(current$$1.type) && popContext(current$$1); break; + case 18: + break; default: invariant( !1, @@ -6387,7 +6396,7 @@ function renderRoot(root, isYieldy) { do { try { if (isYieldy) - for (; null !== nextUnitOfWork && !shouldYieldToRenderer(); ) + for (; null !== nextUnitOfWork && !(frameDeadline <= now$1()); ) nextUnitOfWork = performUnitOfWork(nextUnitOfWork); else for (; null !== nextUnitOfWork; ) @@ -6680,7 +6689,7 @@ function onSuspend( msUntilTimeout ) { root.expirationTime = rootExpirationTime; - 0 !== msUntilTimeout || shouldYieldToRenderer() + 0 !== msUntilTimeout || frameDeadline <= now$1() ? 0 < msUntilTimeout && (root.timeoutHandle = scheduleTimeout( onTimeout.bind(null, root, finishedWork, suspendedExpirationTime), @@ -6780,27 +6789,19 @@ function findHighestPriorityRoot() { nextFlushedRoot = highestPriorityRoot; nextFlushedExpirationTime = highestPriorityWork; } -var didYield = !1; -function shouldYieldToRenderer() { - return didYield ? !0 : frameDeadline <= now$1() ? (didYield = !0) : !1; -} -function performAsyncWork() { - try { - if (!shouldYieldToRenderer() && null !== firstScheduledRoot) { - recomputeCurrentRendererTime(); - var root = firstScheduledRoot; - do { - var expirationTime = root.expirationTime; - 0 !== expirationTime && - currentRendererTime <= expirationTime && - (root.nextExpirationTimeToWorkOn = currentRendererTime); - root = root.nextScheduledRoot; - } while (root !== firstScheduledRoot); - } - performWork(0, !0); - } finally { - didYield = !1; +function performAsyncWork(didTimeout) { + if (didTimeout && null !== firstScheduledRoot) { + recomputeCurrentRendererTime(); + didTimeout = firstScheduledRoot; + do { + var expirationTime = didTimeout.expirationTime; + 0 !== expirationTime && + currentRendererTime <= expirationTime && + (didTimeout.nextExpirationTimeToWorkOn = currentRendererTime); + didTimeout = didTimeout.nextScheduledRoot; + } while (didTimeout !== firstScheduledRoot); } + performWork(0, !0); } function performWork(minExpirationTime, isYieldy) { findHighestPriorityRoot(); @@ -6811,7 +6812,10 @@ function performWork(minExpirationTime, isYieldy) { null !== nextFlushedRoot && 0 !== nextFlushedExpirationTime && minExpirationTime <= nextFlushedExpirationTime && - !(didYield && currentRendererTime > nextFlushedExpirationTime); + !( + frameDeadline <= now$1() && + currentRendererTime > nextFlushedExpirationTime + ); ) performWorkOnRoot( @@ -6879,7 +6883,7 @@ function performWorkOnRoot(root, expirationTime, isYieldy) { renderRoot(root, isYieldy), (_finishedWork = root.finishedWork), null !== _finishedWork && - (shouldYieldToRenderer() + (frameDeadline <= now$1() ? (root.finishedWork = _finishedWork) : completeRoot$1(root, _finishedWork, expirationTime))); } else @@ -7120,18 +7124,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -7140,6 +7146,21 @@ var roots = new Map(), })(React.Component); })(findNodeHandle, findHostInstance), findNodeHandle: findNodeHandle, + setNativeProps: function(handle, nativeProps) { + null != handle._nativeTag && + ((nativeProps = diffProperties( + null, + emptyObject, + nativeProps, + handle.viewConfig.validAttributes + )), + null != nativeProps && + UIManager.updateView( + handle._nativeTag, + handle.viewConfig.uiViewClassName, + nativeProps + )); + }, render: function(element, containerTag, callback) { var root = roots.get(containerTag); if (!root) { @@ -7230,17 +7251,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = maybeInstance.viewConfig; + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = + maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -7276,7 +7300,7 @@ var roots = new Map(), findFiberByHostInstance: getInstanceFromInstance, getInspectorDataForViewTag: getInspectorDataForViewTag, bundleType: 0, - version: "16.8.1", + version: "16.8.3", rendererPackageName: "react-native-renderer" }); var ReactFabric$2 = { default: ReactFabric }, diff --git a/Libraries/Renderer/oss/ReactNativeRenderer-dev.js b/Libraries/Renderer/oss/ReactNativeRenderer-dev.js index 5dad5a29b88266..8afedd6a192196 100644 --- a/Libraries/Renderer/oss/ReactNativeRenderer-dev.js +++ b/Libraries/Renderer/oss/ReactNativeRenderer-dev.js @@ -1090,6 +1090,7 @@ var MemoComponent = 14; var SimpleMemoComponent = 15; var LazyComponent = 16; var IncompleteClassComponent = 17; +var DehydratedSuspenseComponent = 18; function getParent(inst) { do { @@ -3724,6 +3725,19 @@ function warnForStyleProps(props, validAttributes) { } } +var debugRenderPhaseSideEffects = false; +var debugRenderPhaseSideEffectsForStrictMode = false; +var enableUserTimingAPI = true; +var replayFailedUnitOfWorkWithInvokeGuardedCallback = true; +var warnAboutDeprecatedLifecycles = false; +var enableProfilerTimer = true; +var enableSchedulerTracing = true; +var enableSuspenseServerRenderer = false; + +var warnAboutDeprecatedSetNativeProps = false; + +// Only used in www builds. + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); @@ -3789,6 +3803,15 @@ var ReactNativeFiberHostComponent = (function() { nativeProps ) { { + if (warnAboutDeprecatedSetNativeProps) { + warningWithoutStack$1( + false, + "Warning: Calling ref.setNativeProps(nativeProps) " + + "is deprecated and will be removed in a future release. " + + "Use the setNativeProps export from the react-native package instead." + + "\n\timport {setNativeProps} from 'react-native';\n\tsetNativeProps(ref, nativeProps);\n" + ); + } warnForStyleProps(nativeProps, this.viewConfig.validAttributes); } @@ -3892,21 +3915,31 @@ function shim$1() { } // Hydration (when unsupported) + var supportsHydration = false; var canHydrateInstance = shim$1; var canHydrateTextInstance = shim$1; +var canHydrateSuspenseInstance = shim$1; +var isSuspenseInstancePending = shim$1; +var isSuspenseInstanceFallback = shim$1; +var registerSuspenseInstanceRetry = shim$1; var getNextHydratableSibling = shim$1; var getFirstHydratableChild = shim$1; var hydrateInstance = shim$1; var hydrateTextInstance = shim$1; +var getNextHydratableInstanceAfterSuspenseInstance = shim$1; +var clearSuspenseBoundary = shim$1; +var clearSuspenseBoundaryFromContainer = shim$1; var didNotMatchHydratedContainerTextInstance = shim$1; var didNotMatchHydratedTextInstance = shim$1; var didNotHydrateContainerInstance = shim$1; var didNotHydrateInstance = shim$1; var didNotFindHydratableContainerInstance = shim$1; var didNotFindHydratableContainerTextInstance = shim$1; +var didNotFindHydratableContainerSuspenseInstance = shim$1; var didNotFindHydratableInstance = shim$1; var didNotFindHydratableTextInstance = shim$1; +var didNotFindHydratableSuspenseInstance = shim$1; // Modules provided by RN: // Unused @@ -4412,16 +4445,6 @@ function setCurrentPhase(lifeCyclePhase) { } } -var debugRenderPhaseSideEffects = false; -var debugRenderPhaseSideEffectsForStrictMode = false; -var enableUserTimingAPI = true; -var replayFailedUnitOfWorkWithInvokeGuardedCallback = true; -var warnAboutDeprecatedLifecycles = false; -var enableProfilerTimer = true; -var enableSchedulerTracing = true; - -// Only used in www builds. - // Prefix measurements so that it's possible to filter them. // Longer prefixes are hard to read in DevTools. var reactEmoji = "\u269B"; @@ -4693,7 +4716,8 @@ function stopFailedWorkTimer(fiber) { } fiber._debugIsCurrentlyTiming = false; var warning = - fiber.tag === SuspenseComponent + fiber.tag === SuspenseComponent || + fiber.tag === DehydratedSuspenseComponent ? "Rendering was suspended" : "An error was thrown inside this error boundary"; endFiberMark(fiber, null, warning); @@ -9568,17 +9592,6 @@ function mountWorkInProgressHook() { { hook._debugType = currentHookNameInDev; - if ( - currentlyRenderingFiber$1 !== null && - currentlyRenderingFiber$1.alternate !== null - ) { - warning$1( - false, - "%s: Rendered more hooks than during the previous render. This is " + - "not currently supported and may lead to unexpected behavior.", - getComponentName(currentlyRenderingFiber$1.type) - ); - } } if (workInProgressHook === null) { // This is the first hook in the list @@ -9722,7 +9735,6 @@ function updateReducer(reducer, initialArg, init) { } hook.memoizedState = newState; - // Don't persist the state accumlated from the render phase updates to // the base state unless the queue is empty. // TODO: Not sure if this is the desired semantics, but it's what we @@ -9731,6 +9743,9 @@ function updateReducer(reducer, initialArg, init) { hook.baseState = newState; } + queue.eagerReducer = reducer; + queue.eagerState = newState; + return [newState, _dispatch]; } } @@ -10593,6 +10608,18 @@ function enterHydrationState(fiber) { return true; } +function reenterHydrationStateFromDehydratedSuspenseInstance(fiber) { + if (!supportsHydration) { + return false; + } + + var suspenseInstance = fiber.stateNode; + nextHydratableInstance = getNextHydratableSibling(suspenseInstance); + popToNextHostParent(fiber); + isHydrating = true; + return true; +} + function deleteHydratableInstance(returnFiber, instance) { { switch (returnFiber.tag) { @@ -10647,6 +10674,9 @@ function insertNonHydratedInstance(returnFiber, fiber) { var text = fiber.pendingProps; didNotFindHydratableContainerTextInstance(parentContainer, text); break; + case SuspenseComponent: + didNotFindHydratableContainerSuspenseInstance(parentContainer); + break; } break; } @@ -10675,6 +10705,13 @@ function insertNonHydratedInstance(returnFiber, fiber) { _text ); break; + case SuspenseComponent: + didNotFindHydratableSuspenseInstance( + parentType, + parentProps, + parentInstance + ); + break; } break; } @@ -10705,6 +10742,18 @@ function tryHydrate(fiber, nextInstance) { } return false; } + case SuspenseComponent: { + if (enableSuspenseServerRenderer) { + var suspenseInstance = canHydrateSuspenseInstance(nextInstance); + if (suspenseInstance !== null) { + // Downgrade the tag to a dehydrated component until we've hydrated it. + fiber.tag = DehydratedSuspenseComponent; + fiber.stateNode = suspenseInstance; + return true; + } + } + return false; + } default: return false; } @@ -10825,12 +10874,32 @@ function prepareToHydrateHostTextInstance(fiber) { return shouldUpdate; } +function skipPastDehydratedSuspenseInstance(fiber) { + if (!supportsHydration) { + invariant( + false, + "Expected skipPastDehydratedSuspenseInstance() to never be called. " + + "This error is likely caused by a bug in React. Please file an issue." + ); + } + var suspenseInstance = fiber.stateNode; + invariant( + suspenseInstance, + "Expected to have a hydrated suspense instance. " + + "This error is likely caused by a bug in React. Please file an issue." + ); + nextHydratableInstance = getNextHydratableInstanceAfterSuspenseInstance( + suspenseInstance + ); +} + function popToNextHostParent(fiber) { var parent = fiber.return; while ( parent !== null && parent.tag !== HostComponent && - parent.tag !== HostRoot + parent.tag !== HostRoot && + parent.tag !== DehydratedSuspenseComponent ) { parent = parent.return; } @@ -10982,6 +11051,10 @@ function updateForwardRef( nextProps, renderExpirationTime ) { + // TODO: current can be non-null here even if the component + // hasn't yet mounted. This happens after the first render suspends. + // We'll need to figure out if this is fine or can cause issues. + { if (workInProgress.type !== workInProgress.elementType) { // Lazy component props can't be validated in createElement @@ -11171,6 +11244,10 @@ function updateSimpleMemoComponent( updateExpirationTime, renderExpirationTime ) { + // TODO: current can be non-null here even if the component + // hasn't yet mounted. This happens when the inner render suspends. + // We'll need to figure out if this is fine or can cause issues. + { if (workInProgress.type !== workInProgress.elementType) { // Lazy component props can't be validated in createElement @@ -12124,6 +12201,22 @@ function updateSuspenseComponent( // children -- we skip over the primary children entirely. var next = void 0; if (current$$1 === null) { + if (enableSuspenseServerRenderer) { + // If we're currently hydrating, try to hydrate this boundary. + // But only if this has a fallback. + if (nextProps.fallback !== undefined) { + tryToClaimNextHydratableInstance(workInProgress); + // This could've changed the tag if this was a dehydrated suspense component. + if (workInProgress.tag === DehydratedSuspenseComponent) { + return updateDehydratedSuspenseComponent( + null, + workInProgress, + renderExpirationTime + ); + } + } + } + // This is the initial mount. This branch is pretty simple because there's // no previous state that needs to be preserved. if (nextDidTimeout) { @@ -12326,6 +12419,107 @@ function updateSuspenseComponent( return next; } +function updateDehydratedSuspenseComponent( + current$$1, + workInProgress, + renderExpirationTime +) { + if (current$$1 === null) { + // During the first pass, we'll bail out and not drill into the children. + // Instead, we'll leave the content in place and try to hydrate it later. + workInProgress.expirationTime = Never; + return null; + } + if ((workInProgress.effectTag & DidCapture) !== NoEffect) { + // Something suspended. Leave the existing children in place. + // TODO: In non-concurrent mode, should we commit the nodes we have hydrated so far? + workInProgress.child = null; + return null; + } + // We use childExpirationTime to indicate that a child might depend on context, so if + // any context has changed, we need to treat is as if the input might have changed. + var hasContextChanged$$1 = + current$$1.childExpirationTime >= renderExpirationTime; + var suspenseInstance = current$$1.stateNode; + if ( + didReceiveUpdate || + hasContextChanged$$1 || + isSuspenseInstanceFallback(suspenseInstance) + ) { + // This boundary has changed since the first render. This means that we are now unable to + // hydrate it. We might still be able to hydrate it using an earlier expiration time but + // during this render we can't. Instead, we're going to delete the whole subtree and + // instead inject a new real Suspense boundary to take its place, which may render content + // or fallback. The real Suspense boundary will suspend for a while so we have some time + // to ensure it can produce real content, but all state and pending events will be lost. + + // Alternatively, this boundary is in a permanent fallback state. In this case, we'll never + // get an update and we'll never be able to hydrate the final content. Let's just try the + // client side render instead. + + // Detach from the current dehydrated boundary. + current$$1.alternate = null; + workInProgress.alternate = null; + + // Insert a deletion in the effect list. + var returnFiber = workInProgress.return; + invariant( + returnFiber !== null, + "Suspense boundaries are never on the root. " + + "This is probably a bug in React." + ); + var last = returnFiber.lastEffect; + if (last !== null) { + last.nextEffect = current$$1; + returnFiber.lastEffect = current$$1; + } else { + returnFiber.firstEffect = returnFiber.lastEffect = current$$1; + } + current$$1.nextEffect = null; + current$$1.effectTag = Deletion; + + // Upgrade this work in progress to a real Suspense component. + workInProgress.tag = SuspenseComponent; + workInProgress.stateNode = null; + workInProgress.memoizedState = null; + // This is now an insertion. + workInProgress.effectTag |= Placement; + // Retry as a real Suspense component. + return updateSuspenseComponent(null, workInProgress, renderExpirationTime); + } else if (isSuspenseInstancePending(suspenseInstance)) { + // This component is still pending more data from the server, so we can't hydrate its + // content. We treat it as if this component suspended itself. It might seem as if + // we could just try to render it client-side instead. However, this will perform a + // lot of unnecessary work and is unlikely to complete since it often will suspend + // on missing data anyway. Additionally, the server might be able to render more + // than we can on the client yet. In that case we'd end up with more fallback states + // on the client than if we just leave it alone. If the server times out or errors + // these should update this boundary to the permanent Fallback state instead. + // Mark it as having captured (i.e. suspended). + workInProgress.effectTag |= DidCapture; + // Leave the children in place. I.e. empty. + workInProgress.child = null; + // Register a callback to retry this boundary once the server has sent the result. + registerSuspenseInstanceRetry( + suspenseInstance, + retryTimedOutBoundary.bind(null, current$$1) + ); + return null; + } else { + // This is the first attempt. + reenterHydrationStateFromDehydratedSuspenseInstance(workInProgress); + var nextProps = workInProgress.pendingProps; + var nextChildren = nextProps.children; + workInProgress.child = mountChildFibers( + workInProgress, + null, + nextChildren, + renderExpirationTime + ); + return workInProgress.child; + } +} + function updatePortalComponent( current$$1, workInProgress, @@ -12612,6 +12806,15 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { } break; } + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + // We know that this component will suspend again because if it has + // been unsuspended it has committed as a regular Suspense component. + // If it needs to be retried, it should have work scheduled on it. + workInProgress.effectTag |= DidCapture; + break; + } + } } return bailoutOnAlreadyFinishedWork( current$$1, @@ -12785,13 +12988,22 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { renderExpirationTime ); } - default: - invariant( - false, - "Unknown unit of work tag. This error is likely caused by a bug in " + - "React. Please file an issue." - ); + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + return updateDehydratedSuspenseComponent( + current$$1, + workInProgress, + renderExpirationTime + ); + } + break; + } } + invariant( + false, + "Unknown unit of work tag. This error is likely caused by a bug in " + + "React. Please file an issue." + ); } var valueCursor = createCursor(null); @@ -12910,6 +13122,34 @@ function calculateChangedBits(context, newValue, oldValue) { } } +function scheduleWorkOnParentPath(parent, renderExpirationTime) { + // Update the child expiration time of all the ancestors, including + // the alternates. + var node = parent; + while (node !== null) { + var alternate = node.alternate; + if (node.childExpirationTime < renderExpirationTime) { + node.childExpirationTime = renderExpirationTime; + if ( + alternate !== null && + alternate.childExpirationTime < renderExpirationTime + ) { + alternate.childExpirationTime = renderExpirationTime; + } + } else if ( + alternate !== null && + alternate.childExpirationTime < renderExpirationTime + ) { + alternate.childExpirationTime = renderExpirationTime; + } else { + // Neither alternate was updated, which means the rest of the + // ancestor path already has sufficient priority. + break; + } + node = node.return; + } +} + function propagateContextChange( workInProgress, context, @@ -12959,31 +13199,8 @@ function propagateContextChange( ) { alternate.expirationTime = renderExpirationTime; } - // Update the child expiration time of all the ancestors, including - // the alternates. - var node = fiber.return; - while (node !== null) { - alternate = node.alternate; - if (node.childExpirationTime < renderExpirationTime) { - node.childExpirationTime = renderExpirationTime; - if ( - alternate !== null && - alternate.childExpirationTime < renderExpirationTime - ) { - alternate.childExpirationTime = renderExpirationTime; - } - } else if ( - alternate !== null && - alternate.childExpirationTime < renderExpirationTime - ) { - alternate.childExpirationTime = renderExpirationTime; - } else { - // Neither alternate was updated, which means the rest of the - // ancestor path already has sufficient priority. - break; - } - node = node.return; - } + + scheduleWorkOnParentPath(fiber.return, renderExpirationTime); // Mark the expiration time on the list, too. if (list.expirationTime < renderExpirationTime) { @@ -12999,6 +13216,29 @@ function propagateContextChange( } else if (fiber.tag === ContextProvider) { // Don't scan deeper if this is a matching provider nextFiber = fiber.type === workInProgress.type ? null : fiber.child; + } else if ( + enableSuspenseServerRenderer && + fiber.tag === DehydratedSuspenseComponent + ) { + // If a dehydrated suspense component is in this subtree, we don't know + // if it will have any context consumers in it. The best we can do is + // mark it as having updates on its children. + if (fiber.expirationTime < renderExpirationTime) { + fiber.expirationTime = renderExpirationTime; + } + var _alternate = fiber.alternate; + if ( + _alternate !== null && + _alternate.expirationTime < renderExpirationTime + ) { + _alternate.expirationTime = renderExpirationTime; + } + // This is intentionally passing this fiber as the parent + // because we want to schedule this fiber as having work + // on its children. We'll use the childExpirationTime on + // this fiber to indicate that a context has changed. + scheduleWorkOnParentPath(fiber, renderExpirationTime); + nextFiber = fiber.sibling; } else { // Traverse down. nextFiber = fiber.child; @@ -14275,7 +14515,12 @@ function completeWork(current, workInProgress, renderExpirationTime) { var nextDidTimeout = nextState !== null; var prevDidTimeout = current !== null && current.memoizedState !== null; - if (current !== null && !nextDidTimeout && prevDidTimeout) { + if (current === null) { + // In cases where we didn't find a suitable hydration boundary we never + // downgraded this to a DehydratedSuspenseComponent, but we still need to + // pop the hydration state since we might be inside the insertion tree. + popHydrationState(workInProgress); + } else if (!nextDidTimeout && prevDidTimeout) { // We just switched from the fallback to the normal children. Delete // the fallback. // TODO: Would it be better to store the fallback fragment on @@ -14329,6 +14574,29 @@ function completeWork(current, workInProgress, renderExpirationTime) { } break; } + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + if (current === null) { + var _wasHydrated2 = popHydrationState(workInProgress); + invariant( + _wasHydrated2, + "A dehydrated suspense component was completed without a hydrated node. " + + "This is probably a bug in React." + ); + skipPastDehydratedSuspenseInstance(workInProgress); + } else if ((workInProgress.effectTag & DidCapture) === NoEffect) { + // This boundary did not suspend so it's now hydrated. + // To handle any future suspense cases, we're going to now upgrade it + // to a Suspense component. We detach it from the existing current fiber. + current.alternate = null; + workInProgress.alternate = null; + workInProgress.tag = SuspenseComponent; + workInProgress.memoizedState = null; + workInProgress.stateNode = null; + } + } + break; + } default: invariant( false, @@ -14472,7 +14740,7 @@ var didWarnAboutUndefinedSnapshotBeforeUpdate = null; didWarnAboutUndefinedSnapshotBeforeUpdate = new Set(); } -var PossiblyWeakSet = typeof WeakSet === "function" ? WeakSet : Set; +var PossiblyWeakSet$1 = typeof WeakSet === "function" ? WeakSet : Set; function logError(boundary, errorInfo) { var source = errorInfo.source; @@ -15230,7 +15498,11 @@ function getHostSibling(fiber) { } node.sibling.return = node.return; node = node.sibling; - while (node.tag !== HostComponent && node.tag !== HostText) { + while ( + node.tag !== HostComponent && + node.tag !== HostText && + node.tag !== DehydratedSuspenseComponent + ) { // If it is not host node and, we might have a host node inside it. // Try to search down until we find one. if (node.effectTag & Placement) { @@ -15383,6 +15655,16 @@ function unmountHostComponents(current$$1) { removeChild(currentParent, node.stateNode); } // Don't visit children because we already visited them. + } else if ( + enableSuspenseServerRenderer && + node.tag === DehydratedSuspenseComponent + ) { + // Delete the dehydrated suspense boundary and all of its content. + if (currentParentIsContainer) { + clearSuspenseBoundaryFromContainer(currentParent, node.stateNode); + } else { + clearSuspenseBoundary(currentParent, node.stateNode); + } } else if (node.tag === HostPortal) { if (node.child !== null) { // When we go into a portal, it becomes the parent to remove from. @@ -15543,11 +15825,11 @@ function commitWork(current$$1, finishedWork) { finishedWork.updateQueue = null; var retryCache = finishedWork.stateNode; if (retryCache === null) { - retryCache = finishedWork.stateNode = new PossiblyWeakSet(); + retryCache = finishedWork.stateNode = new PossiblyWeakSet$1(); } thenables.forEach(function(thenable) { // Memoize using the boundary fiber to prevent redundant listeners. - var retry = retryTimedOutBoundary.bind(null, finishedWork, thenable); + var retry = resolveRetryThenable.bind(null, finishedWork, thenable); if (enableSchedulerTracing) { retry = tracing.unstable_wrap(retry); } @@ -15580,6 +15862,7 @@ function commitResetTextContent(current$$1) { resetTextContent(current$$1.stateNode); } +var PossiblyWeakSet = typeof WeakSet === "function" ? WeakSet : Set; var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map; function createRootErrorUpdate(fiber, errorInfo, expirationTime) { @@ -15645,6 +15928,39 @@ function createClassErrorUpdate(fiber, errorInfo, expirationTime) { return update; } +function attachPingListener(root, renderExpirationTime, thenable) { + // Attach a listener to the promise to "ping" the root and retry. But + // only if one does not already exist for the current render expiration + // time (which acts like a "thread ID" here). + var pingCache = root.pingCache; + var threadIDs = void 0; + if (pingCache === null) { + pingCache = root.pingCache = new PossiblyWeakMap(); + threadIDs = new Set(); + pingCache.set(thenable, threadIDs); + } else { + threadIDs = pingCache.get(thenable); + if (threadIDs === undefined) { + threadIDs = new Set(); + pingCache.set(thenable, threadIDs); + } + } + if (!threadIDs.has(renderExpirationTime)) { + // Memoize using the thread ID to prevent redundant listeners. + threadIDs.add(renderExpirationTime); + var ping = pingSuspendedRoot.bind( + null, + root, + thenable, + renderExpirationTime + ); + if (enableSchedulerTracing) { + ping = tracing.unstable_wrap(ping); + } + thenable.then(ping, ping); + } +} + function throwException( root, returnFiber, @@ -15699,6 +16015,9 @@ function throwException( } } } + // If there is a DehydratedSuspenseComponent we don't have to do anything because + // if something suspends inside it, we will simply leave that as dehydrated. It + // will never timeout. _workInProgress = _workInProgress.return; } while (_workInProgress !== null); @@ -15765,36 +16084,7 @@ function throwException( // Confirmed that the boundary is in a concurrent mode tree. Continue // with the normal suspend path. - // Attach a listener to the promise to "ping" the root and retry. But - // only if one does not already exist for the current render expiration - // time (which acts like a "thread ID" here). - var pingCache = root.pingCache; - var threadIDs = void 0; - if (pingCache === null) { - pingCache = root.pingCache = new PossiblyWeakMap(); - threadIDs = new Set(); - pingCache.set(thenable, threadIDs); - } else { - threadIDs = pingCache.get(thenable); - if (threadIDs === undefined) { - threadIDs = new Set(); - pingCache.set(thenable, threadIDs); - } - } - if (!threadIDs.has(renderExpirationTime)) { - // Memoize using the thread ID to prevent redundant listeners. - threadIDs.add(renderExpirationTime); - var ping = pingSuspendedRoot.bind( - null, - root, - thenable, - renderExpirationTime - ); - if (enableSchedulerTracing) { - ping = tracing.unstable_wrap(ping); - } - thenable.then(ping, ping); - } + attachPingListener(root, renderExpirationTime, thenable); var absoluteTimeoutMs = void 0; if (earliestTimeoutMs === -1) { @@ -15831,6 +16121,40 @@ function throwException( // whole tree. renderDidSuspend(root, absoluteTimeoutMs, renderExpirationTime); + _workInProgress.effectTag |= ShouldCapture; + _workInProgress.expirationTime = renderExpirationTime; + return; + } else if ( + enableSuspenseServerRenderer && + _workInProgress.tag === DehydratedSuspenseComponent + ) { + attachPingListener(root, renderExpirationTime, thenable); + + // Since we already have a current fiber, we can eagerly add a retry listener. + var retryCache = _workInProgress.memoizedState; + if (retryCache === null) { + retryCache = _workInProgress.memoizedState = new PossiblyWeakSet(); + var _current = _workInProgress.alternate; + invariant( + _current, + "A dehydrated suspense boundary must commit before trying to render. " + + "This is probably a bug in React." + ); + _current.memoizedState = retryCache; + } + // Memoize using the boundary fiber to prevent redundant listeners. + if (!retryCache.has(thenable)) { + retryCache.add(thenable); + var retry = resolveRetryThenable.bind( + null, + _workInProgress, + thenable + ); + if (enableSchedulerTracing) { + retry = tracing.unstable_wrap(retry); + } + thenable.then(retry, retry); + } _workInProgress.effectTag |= ShouldCapture; _workInProgress.expirationTime = renderExpirationTime; return; @@ -15929,6 +16253,7 @@ function unwindWork(workInProgress, renderExpirationTime) { return workInProgress; } case HostComponent: { + // TODO: popHydrationState popHostContext(workInProgress); return null; } @@ -15941,6 +16266,19 @@ function unwindWork(workInProgress, renderExpirationTime) { } return null; } + case DehydratedSuspenseComponent: { + if (enableSuspenseServerRenderer) { + // TODO: popHydrationState + var _effectTag3 = workInProgress.effectTag; + if (_effectTag3 & ShouldCapture) { + workInProgress.effectTag = + (_effectTag3 & ~ShouldCapture) | DidCapture; + // Captured a suspense effect. Re-render the boundary. + return workInProgress; + } + } + return null; + } case HostPortal: popHostContainer(workInProgress); return null; @@ -16360,6 +16698,10 @@ function commitPassiveEffects(root, firstEffect) { if (rootExpirationTime !== NoWork) { requestWork(root, rootExpirationTime); } + // Flush any sync work that was scheduled by effects + if (!isBatchingUpdates && !isRendering) { + performSyncWork(); + } } function isAlreadyFailedLegacyErrorBoundary(instance) { @@ -16975,7 +17317,7 @@ function workLoop(isYieldy) { } } else { // Flush asynchronous work until there's a higher priority event - while (nextUnitOfWork !== null && !shouldYieldToRenderer()) { + while (nextUnitOfWork !== null && !shouldYield$$1()) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } } @@ -17434,17 +17776,7 @@ function pingSuspendedRoot(root, thenable, pingTime) { } } -function retryTimedOutBoundary(boundaryFiber, thenable) { - // The boundary fiber (a Suspense component) previously timed out and was - // rendered in its fallback state. One of the promises that suspended it has - // resolved, which means at least part of the tree was likely unblocked. Try - var retryCache = boundaryFiber.stateNode; - if (retryCache !== null) { - // The thenable resolved, so we no longer need to memoize, because it will - // never be thrown again. - retryCache.delete(thenable); - } - +function retryTimedOutBoundary(boundaryFiber) { var currentTime = requestCurrentTime(); var retryTime = computeExpirationForFiber(currentTime, boundaryFiber); var root = scheduleWorkToRoot(boundaryFiber, retryTime); @@ -17457,6 +17789,38 @@ function retryTimedOutBoundary(boundaryFiber, thenable) { } } +function resolveRetryThenable(boundaryFiber, thenable) { + // The boundary fiber (a Suspense component) previously timed out and was + // rendered in its fallback state. One of the promises that suspended it has + // resolved, which means at least part of the tree was likely unblocked. Try + var retryCache = void 0; + if (enableSuspenseServerRenderer) { + switch (boundaryFiber.tag) { + case SuspenseComponent: + retryCache = boundaryFiber.stateNode; + break; + case DehydratedSuspenseComponent: + retryCache = boundaryFiber.memoizedState; + break; + default: + invariant( + false, + "Pinged unknown suspense boundary type. " + + "This is probably a bug in React." + ); + } + } else { + retryCache = boundaryFiber.stateNode; + } + if (retryCache !== null) { + // The thenable resolved, so we no longer need to memoize, because it will + // never be thrown again. + retryCache.delete(thenable); + } + + retryTimedOutBoundary(boundaryFiber); +} + function scheduleWorkToRoot(fiber, expirationTime) { recordScheduleUpdate(); @@ -17555,8 +17919,10 @@ function warnIfNotCurrentlyBatchingInDev(fiber) { "});\n" + "/* assert on the output */\n\n" + "This ensures that you're testing the behavior the user would see in the browser." + - " Learn more at https://fb.me/react-wrap-tests-with-act", - getComponentName(fiber.type) + " Learn more at https://fb.me/react-wrap-tests-with-act" + + "%s", + getComponentName(fiber.type), + getStackByFiberInDevAndProd(fiber) ); } } @@ -17698,7 +18064,7 @@ function onSuspend( msUntilTimeout ) { root.expirationTime = rootExpirationTime; - if (msUntilTimeout === 0 && !shouldYieldToRenderer()) { + if (msUntilTimeout === 0 && !shouldYield$$1()) { // Don't wait an additional tick. Commit the tree immediately. root.pendingCommitExpirationTime = suspendedExpirationTime; root.finishedWork = finishedWork; @@ -17895,43 +18261,24 @@ function findHighestPriorityRoot() { nextFlushedExpirationTime = highestPriorityWork; } -// TODO: This wrapper exists because many of the older tests (the ones that use -// flushDeferredPri) rely on the number of times `shouldYield` is called. We -// should get rid of it. -var didYield = false; -function shouldYieldToRenderer() { - if (didYield) { - return true; - } - if (shouldYield$$1()) { - didYield = true; - return true; - } - return false; -} - -function performAsyncWork() { - try { - if (!shouldYieldToRenderer()) { - // The callback timed out. That means at least one update has expired. - // Iterate through the root schedule. If they contain expired work, set - // the next render expiration time to the current time. This has the effect - // of flushing all expired work in a single batch, instead of flushing each - // level one at a time. - if (firstScheduledRoot !== null) { - recomputeCurrentRendererTime(); - var root = firstScheduledRoot; - do { - didExpireAtExpirationTime(root, currentRendererTime); - // The root schedule is circular, so this is never null. - root = root.nextScheduledRoot; - } while (root !== firstScheduledRoot); - } +function performAsyncWork(didTimeout) { + if (didTimeout) { + // The callback timed out. That means at least one update has expired. + // Iterate through the root schedule. If they contain expired work, set + // the next render expiration time to the current time. This has the effect + // of flushing all expired work in a single batch, instead of flushing each + // level one at a time. + if (firstScheduledRoot !== null) { + recomputeCurrentRendererTime(); + var root = firstScheduledRoot; + do { + didExpireAtExpirationTime(root, currentRendererTime); + // The root schedule is circular, so this is never null. + root = root.nextScheduledRoot; + } while (root !== firstScheduledRoot); } - performWork(NoWork, true); - } finally { - didYield = false; } + performWork(NoWork, true); } function performSyncWork() { @@ -17957,7 +18304,7 @@ function performWork(minExpirationTime, isYieldy) { nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime && - !(didYield && currentRendererTime > nextFlushedExpirationTime) + !(shouldYield$$1() && currentRendererTime > nextFlushedExpirationTime) ) { performWorkOnRoot( nextFlushedRoot, @@ -18101,7 +18448,7 @@ function performWorkOnRoot(root, expirationTime, isYieldy) { if (_finishedWork !== null) { // We've completed the root. Check the if we should yield one more time // before committing. - if (!shouldYieldToRenderer()) { + if (!shouldYield$$1()) { // Still time left. Commit the root. completeRoot(root, _finishedWork, expirationTime); } else { @@ -18516,7 +18863,7 @@ function createPortal( // TODO: this is special because it gets imported during build. -var ReactVersion = "16.8.1"; +var ReactVersion = "16.8.3"; // Modules provided by RN: var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { @@ -18610,6 +18957,17 @@ var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { * Manipulation](docs/direct-manipulation.html)). */ setNativeProps: function(nativeProps) { + { + if (warnAboutDeprecatedSetNativeProps) { + warningWithoutStack$1( + false, + "Warning: Calling ref.setNativeProps(nativeProps) " + + "is deprecated and will be removed in a future release. " + + "Use the setNativeProps export from the react-native package instead." + + "\n\timport {setNativeProps} from 'react-native';\n\tsetNativeProps(ref, nativeProps);\n" + ); + } + } // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. @@ -18631,7 +18989,10 @@ var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { return; } - var viewConfig = maybeInstance.viewConfig; + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + var viewConfig = + maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; { warnForStyleProps(nativeProps, viewConfig.validAttributes); @@ -18644,7 +19005,7 @@ var NativeMethodsMixin = function(findNodeHandle, findHostInstance) { // view invalidation for certain components (eg RCTTextInput) on iOS. if (updatePayload != null) { UIManager.updateView( - maybeInstance._nativeTag, + nativeTag, viewConfig.uiViewClassName, updatePayload ); @@ -18865,6 +19226,18 @@ var ReactNativeComponent = function(findNodeHandle, findHostInstance) { ReactNativeComponent.prototype.setNativeProps = function setNativeProps( nativeProps ) { + { + if (warnAboutDeprecatedSetNativeProps) { + warningWithoutStack$1( + false, + "Warning: Calling ref.setNativeProps(nativeProps) " + + "is deprecated and will be removed in a future release. " + + "Use the setNativeProps export from the react-native package instead." + + "\n\timport {setNativeProps} from 'react-native';\n\tsetNativeProps(ref, nativeProps);\n" + ); + } + } + // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. @@ -18886,6 +19259,8 @@ var ReactNativeComponent = function(findNodeHandle, findHostInstance) { return; } + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; var viewConfig = maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; @@ -18896,7 +19271,7 @@ var ReactNativeComponent = function(findNodeHandle, findHostInstance) { // view invalidation for certain components (eg RCTTextInput) on iOS. if (updatePayload != null) { UIManager.updateView( - maybeInstance._nativeTag, + nativeTag, viewConfig.uiViewClassName, updatePayload ); @@ -19018,6 +19393,36 @@ var getInspectorDataForViewTag = void 0; }; } +// Module provided by RN: +function setNativeProps(handle, nativeProps) { + if (handle._nativeTag == null) { + !(handle._nativeTag != null) + ? warningWithoutStack$1( + false, + "setNativeProps was called with a ref that isn't a " + + "native component. Use React.forwardRef to get access to the underlying native component" + ) + : void 0; + return; + } + + { + warnForStyleProps(nativeProps, handle.viewConfig.validAttributes); + } + + var updatePayload = create(nativeProps, handle.viewConfig.validAttributes); + // Avoid the overhead of bridge calls if there's no update. + // This is an expensive no-op for Android, and causes an unnecessary + // view invalidation for certain components (eg RCTTextInput) on iOS. + if (updatePayload != null) { + UIManager.updateView( + handle._nativeTag, + handle.viewConfig.uiViewClassName, + updatePayload + ); + } +} + // TODO: direct imports like some-package/src/* are bad. Fix me. // Module provided by RN: var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; @@ -19093,6 +19498,8 @@ var ReactNativeRenderer = { findNodeHandle: findNodeHandle, + setNativeProps: setNativeProps, + render: function(element, containerTag, callback) { var root = roots.get(containerTag); diff --git a/Libraries/Renderer/oss/ReactNativeRenderer-prod.js b/Libraries/Renderer/oss/ReactNativeRenderer-prod.js index a9a20a350ad2f6..ef16155a0862cb 100644 --- a/Libraries/Renderer/oss/ReactNativeRenderer-prod.js +++ b/Libraries/Renderer/oss/ReactNativeRenderer-prod.js @@ -3224,6 +3224,8 @@ function updateReducer(reducer) { is(newState, hook.memoizedState) || (didReceiveUpdate = !0); hook.memoizedState = newState; hook.baseUpdate === queue.last && (hook.baseState = newState); + queue.eagerReducer = reducer; + queue.eagerState = newState; return [newState, _dispatch]; } } @@ -3544,6 +3546,8 @@ function tryHydrate(fiber, nextInstance) { (nextInstance = shim$1(nextInstance, fiber.pendingProps)), null !== nextInstance ? ((fiber.stateNode = nextInstance), !0) : !1 ); + case 13: + return !1; default: return !1; } @@ -4607,19 +4611,19 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { null !== dependency && dependency.expirationTime < renderExpirationTime && (dependency.expirationTime = renderExpirationTime); + dependency = renderExpirationTime; for (var node = oldValue.return; null !== node; ) { - dependency = node.alternate; - if (node.childExpirationTime < renderExpirationTime) - (node.childExpirationTime = renderExpirationTime), - null !== dependency && - dependency.childExpirationTime < - renderExpirationTime && - (dependency.childExpirationTime = renderExpirationTime); + var alternate = node.alternate; + if (node.childExpirationTime < dependency) + (node.childExpirationTime = dependency), + null !== alternate && + alternate.childExpirationTime < dependency && + (alternate.childExpirationTime = dependency); else if ( - null !== dependency && - dependency.childExpirationTime < renderExpirationTime + null !== alternate && + alternate.childExpirationTime < dependency ) - dependency.childExpirationTime = renderExpirationTime; + alternate.childExpirationTime = dependency; else break; node = node.return; } @@ -4749,12 +4753,11 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { renderExpirationTime ) ); - default: - invariant( - !1, - "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." - ); } + invariant( + !1, + "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." + ); } var valueCursor = { current: null }, currentlyRenderingFiber = null, @@ -5091,7 +5094,7 @@ function logCapturedError(capturedError) { : Error("Unspecified error at:" + componentStack); ExceptionsManager.handleException(error, !1); } -var PossiblyWeakSet = "function" === typeof WeakSet ? WeakSet : Set; +var PossiblyWeakSet$1 = "function" === typeof WeakSet ? WeakSet : Set; function logError(boundary, errorInfo) { var source = errorInfo.source, stack = errorInfo.stack; @@ -5304,7 +5307,7 @@ function commitPlacement(finishedWork) { parentFiber.sibling.return = parentFiber.return; for ( parentFiber = parentFiber.sibling; - 5 !== parentFiber.tag && 6 !== parentFiber.tag; + 5 !== parentFiber.tag && 6 !== parentFiber.tag && 18 !== parentFiber.tag; ) { if (parentFiber.effectTag & 2) continue b; @@ -5548,9 +5551,9 @@ function commitWork(current$$1, finishedWork) { finishedWork.updateQueue = null; var retryCache = finishedWork.stateNode; null === retryCache && - (retryCache = finishedWork.stateNode = new PossiblyWeakSet()); + (retryCache = finishedWork.stateNode = new PossiblyWeakSet$1()); instance.forEach(function(thenable) { - var retry = retryTimedOutBoundary.bind(null, finishedWork, thenable); + var retry = resolveRetryThenable.bind(null, finishedWork, thenable); retryCache.has(thenable) || (retryCache.add(thenable), thenable.then(retry, retry)); }); @@ -5635,6 +5638,8 @@ function unwindWork(workInProgress) { workInProgress) : null ); + case 18: + return null; case 4: return popHostContainer(workInProgress), null; case 10: @@ -5890,6 +5895,7 @@ function commitPassiveEffects(root, firstEffect) { isRendering = previousIsRendering; previousIsRendering = root.expirationTime; 0 !== previousIsRendering && requestWork(root, previousIsRendering); + isBatchingUpdates || isRendering || performWork(1073741823, !1); } function flushPassiveEffects() { if (null !== passiveEffectCallbackHandle) { @@ -6167,6 +6173,8 @@ function completeUnitOfWork(workInProgress) { case 17: isContextProvider(current$$1.type) && popContext(current$$1); break; + case 18: + break; default: invariant( !1, @@ -6260,7 +6268,7 @@ function renderRoot(root$jscomp$0, isYieldy) { do { try { if (isYieldy) - for (; null !== nextUnitOfWork && !shouldYieldToRenderer(); ) + for (; null !== nextUnitOfWork && !(frameDeadline <= now$1()); ) nextUnitOfWork = performUnitOfWork(nextUnitOfWork); else for (; null !== nextUnitOfWork; ) @@ -6346,27 +6354,24 @@ function renderRoot(root$jscomp$0, isYieldy) { sourceFiber$jscomp$0.expirationTime = 1073741823; break a; } - sourceFiber$jscomp$0 = root.pingCache; - null === sourceFiber$jscomp$0 - ? ((sourceFiber$jscomp$0 = root.pingCache = new PossiblyWeakMap()), - (returnFiber$jscomp$0 = new Set()), - sourceFiber$jscomp$0.set(thenable, returnFiber$jscomp$0)) - : ((returnFiber$jscomp$0 = sourceFiber$jscomp$0.get( - thenable - )), - void 0 === returnFiber$jscomp$0 && - ((returnFiber$jscomp$0 = new Set()), - sourceFiber$jscomp$0.set( - thenable, - returnFiber$jscomp$0 - ))); - returnFiber$jscomp$0.has(returnFiber) || - (returnFiber$jscomp$0.add(returnFiber), + sourceFiber$jscomp$0 = root; + returnFiber$jscomp$0 = returnFiber; + var pingCache = sourceFiber$jscomp$0.pingCache; + null === pingCache + ? ((pingCache = sourceFiber$jscomp$0.pingCache = new PossiblyWeakMap()), + (current$$1 = new Set()), + pingCache.set(thenable, current$$1)) + : ((current$$1 = pingCache.get(thenable)), + void 0 === current$$1 && + ((current$$1 = new Set()), + pingCache.set(thenable, current$$1))); + current$$1.has(returnFiber$jscomp$0) || + (current$$1.add(returnFiber$jscomp$0), (sourceFiber$jscomp$0 = pingSuspendedRoot.bind( null, - root, + sourceFiber$jscomp$0, thenable, - returnFiber + returnFiber$jscomp$0 )), thenable.then(sourceFiber$jscomp$0, sourceFiber$jscomp$0)); -1 === earliestTimeoutMs @@ -6410,24 +6415,25 @@ function renderRoot(root$jscomp$0, isYieldy) { break a; case 1: if ( - ((thenable = value), - (earliestTimeoutMs = root.type), - (startTimeMs = root.stateNode), + ((earliestTimeoutMs = value), + (startTimeMs = root.type), + (sourceFiber$jscomp$0 = root.stateNode), 0 === (root.effectTag & 64) && ("function" === - typeof earliestTimeoutMs.getDerivedStateFromError || - (null !== startTimeMs && - "function" === typeof startTimeMs.componentDidCatch && + typeof startTimeMs.getDerivedStateFromError || + (null !== sourceFiber$jscomp$0 && + "function" === + typeof sourceFiber$jscomp$0.componentDidCatch && (null === legacyErrorBoundariesThatAlreadyFailed || !legacyErrorBoundariesThatAlreadyFailed.has( - startTimeMs + sourceFiber$jscomp$0 ))))) ) { root.effectTag |= 2048; root.expirationTime = returnFiber; returnFiber = createClassErrorUpdate( root, - thenable, + earliestTimeoutMs, returnFiber ); enqueueCapturedUpdate(root, returnFiber); @@ -6604,7 +6610,7 @@ function pingSuspendedRoot(root, thenable, pingTime) { 0 !== pingTime && requestWork(root, pingTime); } } -function retryTimedOutBoundary(boundaryFiber, thenable) { +function resolveRetryThenable(boundaryFiber, thenable) { var retryCache = boundaryFiber.stateNode; null !== retryCache && retryCache.delete(thenable); thenable = requestCurrentTime(); @@ -6701,7 +6707,7 @@ function onSuspend( msUntilTimeout ) { root.expirationTime = rootExpirationTime; - 0 !== msUntilTimeout || shouldYieldToRenderer() + 0 !== msUntilTimeout || frameDeadline <= now$1() ? 0 < msUntilTimeout && (root.timeoutHandle = scheduleTimeout( onTimeout.bind(null, root, finishedWork, suspendedExpirationTime), @@ -6801,27 +6807,19 @@ function findHighestPriorityRoot() { nextFlushedRoot = highestPriorityRoot; nextFlushedExpirationTime = highestPriorityWork; } -var didYield = !1; -function shouldYieldToRenderer() { - return didYield ? !0 : frameDeadline <= now$1() ? (didYield = !0) : !1; -} -function performAsyncWork() { - try { - if (!shouldYieldToRenderer() && null !== firstScheduledRoot) { - recomputeCurrentRendererTime(); - var root = firstScheduledRoot; - do { - var expirationTime = root.expirationTime; - 0 !== expirationTime && - currentRendererTime <= expirationTime && - (root.nextExpirationTimeToWorkOn = currentRendererTime); - root = root.nextScheduledRoot; - } while (root !== firstScheduledRoot); - } - performWork(0, !0); - } finally { - didYield = !1; +function performAsyncWork(didTimeout) { + if (didTimeout && null !== firstScheduledRoot) { + recomputeCurrentRendererTime(); + didTimeout = firstScheduledRoot; + do { + var expirationTime = didTimeout.expirationTime; + 0 !== expirationTime && + currentRendererTime <= expirationTime && + (didTimeout.nextExpirationTimeToWorkOn = currentRendererTime); + didTimeout = didTimeout.nextScheduledRoot; + } while (didTimeout !== firstScheduledRoot); } + performWork(0, !0); } function performWork(minExpirationTime, isYieldy) { findHighestPriorityRoot(); @@ -6832,7 +6830,10 @@ function performWork(minExpirationTime, isYieldy) { null !== nextFlushedRoot && 0 !== nextFlushedExpirationTime && minExpirationTime <= nextFlushedExpirationTime && - !(didYield && currentRendererTime > nextFlushedExpirationTime); + !( + frameDeadline <= now$1() && + currentRendererTime > nextFlushedExpirationTime + ); ) performWorkOnRoot( @@ -6900,7 +6901,7 @@ function performWorkOnRoot(root, expirationTime, isYieldy) { renderRoot(root, isYieldy), (_finishedWork = root.finishedWork), null !== _finishedWork && - (shouldYieldToRenderer() + (frameDeadline <= now$1() ? (root.finishedWork = _finishedWork) : completeRoot(root, _finishedWork, expirationTime))); } else @@ -7141,18 +7142,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -7161,6 +7164,21 @@ var roots = new Map(), })(React.Component); })(findNodeHandle, findHostInstance), findNodeHandle: findNodeHandle, + setNativeProps: function(handle, nativeProps) { + null != handle._nativeTag && + ((nativeProps = diffProperties( + null, + emptyObject, + nativeProps, + handle.viewConfig.validAttributes + )), + null != nativeProps && + UIManager.updateView( + handle._nativeTag, + handle.viewConfig.uiViewClassName, + nativeProps + )); + }, render: function(element, containerTag, callback) { var root = roots.get(containerTag); if (!root) { @@ -7251,17 +7269,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = maybeInstance.viewConfig; + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = + maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -7302,7 +7323,7 @@ var roots = new Map(), findFiberByHostInstance: getInstanceFromTag, getInspectorDataForViewTag: getInspectorDataForViewTag, bundleType: 0, - version: "16.8.1", + version: "16.8.3", rendererPackageName: "react-native-renderer" }); var ReactNativeRenderer$2 = { default: ReactNativeRenderer }, diff --git a/Libraries/Renderer/oss/ReactNativeRenderer-profiling.js b/Libraries/Renderer/oss/ReactNativeRenderer-profiling.js index b0636f842253b4..3490e98d5f3ebf 100644 --- a/Libraries/Renderer/oss/ReactNativeRenderer-profiling.js +++ b/Libraries/Renderer/oss/ReactNativeRenderer-profiling.js @@ -3244,6 +3244,8 @@ function updateReducer(reducer) { is(newState, hook.memoizedState) || (didReceiveUpdate = !0); hook.memoizedState = newState; hook.baseUpdate === queue.last && (hook.baseState = newState); + queue.eagerReducer = reducer; + queue.eagerState = newState; return [newState, _dispatch]; } } @@ -3574,6 +3576,8 @@ function tryHydrate(fiber, nextInstance) { (nextInstance = shim$1(nextInstance, fiber.pendingProps)), null !== nextInstance ? ((fiber.stateNode = nextInstance), !0) : !1 ); + case 13: + return !1; default: return !1; } @@ -4680,19 +4684,19 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { null !== dependency && dependency.expirationTime < renderExpirationTime && (dependency.expirationTime = renderExpirationTime); + dependency = renderExpirationTime; for (var node = oldValue.return; null !== node; ) { - dependency = node.alternate; - if (node.childExpirationTime < renderExpirationTime) - (node.childExpirationTime = renderExpirationTime), - null !== dependency && - dependency.childExpirationTime < - renderExpirationTime && - (dependency.childExpirationTime = renderExpirationTime); + var alternate = node.alternate; + if (node.childExpirationTime < dependency) + (node.childExpirationTime = dependency), + null !== alternate && + alternate.childExpirationTime < dependency && + (alternate.childExpirationTime = dependency); else if ( - null !== dependency && - dependency.childExpirationTime < renderExpirationTime + null !== alternate && + alternate.childExpirationTime < dependency ) - dependency.childExpirationTime = renderExpirationTime; + alternate.childExpirationTime = dependency; else break; node = node.return; } @@ -4822,12 +4826,11 @@ function beginWork(current$$1, workInProgress, renderExpirationTime) { renderExpirationTime ) ); - default: - invariant( - !1, - "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." - ); } + invariant( + !1, + "Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue." + ); } var valueCursor = { current: null }, currentlyRenderingFiber = null, @@ -5164,7 +5167,7 @@ function logCapturedError(capturedError) { : Error("Unspecified error at:" + componentStack); ExceptionsManager.handleException(error, !1); } -var PossiblyWeakSet = "function" === typeof WeakSet ? WeakSet : Set; +var PossiblyWeakSet$1 = "function" === typeof WeakSet ? WeakSet : Set; function logError(boundary, errorInfo) { var source = errorInfo.source, stack = errorInfo.stack; @@ -5377,7 +5380,7 @@ function commitPlacement(finishedWork) { parentFiber.sibling.return = parentFiber.return; for ( parentFiber = parentFiber.sibling; - 5 !== parentFiber.tag && 6 !== parentFiber.tag; + 5 !== parentFiber.tag && 6 !== parentFiber.tag && 18 !== parentFiber.tag; ) { if (parentFiber.effectTag & 2) continue b; @@ -5621,9 +5624,9 @@ function commitWork(current$$1, finishedWork) { finishedWork.updateQueue = null; var retryCache = finishedWork.stateNode; null === retryCache && - (retryCache = finishedWork.stateNode = new PossiblyWeakSet()); + (retryCache = finishedWork.stateNode = new PossiblyWeakSet$1()); instance.forEach(function(thenable) { - var retry = retryTimedOutBoundary.bind(null, finishedWork, thenable); + var retry = resolveRetryThenable.bind(null, finishedWork, thenable); retry = tracing.unstable_wrap(retry); retryCache.has(thenable) || (retryCache.add(thenable), thenable.then(retry, retry)); @@ -5740,22 +5743,23 @@ function throwException( sourceFiber.expirationTime = 1073741823; return; } - sourceFiber = root.pingCache; - null === sourceFiber - ? ((sourceFiber = root.pingCache = new PossiblyWeakMap()), - (returnFiber = new Set()), - sourceFiber.set(thenable, returnFiber)) - : ((returnFiber = sourceFiber.get(thenable)), - void 0 === returnFiber && - ((returnFiber = new Set()), - sourceFiber.set(thenable, returnFiber))); - returnFiber.has(renderExpirationTime) || - (returnFiber.add(renderExpirationTime), + sourceFiber = root; + returnFiber = renderExpirationTime; + var pingCache = sourceFiber.pingCache; + null === pingCache + ? ((pingCache = sourceFiber.pingCache = new PossiblyWeakMap()), + (current$$1 = new Set()), + pingCache.set(thenable, current$$1)) + : ((current$$1 = pingCache.get(thenable)), + void 0 === current$$1 && + ((current$$1 = new Set()), pingCache.set(thenable, current$$1))); + current$$1.has(returnFiber) || + (current$$1.add(returnFiber), (sourceFiber = pingSuspendedRoot.bind( null, - root, + sourceFiber, thenable, - renderExpirationTime + returnFiber )), (sourceFiber = tracing.unstable_wrap(sourceFiber)), thenable.then(sourceFiber, sourceFiber)); @@ -5803,21 +5807,21 @@ function throwException( return; case 1: if ( - ((thenable = value), - (earliestTimeoutMs = root.type), - (startTimeMs = root.stateNode), + ((earliestTimeoutMs = value), + (startTimeMs = root.type), + (sourceFiber = root.stateNode), 0 === (root.effectTag & 64) && - ("function" === typeof earliestTimeoutMs.getDerivedStateFromError || - (null !== startTimeMs && - "function" === typeof startTimeMs.componentDidCatch && + ("function" === typeof startTimeMs.getDerivedStateFromError || + (null !== sourceFiber && + "function" === typeof sourceFiber.componentDidCatch && (null === legacyErrorBoundariesThatAlreadyFailed || - !legacyErrorBoundariesThatAlreadyFailed.has(startTimeMs))))) + !legacyErrorBoundariesThatAlreadyFailed.has(sourceFiber))))) ) { root.effectTag |= 2048; root.expirationTime = renderExpirationTime; renderExpirationTime = createClassErrorUpdate( root, - thenable, + earliestTimeoutMs, renderExpirationTime ); enqueueCapturedUpdate(root, renderExpirationTime); @@ -5858,6 +5862,8 @@ function unwindWork(workInProgress) { workInProgress) : null ); + case 18: + return null; case 4: return popHostContainer(workInProgress), null; case 10: @@ -6132,6 +6138,7 @@ function commitPassiveEffects(root, firstEffect) { isRendering = previousIsRendering; previousIsRendering = root.expirationTime; 0 !== previousIsRendering && requestWork(root, previousIsRendering); + isBatchingUpdates || isRendering || performWork(1073741823, !1); } function flushPassiveEffects() { if (null !== passiveEffectCallbackHandle) { @@ -6453,6 +6460,8 @@ function completeUnitOfWork(workInProgress) { case 17: isContextProvider(current$$1.type) && popContext(current$$1); break; + case 18: + break; default: invariant( !1, @@ -6607,7 +6616,7 @@ function renderRoot(root, isYieldy) { do { try { if (isYieldy) - for (; null !== nextUnitOfWork && !shouldYieldToRenderer(); ) + for (; null !== nextUnitOfWork && !(frameDeadline <= now$1()); ) nextUnitOfWork = performUnitOfWork(nextUnitOfWork); else for (; null !== nextUnitOfWork; ) @@ -6788,7 +6797,7 @@ function pingSuspendedRoot(root, thenable, pingTime) { 0 !== pingTime && requestWork(root, pingTime); } } -function retryTimedOutBoundary(boundaryFiber, thenable) { +function resolveRetryThenable(boundaryFiber, thenable) { var retryCache = boundaryFiber.stateNode; null !== retryCache && retryCache.delete(thenable); thenable = requestCurrentTime(); @@ -6911,7 +6920,7 @@ function onSuspend( msUntilTimeout ) { root.expirationTime = rootExpirationTime; - 0 !== msUntilTimeout || shouldYieldToRenderer() + 0 !== msUntilTimeout || frameDeadline <= now$1() ? 0 < msUntilTimeout && (root.timeoutHandle = scheduleTimeout( onTimeout.bind(null, root, finishedWork, suspendedExpirationTime), @@ -7011,27 +7020,19 @@ function findHighestPriorityRoot() { nextFlushedRoot = highestPriorityRoot; nextFlushedExpirationTime = highestPriorityWork; } -var didYield = !1; -function shouldYieldToRenderer() { - return didYield ? !0 : frameDeadline <= now$1() ? (didYield = !0) : !1; -} -function performAsyncWork() { - try { - if (!shouldYieldToRenderer() && null !== firstScheduledRoot) { - recomputeCurrentRendererTime(); - var root = firstScheduledRoot; - do { - var expirationTime = root.expirationTime; - 0 !== expirationTime && - currentRendererTime <= expirationTime && - (root.nextExpirationTimeToWorkOn = currentRendererTime); - root = root.nextScheduledRoot; - } while (root !== firstScheduledRoot); - } - performWork(0, !0); - } finally { - didYield = !1; +function performAsyncWork(didTimeout) { + if (didTimeout && null !== firstScheduledRoot) { + recomputeCurrentRendererTime(); + didTimeout = firstScheduledRoot; + do { + var expirationTime = didTimeout.expirationTime; + 0 !== expirationTime && + currentRendererTime <= expirationTime && + (didTimeout.nextExpirationTimeToWorkOn = currentRendererTime); + didTimeout = didTimeout.nextScheduledRoot; + } while (didTimeout !== firstScheduledRoot); } + performWork(0, !0); } function performWork(minExpirationTime, isYieldy) { findHighestPriorityRoot(); @@ -7042,7 +7043,10 @@ function performWork(minExpirationTime, isYieldy) { null !== nextFlushedRoot && 0 !== nextFlushedExpirationTime && minExpirationTime <= nextFlushedExpirationTime && - !(didYield && currentRendererTime > nextFlushedExpirationTime); + !( + frameDeadline <= now$1() && + currentRendererTime > nextFlushedExpirationTime + ); ) performWorkOnRoot( @@ -7110,7 +7114,7 @@ function performWorkOnRoot(root, expirationTime, isYieldy) { renderRoot(root, isYieldy), (_finishedWork = root.finishedWork), null !== _finishedWork && - (shouldYieldToRenderer() + (frameDeadline <= now$1() ? (root.finishedWork = _finishedWork) : completeRoot(root, _finishedWork, expirationTime))); } else @@ -7351,18 +7355,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -7371,6 +7377,21 @@ var roots = new Map(), })(React.Component); })(findNodeHandle, findHostInstance), findNodeHandle: findNodeHandle, + setNativeProps: function(handle, nativeProps) { + null != handle._nativeTag && + ((nativeProps = diffProperties( + null, + emptyObject, + nativeProps, + handle.viewConfig.validAttributes + )), + null != nativeProps && + UIManager.updateView( + handle._nativeTag, + handle.viewConfig.uiViewClassName, + nativeProps + )); + }, render: function(element, containerTag, callback) { var root = roots.get(containerTag); if (!root) { @@ -7466,17 +7487,20 @@ var roots = new Map(), maybeInstance = findHostInstance(this); } catch (error) {} if (null != maybeInstance) { - var viewConfig = maybeInstance.viewConfig; + var nativeTag = + maybeInstance._nativeTag || maybeInstance.canonical._nativeTag; + maybeInstance = + maybeInstance.viewConfig || maybeInstance.canonical.viewConfig; nativeProps = diffProperties( null, emptyObject, nativeProps, - viewConfig.validAttributes + maybeInstance.validAttributes ); null != nativeProps && UIManager.updateView( - maybeInstance._nativeTag, - viewConfig.uiViewClassName, + nativeTag, + maybeInstance.uiViewClassName, nativeProps ); } @@ -7517,7 +7541,7 @@ var roots = new Map(), findFiberByHostInstance: getInstanceFromTag, getInspectorDataForViewTag: getInspectorDataForViewTag, bundleType: 0, - version: "16.8.1", + version: "16.8.3", rendererPackageName: "react-native-renderer" }); var ReactNativeRenderer$2 = { default: ReactNativeRenderer }, diff --git a/Libraries/Renderer/shims/ReactNativeTypes.js b/Libraries/Renderer/shims/ReactNativeTypes.js index f9058abc1e939a..1e145ed5e1d5ce 100644 --- a/Libraries/Renderer/shims/ReactNativeTypes.js +++ b/Libraries/Renderer/shims/ReactNativeTypes.js @@ -131,6 +131,7 @@ type SecretInternalsFabricType = { export type ReactNativeType = { NativeComponent: typeof ReactNativeComponent, findNodeHandle(componentOrHandle: any): ?number, + setNativeProps(handle: any, nativeProps: Object): void, render( element: React$Element, containerTag: any, @@ -146,6 +147,7 @@ export type ReactNativeType = { export type ReactFabricType = { NativeComponent: typeof ReactNativeComponent, findNodeHandle(componentOrHandle: any): ?number, + setNativeProps(handle: any, nativeProps: Object): void, render( element: React$Element, containerTag: any, diff --git a/Libraries/Text/RCTTextAttributes.h b/Libraries/Text/RCTTextAttributes.h index 01070aa4e2192c..d48b10b131b769 100644 --- a/Libraries/Text/RCTTextAttributes.h +++ b/Libraries/Text/RCTTextAttributes.h @@ -66,6 +66,11 @@ extern NSString *const RCTTextAttributesTagAttributeName; */ - (NSDictionary *)effectiveTextAttributes; +/** + * Constructed paragraph style. + */ +- (NSParagraphStyle *_Nullable)effectiveParagraphStyle; + /** * Constructed font. */ diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m index f1bf6beab67f1a..15b6851ed96310 100644 --- a/Libraries/Text/RCTTextAttributes.m +++ b/Libraries/Text/RCTTextAttributes.m @@ -79,6 +79,44 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes _textTransform = textAttributes->_textTransform != RCTTextTransformUndefined ? textAttributes->_textTransform : _textTransform; } +- (NSParagraphStyle *)effectiveParagraphStyle +{ + // Paragraph Style + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + BOOL isParagraphStyleUsed = NO; + if (_alignment != NSTextAlignmentNatural) { + NSTextAlignment alignment = _alignment; + if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { + if (alignment == NSTextAlignmentRight) { + alignment = NSTextAlignmentLeft; + } else if (alignment == NSTextAlignmentLeft) { + alignment = NSTextAlignmentRight; + } + } + + paragraphStyle.alignment = alignment; + isParagraphStyleUsed = YES; + } + + if (_baseWritingDirection != NSWritingDirectionNatural) { + paragraphStyle.baseWritingDirection = _baseWritingDirection; + isParagraphStyleUsed = YES; + } + + if (!isnan(_lineHeight)) { + CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier; + paragraphStyle.minimumLineHeight = lineHeight; + paragraphStyle.maximumLineHeight = lineHeight; + isParagraphStyleUsed = YES; + } + + if (isParagraphStyleUsed) { + return [paragraphStyle copy]; + } + + return nil; +} + - (NSDictionary *)effectiveTextAttributes { NSMutableDictionary *attributes = @@ -107,35 +145,8 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes } // Paragraph Style - NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; - BOOL isParagraphStyleUsed = NO; - if (_alignment != NSTextAlignmentNatural) { - NSTextAlignment alignment = _alignment; - if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { - if (alignment == NSTextAlignmentRight) { - alignment = NSTextAlignmentLeft; - } else if (alignment == NSTextAlignmentLeft) { - alignment = NSTextAlignmentRight; - } - } - - paragraphStyle.alignment = alignment; - isParagraphStyleUsed = YES; - } - - if (_baseWritingDirection != NSWritingDirectionNatural) { - paragraphStyle.baseWritingDirection = _baseWritingDirection; - isParagraphStyleUsed = YES; - } - - if (!isnan(_lineHeight)) { - CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier; - paragraphStyle.minimumLineHeight = lineHeight; - paragraphStyle.maximumLineHeight = lineHeight; - isParagraphStyleUsed = YES; - } - - if (isParagraphStyleUsed) { + NSParagraphStyle *paragraphStyle = [self effectiveParagraphStyle]; + if (paragraphStyle) { attributes[NSParagraphStyleAttributeName] = paragraphStyle; } @@ -252,6 +263,9 @@ - (RCTTextAttributes *)copyWithZone:(NSZone *)zone - (BOOL)isEqual:(RCTTextAttributes *)textAttributes { + if (!textAttributes) { + return NO; + } if (self == textAttributes) { return YES; } diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index c50638fc54aa3c..4ff8cc90ef42fc 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -11,6 +11,7 @@ #import #import "RCTBackedTextInputDelegateAdapter.h" +#import "RCTTextAttributes.h" @implementation RCTUITextView { @@ -19,6 +20,8 @@ @implementation RCTUITextView RCTBackedTextViewDelegateAdapter *_textInputDelegateAdapter; } +@synthesize reactTextAttributes = _reactTextAttributes; + static UIFont *defaultPlaceholderFont() { return [UIFont systemFontOfSize:17]; @@ -79,7 +82,7 @@ - (NSString *)accessibilityLabel - (void)setPlaceholder:(NSString *)placeholder { _placeholder = placeholder; - _placeholderView.text = _placeholder; + _placeholderView.attributedText = [[NSAttributedString alloc] initWithString:_placeholder ?: @"" attributes:[self placeholderEffectiveTextAttributes]]; } - (void)setPlaceholderColor:(UIColor *)placeholderColor @@ -88,6 +91,22 @@ - (void)setPlaceholderColor:(UIColor *)placeholderColor _placeholderView.textColor = _placeholderColor ?: defaultPlaceholderColor(); } +- (void)setReactTextAttributes:(RCTTextAttributes *)reactTextAttributes +{ + if ([reactTextAttributes isEqual:_reactTextAttributes]) { + return; + } + self.typingAttributes = reactTextAttributes.effectiveTextAttributes; + _reactTextAttributes = reactTextAttributes; + // Update placeholder text attributes + [self setPlaceholder:_placeholder]; +} + +- (RCTTextAttributes *)reactTextAttributes +{ + return _reactTextAttributes; +} + - (void)textDidChange { _textWasPasted = NO; @@ -166,7 +185,8 @@ - (CGSize)placeholderSize { UIEdgeInsets textContainerInset = self.textContainerInset; NSString *placeholder = self.placeholder ?: @""; - CGSize placeholderSize = [placeholder sizeWithAttributes:@{NSFontAttributeName: self.font ?: defaultPlaceholderFont()}]; + CGSize maxPlaceholderSize = CGSizeMake(UIEdgeInsetsInsetRect(self.bounds, textContainerInset).size.width, CGFLOAT_MAX); + CGSize placeholderSize = [placeholder boundingRectWithSize:maxPlaceholderSize options:NSStringDrawingUsesLineFragmentOrigin attributes:[self placeholderEffectiveTextAttributes] context:nil].size; placeholderSize = CGSizeMake(RCTCeilPixelValue(placeholderSize.width), RCTCeilPixelValue(placeholderSize.height)); placeholderSize.width += textContainerInset.left + textContainerInset.right; placeholderSize.height += textContainerInset.top + textContainerInset.bottom; @@ -177,7 +197,7 @@ - (CGSize)placeholderSize - (CGSize)contentSize { CGSize contentSize = super.contentSize; - CGSize placeholderSize = self.placeholderSize; + CGSize placeholderSize = _placeholderView.isHidden ? CGSizeZero : self.placeholderSize; // When a text input is empty, it actually displays a placehoder. // So, we have to consider `placeholderSize` as a minimum `contentSize`. // Returning size DOES contain `textContainerInset` (aka `padding`). @@ -205,37 +225,12 @@ - (CGSize)intrinsicContentSize - (CGSize)sizeThatFits:(CGSize)size { // Returned fitting size depends on text size and placeholder size. - CGSize textSize = [self fixedSizeThatFits:size]; + CGSize textSize = [super sizeThatFits:size]; CGSize placeholderSize = self.placeholderSize; // Returning size DOES contain `textContainerInset` (aka `padding`). return CGSizeMake(MAX(textSize.width, placeholderSize.width), MAX(textSize.height, placeholderSize.height)); } -- (CGSize)fixedSizeThatFits:(CGSize)size -{ - // UITextView on iOS 8 has a bug that automatically scrolls to the top - // when calling `sizeThatFits:`. Use a copy so that self is not screwed up. - static BOOL useCustomImplementation = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - useCustomImplementation = ![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9,0,0}]; - }); - - if (!useCustomImplementation) { - return [super sizeThatFits:size]; - } - - if (!_detachedTextView) { - _detachedTextView = [UITextView new]; - } - - _detachedTextView.attributedText = self.attributedText; - _detachedTextView.font = self.font; - _detachedTextView.textContainerInset = self.textContainerInset; - - return [_detachedTextView sizeThatFits:size]; -} - #pragma mark - Context Menu - (BOOL)canPerformAction:(SEL)action withSender:(id)sender @@ -255,6 +250,21 @@ - (void)invalidatePlaceholderVisibility _placeholderView.hidden = !isVisible; } +- (NSDictionary *)placeholderEffectiveTextAttributes +{ + NSMutableDictionary *effectiveTextAttributes = [NSMutableDictionary dictionaryWithDictionary:@{ + NSFontAttributeName: _reactTextAttributes.effectiveFont ?: defaultPlaceholderFont(), + NSForegroundColorAttributeName: self.placeholderColor ?: defaultPlaceholderColor(), + NSKernAttributeName:isnan(_reactTextAttributes.letterSpacing) ? @0 : @(_reactTextAttributes.letterSpacing) + }]; + NSParagraphStyle *paragraphStyle = [_reactTextAttributes effectiveParagraphStyle]; + if (paragraphStyle) { + effectiveTextAttributes[NSParagraphStyleAttributeName] = paragraphStyle; + } + + return [effectiveTextAttributes copy]; +} + #pragma mark - Utility Methods - (void)copyTextAttributesFrom:(NSAttributedString *)sourceString diff --git a/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h b/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h index 4beb21bc974c10..f711fe683edaec 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h @@ -27,6 +27,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)textInputDidChangeSelection; +@optional + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView; + @end NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m index 75421035523c78..fe5ee67647d950 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m +++ b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m @@ -213,6 +213,15 @@ - (void)textViewDidChangeSelection:(__unused UITextView *)textView [self textViewProbablyDidChangeSelection]; } +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + if ([_backedTextInputView.textInputDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { + [_backedTextInputView.textInputDelegate scrollViewDidScroll:scrollView]; + } +} + #pragma mark - Public Interface - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange diff --git a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 9992986a46badb..f2fb4f3c5e1164 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -8,6 +8,7 @@ #import @protocol RCTBackedTextInputDelegate; +@class RCTTextAttributes; NS_ASSUME_NONNULL_BEGIN @@ -24,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nullable) UIView *inputAccessoryView; @property (nonatomic, weak, nullable) id textInputDelegate; @property (nonatomic, readonly) CGSize contentSize; +@property (nonatomic, strong, nullable) RCTTextAttributes *reactTextAttributes; // This protocol disallows direct access to `selectedTextRange` property because // unwise usage of it can break the `delegate` behavior. So, we always have to diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index 2ad41b2c974757..0a8ea15b7c0eaf 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -68,13 +68,7 @@ - (void)setTextAttributes:(RCTTextAttributes *)textAttributes - (void)enforceTextAttributesIfNeeded { id backedTextInputView = self.backedTextInputView; - if (backedTextInputView.attributedText.string.length == 0) { - return; - } - - backedTextInputView.font = _textAttributes.effectiveFont; - backedTextInputView.textColor = _textAttributes.effectiveForegroundColor; - backedTextInputView.textAlignment = _textAttributes.alignment; + backedTextInputView.reactTextAttributes = _textAttributes; } - (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets diff --git a/Libraries/Text/TextInput/Singleline/RCTUITextField.m b/Libraries/Text/TextInput/Singleline/RCTUITextField.m index 03a9198e18446b..92db734ce7cd43 100644 --- a/Libraries/Text/TextInput/Singleline/RCTUITextField.m +++ b/Libraries/Text/TextInput/Singleline/RCTUITextField.m @@ -11,11 +11,14 @@ #import #import "RCTBackedTextInputDelegateAdapter.h" +#import "RCTTextAttributes.h" @implementation RCTUITextField { RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter; } +@synthesize reactTextAttributes = _reactTextAttributes; + - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { @@ -60,19 +63,29 @@ - (void)setPlaceholderColor:(UIColor *)placeholderColor [self _updatePlaceholder]; } -- (void)_updatePlaceholder +- (void)setReactTextAttributes:(RCTTextAttributes *)reactTextAttributes { - if (self.placeholder == nil) { + if ([reactTextAttributes isEqual:_reactTextAttributes]) { return; } + self.defaultTextAttributes = reactTextAttributes.effectiveTextAttributes; + _reactTextAttributes = reactTextAttributes; + [self _updatePlaceholder]; +} - NSMutableDictionary *attributes = [NSMutableDictionary new]; - if (_placeholderColor) { - [attributes setObject:_placeholderColor forKey:NSForegroundColorAttributeName]; +- (RCTTextAttributes *)reactTextAttributes +{ + return _reactTextAttributes; +} + +- (void)_updatePlaceholder +{ + if (self.placeholder == nil) { + return; } self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder - attributes:attributes]; + attributes:[self placeholderEffectiveTextAttributes]]; } - (BOOL)isEditable @@ -95,6 +108,28 @@ - (BOOL)scrollEnabled return NO; } +#pragma mark - Placeholder + +- (NSDictionary *)placeholderEffectiveTextAttributes +{ + NSMutableDictionary *effectiveTextAttributes = [NSMutableDictionary dictionary]; + + if (_placeholderColor) { + effectiveTextAttributes[NSForegroundColorAttributeName] = _placeholderColor; + } + // Kerning + if (!isnan(_reactTextAttributes.letterSpacing)) { + effectiveTextAttributes[NSKernAttributeName] = @(_reactTextAttributes.letterSpacing); + } + + NSParagraphStyle *paragraphStyle = [_reactTextAttributes effectiveParagraphStyle]; + if (paragraphStyle) { + effectiveTextAttributes[NSParagraphStyleAttributeName] = paragraphStyle; + } + + return [effectiveTextAttributes copy]; +} + #pragma mark - Context Menu - (BOOL)canPerformAction:(SEL)action withSender:(id)sender @@ -161,7 +196,7 @@ - (CGSize)intrinsicContentSize { // Note: `placeholder` defines intrinsic size for ``. NSString *text = self.placeholder ?: @""; - CGSize size = [text sizeWithAttributes:@{NSFontAttributeName: self.font}]; + CGSize size = [text sizeWithAttributes:[self placeholderEffectiveTextAttributes]]; size = CGSizeMake(RCTCeilPixelValue(size.width), RCTCeilPixelValue(size.height)); size.width += _textContainerInset.left + _textContainerInset.right; size.height += _textContainerInset.top + _textContainerInset.bottom; diff --git a/Libraries/Utilities/GlobalPerformanceLogger.js b/Libraries/Utilities/GlobalPerformanceLogger.js new file mode 100644 index 00000000000000..abbd9e4342ffbd --- /dev/null +++ b/Libraries/Utilities/GlobalPerformanceLogger.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ +'use strict'; + +const createPerformanceLogger = require('createPerformanceLogger'); + +const GlobalPerformanceLogger = createPerformanceLogger(); +module.exports = GlobalPerformanceLogger; diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js deleted file mode 100644 index 0054b2464b77a9..00000000000000 --- a/Libraries/Utilities/PerformanceLogger.js +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ -'use strict'; - -const Systrace = require('Systrace'); - -const infoLog = require('infoLog'); -const performanceNow = - global.nativeQPLTimestamp || - global.nativePerformanceNow || - require('fbjs/lib/performanceNow'); - -type Timespan = { - description?: string, - totalTime?: number, - startTime?: number, - endTime?: number, -}; - -let timespans: {[key: string]: Timespan} = {}; -let extras: {[key: string]: any} = {}; -let points: {[key: string]: number} = {}; -const cookies: {[key: string]: number} = {}; - -const PRINT_TO_CONSOLE: false = false; // Type as false to prevent accidentally committing `true`; - -/** - * This is meant to collect and log performance data in production, which means - * it needs to have minimal overhead. - */ -const PerformanceLogger = { - addTimespan(key: string, lengthInMs: number, description?: string) { - if (timespans[key]) { - if (__DEV__) { - infoLog( - 'PerformanceLogger: Attempting to add a timespan that already exists ', - key, - ); - } - return; - } - - timespans[key] = { - description: description, - totalTime: lengthInMs, - }; - }, - - startTimespan(key: string, description?: string) { - if (timespans[key]) { - if (__DEV__) { - infoLog( - 'PerformanceLogger: Attempting to start a timespan that already exists ', - key, - ); - } - return; - } - - timespans[key] = { - description: description, - startTime: performanceNow(), - }; - cookies[key] = Systrace.beginAsyncEvent(key); - if (PRINT_TO_CONSOLE) { - infoLog('PerformanceLogger.js', 'start: ' + key); - } - }, - - stopTimespan(key: string) { - const timespan = timespans[key]; - if (!timespan || !timespan.startTime) { - if (__DEV__) { - infoLog( - 'PerformanceLogger: Attempting to end a timespan that has not started ', - key, - ); - } - return; - } - if (timespan.endTime) { - if (__DEV__) { - infoLog( - 'PerformanceLogger: Attempting to end a timespan that has already ended ', - key, - ); - } - return; - } - - timespan.endTime = performanceNow(); - timespan.totalTime = timespan.endTime - (timespan.startTime || 0); - if (PRINT_TO_CONSOLE) { - infoLog('PerformanceLogger.js', 'end: ' + key); - } - - Systrace.endAsyncEvent(key, cookies[key]); - delete cookies[key]; - }, - - clear() { - timespans = {}; - extras = {}; - points = {}; - if (PRINT_TO_CONSOLE) { - infoLog('PerformanceLogger.js', 'clear'); - } - }, - - clearCompleted() { - for (const key in timespans) { - if (timespans[key].totalTime) { - delete timespans[key]; - } - } - extras = {}; - points = {}; - if (PRINT_TO_CONSOLE) { - infoLog('PerformanceLogger.js', 'clearCompleted'); - } - }, - - clearExceptTimespans(keys: Array) { - timespans = Object.keys(timespans).reduce(function(previous, key) { - if (keys.indexOf(key) !== -1) { - previous[key] = timespans[key]; - } - return previous; - }, {}); - extras = {}; - points = {}; - if (PRINT_TO_CONSOLE) { - infoLog('PerformanceLogger.js', 'clearExceptTimespans', keys); - } - }, - - currentTimestamp() { - return performanceNow(); - }, - - getTimespans() { - return timespans; - }, - - hasTimespan(key: string) { - return !!timespans[key]; - }, - - logTimespans() { - for (const key in timespans) { - if (timespans[key].totalTime) { - infoLog(key + ': ' + timespans[key].totalTime + 'ms'); - } - } - }, - - addTimespans(newTimespans: Array, labels: Array) { - for (let ii = 0, l = newTimespans.length; ii < l; ii += 2) { - const label = labels[ii / 2]; - PerformanceLogger.addTimespan( - label, - newTimespans[ii + 1] - newTimespans[ii], - label, - ); - } - }, - - setExtra(key: string, value: any) { - if (extras[key]) { - if (__DEV__) { - infoLog( - 'PerformanceLogger: Attempting to set an extra that already exists ', - {key, currentValue: extras[key], attemptedValue: value}, - ); - } - return; - } - extras[key] = value; - }, - - getExtras() { - return extras; - }, - - logExtras() { - infoLog(extras); - }, - - markPoint(key: string, timestamp?: number) { - if (points[key]) { - if (__DEV__) { - infoLog( - 'PerformanceLogger: Attempting to mark a point that has been already logged ', - key, - ); - } - return; - } - points[key] = timestamp ?? performanceNow(); - }, - - getPoints() { - return points; - }, - - logPoints() { - for (const key in points) { - infoLog(key + ': ' + points[key] + 'ms'); - } - }, -}; - -module.exports = PerformanceLogger; diff --git a/Libraries/Utilities/PerformanceLoggerContext.js b/Libraries/Utilities/PerformanceLoggerContext.js new file mode 100644 index 00000000000000..e00b61a740011f --- /dev/null +++ b/Libraries/Utilities/PerformanceLoggerContext.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import * as React from 'react'; +import GlobalPerformanceLogger from 'GlobalPerformanceLogger'; +import type {IPerformanceLogger} from 'createPerformanceLogger'; + +const PerformanceLoggerContext: React.Context< + IPerformanceLogger, +> = React.createContext(GlobalPerformanceLogger); +export default PerformanceLoggerContext; diff --git a/Libraries/Utilities/__mocks__/GlobalPerformanceLogger.js b/Libraries/Utilities/__mocks__/GlobalPerformanceLogger.js new file mode 100644 index 00000000000000..b47df421a21400 --- /dev/null +++ b/Libraries/Utilities/__mocks__/GlobalPerformanceLogger.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +const GlobalPerformanceLogger = jest + .unmock('createPerformanceLogger') + .genMockFromModule('GlobalPerformanceLogger'); + +module.exports = GlobalPerformanceLogger; diff --git a/Libraries/Utilities/__tests__/PerformanceLogger-test.js b/Libraries/Utilities/__tests__/PerformanceLogger-test.js new file mode 100644 index 00000000000000..b72fd9ebc68f6b --- /dev/null +++ b/Libraries/Utilities/__tests__/PerformanceLogger-test.js @@ -0,0 +1,142 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +import GlobalPerformanceLogger from 'GlobalPerformanceLogger'; +import createPerformanceLogger from 'createPerformanceLogger'; +import type {IPerformanceLogger} from 'createPerformanceLogger'; + +const TIMESPAN_1 = ''; +const TIMESPAN_2 = ''; +const TIMESPAN_2_DURATION = 123; +const EXTRA_KEY = ''; +const EXTRA_VALUE = ''; +const EXTRA_VALUE_2 = ''; +const POINT = ''; +const POINT_TIMESTAMP = 99; +const POINT_TIMESTAMP_2 = 999; + +describe('PerformanceLogger', () => { + beforeEach(() => { + GlobalPerformanceLogger.clear(); + }); + + it('starts & stops and adds a timespan', () => { + let perfLogger = createPerformanceLogger(); + perfLogger.startTimespan(TIMESPAN_1); + perfLogger.stopTimespan(TIMESPAN_1); + perfLogger.addTimespan(TIMESPAN_2, TIMESPAN_2_DURATION); + expect(perfLogger.hasTimespan(TIMESPAN_1)).toBe(true); + expect(perfLogger.hasTimespan(TIMESPAN_2)).toBe(true); + expect(perfLogger.getTimespans()[TIMESPAN_2].totalTime).toBe( + TIMESPAN_2_DURATION, + ); + }); + + it('does not override a timespan', () => { + let perfLogger = createPerformanceLogger(); + perfLogger.startTimespan(TIMESPAN_1); + let old = perfLogger.getTimespans()[TIMESPAN_1]; + perfLogger.startTimespan(TIMESPAN_1); + expect(perfLogger.getTimespans()[TIMESPAN_1]).toBe(old); + perfLogger.addTimespan(TIMESPAN_1, 1); + expect(perfLogger.getTimespans()[TIMESPAN_1]).toBe(old); + }); + + it('logs an extra', () => { + let perfLogger = createPerformanceLogger(); + perfLogger.setExtra(EXTRA_KEY, EXTRA_VALUE); + expect(perfLogger.getExtras()).toEqual({ + [EXTRA_KEY]: EXTRA_VALUE, + }); + }); + + it('does not override a extra', () => { + let perfLogger = createPerformanceLogger(); + perfLogger.setExtra(EXTRA_KEY, EXTRA_VALUE); + expect(perfLogger.getExtras()).toEqual({ + [EXTRA_KEY]: EXTRA_VALUE, + }); + perfLogger.setExtra(EXTRA_KEY, EXTRA_VALUE_2); + expect(perfLogger.getExtras()).toEqual({ + [EXTRA_KEY]: EXTRA_VALUE, + }); + }); + + it('logs a point', () => { + let perfLogger = createPerformanceLogger(); + perfLogger.markPoint(POINT, POINT_TIMESTAMP); + expect(perfLogger.getPoints()).toEqual({ + [POINT]: POINT_TIMESTAMP, + }); + }); + + it('does not override a point', () => { + let perfLogger = createPerformanceLogger(); + perfLogger.markPoint(POINT, POINT_TIMESTAMP); + expect(perfLogger.getPoints()).toEqual({ + [POINT]: POINT_TIMESTAMP, + }); + perfLogger.markPoint(POINT, POINT_TIMESTAMP_2); + expect(perfLogger.getPoints()).toEqual({ + [POINT]: POINT_TIMESTAMP, + }); + }); + + it('global and local loggers do not conflict', () => { + let checkLogger = (logger: IPerformanceLogger, shouldBeEmpty: boolean) => { + expect(Object.keys(logger.getTimespans())).toEqual( + shouldBeEmpty ? [] : [TIMESPAN_1], + ); + expect(logger.getExtras()).toEqual( + shouldBeEmpty + ? {} + : { + [EXTRA_KEY]: EXTRA_VALUE, + }, + ); + expect(Object.keys(logger.getPoints())).toEqual( + shouldBeEmpty ? [] : [POINT], + ); + }; + let localPerformanceLogger1 = createPerformanceLogger(); + let localPerformanceLogger2 = createPerformanceLogger(); + localPerformanceLogger1.startTimespan(TIMESPAN_1); + localPerformanceLogger1.stopTimespan(TIMESPAN_1); + localPerformanceLogger1.setExtra(EXTRA_KEY, EXTRA_VALUE); + localPerformanceLogger1.markPoint(POINT); + checkLogger(localPerformanceLogger1, false); + checkLogger(localPerformanceLogger2, true); + checkLogger(GlobalPerformanceLogger, true); + localPerformanceLogger2.startTimespan(TIMESPAN_1); + localPerformanceLogger2.stopTimespan(TIMESPAN_1); + localPerformanceLogger2.setExtra(EXTRA_KEY, EXTRA_VALUE); + localPerformanceLogger2.markPoint(POINT, undefined); + checkLogger(localPerformanceLogger2, false); + checkLogger(GlobalPerformanceLogger, true); + GlobalPerformanceLogger.startTimespan(TIMESPAN_1); + GlobalPerformanceLogger.stopTimespan(TIMESPAN_1); + GlobalPerformanceLogger.setExtra(EXTRA_KEY, EXTRA_VALUE); + GlobalPerformanceLogger.markPoint(POINT); + checkLogger(GlobalPerformanceLogger, false); + localPerformanceLogger1.clear(); + checkLogger(localPerformanceLogger1, true); + checkLogger(localPerformanceLogger2, false); + checkLogger(GlobalPerformanceLogger, false); + GlobalPerformanceLogger.clear(); + checkLogger(localPerformanceLogger1, true); + checkLogger(localPerformanceLogger2, false); + checkLogger(GlobalPerformanceLogger, true); + localPerformanceLogger2.clear(); + checkLogger(localPerformanceLogger1, true); + checkLogger(localPerformanceLogger2, true); + checkLogger(GlobalPerformanceLogger, true); + }); +}); diff --git a/Libraries/Utilities/createPerformanceLogger.js b/Libraries/Utilities/createPerformanceLogger.js new file mode 100644 index 00000000000000..b432f19c68a1b6 --- /dev/null +++ b/Libraries/Utilities/createPerformanceLogger.js @@ -0,0 +1,249 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ +'use strict'; + +const Systrace = require('Systrace'); + +const infoLog = require('infoLog'); +const performanceNow = + global.nativeQPLTimestamp || + global.nativePerformanceNow || + require('fbjs/lib/performanceNow'); + +type Timespan = { + description?: string, + totalTime?: number, + startTime?: number, + endTime?: number, +}; + +export type IPerformanceLogger = { + addTimespan(string, number, string | void): void, + startTimespan(string, string | void): void, + stopTimespan(string): void, + clear(): void, + clearCompleted(): void, + clearExceptTimespans(Array): void, + currentTimestamp(): number, + getTimespans(): {[key: string]: Timespan}, + hasTimespan(string): boolean, + logTimespans(): void, + addTimespans(Array, Array): void, + setExtra(string, any): void, + getExtras(): {[key: string]: any}, + logExtras(): void, + markPoint(string, number | void): void, + getPoints(): {[key: string]: number}, + logPoints(): void, +}; + +const _cookies: {[key: string]: number} = {}; + +const PRINT_TO_CONSOLE: false = false; // Type as false to prevent accidentally committing `true`; + +/** + * This function creates peformance loggers that can be used to collect and log + * various performance data such as timespans, points and extras. + * The loggers need to have minimal overhead since they're used in production. + */ +function createPerformanceLogger(): IPerformanceLogger { + const result: IPerformanceLogger & { + _timespans: {[key: string]: Timespan}, + _extras: {[key: string]: any}, + _points: {[key: string]: number}, + } = { + _timespans: {}, + _extras: {}, + _points: {}, + + addTimespan(key: string, lengthInMs: number, description?: string) { + if (this._timespans[key]) { + if (__DEV__) { + infoLog( + 'PerformanceLogger: Attempting to add a timespan that already exists ', + key, + ); + } + return; + } + + this._timespans[key] = { + description: description, + totalTime: lengthInMs, + }; + }, + + startTimespan(key: string, description?: string) { + if (this._timespans[key]) { + if (__DEV__) { + infoLog( + 'PerformanceLogger: Attempting to start a timespan that already exists ', + key, + ); + } + return; + } + + this._timespans[key] = { + description: description, + startTime: performanceNow(), + }; + _cookies[key] = Systrace.beginAsyncEvent(key); + if (PRINT_TO_CONSOLE) { + infoLog('PerformanceLogger.js', 'start: ' + key); + } + }, + + stopTimespan(key: string) { + const timespan = this._timespans[key]; + if (!timespan || !timespan.startTime) { + if (__DEV__) { + infoLog( + 'PerformanceLogger: Attempting to end a timespan that has not started ', + key, + ); + } + return; + } + if (timespan.endTime) { + if (__DEV__) { + infoLog( + 'PerformanceLogger: Attempting to end a timespan that has already ended ', + key, + ); + } + return; + } + + timespan.endTime = performanceNow(); + timespan.totalTime = timespan.endTime - (timespan.startTime || 0); + if (PRINT_TO_CONSOLE) { + infoLog('PerformanceLogger.js', 'end: ' + key); + } + + Systrace.endAsyncEvent(key, _cookies[key]); + delete _cookies[key]; + }, + + clear() { + this._timespans = {}; + this._extras = {}; + this._points = {}; + if (PRINT_TO_CONSOLE) { + infoLog('PerformanceLogger.js', 'clear'); + } + }, + + clearCompleted() { + for (const key in this._timespans) { + if (this._timespans[key].totalTime) { + delete this._timespans[key]; + } + } + this._extras = {}; + this._points = {}; + if (PRINT_TO_CONSOLE) { + infoLog('PerformanceLogger.js', 'clearCompleted'); + } + }, + + clearExceptTimespans(keys: Array) { + this._timespans = Object.keys(this._timespans).reduce(function( + previous, + key, + ) { + if (keys.indexOf(key) !== -1) { + previous[key] = this._timespans[key]; + } + return previous; + }, + {}); + this._extras = {}; + this._points = {}; + if (PRINT_TO_CONSOLE) { + infoLog('PerformanceLogger.js', 'clearExceptTimespans', keys); + } + }, + + currentTimestamp() { + return performanceNow(); + }, + + getTimespans() { + return this._timespans; + }, + + hasTimespan(key: string) { + return !!this._timespans[key]; + }, + + logTimespans() { + for (const key in this._timespans) { + if (this._timespans[key].totalTime) { + infoLog(key + ': ' + this._timespans[key].totalTime + 'ms'); + } + } + }, + + addTimespans(newTimespans: Array, labels: Array) { + for (let ii = 0, l = newTimespans.length; ii < l; ii += 2) { + const label = labels[ii / 2]; + this.addTimespan(label, newTimespans[ii + 1] - newTimespans[ii], label); + } + }, + + setExtra(key: string, value: any) { + if (this._extras[key]) { + if (__DEV__) { + infoLog( + 'PerformanceLogger: Attempting to set an extra that already exists ', + {key, currentValue: this._extras[key], attemptedValue: value}, + ); + } + return; + } + this._extras[key] = value; + }, + + getExtras() { + return this._extras; + }, + + logExtras() { + infoLog(this._extras); + }, + + markPoint(key: string, timestamp?: number) { + if (this._points[key]) { + if (__DEV__) { + infoLog( + 'PerformanceLogger: Attempting to mark a point that has been already logged ', + key, + ); + } + return; + } + this._points[key] = timestamp ?? performanceNow(); + }, + + getPoints() { + return this._points; + }, + + logPoints() { + for (const key in this._points) { + infoLog(key + ': ' + this._points[key] + 'ms'); + } + }, + }; + return result; +} + +module.exports = createPerformanceLogger; diff --git a/Libraries/WebSocket/RCTSRWebSocket.m b/Libraries/WebSocket/RCTSRWebSocket.m index 325451c3af836e..6f1e5e8025164f 100644 --- a/Libraries/WebSocket/RCTSRWebSocket.m +++ b/Libraries/WebSocket/RCTSRWebSocket.m @@ -645,6 +645,9 @@ - (void)_writeData:(NSData *)data; - (void)send:(id)data; { RCTAssert(self.readyState != RCTSR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); + if (nil == data) { + return; + } // TODO: maybe not copy this for performance data = [data copy]; dispatch_async(_workQueue, ^{ @@ -652,8 +655,6 @@ - (void)send:(id)data; [self _sendFrameWithOpcode:RCTSROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; } else if ([data isKindOfClass:[NSData class]]) { [self _sendFrameWithOpcode:RCTSROpCodeBinaryFrame data:data]; - } else if (data == nil) { - [self _sendFrameWithOpcode:RCTSROpCodeTextFrame data:data]; } else { assert(NO); } @@ -1223,7 +1224,7 @@ - (void)_pumpScanner; static const size_t RCTSRFrameHeaderOverhead = 32; -- (void)_sendFrameWithOpcode:(RCTSROpCode)opcode data:(id)data; +- (void)_sendFrameWithOpcode:(RCTSROpCode)opcode data:(NSData *)data; { [self assertOnWorkQueue]; @@ -1231,9 +1232,7 @@ - (void)_sendFrameWithOpcode:(RCTSROpCode)opcode data:(id)data; return; } - RCTAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"NSString or NSData"); - - size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; + size_t payloadLength = [data length]; NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + RCTSRFrameHeaderOverhead]; if (!frame) { @@ -1257,14 +1256,7 @@ - (void)_sendFrameWithOpcode:(RCTSROpCode)opcode data:(id)data; size_t frame_buffer_size = 2; - const uint8_t *unmasked_payload = NULL; - if ([data isKindOfClass:[NSData class]]) { - unmasked_payload = (uint8_t *)[data bytes]; - } else if ([data isKindOfClass:[NSString class]]) { - unmasked_payload = (const uint8_t *)[data UTF8String]; - } else { - return; - } + const uint8_t *unmasked_payload = (uint8_t *)[data bytes]; if (payloadLength < 126) { frame_buffer[1] |= payloadLength; diff --git a/Libraries/YellowBox/Data/YellowBoxRegistry.js b/Libraries/YellowBox/Data/YellowBoxRegistry.js index 0c98ca00fa51d0..b6a445f893aabf 100644 --- a/Libraries/YellowBox/Data/YellowBoxRegistry.js +++ b/Libraries/YellowBox/Data/YellowBoxRegistry.js @@ -18,7 +18,7 @@ export type Registry = Map>; export type Observer = (registry: Registry) => void; -type IgnorePattern = string | RegExp; +export type IgnorePattern = string | RegExp; export type Subscription = $ReadOnly<{| unsubscribe: () => void, diff --git a/Libraries/YellowBox/YellowBox.js b/Libraries/YellowBox/YellowBox.js index 0b6768110e85d7..4f7d4128412b73 100644 --- a/Libraries/YellowBox/YellowBox.js +++ b/Libraries/YellowBox/YellowBox.js @@ -13,7 +13,7 @@ const React = require('React'); import type {Category} from 'YellowBoxCategory'; -import type {Registry, Subscription} from 'YellowBoxRegistry'; +import type {Registry, Subscription, IgnorePattern} from 'YellowBoxRegistry'; type Props = $ReadOnly<{||}>; type State = {| @@ -50,7 +50,7 @@ if (__DEV__) { // eslint-disable-next-line no-shadow YellowBox = class YellowBox extends React.Component { - static ignoreWarnings(patterns: $ReadOnlyArray): void { + static ignoreWarnings(patterns: $ReadOnlyArray): void { YellowBoxRegistry.addIgnorePatterns(patterns); } @@ -135,7 +135,7 @@ if (__DEV__) { }; } else { YellowBox = class extends React.Component { - static ignoreWarnings(patterns: $ReadOnlyArray): void { + static ignoreWarnings(patterns: $ReadOnlyArray): void { // Do nothing. } diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index b35910c818ac4e..adf7b4d488aaa1 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -71,14 +71,6 @@ module.exports = { get KeyboardAvoidingView() { return require('KeyboardAvoidingView'); }, - get ListView() { - warnOnce( - 'listview-deprecation', - 'ListView is deprecated and will be removed in a future release. ' + - 'See https://fb.me/nolistview for more information', - ); - return require('ListView'); - }, get MaskedViewIOS() { warnOnce( 'maskedviewios-moved', @@ -136,14 +128,6 @@ module.exports = { get SwipeableFlatList() { return require('SwipeableFlatList'); }, - get SwipeableListView() { - warnOnce( - 'swipablelistview-deprecation', - 'ListView and SwipeableListView are deprecated and will be removed in a future release. ' + - 'See https://fb.me/nolistview for more information', - ); - return require('SwipeableListView'); - }, get Text() { return require('Text'); }, @@ -229,6 +213,12 @@ module.exports = { return require('BackHandler'); }, get CameraRoll() { + warnOnce( + 'cameraroll-moved', + 'CameraRoll has been extracted from react-native core and will be removed in a future release. ' + + "It can now be installed and imported from '@react-native-community/cameraroll' instead of 'react-native'. " + + 'See https://github.com/react-native-community/react-native-cameraroll', + ); return require('CameraRoll'); }, get Clipboard() { @@ -374,3 +364,31 @@ module.exports = { return require('DeprecatedViewPropTypes'); }, }; + +if (__DEV__) { + // $FlowFixMe This is intentional: Flow will error when attempting to access ListView. + Object.defineProperty(module.exports, 'ListView', { + configurable: true, + get() { + invariant( + false, + 'ListView has been removed from React Native. ' + + 'See https://fb.me/nolistview for more information or use ' + + '`deprecated-react-native-listview`.', + ); + }, + }); + + // $FlowFixMe This is intentional: Flow will error when attempting to access SwipeableListView. + Object.defineProperty(module.exports, 'SwipeableListView', { + configurable: true, + get() { + invariant( + false, + 'SwipeableListView has been removed from React Native. ' + + 'See https://fb.me/nolistview for more information or use ' + + '`deprecated-react-native-swipeable-listview`.', + ); + }, + }); +} diff --git a/README.md b/README.md index 0a3250a9a3ccea..1bd961a60aec2d 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,147 @@ -# [React Native](https://facebook.github.io/react-native/) · [![Circle CI Status](https://circleci.com/gh/facebook/react-native.svg?style=shield)](https://circleci.com/gh/facebook/react-native) [![Build status](https://ci.appveyor.com/api/projects/status/g8d58ipi3auqdtrk/branch/master?svg=true)](https://ci.appveyor.com/project/facebook/react-native/branch/master) [![npm version](https://badge.fury.io/js/react-native.svg)](https://badge.fury.io/js/react-native) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md#pull-requests) - -Learn once, write anywhere: Build mobile apps with React. - -See the official [React Native website](https://facebook.github.io/react-native/) for an introduction to React Native. +

+ + React Native + +

+ +

+ Learn once, write anywhere:
+ Build mobile apps with React. +

+ +

+ + React Native is released under the MIT license. + + + Current CircleCI build status. + + + Current Appveyor build status. + + + Current npm package version. + + + PRs welcome! + + + Follow @reactnative + +

+ +

+ Getting Started + Β· + Learn the Basics + Β· + Showcase + Β· + Contribute + Β· + Community + Β· + Support +

+ +React Native brings [**React**'s][r] declarative UI framework to iOS and Android. With React Native, you use native UI controls and have full access to the native platform. + +- **Declarative.** React makes it painless to create interactive UIs. Declarative views make your code more predictable and easier to debug. +- **Component-Based.** Build encapsulated components that manage their own state, then compose them to make complex UIs. +- **Developer Velocity.** See local changes in seconds. Changes to JavaScript code can be live reloaded without rebuilding the native app. +- **Portability.** Reuse code across iOS, Android, and [other platforms][p]. + +[r]: https://reactjs.org/ +[p]: https://facebook.github.io/react-native/docs/out-of-tree-platforms + +## Contents - [Requirements](#requirements) -- [Getting Started](#building-your-first-react-native-app) -- [Documentation](#full-documentation) -- [Upgrading](https://facebook.github.io/react-native/docs/upgrading) -- [Contributing](#join-the-react-native-community) -- [Code of Conduct](./CODE_OF_CONDUCT.md) +- [Building your first React Native app](#building-your-first-react-native-app) +- [Documentation](#documentation) +- [Upgrading](#upgrading) +- [How to Contribute](#how-to-contribute) +- [Code of Conduct](#code-of-conduct) - [License](#license) ---- -## Requirements +## πŸ“‹ Requirements -Supported target operating systems are >= Android 4.1 (API 16) and >= iOS 9.0. You may use Windows, macOS, or Linux as your development operating system, though building and running iOS apps is limited to macOS by default (tools like [Expo](https://expo.io) can be used to get around this). +React Native apps may target iOS 9.0 and Android 4.1 (API 16) or newer. You may use Windows, macOS, or Linux as your development operating system, though building and running iOS apps is limited to macOS. Tools like [Expo](https://expo.io) can be used to work around this. -## Building your first React Native app +## πŸŽ‰ Building your first React Native app Follow the [Getting Started guide](https://facebook.github.io/react-native/docs/getting-started.html). The recommended way to install React Native depends on your project. Here you can find short guides for the most common scenarios: -- [Trying out React Native](https://snack.expo.io/BJ-uC-nrb) -- [Creating a New Application](https://facebook.github.io/react-native/docs/getting-started.html) -- [Adding React Native to an Existing Application](https://facebook.github.io/react-native/docs/integration-with-existing-apps.html) +- [Trying out React Native][hello-world] +- [Creating a New Application][new-app] +- [Adding React Native to an Existing Application][existing] + +[hello-world]: https://snack.expo.io/@hramos/hello,-world! +[new-app]: https://facebook.github.io/react-native/docs/getting-started.html +[existing]: https://facebook.github.io/react-native/docs/integration-with-existing-apps.html + +## πŸ“– Documentation + +The full documentation for React Native can be found on our [website][docs]. + +The React Native documentation discusses components, APIs, and topics that are specific to React Native. For further documentation on the React API that is shared between React Native and React DOM, refer to the [React documentation][r-docs]. + +The source for the React Native documentation and website is hosted on a separate repo, [**@facebook/react-native-website**][repo-website]. + +[docs]: https://facebook.github.io/react-native/docs/getting-started.html +[r-docs]: https://reactjs.org/docs/getting-started.html +[repo-website]: (https://github.com/facebook/react-native-website) + +## πŸš€ Upgrading + +Upgrading to new versions of React Native may give you access to more APIs, views, developer tools and other goodies. See the [Upgrading Guide][u] for instructions. + +React Native releases are discussed in the React Native Community, [**@react-native-community/react-native-releases**][repo-releases]. + +[u]: https://facebook.github.io/react-native/docs/upgrading +[repo-releases]: https://github.com/react-native-community/react-native-releases + +## πŸ‘ How to Contribute + +The main purpose of this repository is to continue evolving React Native core. We want to make contributing to this project as easy and transparent as possible, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving React Native. + +### [Code of Conduct][code] + +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. +Please read the [full text][code] so that you can understand what actions will and will not be tolerated. + +[code]: https://code.fb.com/codeofconduct/ + +### [Contributing Guide][contribute] + +Read our [**Contributing Guide**][contribute] to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to React Native. -## How React Native works +[contribute]: https://facebook.github.io/react-native/docs/contributing -React Native lets you build mobile apps using JavaScript. It uses the same design as [React](https://facebook.github.io/react), letting you compose a rich mobile UI from declarative components. +### [Open Source Roadmap][roadmap] -With React Native, you don't build a "mobile web app", an "HTML5 app", or a "hybrid app". You build a real mobile app that's indistinguishable from an app built using Objective-C, Java, Kotlin, or Swift. React Native uses the same fundamental UI building blocks as regular iOS and Android apps. You just put those building blocks together using JavaScript and React. +You can learn more about our vision for React Native in the [**Roadmap**][roadmap]. -React Native lets you build your app faster. Instead of recompiling, you can reload your app instantly. With hot reloading, you can even run new code while retaining your application state. +[roadmap]: https://github.com/facebook/react-native/wiki/Roadmap -React Native combines smoothly with components written in Objective-C, Java, Kotlin, or Swift. It's simple to drop down to native code if you need to optimize a few aspects of your application. It's also easy to build part of your app in React Native, and part of your app using native code directly - that's how the Facebook app works. +### Good First Issues -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. You can learn more about our open source roadmap in this blog post: [Open Source Roadmap](https://facebook.github.io/react-native/blog/2018/11/01/oss-roadmap). +We have a list of [good first issues][gfi] that contain bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process. -## Full documentation +[gfi]: https://github.com/facebook/react-native/labels/good%20first%20issue -The full documentation for React Native can be found on our [website](https://facebook.github.io/react-native/docs/getting-started.html). The source for the React Native documentation and website is hosted on a separate repo, . Releases are discussed in the React Native Community, , and larger discussions and proposals are in . +### Discussions -The React Native documentation only discusses the components, APIs, and topics specific to React Native (React on iOS and Android). For further documentation on the React API that is shared between React Native and React DOM, refer to the [React documentation](https://facebook.github.io/react/). +Larger discussions and proposals are discussed in [**@react-native-community/discussions-and-proposals**][repo-meta]. -## Join the React Native community -* Website: https://facebook.github.io/react-native -* Twitter: https://twitter.com/reactnative -* Help: https://facebook.github.io/react-native/en/help +[repo-meta]: https://github.com/react-native-community/discussions-and-proposals -See the [CONTRIBUTING](./CONTRIBUTING.md) file for how to help out. +## πŸ“„ License -## License +React Native is MIT licensed, as found in the [LICENSE][l] file. -React Native is MIT licensed, as found in the LICENSE file. +React Native documentation is Creative Commons licensed, as found in the [LICENSE-docs][ld] file. -React Native documentation is Creative Commons licensed, as found in the LICENSE-docs file. +[l]: https://github.com/facebook/react-native/blob/master/LICENSE +[ld]: https://github.com/facebook/react-native/blob/master/LICENSE-docs diff --git a/RNTester/README.md b/RNTester/README.md index 0ad4267787ce2f..cb4b7162a2258a 100644 --- a/RNTester/README.md +++ b/RNTester/README.md @@ -12,7 +12,7 @@ Before running the app, make sure you ran: ### Running on iOS -Mac OS and Xcode are required. +Both macOS and Xcode are required. - Open `RNTester/RNTester.xcodeproj` in Xcode - Hit the Run button diff --git a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1-iOS12@2x.png b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1-iOS12@2x.png index 06297ec33c570a..1232aed8ba7ef3 100644 Binary files a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1-iOS12@2x.png and b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1-iOS12@2x.png differ diff --git a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSliderExample_1-iOS12@2x.png b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSliderExample_1-iOS12@2x.png index 3476ceda8b6ca2..6f22306ea6f211 100644 Binary files a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSliderExample_1-iOS12@2x.png and b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSliderExample_1-iOS12@2x.png differ diff --git a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSwitchExample_1-iOS12@2x.png b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSwitchExample_1-iOS12@2x.png index aa1292f087c250..d844d1148234c0 100644 Binary files a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSwitchExample_1-iOS12@2x.png and b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSwitchExample_1-iOS12@2x.png differ diff --git a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1-iOS12@2x.png b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1-iOS12@2x.png index 9854157e297f9e..600673dc5588c9 100644 Binary files a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1-iOS12@2x.png and b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1-iOS12@2x.png differ diff --git a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1-iOS12@2x.png b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1-iOS12@2x.png index a79c579819b947..c0f4ac370ed7ae 100644 Binary files a/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1-iOS12@2x.png and b/RNTester/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1-iOS12@2x.png differ diff --git a/RNTester/RNTesterUnitTests/RCTEventDispatcherTests.m b/RNTester/RNTesterUnitTests/RCTEventDispatcherTests.m index bffde88f8977ce..6ad636fcfc6b6e 100644 --- a/RNTester/RNTesterUnitTests/RCTEventDispatcherTests.m +++ b/RNTester/RNTesterUnitTests/RCTEventDispatcherTests.m @@ -64,8 +64,8 @@ - (void)dispatchBlock:(dispatch_block_t)block @end @implementation RCTDummyBridge -- (void)dispatchBlock:(dispatch_block_t)block - queue:(dispatch_queue_t)queue +- (void)dispatchBlock:(dispatch_block_t __unused)block + queue:(dispatch_queue_t __unused)queue {} @end diff --git a/RNTester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m b/RNTester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m index eee726bf6bf32c..38d64d864e60ba 100644 --- a/RNTester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m +++ b/RNTester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m @@ -127,8 +127,10 @@ - (void)setUp { [super setUp]; + RCTBridge *bridge = [OCMockObject niceMockForClass:[RCTBridge class]]; _uiManager = [OCMockObject niceMockForClass:[RCTUIManager class]]; - _nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithUIManager:_uiManager]; + OCMStub([bridge uiManager]).andReturn(_uiManager); + _nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithBridge:bridge]; _displayLink = [RCTFakeDisplayLink new]; } diff --git a/RNTester/e2e/__tests__/Switch-test.js b/RNTester/e2e/__tests__/Switch-test.js deleted file mode 100644 index 783685d724a783..00000000000000 --- a/RNTester/e2e/__tests__/Switch-test.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails oncall+react_native - * @format - */ - -/* global device, element, by, expect */ - -const jestExpect = require('expect'); -const { - openComponentWithLabel, - openExampleWithTitle, -} = require('../e2e-helpers'); - -describe('Switch', () => { - beforeAll(async () => { - await device.reloadReactNative(); - await openComponentWithLabel('', ' Native boolean input'); - }); - - describe('Switches can be set to true or false', () => { - beforeAll(async () => { - await openExampleWithTitle('Switches can be set to true or false'); - }); - - it('Switch that starts off should switch', async () => { - const testID = 'on-off-initial-off'; - const indicatorID = 'on-off-initial-off-indicator'; - - await expect(element(by.id(testID))).toHaveValue('0'); - await expect(element(by.id(indicatorID))).toHaveText('Off'); - await element(by.id(testID)).tap(); - await expect(element(by.id(testID))).toHaveValue('1'); - await expect(element(by.id(indicatorID))).toHaveText('On'); - }); - }); - - describe('Switches can be disabled', () => { - beforeAll(async () => { - await openExampleWithTitle('Switches can be disabled'); - }); - - it('disabled switch should not toggle', async () => { - const onTestID = 'disabled-initial-on'; - const offTestID = 'disabled-initial-off'; - const onIndicatorID = 'disabled-initial-on-indicator'; - const offIndicatorID = 'disabled-initial-off-indicator'; - - await expect(element(by.id(onTestID))).toHaveValue('1'); - await expect(element(by.id(onIndicatorID))).toHaveText('On'); - - try { - await element(by.id(onTestID)).tap(); - throw new Error('Does not match'); - } catch (err) { - jestExpect(err.message.message).toEqual( - jestExpect.stringContaining( - 'Cannot perform action due to constraint(s) failure', - ), - ); - } - await expect(element(by.id(onTestID))).toHaveValue('1'); - await expect(element(by.id(onIndicatorID))).toHaveText('On'); - - await expect(element(by.id(offTestID))).toHaveValue('0'); - await expect(element(by.id(offIndicatorID))).toHaveText('Off'); - try { - await element(by.id(offTestID)).tap(); - throw new Error('Does not match'); - } catch (err) { - jestExpect(err.message.message).toEqual( - jestExpect.stringContaining( - 'Cannot perform action due to constraint(s) failure', - ), - ); - } - await expect(element(by.id(offTestID))).toHaveValue('0'); - await expect(element(by.id(offIndicatorID))).toHaveText('Off'); - }); - }); -}); diff --git a/RNTester/js/AccessibilityAndroidExample.android.js b/RNTester/js/AccessibilityAndroidExample.android.js index ac4b6c00108667..b6e4c89bda0877 100644 --- a/RNTester/js/AccessibilityAndroidExample.android.js +++ b/RNTester/js/AccessibilityAndroidExample.android.js @@ -35,32 +35,6 @@ class AccessibilityAndroidExample extends React.Component { count: 0, backgroundImportantForAcc: 0, forgroundImportantForAcc: 0, - screenReaderEnabled: false, - }; - - componentDidMount() { - AccessibilityInfo.addEventListener( - 'change', - this._handleScreenReaderToggled, - ); - AccessibilityInfo.fetch().done(isEnabled => { - this.setState({ - screenReaderEnabled: isEnabled, - }); - }); - } - - componentWillUnmount() { - AccessibilityInfo.removeEventListener( - 'change', - this._handleScreenReaderToggled, - ); - } - - _handleScreenReaderToggled = isEnabled => { - this.setState({ - screenReaderEnabled: isEnabled, - }); }; _addOne = () => { @@ -83,109 +57,7 @@ class AccessibilityAndroidExample extends React.Component { render() { return ( - - - - This is - nontouchable normal view. - - - - - - This is - - nontouchable accessible view without label. - - - - - - - This is - - nontouchable accessible view with label. - - - - - - - ToastAndroid.show('Toasts work by default', ToastAndroid.SHORT) - } - accessibilityRole="button"> - - Click me - Or not - - - - - - - ToastAndroid.show('Toasts work by default', ToastAndroid.SHORT) - } - accessibilityRole="button" - accessibilityStates={['disabled']} - disabled={true}> - - I am disabled - Clicking me will not trigger any action. - - - - - - - ToastAndroid.show('Toasts work by default', ToastAndroid.SHORT) - } - accessibilityRole="button" - accessibilityHint="Triggers - Toasts"> - - Click Me! - - - - - - - Accessible view with hint, role, and state - - Talkback will say: accessibility hint button, selected{' '} - - - - - - - Accessible view with label, hint, role, and state - - Talkback will say: accessibility label, hint button, selected{' '} - - - - - - - This accessible view has no label, so the text is read. - - - + @@ -197,13 +69,6 @@ class AccessibilityAndroidExample extends React.Component { - - - The screen reader is{' '} - {this.state.screenReaderEnabled ? 'enabled' : 'disabled'}. - - - + + + Text's accessibilityLabel is the raw text itself unless it is set + explicitly. + + + + + + This text component's accessibilityLabel is set explicitly. + + + + + + This is text one. + This is text two. + + + + + + This is text one. + This is text two. + + + + + + This is text one. + This is text two. + + + + {/* Android screen readers will say the accessibility hint instead of the text + since the view doesn't have a label. */} + + + This is text one. + This is text two. + + + + + + This is text one. + This is text two. + + + + + This is a title. + + + + Alert.alert('Link has been clicked!')} + accessibilityRole="link"> + + Click me + + + + + + Alert.alert('Button has been pressed!')} + accessibilityRole="button"> + Click me + + + + + Alert.alert('Button has been pressed!')} + accessibilityRole="button" + accessibilityStates={['disabled']} + disabled={true}> + + + I am disabled. Clicking me will not trigger any action. + + + + + + + + This view is selected and disabled. + + + + + + Accessible view with label, hint, role, and state + + + + ); + } +} + +class ScreenReaderStatusExample extends React.Component<{}> { + state = { + screenReaderEnabled: false, + }; + + componentDidMount() { + AccessibilityInfo.addEventListener( + 'change', + this._handleScreenReaderToggled, + ); + AccessibilityInfo.fetch().done(isEnabled => { + this.setState({ + screenReaderEnabled: isEnabled, + }); + }); + } + + componentWillUnmount() { + AccessibilityInfo.removeEventListener( + 'change', + this._handleScreenReaderToggled, + ); + } + + _handleScreenReaderToggled = isEnabled => { + this.setState({ + screenReaderEnabled: isEnabled, + }); + }; + + render() { + return ( + + + The screen reader is{' '} + {this.state.screenReaderEnabled ? 'enabled' : 'disabled'}. + + + ); + } +} + +exports.title = 'Accessibility'; +exports.description = 'Examples of using Accessibility APIs.'; +exports.examples = [ + { + title: 'Accessibility elements', + render(): React.Element { + return ; + }, + }, + { + title: 'Check if the screen reader is enabled', + render(): React.Element { + return ; + }, + }, +]; diff --git a/RNTester/js/AccessibilityIOSExample.js b/RNTester/js/AccessibilityIOSExample.js index f9feffc05c76ad..bcd6bccd670f77 100644 --- a/RNTester/js/AccessibilityIOSExample.js +++ b/RNTester/js/AccessibilityIOSExample.js @@ -14,11 +14,13 @@ const React = require('react'); const ReactNative = require('react-native'); const {AccessibilityInfo, Text, View, TouchableOpacity, Alert} = ReactNative; +const RNTesterBlock = require('./RNTesterBlock'); + type Props = $ReadOnly<{||}>; class AccessibilityIOSExample extends React.Component { render() { return ( - + Alert.alert('Alert', 'onAccessibilityTap success') @@ -36,112 +38,23 @@ class AccessibilityIOSExample extends React.Component { accessible={true}> Accessibility escape example - - Accessibility label example - - - Accessibility traits example - - - Text's accessibilityLabel is the raw text itself unless it is set - explicitly. - - - This text component's accessibilityLabel is set explicitly. - - - - This view component has both an accessibilityLabel and an - accessibilityHint explicitly set. - - - - This text component has both an accessibilityLabel and an - accessibilityHint explicitly set. - - - - - This button has both an accessibilityLabel and an - accessibilityHint explicitly set. - - - This view's children are hidden from the accessibility tree - - ); - } -} - -class ScreenReaderStatusExample extends React.Component<{}, $FlowFixMeState> { - state = { - screenReaderEnabled: false, - }; - - componentDidMount() { - AccessibilityInfo.addEventListener( - 'change', - this._handleScreenReaderToggled, - ); - AccessibilityInfo.fetch().done(isEnabled => { - this.setState({ - screenReaderEnabled: isEnabled, - }); - }); - } - - componentWillUnmount() { - AccessibilityInfo.removeEventListener( - 'change', - this._handleScreenReaderToggled, - ); - } - - _handleScreenReaderToggled = isEnabled => { - this.setState({ - screenReaderEnabled: isEnabled, - }); - }; - - render() { - return ( - - - The screen reader is{' '} - {this.state.screenReaderEnabled ? 'enabled' : 'disabled'}. - - + ); } } exports.title = 'AccessibilityIOS'; -exports.description = "Interface to show iOS' accessibility samples"; +exports.description = 'iOS specific Accessibility APIs'; exports.examples = [ { - title: 'Accessibility elements', + title: 'iOS Accessibility elements', render(): React.Element { return ; }, }, - { - title: 'Check if the screen reader is enabled', - render(): React.Element { - return ; - }, - }, ]; diff --git a/RNTester/js/ImageExample.js b/RNTester/js/ImageExample.js index 7b55b1c4f2569f..6f32a108426b47 100644 --- a/RNTester/js/ImageExample.js +++ b/RNTester/js/ImageExample.js @@ -93,7 +93,7 @@ class NetworkImageCallbackExample extends React.Component< `βœ” Prefetch OK (+${new Date() - mountTime}ms)`, ); Image.queryCache([IMAGE_PREFETCH_URL]).then(map => { - const result = map.get(IMAGE_PREFETCH_URL); + const result = map[IMAGE_PREFETCH_URL]; if (result) { this._loadEventFired( `βœ” queryCache "${result}" (+${new Date() - @@ -836,10 +836,7 @@ exports.examples = [ return ( ); }, diff --git a/RNTester/js/ListViewExample.js b/RNTester/js/ListViewExample.js deleted file mode 100644 index a8663da20bd5b7..00000000000000 --- a/RNTester/js/ListViewExample.js +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow strict-local - */ - -'use strict'; - -const React = require('react'); -const ReactNative = require('react-native'); -const { - Image, - ListView, - TouchableHighlight, - StyleSheet, - Text, - View, -} = ReactNative; -const ListViewDataSource = require('ListViewDataSource'); -const RNTesterPage = require('./RNTesterPage'); - -import type {RNTesterProps} from 'RNTesterTypes'; - -type State = {| - dataSource: ListViewDataSource, -|}; - -class ListViewExample extends React.Component { - state = { - dataSource: this.getInitialDataSource(), - }; - - _pressData: {[key: number]: boolean} = {}; - - getInitialDataSource() { - const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); - return ds.cloneWithRows(this._genRows({})); - } - - UNSAFE_componentWillMount() { - this._pressData = {}; - } - - render() { - return ( - '} - noSpacer={true} - noScroll={true}> - - - ); - } - - _renderRow = ( - rowData: string, - sectionID: number, - rowID: number, - highlightRow: (sectionID: number, rowID: number) => void, - ) => { - const rowHash = Math.abs(hashCode(rowData)); - const imgSource = THUMB_URLS[rowHash % THUMB_URLS.length]; - return ( - { - this._pressRow(rowID); - highlightRow(sectionID, rowID); - }}> - - - - - {rowData + ' - ' + LOREM_IPSUM.substr(0, (rowHash % 301) + 10)} - - - - - ); - }; - - _genRows(pressData: {[key: number]: boolean}): Array { - const dataBlob = []; - for (let ii = 0; ii < 100; ii++) { - const pressedText = pressData[ii] ? ' (pressed)' : ''; - dataBlob.push('Row ' + ii + pressedText); - } - return dataBlob; - } - - _pressRow = (rowID: number) => { - this._pressData[rowID] = !this._pressData[rowID]; - this.setState({ - dataSource: this.state.dataSource.cloneWithRows( - this._genRows(this._pressData), - ), - }); - }; - - _renderSeparator( - sectionID: number, - rowID: number, - adjacentRowHighlighted: boolean, - ) { - return ( - - ); - } -} - -const THUMB_URLS = [ - require('./Thumbnails/like.png'), - require('./Thumbnails/dislike.png'), - require('./Thumbnails/call.png'), - require('./Thumbnails/fist.png'), - require('./Thumbnails/bandaged.png'), - require('./Thumbnails/flowers.png'), - require('./Thumbnails/heart.png'), - require('./Thumbnails/liking.png'), - require('./Thumbnails/party.png'), - require('./Thumbnails/poke.png'), - require('./Thumbnails/superlike.png'), - require('./Thumbnails/victory.png'), -]; -const LOREM_IPSUM = - 'Lorem ipsum dolor sit amet, ius ad pertinax oportere accommodare, an vix civibus corrumpit referrentur. Te nam case ludus inciderint, te mea facilisi adipiscing. Sea id integre luptatum. In tota sale consequuntur nec. Erat ocurreret mei ei. Eu paulo sapientem vulputate est, vel an accusam intellegam interesset. Nam eu stet pericula reprimique, ea vim illud modus, putant invidunt reprehendunt ne qui.'; - -/* eslint no-bitwise: 0 */ -const hashCode = function(str) { - let hash = 15; - for (let ii = str.length - 1; ii >= 0; ii--) { - hash = (hash << 5) - hash + str.charCodeAt(ii); - } - return hash; -}; - -const styles = StyleSheet.create({ - row: { - flexDirection: 'row', - justifyContent: 'center', - padding: 10, - backgroundColor: '#F6F6F6', - }, - thumb: { - width: 64, - height: 64, - }, - text: { - flex: 1, - }, -}); - -exports.title = ''; -exports.description = 'Performant, scrollable list of data.'; -exports.examples = [ - { - title: 'Simple list of items', - render: function(): React.Element { - return ; - }, - }, -]; diff --git a/RNTester/js/ListViewGridLayoutExample.js b/RNTester/js/ListViewGridLayoutExample.js deleted file mode 100644 index e3d6cb5b63c973..00000000000000 --- a/RNTester/js/ListViewGridLayoutExample.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow strict-local - */ - -'use strict'; - -const React = require('react'); -const ReactNative = require('react-native'); -const { - Image, - ListView, - TouchableHighlight, - StyleSheet, - Text, - View, -} = ReactNative; -const ListViewDataSource = require('ListViewDataSource'); - -import type {RNTesterProps} from 'RNTesterTypes'; - -const THUMB_URLS = [ - require('./Thumbnails/like.png'), - require('./Thumbnails/dislike.png'), - require('./Thumbnails/call.png'), - require('./Thumbnails/fist.png'), - require('./Thumbnails/bandaged.png'), - require('./Thumbnails/flowers.png'), - require('./Thumbnails/heart.png'), - require('./Thumbnails/liking.png'), - require('./Thumbnails/party.png'), - require('./Thumbnails/poke.png'), - require('./Thumbnails/superlike.png'), - require('./Thumbnails/victory.png'), -]; - -type State = {| - dataSource: ListViewDataSource, -|}; - -class ListViewGridLayoutExample extends React.Component { - state = { - dataSource: this.getInitialDataSource(), - }; - - getInitialDataSource() { - const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); - return ds.cloneWithRows(this._genRows({})); - } - - _pressData: {[key: number]: boolean} = {}; - - UNSAFE_componentWillMount() { - this._pressData = {}; - } - - render() { - return ( - // ListView wraps ScrollView and so takes on its properties. - // With that in mind you can use the ScrollView's contentContainerStyle prop to style the items. - - ); - } - - _renderRow = (rowData: string, sectionID: number, rowID: number) => { - const rowHash = Math.abs(hashCode(rowData)); - const imgSource = THUMB_URLS[rowHash % THUMB_URLS.length]; - return ( - this._pressRow(rowID)} - underlayColor="transparent"> - - - - {rowData} - - - - ); - }; - - _genRows(pressData: {[key: number]: boolean}): Array { - const dataBlob = []; - for (let ii = 0; ii < 100; ii++) { - const pressedText = pressData[ii] ? ' (X)' : ''; - dataBlob.push('Cell ' + ii + pressedText); - } - return dataBlob; - } - - _pressRow = (rowID: number) => { - this._pressData[rowID] = !this._pressData[rowID]; - this.setState({ - dataSource: this.state.dataSource.cloneWithRows( - this._genRows(this._pressData), - ), - }); - }; -} - -/* eslint no-bitwise: 0 */ -const hashCode = function(str) { - let hash = 15; - for (let ii = str.length - 1; ii >= 0; ii--) { - hash = (hash << 5) - hash + str.charCodeAt(ii); - } - return hash; -}; - -const styles = StyleSheet.create({ - list: { - justifyContent: 'space-around', - flexDirection: 'row', - flexWrap: 'wrap', - alignItems: 'flex-start', - }, - row: { - justifyContent: 'center', - padding: 5, - margin: 3, - width: 100, - height: 100, - backgroundColor: '#F6F6F6', - alignItems: 'center', - borderWidth: 1, - borderRadius: 5, - borderColor: '#CCC', - }, - thumb: { - width: 64, - height: 64, - }, - text: { - flex: 1, - marginTop: 5, - fontWeight: 'bold', - }, -}); - -exports.title = ' - Grid Layout'; -exports.description = 'Flexbox grid layout.'; -exports.examples = [ - { - title: 'Simple list view with grid layout', - render: function(): React.Element { - return ; - }, - }, -]; diff --git a/RNTester/js/ListViewPagingExample.js b/RNTester/js/ListViewPagingExample.js deleted file mode 100644 index aa41c9d4e5da74..00000000000000 --- a/RNTester/js/ListViewPagingExample.js +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -const React = require('react'); -const ReactNative = require('react-native'); -const { - Image, - LayoutAnimation, - ListView, - StyleSheet, - Text, - TouchableOpacity, - View, -} = ReactNative; - -const NativeModules = require('NativeModules'); -const {UIManager} = NativeModules; - -const THUMB_URLS = [ - require('./Thumbnails/like.png'), - require('./Thumbnails/dislike.png'), - require('./Thumbnails/call.png'), - require('./Thumbnails/fist.png'), - require('./Thumbnails/bandaged.png'), - require('./Thumbnails/flowers.png'), - require('./Thumbnails/heart.png'), - require('./Thumbnails/liking.png'), - require('./Thumbnails/party.png'), - require('./Thumbnails/poke.png'), - require('./Thumbnails/superlike.png'), - require('./Thumbnails/victory.png'), -]; -const NUM_SECTIONS = 100; -const NUM_ROWS_PER_SECTION = 10; - -class Thumb extends React.Component<{}, $FlowFixMeState> { - UNSAFE_componentWillMount() { - UIManager.setLayoutAnimationEnabledExperimental && - UIManager.setLayoutAnimationEnabledExperimental(true); - } - - _getThumbIdx = () => { - return Math.floor(Math.random() * THUMB_URLS.length); - }; - - _onPressThumb = () => { - const config = - layoutAnimationConfigs[ - this.state.thumbIndex % layoutAnimationConfigs.length - ]; - LayoutAnimation.configureNext(config); - this.setState({ - thumbIndex: this._getThumbIdx(), - dir: this.state.dir === 'row' ? 'column' : 'row', - }); - }; - - state = {thumbIndex: this._getThumbIdx(), dir: 'row'}; - - render() { - return ( - - - - - {this.state.dir === 'column' ? ( - - Oooo, look at this new text! So awesome it may just be crazy. Let me - keep typing here so it wraps at least one line. - - ) : ( - - )} - - ); - } -} - -class ListViewPagingExample extends React.Component<$FlowFixMeProps, *> { - constructor(props) { - super(props); - const getSectionData = (dataBlob, sectionID) => { - return dataBlob[sectionID]; - }; - const getRowData = (dataBlob, sectionID, rowID) => { - return dataBlob[rowID]; - }; - - const dataSource = new ListView.DataSource({ - getRowData: getRowData, - getSectionHeaderData: getSectionData, - rowHasChanged: (row1, row2) => row1 !== row2, - sectionHeaderHasChanged: (s1, s2) => s1 !== s2, - }); - - const dataBlob = {}; - const sectionIDs = []; - const rowIDs = []; - for (let ii = 0; ii < NUM_SECTIONS; ii++) { - const sectionName = 'Section ' + ii; - sectionIDs.push(sectionName); - dataBlob[sectionName] = sectionName; - rowIDs[ii] = []; - - for (let jj = 0; jj < NUM_ROWS_PER_SECTION; jj++) { - const rowName = 'S' + ii + ', R' + jj; - rowIDs[ii].push(rowName); - dataBlob[rowName] = rowName; - } - } - - this.state = { - dataSource: dataSource.cloneWithRowsAndSections( - dataBlob, - sectionIDs, - rowIDs, - ), - headerPressCount: 0, - }; - } - - renderRow = ( - rowData: string, - sectionID: string, - rowID: string, - ): React.Element => { - return ; - }; - - renderSectionHeader = (sectionData: string, sectionID: string) => { - return ( - - {sectionData} - - ); - }; - - renderHeader = () => { - const headerLikeText = - this.state.headerPressCount % 2 ? ( - - 1 Like - - ) : null; - return ( - - {headerLikeText} - - Table Header (click me) - - - ); - }; - - renderFooter = () => { - return ( - - console.log('Footer!')} style={styles.text}> - Table Footer - - - ); - }; - - render() { - return ( - - console.log({visibleRows, changedRows}) - } - renderHeader={this.renderHeader} - renderFooter={this.renderFooter} - renderSectionHeader={this.renderSectionHeader} - renderRow={this.renderRow} - initialListSize={10} - pageSize={4} - scrollRenderAheadDistance={500} - stickySectionHeadersEnabled - /> - ); - } - - _onPressHeader = () => { - const config = - layoutAnimationConfigs[ - Math.floor(this.state.headerPressCount / 2) % - layoutAnimationConfigs.length - ]; - LayoutAnimation.configureNext(config); - this.setState({headerPressCount: this.state.headerPressCount + 1}); - }; -} - -const styles = StyleSheet.create({ - listview: { - backgroundColor: '#B0C4DE', - }, - header: { - height: 40, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#3B5998', - flexDirection: 'row', - }, - text: { - color: 'white', - paddingHorizontal: 8, - }, - buttonContents: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - marginHorizontal: 5, - marginVertical: 3, - padding: 5, - backgroundColor: '#EAEAEA', - borderRadius: 3, - paddingVertical: 10, - }, - img: { - width: 64, - height: 64, - marginHorizontal: 10, - backgroundColor: 'transparent', - }, - section: { - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'flex-start', - padding: 6, - backgroundColor: '#5890ff', - }, -}); - -const animations = { - layout: { - spring: { - duration: 750, - create: { - duration: 300, - type: LayoutAnimation.Types.easeInEaseOut, - property: LayoutAnimation.Properties.opacity, - }, - update: { - type: LayoutAnimation.Types.spring, - springDamping: 0.4, - }, - }, - easeInEaseOut: { - duration: 300, - create: { - type: LayoutAnimation.Types.easeInEaseOut, - property: LayoutAnimation.Properties.scaleXY, - }, - update: { - delay: 100, - type: LayoutAnimation.Types.easeInEaseOut, - }, - }, - }, -}; - -const layoutAnimationConfigs = [ - animations.layout.spring, - animations.layout.easeInEaseOut, -]; - -exports.title = ' - Paging'; -exports.description = 'Floating headers & layout animations.'; -exports.examples = [ - { - title: 'Simple list view with pagination', - render: function(): React.Element { - return ; - }, - }, -]; diff --git a/RNTester/js/RNTesterApp.ios.js b/RNTester/js/RNTesterApp.ios.js index 0ba8d62eb7f062..c0c467261a82a5 100644 --- a/RNTester/js/RNTesterApp.ios.js +++ b/RNTester/js/RNTesterApp.ios.js @@ -43,8 +43,6 @@ type Props = { }; YellowBox.ignoreWarnings([ - 'ListView and SwipeableListView are deprecated', - 'ListView is deprecated', 'Module RCTImagePickerManager requires main queue setup', ]); diff --git a/RNTester/js/RNTesterList.android.js b/RNTester/js/RNTesterList.android.js index 9f1b648c7a5b41..61684752699a73 100644 --- a/RNTester/js/RNTesterList.android.js +++ b/RNTester/js/RNTesterList.android.js @@ -33,18 +33,6 @@ const ComponentExamples: Array = [ key: 'ImageExample', module: require('./ImageExample'), }, - { - key: 'ListViewExample', - module: require('./ListViewExample'), - }, - { - key: 'ListViewGridLayoutExample', - module: require('./ListViewGridLayoutExample'), - }, - { - key: 'ListViewPagingExample', - module: require('./ListViewPagingExample'), - }, { key: 'ModalExample', module: require('./ModalExample'), @@ -87,10 +75,6 @@ const ComponentExamples: Array = [ key: 'SwipeableFlatListExample', module: require('./SwipeableFlatListExample'), }, - { - key: 'SwipeableListViewExample', - module: require('./SwipeableListViewExample'), - }, { key: 'SwitchExample', module: require('./SwitchExample'), @@ -134,6 +118,10 @@ const ComponentExamples: Array = [ ]; const APIExamples: Array = [ + { + key: 'AccessibilityExample', + module: require('./AccessibilityExample'), + }, { key: 'AccessibilityAndroidExample', /* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found diff --git a/RNTester/js/RNTesterList.ios.js b/RNTester/js/RNTesterList.ios.js index b2302c238c4ded..b2e9b49ab5db3e 100644 --- a/RNTester/js/RNTesterList.ios.js +++ b/RNTester/js/RNTesterList.ios.js @@ -58,21 +58,6 @@ const ComponentExamples: Array = [ module: require('./LayoutEventsExample'), supportsTVOS: true, }, - { - key: 'ListViewExample', - module: require('./ListViewExample'), - supportsTVOS: true, - }, - { - key: 'ListViewGridLayoutExample', - module: require('./ListViewGridLayoutExample'), - supportsTVOS: true, - }, - { - key: 'ListViewPagingExample', - module: require('./ListViewPagingExample'), - supportsTVOS: true, - }, { key: 'MaskedViewExample', module: require('./MaskedViewExample'), @@ -148,11 +133,6 @@ const ComponentExamples: Array = [ module: require('./SwipeableFlatListExample'), supportsTVOS: false, }, - { - key: 'SwipeableListViewExample', - module: require('./SwipeableListViewExample'), - supportsTVOS: false, - }, { key: 'SwitchExample', module: require('./SwitchExample'), @@ -191,6 +171,11 @@ const ComponentExamples: Array = [ ]; const APIExamples: Array = [ + { + key: 'AccessibilityExample', + module: require('./AccessibilityExample'), + supportsTVOS: false, + }, { key: 'AccessibilityIOSExample', module: require('./AccessibilityIOSExample'), diff --git a/RNTester/js/SwipeableFlatListExample.js b/RNTester/js/SwipeableFlatListExample.js index feef9fff12a9f6..d79336cc30aedb 100644 --- a/RNTester/js/SwipeableFlatListExample.js +++ b/RNTester/js/SwipeableFlatListExample.js @@ -47,7 +47,7 @@ class SwipeableFlatListExample extends React.Component { render() { return ( '} + title={this.props.navigator ? null : ''} noSpacer={true} noScroll={true}> { - state = { - dataSource: SwipeableListView.getNewDataSource().cloneWithRowsAndSections( - ...this._genDataSource({}), - ), - }; - - _pressData: {[key: number]: boolean} = {}; - - render(): React.Node { - return ( - '} - noSpacer={true} - noScroll={true}> - { - return ( - - { - Alert.alert( - 'Tips', - 'You could do something with this row: ' + rowData.text, - ); - }}> - Remove - - - ); - }} - renderRow={this._renderRow} - renderSeparator={this._renderSeperator} - /> - - ); - } - - _renderRow(rowData: Object, sectionID: string, rowID: string) { - const rowHash = Math.abs(hashCode(rowData.id)); - const imgSource = THUMB_URLS[rowHash % THUMB_URLS.length]; - return ( - {}}> - - - - - {rowData.id + ' - ' + LOREM_IPSUM.substr(0, (rowHash % 301) + 10)} - - - - - ); - } - - _genDataSource(pressData: {[key: number]: boolean}) { - const dataBlob = {}; - const sectionIDs = ['Section 0']; - const rowIDs = [[]]; - /** - * dataBlob example below: - * { - * 'Section 0': { - * 'Row 0': { - * id: '0', - * text: 'row 0 text' - * }, - * 'Row 1': { - * id: '1', - * text: 'row 1 text' - * } - * } - * } - */ - - // Only one section in this example - dataBlob['Section 0'] = {}; - for (let ii = 0; ii < 100; ii++) { - const pressedText = pressData[ii] ? ' (pressed)' : ''; - dataBlob[sectionIDs[0]]['Row ' + ii] = { - id: 'Row ' + ii, - text: 'Row ' + ii + pressedText, - }; - rowIDs[0].push('Row ' + ii); - } - return [dataBlob, sectionIDs, rowIDs]; - } - - _renderSeperator( - sectionID: string, - rowID: string, - adjacentRowHighlighted: boolean, - ) { - return ( - - ); - } -} - -const THUMB_URLS = [ - require('./Thumbnails/like.png'), - require('./Thumbnails/dislike.png'), - require('./Thumbnails/call.png'), - require('./Thumbnails/fist.png'), - require('./Thumbnails/bandaged.png'), - require('./Thumbnails/flowers.png'), - require('./Thumbnails/heart.png'), - require('./Thumbnails/liking.png'), - require('./Thumbnails/party.png'), - require('./Thumbnails/poke.png'), - require('./Thumbnails/superlike.png'), - require('./Thumbnails/victory.png'), -]; - -const LOREM_IPSUM = [ - 'Lorem ipsum dolor sit amet, ius ad pertinax oportere accommodare, an vix ', - 'civibus corrumpit referrentur. Te nam case ludus inciderint, te mea facilisi ', - 'adipiscing. Sea id integre luptatum. In tota sale consequuntur nec. Erat ', - 'ocurreret mei ei. Eu paulo sapientem vulputate est, vel an accusam ', - 'intellegam interesset. Nam eu stet pericula reprimique, ea vim illud modus, ', - 'putant invidunt reprehendunt ne qui.', -].join(''); - -/* eslint-disable no-bitwise */ -const hashCode = str => { - let hash = 15; - for (let ii = str.length - 1; ii >= 0; ii--) { - hash = (hash << 5) - hash + str.charCodeAt(ii); - } - return hash; -}; -/* eslint-enable no-bitwise */ - -const styles = StyleSheet.create({ - row: { - flexDirection: 'row', - justifyContent: 'center', - padding: 10, - backgroundColor: '#F6F6F6', - }, - thumb: { - width: 64, - height: 64, - }, - text: { - flex: 1, - }, - actionsContainer: { - flex: 1, - flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'center', - }, -}); - -exports.title = ''; -exports.description = 'Performant, scrollable, swipeable list of data.'; -exports.examples = [ - { - title: 'Simple swipable list', - render: function(): React.Element { - return ; - }, - }, -]; diff --git a/RNTester/js/TextExample.android.js b/RNTester/js/TextExample.android.js index 6726943b261f52..1eeeda49cc6bcd 100644 --- a/RNTester/js/TextExample.android.js +++ b/RNTester/js/TextExample.android.js @@ -325,6 +325,12 @@ class TextExample extends React.Component<{}> { right right right right right right right right right right right right right + + justify (works when api level >= 26 otherwise fallbacks to "left"): + this text component{"'"}s contents are laid out with "textAlign: + justify" and as you can see all of the lines except the last one + span the available width of the parent container. + @@ -615,11 +621,13 @@ class TextExample extends React.Component<{}> { Works with other text styles + + {'testπŸ™ƒ'.substring(0, 5)} + ); } } - const styles = StyleSheet.create({ backgroundColorText: { left: 5, diff --git a/RNTester/js/TextExample.ios.js b/RNTester/js/TextExample.ios.js index d52f9a8c6025c8..320854f88ae017 100644 --- a/RNTester/js/TextExample.ios.js +++ b/RNTester/js/TextExample.ios.js @@ -428,6 +428,12 @@ exports.examples = [ ); }, }, + { + title: "Substring Emoji (should only see 'test')", + render: function() { + return {'testπŸ™ƒ'.substring(0, 5)}; + }, + }, { title: 'Text metrics', render: function() { diff --git a/RNTester/js/tumblr_mfqekpMktw1rn90umo1_500.gif b/RNTester/js/tumblr_mfqekpMktw1rn90umo1_500.gif new file mode 100644 index 00000000000000..3e945c8c207d18 Binary files /dev/null and b/RNTester/js/tumblr_mfqekpMktw1rn90umo1_500.gif differ diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index 4e05e2b43eb2c3..9530b5a225f0e2 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -85,11 +85,15 @@ - (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]], @"Event body dictionary must include a 'target' property containing a React tag"); } + + if (!body[@"target"]) { + return; + } name = RCTNormalizeInputEventName(name); [_bridge enqueueJSCall:@"RCTEventEmitter" method:@"receiveEvent" - args:body ? @[body[@"target"], name, body] : @[body[@"target"], name] + args:@[body[@"target"], name, body] completion:NULL]; } diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index 4bde8957cb7cf9..8f8f19b8354527 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -14,11 +14,6 @@ #if RCT_DEV -static BOOL RCTIsIOS8OrEarlier() -{ - return [UIDevice currentDevice].systemVersion.floatValue < 9; -} - @interface RCTKeyCommand : NSObject @property (nonatomic, strong) UIKeyCommand *keyCommand; @@ -119,7 +114,7 @@ - (void)RCT_handleKeyCommand:(UIKeyCommand *)key // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand: // method gets called repeatedly if the command key is held down. static NSTimeInterval lastCommand = 0; - if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastCommand > 0.5) { + if (CACurrentMediaTime() - lastCommand > 0.5) { for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { if ([command.keyCommand.input isEqualToString:key.input] && command.keyCommand.modifierFlags == key.modifierFlags) { @@ -171,7 +166,7 @@ - (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand: // method gets called repeatedly if the command key is held down. - if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastDoubleCommand > 0.5) { + if (CACurrentMediaTime() - lastDoubleCommand > 0.5) { command.block(key); lastDoubleCommand = CACurrentMediaTime(); } @@ -189,44 +184,14 @@ - (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key @end -@implementation UIApplication (RCTKeyCommands) - -// Required for iOS 8.x -- (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event -{ - if (action == @selector(RCT_handleKeyCommand:)) { - [self RCT_handleKeyCommand:sender]; - return YES; - } else if (action == @selector(RCT_handleDoublePressKeyCommand:)) { - [self RCT_handleDoublePressKeyCommand:sender]; - return YES; - } - return [self RCT_sendAction:action to:target from:sender forEvent:event]; -} - -@end - @implementation RCTKeyCommands + (void)initialize { - if (RCTIsIOS8OrEarlier()) { - - // swizzle UIApplication - RCTSwapInstanceMethods([UIApplication class], - @selector(keyCommands), - @selector(RCT_keyCommands)); - - RCTSwapInstanceMethods([UIApplication class], - @selector(sendAction:to:from:forEvent:), - @selector(RCT_sendAction:to:from:forEvent:)); - } else { - - // swizzle UIResponder - RCTSwapInstanceMethods([UIResponder class], - @selector(keyCommands), - @selector(RCT_keyCommands)); - } + // swizzle UIResponder + RCTSwapInstanceMethods([UIResponder class], + @selector(keyCommands), + @selector(RCT_keyCommands)); } + (instancetype)sharedInstance @@ -254,17 +219,6 @@ - (void)registerKeyCommandWithInput:(NSString *)input { RCTAssertMainQueue(); - if (input.length && flags && RCTIsIOS8OrEarlier()) { - - // Workaround around the first cmd not working: http://openradar.appspot.com/19613391 - // You can register just the cmd key and do nothing. This ensures that - // command-key modified commands will work first time. Fixed in iOS 9. - - [self registerKeyCommandWithInput:@"" - modifierFlags:flags - action:nil]; - } - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input modifierFlags:flags action:@selector(RCT_handleKeyCommand:)]; @@ -306,17 +260,6 @@ - (void)registerDoublePressKeyCommandWithInput:(NSString *)input { RCTAssertMainQueue(); - if (input.length && flags && RCTIsIOS8OrEarlier()) { - - // Workaround around the first cmd not working: http://openradar.appspot.com/19613391 - // You can register just the cmd key and do nothing. This ensures that - // command-key modified commands will work first time. Fixed in iOS 9. - - [self registerDoublePressKeyCommandWithInput:@"" - modifierFlags:flags - action:nil]; - } - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input modifierFlags:flags action:@selector(RCT_handleDoublePressKeyCommand:)]; diff --git a/React/Base/RCTModuleData.mm b/React/Base/RCTModuleData.mm index ba2dbd5196f9d4..c02c8810f6e171 100644 --- a/React/Base/RCTModuleData.mm +++ b/React/Base/RCTModuleData.mm @@ -330,9 +330,11 @@ - (void)gatherConstants - (dispatch_queue_t)methodQueue { - (void)[self instance]; - RCTAssert(_methodQueue != nullptr, @"Module %@ has no methodQueue (instance: %@, bridge.valid: %d)", - self, _instance, _bridge.valid); + if (_bridge.valid) { + id instance = self.instance; + RCTAssert(_methodQueue != nullptr, @"Module %@ has no methodQueue (instance: %@)", + self, instance); + } return _methodQueue; } diff --git a/React/Base/RCTModuleMethod.mm b/React/Base/RCTModuleMethod.mm index 767e11abd4116a..3d93a785b3147f 100644 --- a/React/Base/RCTModuleMethod.mm +++ b/React/Base/RCTModuleMethod.mm @@ -90,8 +90,8 @@ static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector) static BOOL RCTParseUnused(const char **input) { - return RCTReadString(input, "__unused") || - RCTReadString(input, "__attribute__((unused))"); + return RCTReadString(input, "__attribute__((unused))") || + RCTReadString(input, "__unused"); } static RCTNullability RCTParseNullability(const char **input) @@ -106,9 +106,11 @@ static RCTNullability RCTParseNullability(const char **input) static RCTNullability RCTParseNullabilityPostfix(const char **input) { - if (RCTReadString(input, "_Nullable")) { + if (RCTReadString(input, "_Nullable") || + RCTReadString(input, "__nullable")) { return RCTNullable; - } else if (RCTReadString(input, "_Nonnull")) { + } else if (RCTReadString(input, "_Nonnull") || + RCTReadString(input, "__nonnull")) { return RCTNonnullable; } return RCTNullabilityUnspecified; @@ -142,17 +144,34 @@ static BOOL checkCallbackMultipleInvocations(BOOL *didInvoke) { // Parse type if (RCTReadChar(&input, '(')) { RCTSkipWhitespace(&input); - - BOOL unused = RCTParseUnused(&input); - RCTSkipWhitespace(&input); - + + // 5 cases that both nullable and __unused exist + // 1: foo:(nullable __unused id)foo 2: foo:(nullable id __unused)foo + // 3: foo:(__unused id _Nullable)foo 4: foo:(id __unused _Nullable)foo + // 5: foo:(id _Nullable __unused)foo RCTNullability nullability = RCTParseNullability(&input); RCTSkipWhitespace(&input); + + BOOL unused = RCTParseUnused(&input); + RCTSkipWhitespace(&input); NSString *type = RCTParseType(&input); RCTSkipWhitespace(&input); + if (nullability == RCTNullabilityUnspecified) { nullability = RCTParseNullabilityPostfix(&input); + RCTSkipWhitespace(&input); + if (!unused) { + unused = RCTParseUnused(&input); + RCTSkipWhitespace(&input); + if (unused && nullability == RCTNullabilityUnspecified) { + nullability = RCTParseNullabilityPostfix(&input); + RCTSkipWhitespace(&input); + } + } + } else if (!unused) { + unused = RCTParseUnused(&input); + RCTSkipWhitespace(&input); } [args addObject:[[RCTMethodArgument alloc] initWithType:type nullability:nullability diff --git a/React/Base/RCTMultipartDataTask.m b/React/Base/RCTMultipartDataTask.m index 3708a7d3575c34..285eaa9aa6c243 100644 --- a/React/Base/RCTMultipartDataTask.m +++ b/React/Base/RCTMultipartDataTask.m @@ -11,20 +11,6 @@ @interface RCTMultipartDataTask () + #include #include @@ -28,7 +30,7 @@ class DispatchMessageQueueThread : public MessageQueueThread { dispatch_async(queue, block); } } - void runOnQueueSync(std::function&& func) override { + void runOnQueueSync(std::function&& __unused func) override { LOG(FATAL) << "Unsupported operation"; } void quitSynchronous() override { diff --git a/React/CxxModule/RCTNativeModule.mm b/React/CxxModule/RCTNativeModule.mm index ec88558b873da8..b59a68312a80d5 100644 --- a/React/CxxModule/RCTNativeModule.mm +++ b/React/CxxModule/RCTNativeModule.mm @@ -71,17 +71,19 @@ invokeInner(weakBridge, weakModuleData, methodId, std::move(params)); }; - if (m_bridge.valid) { - dispatch_queue_t queue = m_moduleData.methodQueue; - if (queue == RCTJSThread) { - block(); - } else if (queue) { - dispatch_async(queue, block); - } - } else { - RCTLog(@"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) with an invalid bridge.", - methodId, m_moduleData.name); + dispatch_queue_t queue = m_moduleData.methodQueue; + if (queue == RCTJSThread) { + block(); + } else if (queue) { + dispatch_async(queue, block); + } + + #ifdef RCT_DEV + if (!queue) { + RCTLog(@"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) without a method queue.", + methodId, m_moduleData.name); } + #endif } MethodCallResult RCTNativeModule::callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &¶ms) { diff --git a/React/DevSupport/RCTDevMenu.m b/React/DevSupport/RCTDevMenu.m index eff6040f8d1eb0..d631fa5dc7525c 100644 --- a/React/DevSupport/RCTDevMenu.m +++ b/React/DevSupport/RCTDevMenu.m @@ -219,9 +219,12 @@ - (void)setDefaultJSBundle { if (!devSettings.isRemoteDebuggingAvailable) { [items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Remote JS Debugger Unavailable" handler:^{ + NSString *message = RCTTurboModuleEnabled() ? + @"You cannot use remote JS debugging when TurboModule system is enabled" : + @"You need to include the RCTWebSocket library to enable remote JS debugging"; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Remote JS Debugger Unavailable" - message:@"You need to include the RCTWebSocket library to enable remote JS debugging" + message:message preferredStyle:UIAlertControllerStyleAlert]; __weak typeof(alertController) weakAlertController = alertController; [alertController addAction: diff --git a/React/Fabric/Mounting/ComponentViews/ActivityIndicator/RCTActivityIndicatorViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ActivityIndicator/RCTActivityIndicatorViewComponentView.mm index d6270063ba1ca2..95f21877cc1164 100644 --- a/React/Fabric/Mounting/ComponentViews/ActivityIndicator/RCTActivityIndicatorViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/ActivityIndicator/RCTActivityIndicatorViewComponentView.mm @@ -7,8 +7,9 @@ #import "RCTActivityIndicatorViewComponentView.h" -#import -#import +#import +#import +#import using namespace facebook::react; diff --git a/React/Fabric/Mounting/ComponentViews/Slider/RCTSliderComponentView.mm b/React/Fabric/Mounting/ComponentViews/Slider/RCTSliderComponentView.mm index 2fc56881ee8cfd..2f6f670d883bb7 100644 --- a/React/Fabric/Mounting/ComponentViews/Slider/RCTSliderComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/Slider/RCTSliderComponentView.mm @@ -8,9 +8,9 @@ #import "RCTSliderComponentView.h" #import -#import +#import +#import #import -#import #import #import "MainQueueExecutor.h" @@ -301,10 +301,12 @@ - (void)onChange:(UISlider *)sender withContinuous:(BOOL)continuous } if (continuous && _previousValue != value) { - std::dynamic_pointer_cast(_eventEmitter)->onValueChange(value); + std::dynamic_pointer_cast(_eventEmitter) + ->onValueChange(SliderOnValueChangeStruct{.value = static_cast(value)}); } if (!continuous) { - std::dynamic_pointer_cast(_eventEmitter)->onSlidingComplete(value); + std::dynamic_pointer_cast(_eventEmitter) + ->onSlidingComplete(SliderOnSlidingCompleteStruct{.value = static_cast(value)}); } _previousValue = value; diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h index 3e85e52aac64c3..a289ed2e82fa1e 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h @@ -12,9 +12,9 @@ #import #import #import +#import #import #import -#import NS_ASSUME_NONNULL_BEGIN diff --git a/React/Fabric/Mounting/MountItems/RCTUpdateEventEmitterMountItem.h b/React/Fabric/Mounting/MountItems/RCTUpdateEventEmitterMountItem.h index 7d16a9dc27edd3..5c0595f7fc4230 100644 --- a/React/Fabric/Mounting/MountItems/RCTUpdateEventEmitterMountItem.h +++ b/React/Fabric/Mounting/MountItems/RCTUpdateEventEmitterMountItem.h @@ -9,7 +9,7 @@ #import #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/React/Fabric/Mounting/MountItems/RCTUpdateStateMountItem.h b/React/Fabric/Mounting/MountItems/RCTUpdateStateMountItem.h new file mode 100644 index 00000000000000..f92eb00be2422c --- /dev/null +++ b/React/Fabric/Mounting/MountItems/RCTUpdateStateMountItem.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Updates state of a component view. + */ +@interface RCTUpdateStateMountItem : NSObject + +- (instancetype)initWithTag:(ReactTag)tag + oldState:(facebook::react::State::Shared)oldState + newState:(facebook::react::State::Shared)newState; + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/MountItems/RCTUpdateStateMountItem.mm b/React/Fabric/Mounting/MountItems/RCTUpdateStateMountItem.mm new file mode 100644 index 00000000000000..ec6949f204a750 --- /dev/null +++ b/React/Fabric/Mounting/MountItems/RCTUpdateStateMountItem.mm @@ -0,0 +1,39 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTUpdateStateMountItem.h" + +#import "RCTComponentViewRegistry.h" + +using namespace facebook::react; + +@implementation RCTUpdateStateMountItem { + ReactTag _tag; + State::Shared _oldState; + State::Shared _newState; +} + +- (instancetype)initWithTag:(ReactTag)tag + oldState:(facebook::react::State::Shared)oldState + newState:(facebook::react::State::Shared)newState +{ + if (self = [super init]) { + _tag = tag; + _oldState = oldState; + _newState = newState; + } + + return self; +} + +- (void)executeWithRegistry:(RCTComponentViewRegistry *)registry +{ + UIView *componentView = [registry componentViewByTag:_tag]; + [componentView updateState:_newState oldState:_oldState]; +} + +@end diff --git a/React/Fabric/Mounting/RCTComponentViewProtocol.h b/React/Fabric/Mounting/RCTComponentViewProtocol.h index 116f6ef4295ba2..f049b8f33a66c6 100644 --- a/React/Fabric/Mounting/RCTComponentViewProtocol.h +++ b/React/Fabric/Mounting/RCTComponentViewProtocol.h @@ -8,10 +8,11 @@ #import #import +#import #import #import #import -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -56,6 +57,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateLocalData:(facebook::react::SharedLocalData)localData oldLocalData:(facebook::react::SharedLocalData)oldLocalData; +/* + * Called for updating component's state. + * Receiver must update native view according to changed state. + */ +- (void)updateState:(facebook::react::State::Shared)state oldState:(facebook::react::State::Shared)oldState; + /* * Called for updating component's event handlers set. * Receiver must cache `eventEmitter` object inside and use it for emitting diff --git a/React/Fabric/Mounting/RCTMountingManager.h b/React/Fabric/Mounting/RCTMountingManager.h index d4a64da800e6bd..0f65e39812a45f 100644 --- a/React/Fabric/Mounting/RCTMountingManager.h +++ b/React/Fabric/Mounting/RCTMountingManager.h @@ -39,6 +39,9 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)optimisticallyCreateComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle; +- (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag + oldProps:(facebook::react::SharedProps)oldProps + newProps:(facebook::react::SharedProps)newProps; @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index 105f39919ecc1d..8bcc71cc454d8d 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -25,6 +25,7 @@ #import "RCTUpdateLayoutMetricsMountItem.h" #import "RCTUpdateLocalDataMountItem.h" #import "RCTUpdatePropsMountItem.h" +#import "RCTUpdateStateMountItem.h" using namespace facebook::react; @@ -87,6 +88,13 @@ - (void)performTransactionWithMutations:(facebook::react::ShadowViewMutationList newLocalData:mutation.newChildShadowView.localData]]; } + // State + if (mutation.newChildShadowView.state) { + [mountItems addObject:[[RCTUpdateStateMountItem alloc] initWithTag:mutation.newChildShadowView.tag + oldState:nullptr + newState:mutation.newChildShadowView.state]]; + } + // Layout if (mutation.newChildShadowView.layoutMetrics != EmptyLayoutMetrics) { [mountItems addObject:[[RCTUpdateLayoutMetricsMountItem alloc] @@ -142,6 +150,14 @@ - (void)performTransactionWithMutations:(facebook::react::ShadowViewMutationList [mountItems addObject:mountItem]; } + // State + if (oldChildShadowView.state != newChildShadowView.state) { + RCTUpdateStateMountItem *mountItem = [[RCTUpdateStateMountItem alloc] initWithTag:newChildShadowView.tag + oldState:oldChildShadowView.state + newState:newChildShadowView.state]; + [mountItems addObject:mountItem]; + } + // Layout if (oldChildShadowView.layoutMetrics != newChildShadowView.layoutMetrics) { RCTUpdateLayoutMetricsMountItem *mountItem = @@ -176,6 +192,17 @@ - (void)_performMountItems:(NSArray *)mountItems rootTag:( [self.delegate mountingManager:self didMountComponentsWithRootTag:rootTag]; } +- (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag + oldProps:(SharedProps)oldProps + newProps:(SharedProps)newProps +{ + RCTUpdatePropsMountItem *mountItem = [[RCTUpdatePropsMountItem alloc] initWithTag:reactTag + oldProps:oldProps + newProps:newProps]; + RCTAssertMainQueue(); + [mountItem executeWithRegistry:self->_componentViewRegistry]; +} + - (void)optimisticallyCreateComponentViewWithComponentHandle:(ComponentHandle)componentHandle { if (RCTIsMainQueue()) { diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.h b/React/Fabric/Mounting/UIView+ComponentViewProtocol.h index b30cff79fb6bf0..7ea8dc5d7d7f5e 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.h +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.h @@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateLocalData:(facebook::react::SharedLocalData)localData oldLocalData:(facebook::react::SharedLocalData)oldLocalData; +- (void)updateState:(facebook::react::State::Shared)state oldState:(facebook::react::State::Shared)oldState; + - (void)updateLayoutMetrics:(facebook::react::LayoutMetrics)layoutMetrics oldLayoutMetrics:(facebook::react::LayoutMetrics)oldLayoutMetrics; diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm index 423945d261f5b4..428a7844cd8c19 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm @@ -43,6 +43,11 @@ - (void)updateLocalData:(SharedLocalData)localData oldLocalData:(SharedLocalData // Default implementation does nothing. } +- (void)updateState:(facebook::react::State::Shared)state oldState:(facebook::react::State::Shared)oldState +{ + // Default implementation does nothing. +} + - (void)updateLayoutMetrics:(LayoutMetrics)layoutMetrics oldLayoutMetrics:(LayoutMetrics)oldLayoutMetrics { if (layoutMetrics.frame != oldLayoutMetrics.frame) { diff --git a/React/Fabric/RCTScheduler.h b/React/Fabric/RCTScheduler.h index a57c2cbf981989..709250d39bfad8 100644 --- a/React/Fabric/RCTScheduler.h +++ b/React/Fabric/RCTScheduler.h @@ -9,6 +9,7 @@ #import #import +#import #import #import #import @@ -54,6 +55,8 @@ NS_ASSUME_NONNULL_BEGIN layoutContext:(facebook::react::LayoutContext)layoutContext surfaceId:(facebook::react::SurfaceId)surfaceId; +- (const facebook::react::ComponentDescriptor &)getComponentDescriptor:(facebook::react::ComponentHandle)handle; + @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index 60752913c55ed3..d749e5b5ddd621 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -19,26 +19,32 @@ using namespace facebook::react; -class SchedulerDelegateProxy: public SchedulerDelegate { -public: - SchedulerDelegateProxy(void *scheduler): - scheduler_(scheduler) {} - - void schedulerDidFinishTransaction(Tag rootTag, const ShadowViewMutationList &mutations, const long commitStartTime, const long layoutTime) override { +class SchedulerDelegateProxy : public SchedulerDelegate { + public: + SchedulerDelegateProxy(void *scheduler) : scheduler_(scheduler) {} + + void schedulerDidFinishTransaction( + Tag rootTag, + const ShadowViewMutationList &mutations, + const long commitStartTime, + const long layoutTime) override + { RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; [scheduler.delegate schedulerDidFinishTransaction:mutations rootTag:rootTag]; } - void schedulerDidRequestPreliminaryViewAllocation(SurfaceId surfaceId, ComponentName componentName, bool isLayoutable, ComponentHandle componentHandle) override { - if (!isLayoutable) { + void schedulerDidRequestPreliminaryViewAllocation(SurfaceId surfaceId, const ShadowView &shadowView) override + { + bool isLayoutableShadowNode = shadowView.layoutMetrics != EmptyLayoutMetrics; + if (!isLayoutableShadowNode) { return; } RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; - [scheduler.delegate schedulerOptimisticallyCreateComponentViewWithComponentHandle:componentHandle]; + [scheduler.delegate schedulerOptimisticallyCreateComponentViewWithComponentHandle:shadowView.componentHandle]; } -private: + private: void *scheduler_; }; @@ -51,7 +57,8 @@ - (instancetype)initWithContextContainer:(std::shared_ptr)contextContainer { if (self = [super init]) { _delegateProxy = std::make_shared((__bridge void *)self); - _scheduler = std::make_shared(std::static_pointer_cast(contextContainer), getDefaultComponentRegistryFactory()); + _scheduler = std::make_shared( + std::static_pointer_cast(contextContainer), getDefaultComponentRegistryFactory()); _scheduler->setDelegate(_delegateProxy.get()); } @@ -72,17 +79,9 @@ - (void)startSurfaceWithSurfaceId:(SurfaceId)surfaceId SystraceSection s("-[RCTScheduler startSurfaceWithSurfaceId:...]"); auto props = convertIdToFollyDynamic(initialProps); - _scheduler->startSurface( - surfaceId, - RCTStringFromNSString(moduleName), - props, - layoutConstraints, - layoutContext); + _scheduler->startSurface(surfaceId, RCTStringFromNSString(moduleName), props, layoutConstraints, layoutContext); _scheduler->renderTemplateToSurface( - surfaceId, - props.getDefault("navigationConfig") - .getDefault("initialUITemplate", "") - .getString()); + surfaceId, props.getDefault("navigationConfig").getDefault("initialUITemplate", "").getString()); } - (void)stopSurfaceWithSurfaceId:(SurfaceId)surfaceId @@ -107,4 +106,9 @@ - (void)constraintSurfaceLayoutWithLayoutConstraints:(LayoutConstraints)layoutCo _scheduler->constraintSurfaceLayout(surfaceId, layoutConstraints, layoutContext); } +- (const ComponentDescriptor &)getComponentDescriptor:(ComponentHandle)handle +{ + return _scheduler->getComponentDescriptor(handle); +} + @end diff --git a/React/Fabric/RCTSurfacePresenter.h b/React/Fabric/RCTSurfacePresenter.h index c4f1f98fbfa939..1e684dd5a4f0de 100644 --- a/React/Fabric/RCTSurfacePresenter.h +++ b/React/Fabric/RCTSurfacePresenter.h @@ -10,9 +10,9 @@ #import #import -#import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -66,6 +66,8 @@ NS_ASSUME_NONNULL_BEGIN maximumSize:(CGSize)maximumSize surface:(RCTFabricSurface *)surface; +- (BOOL)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag props:(NSDictionary *)props; + @end @interface RCTSurfacePresenter (Deprecated) diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 3a18def1d25918..c2036387166ed3 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -16,17 +16,18 @@ #import #import #import +#import #import #import #import #import #import -#import #import +#import #import -#import -#import #import +#import +#import #import #import @@ -59,6 +60,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge config:(std::shared_ptr *componentView = [_mountingManager.componentViewRegistry componentViewByTag:tag]; + if (componentView == nil) { + return NO; // This view probably isn't managed by Fabric + } + ComponentHandle handle = [[componentView class] componentHandle]; + const facebook::react::ComponentDescriptor &componentDescriptor = [self._scheduler getComponentDescriptor:handle]; + + // Note: we use an empty object for `oldProps` to rely on the diffing algorithm internal to the + // RCTComponentViewProtocol::updateProps method. If there is a bug in that diffing, some props + // could get reset. One way around this would be to require all RCTComponentViewProtocol + // implementations to expose their current props so we could clone them, but that could be + // problematic for threading and other reasons. + facebook::react::SharedProps newProps = + componentDescriptor.cloneProps(nullptr, RawProps(convertIdToFollyDynamic(props))); + facebook::react::SharedProps oldProps = componentDescriptor.cloneProps(nullptr, RawProps(folly::dynamic::object())); + + [self->_mountingManager synchronouslyUpdateViewOnUIThread:tag oldProps:oldProps newProps:newProps]; + return YES; +} + #pragma mark - Private - (RCTScheduler *)_scheduler diff --git a/React/Fabric/Utils/MainRunLoopEventBeat.h b/React/Fabric/Utils/MainRunLoopEventBeat.h index eb7c94694fbbe6..b2878d9a198f46 100644 --- a/React/Fabric/Utils/MainRunLoopEventBeat.h +++ b/React/Fabric/Utils/MainRunLoopEventBeat.h @@ -7,7 +7,7 @@ #include #include -#include +#include #include namespace facebook { diff --git a/React/Fabric/Utils/RuntimeEventBeat.h b/React/Fabric/Utils/RuntimeEventBeat.h index ed7055a374aa3c..0fbecf47b809d6 100644 --- a/React/Fabric/Utils/RuntimeEventBeat.h +++ b/React/Fabric/Utils/RuntimeEventBeat.h @@ -7,7 +7,7 @@ #include #include -#include +#include #include namespace facebook { diff --git a/React/Modules/RCTDevSettings.mm b/React/Modules/RCTDevSettings.mm index 70454aebc1147e..3a2652c6e24f57 100644 --- a/React/Modules/RCTDevSettings.mm +++ b/React/Modules/RCTDevSettings.mm @@ -220,6 +220,9 @@ - (BOOL)isNuclideDebuggingAvailable - (BOOL)isRemoteDebuggingAvailable { + if (RCTTurboModuleEnabled()) { + return NO; + } Class jsDebuggingExecutorClass = objc_lookUpClass("RCTWebSocketExecutor"); return (jsDebuggingExecutorClass != nil); } diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index 5136b36907ccdd..9b6f9d6698c156 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -108,9 +108,12 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification RCTLogError(@"RCTStatusBarManager module requires that the \ UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [RCTSharedApplication() setStatusBarStyle:statusBarStyle animated:animated]; } +#pragma clang diagnostic pop } RCT_EXPORT_METHOD(setHidden:(BOOL)hidden @@ -120,8 +123,11 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification RCTLogError(@"RCTStatusBarManager module requires that the \ UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [RCTSharedApplication() setStatusBarHidden:hidden withAnimation:animation]; +#pragma clang diagnostic pop } } diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index 63f85bd1987fe5..91fcdb36120380 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -135,6 +135,14 @@ RCT_EXTERN NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplier */ - (UIView *)viewForNativeID:(NSString *)nativeID withRootTag:(NSNumber *)rootTag; +/** + * Register a view that is tagged with nativeID as its nativeID prop + * + * @param nativeID the id reference to native component relative to root view. + * @param view the view that is tagged with nativeID as its nativeID prop. + */ +- (void)setNativeID:(NSString *)nativeID forView:(UIView *)view; + /** * The view that is currently first responder, according to the JS context. */ diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 2bd50b845cc2ab..83c04e1091e6dc 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -50,6 +50,14 @@ static void RCTTraverseViewNodes(id view, void (^block)(id *_shadowViewRegistry; // RCT thread only NSMutableDictionary *_viewRegistry; // Main thread only + NSMapTable *_nativeIDRegistry; // Main thread only NSMapTable *> *_shadowViewsWithUpdatedProps; // UIManager queue only. NSHashTable *_shadowViewsWithUpdatedChildren; // UIManager queue only. @@ -106,6 +115,7 @@ - (void)invalidate self->_rootViewTags = nil; self->_shadowViewRegistry = nil; self->_viewRegistry = nil; + self->_nativeIDRegistry = nil; self->_bridge = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -131,6 +141,15 @@ - (void)invalidate return _viewRegistry; } +- (NSMapTable *)nativeIDRegistry +{ + // Should be called on main queue + if (!_nativeIDRegistry) { + _nativeIDRegistry = [NSMapTable strongToWeakObjectsMapTable]; + } + return _nativeIDRegistry; +} + - (void)setBridge:(RCTBridge *)bridge { RCTAssert(_bridge == nil, @"Should not re-use same UIManager instance"); @@ -138,6 +157,7 @@ - (void)setBridge:(RCTBridge *)bridge _shadowViewRegistry = [NSMutableDictionary new]; _viewRegistry = [NSMutableDictionary new]; + _nativeIDRegistry = [NSMapTable strongToWeakObjectsMapTable]; _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable]; _shadowViewsWithUpdatedChildren = [NSHashTable weakObjectsHashTable]; @@ -366,31 +386,27 @@ - (void)setLocalData:(NSObject *)localData forView:(UIView *)view } forTag:view.reactTag]; } -/** - * TODO(yuwang): implement the nativeID functionality in a more efficient way - * instead of searching the whole view tree - */ - (UIView *)viewForNativeID:(NSString *)nativeID withRootTag:(NSNumber *)rootTag { RCTAssertMainQueue(); - UIView *view = [self viewForReactTag:rootTag]; - return [self _lookupViewForNativeID:nativeID inView:view]; + if (!nativeID || !rootTag) { + return nil; + } + return [_nativeIDRegistry objectForKey:RCTNativeIDRegistryKey(nativeID, rootTag)]; } -- (UIView *)_lookupViewForNativeID:(NSString *)nativeID inView:(UIView *)view +- (void)setNativeID:(NSString *)nativeID forView:(UIView *)view { RCTAssertMainQueue(); - if (view != nil && [nativeID isEqualToString:view.nativeID]) { - return view; + if (!nativeID) { + return; } - - for (UIView *subview in view.subviews) { - UIView *targetView = [self _lookupViewForNativeID:nativeID inView:subview]; - if (targetView != nil) { - return targetView; + __weak RCTUIManager *weakSelf = self; + [self rootViewForReactTag:view.reactTag withCompletion:^(UIView *rootView) { + if (rootView) { + [weakSelf.nativeIDRegistry setObject:view forKey:RCTNativeIDRegistryKey(nativeID, rootView.reactTag)]; } - } - return nil; + }]; } - (void)setSize:(CGSize)size forView:(UIView *)view diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index ff871363e71b8c..d0a7f230a5d70d 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -4755,6 +4755,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; DEBUG_INFORMATION_FORMAT = dwarf; + GCC_WARN_SHADOW = NO; OTHER_LDFLAGS = "-ObjC"; PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include/double-conversion"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4772,6 +4773,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_WARN_SHADOW = NO; OTHER_LDFLAGS = "-ObjC"; PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include/double-conversion"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4787,7 +4789,7 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_WARN_COMMA = NO; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_SUSPICIOUS_MOVES = YES; DEBUG_INFORMATION_FORMAT = dwarf; HEADER_SEARCH_PATHS = ( @@ -4811,7 +4813,7 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_WARN_COMMA = NO; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_SUSPICIOUS_MOVES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -4876,7 +4878,7 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_WARN_COMMA = NO; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_SUSPICIOUS_MOVES = YES; DEBUG_INFORMATION_FORMAT = dwarf; HEADER_SEARCH_PATHS = ( @@ -4901,7 +4903,7 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_WARN_COMMA = NO; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_SUSPICIOUS_MOVES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -4928,6 +4930,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; DEBUG_INFORMATION_FORMAT = dwarf; + GCC_WARN_SHADOW = NO; OTHER_LDFLAGS = "-ObjC"; PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include/double-conversion"; PRODUCT_NAME = "double-conversion"; @@ -4946,6 +4949,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_WARN_SHADOW = NO; OTHER_LDFLAGS = "-ObjC"; PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include/double-conversion"; PRODUCT_NAME = "double-conversion"; diff --git a/React/Views/RCTFont.mm b/React/Views/RCTFont.mm index f36f5438ebd7ca..e686637953a751 100644 --- a/React/Views/RCTFont.mm +++ b/React/Views/RCTFont.mm @@ -142,19 +142,8 @@ static inline BOOL CompareFontWeights(UIFontWeight firstWeight, UIFontWeight sec if (defaultFontHandler) { NSString *fontWeightDescription = FontWeightDescriptionFromUIFontWeight(weight); font = defaultFontHandler(size, fontWeightDescription); - } else if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { - // Only supported on iOS8.2 and above - font = [UIFont systemFontOfSize:size weight:weight]; } else { - if (weight >= UIFontWeightBold) { - font = [UIFont boldSystemFontOfSize:size]; - } else if (weight >= UIFontWeightMedium) { - font = [UIFont fontWithName:@"HelveticaNeue-Medium" size:size]; - } else if (weight <= UIFontWeightLight) { - font = [UIFont fontWithName:@"HelveticaNeue-Light" size:size]; - } else { - font = [UIFont systemFontOfSize:size]; - } + font = [UIFont systemFontOfSize:size weight:weight]; } { diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 35cd1dafe1d57a..ea84d99903e1b1 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -105,8 +105,6 @@ - (RCTShadowView *)shadowView RCT_EXPORT_VIEW_PROPERTY(tvParallaxProperties, NSDictionary) #endif -RCT_EXPORT_VIEW_PROPERTY(nativeID, NSString) - // Acessibility related properties RCT_REMAP_VIEW_PROPERTY(accessible, reactAccessibilityElement.isAccessibilityElement, BOOL) RCT_REMAP_VIEW_PROPERTY(accessibilityActions, reactAccessibilityElement.accessibilityActions, NSArray) @@ -171,6 +169,12 @@ - (RCTShadowView *)shadowView view.reactAccessibilityElement.accessibilityTraits = (view.reactAccessibilityElement.accessibilityTraits & ~AccessibilityStatesMask) | maskedTraits; } +RCT_CUSTOM_VIEW_PROPERTY(nativeID, NSString *, RCTView) +{ + view.nativeID = json ? [RCTConvert NSString:json] : defaultView.nativeID; + [_bridge.uiManager setNativeID:view.nativeID forView:view]; +} + RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTPointerEvents, RCTView) { if ([view respondsToSelector:@selector(setPointerEvents:)]) { diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index 2ed87866bd888e..75da9bb0841cf0 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -280,8 +280,13 @@ - (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated */ - (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view { - //TODO: shouldn't this call super if _shouldDisableScrollInteraction returns NO? - return ![self _shouldDisableScrollInteraction]; + BOOL shouldDisableScrollInteraction = [self _shouldDisableScrollInteraction]; + + if (shouldDisableScrollInteraction == NO) { + [super touchesShouldCancelInContentView:view]; + } + + return !shouldDisableScrollInteraction; } /* diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index 99811b7fefc10d..26a95665654d32 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -70,9 +70,14 @@ public ReactInstanceManager getReactInstanceManager() { return getReactNativeHost().getReactInstanceManager(); } + public String getMainComponentName() { + return mMainComponentName; + } + protected void onCreate(Bundle savedInstanceState) { - if (mMainComponentName != null) { - loadApp(mMainComponentName); + String mainComponentName = getMainComponentName(); + if (mainComponentName != null) { + loadApp(mainComponentName); } mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index d791cfbe1e6b68..e8ff4ab6d9e68c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -1045,7 +1045,7 @@ private void attachRootViewToInstance(final ReactRootView rootView) { @Nullable Bundle initialProperties = rootView.getAppProperties(); final int rootTag = uiManagerModule.addRootView( - rootView, + rootView.getView(), initialProperties == null ? new WritableNativeMap() : Arguments.fromBundle(initialProperties), rootView.getInitialUITemplate()); @@ -1075,10 +1075,10 @@ private void detachViewFromInstance( UiThreadUtil.assertOnUiThread(); if (rootView.getUIManagerType() == FABRIC) { catalystInstance.getJSModule(ReactFabric.class) - .unmountComponentAtNode(rootView.getId()); + .unmountComponentAtNode(rootView.getRootViewTag()); } else { catalystInstance.getJSModule(AppRegistry.class) - .unmountApplicationComponentAtRootTag(rootView.getId()); + .unmountApplicationComponentAtRootTag(rootView.getRootViewTag()); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 83bdf1d6e5b7b0..2cde3cbc2866fc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -91,20 +91,32 @@ public interface ReactRootViewEventListener { private boolean mWasMeasured = false; private int mWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); private int mHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + private int mLastWidth = 0; + private int mLastHeight = 0; private @UIManagerType int mUIManagerType = DEFAULT; + private final boolean mUseSurface; public ReactRootView(Context context) { super(context); + mUseSurface = false; + init(); + } + + public ReactRootView(Context context, boolean useSurface) { + super(context); + mUseSurface = useSurface; init(); } public ReactRootView(Context context, AttributeSet attrs) { super(context, attrs); + mUseSurface = false; init(); } public ReactRootView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mUseSurface = false; init(); } @@ -112,10 +124,22 @@ private void init() { setClipChildren(false); } + public View getView() { + // TODO add mUseSurface to return surface here + return this; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mUseSurface) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.onMeasure"); try { + boolean measureSpecsUpdated = widthMeasureSpec != mWidthMeasureSpec || + heightMeasureSpec != mHeightMeasureSpec; mWidthMeasureSpec = widthMeasureSpec; mHeightMeasureSpec = heightMeasureSpec; @@ -155,9 +179,11 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Check if we were waiting for onMeasure to attach the root view. if (mReactInstanceManager != null && !mIsAttachedToInstance) { attachToReactInstanceManager(); - } else { + } else if (measureSpecsUpdated || mLastWidth != width || mLastHeight != height) { updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec); } + mLastWidth = width; + mLastHeight = height; } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); @@ -281,6 +307,9 @@ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (mUseSurface) { + super.onLayout(changed, left, top, right, bottom); + } // No-op since UIManagerModule handles actually laying out children. } @@ -359,6 +388,10 @@ public void startReactApplication( mAppProperties = initialProperties; mInitialUITemplate = initialUITemplate; + if (mUseSurface) { + // TODO initialize surface here + } + if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.createReactContextInBackground(); } @@ -451,25 +484,31 @@ public void setAppProperties(@Nullable Bundle appProperties) { return; } - if (mWasMeasured) { - updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec); - } CatalystInstance catalystInstance = reactContext.getCatalystInstance(); + String jsAppModuleName = getJSModuleName(); - WritableNativeMap appParams = new WritableNativeMap(); - appParams.putDouble("rootTag", getRootViewTag()); - @Nullable Bundle appProperties = getAppProperties(); - if (appProperties != null) { - appParams.putMap("initialProps", Arguments.fromBundle(appProperties)); - } - if (getUIManagerType() == FABRIC) { - appParams.putBoolean("fabric", true); - } + if (mUseSurface) { + // TODO call surface's runApplication + } else { - mShouldLogContentAppeared = true; + if (mWasMeasured) { + updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec); + } - String jsAppModuleName = getJSModuleName(); - catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); + WritableNativeMap appParams = new WritableNativeMap(); + appParams.putDouble("rootTag", getRootViewTag()); + @Nullable Bundle appProperties = getAppProperties(); + if (appProperties != null) { + appParams.putMap("initialProps", Arguments.fromBundle(appProperties)); + } + if (getUIManagerType() == FABRIC) { + appParams.putBoolean("fabric", true); + } + + mShouldLogContentAppeared = true; + + catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); + } } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index d07d4ae28a28dd..1a4c8949c25ed3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -18,7 +18,6 @@ import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.LifecycleState; import java.lang.ref.WeakReference; -import java.util.concurrent.CopyOnWriteArraySet; import javax.annotation.Nullable; /** @@ -32,10 +31,35 @@ public class ReactContext extends ContextWrapper { "ReactContext#getJSModule should only happen once initialize() has been called on your " + "native module."; - private final CopyOnWriteArraySet mLifecycleEventListeners = - new CopyOnWriteArraySet<>(); - private final CopyOnWriteArraySet mActivityEventListeners = - new CopyOnWriteArraySet<>(); + private final SynchronizedWeakHashSet mLifecycleEventListeners = + new SynchronizedWeakHashSet<>(); + private final SynchronizedWeakHashSet mActivityEventListeners = + new SynchronizedWeakHashSet<>(); + + + private final GuardedIteration mResumeIteration = + new GuardedIteration() { + @Override + public void onIterate(LifecycleEventListener listener) { + listener.onHostResume(); + } + }; + + private final GuardedIteration mPauseIteration = + new GuardedIteration() { + @Override + public void onIterate(LifecycleEventListener listener) { + listener.onHostPause(); + } + }; + + private final GuardedIteration mDestroyIteration = + new GuardedIteration() { + @Override + public void onIterate(LifecycleEventListener listener) { + listener.onHostDestroy(); + } + }; private LifecycleState mLifecycleState = LifecycleState.BEFORE_CREATE; @@ -188,26 +212,19 @@ public void onHostResume(@Nullable Activity activity) { mLifecycleState = LifecycleState.RESUMED; mCurrentActivity = new WeakReference(activity); ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_START); - for (LifecycleEventListener listener : mLifecycleEventListeners) { - try { - listener.onHostResume(); - } catch (RuntimeException e) { - handleException(e); - } - } + mLifecycleEventListeners.iterate(mResumeIteration); ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_END); } - public void onNewIntent(@Nullable Activity activity, Intent intent) { + public void onNewIntent(@Nullable Activity activity, final Intent intent) { UiThreadUtil.assertOnUiThread(); mCurrentActivity = new WeakReference(activity); - for (ActivityEventListener listener : mActivityEventListeners) { - try { + mActivityEventListeners.iterate(new GuardedIteration() { + @Override + public void onIterate(ActivityEventListener listener) { listener.onNewIntent(intent); - } catch (RuntimeException e) { - handleException(e); } - } + }); } /** @@ -216,13 +233,7 @@ public void onNewIntent(@Nullable Activity activity, Intent intent) { public void onHostPause() { mLifecycleState = LifecycleState.BEFORE_RESUME; ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_PAUSE_START); - for (LifecycleEventListener listener : mLifecycleEventListeners) { - try { - listener.onHostPause(); - } catch (RuntimeException e) { - handleException(e); - } - } + mLifecycleEventListeners.iterate(mPauseIteration); ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_PAUSE_END); } @@ -232,13 +243,7 @@ public void onHostPause() { public void onHostDestroy() { UiThreadUtil.assertOnUiThread(); mLifecycleState = LifecycleState.BEFORE_CREATE; - for (LifecycleEventListener listener : mLifecycleEventListeners) { - try { - listener.onHostDestroy(); - } catch (RuntimeException e) { - handleException(e); - } - } + mLifecycleEventListeners.iterate(mDestroyIteration); mCurrentActivity = null; } @@ -256,14 +261,13 @@ public void destroy() { /** * Should be called by the hosting Fragment in {@link Fragment#onActivityResult} */ - public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { - for (ActivityEventListener listener : mActivityEventListeners) { - try { + public void onActivityResult(final Activity activity, final int requestCode, final int resultCode, final Intent data) { + mActivityEventListeners.iterate(new GuardedIteration() { + @Override + public void onIterate(ActivityEventListener listener) { listener.onActivityResult(activity, requestCode, resultCode, data); - } catch (RuntimeException e) { - handleException(e); } - } + }); } public void assertOnUiQueueThread() { @@ -359,4 +363,16 @@ public JavaScriptContextHolder getJavaScriptContextHolder() { return mCatalystInstance.getJavaScriptContextHolder(); } + private abstract class GuardedIteration implements SynchronizedWeakHashSet.Iteration { + @Override + public void iterate(T listener) { + try { + onIterate(listener); + } catch (RuntimeException e) { + handleException(e); + } + } + + public abstract void onIterate(T listener); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/SynchronizedWeakHashSet.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/SynchronizedWeakHashSet.java new file mode 100644 index 00000000000000..b7ce40b0cad46a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/SynchronizedWeakHashSet.java @@ -0,0 +1,86 @@ +// Copyright (c) Facebook, Inc. and its affiliates. + +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. +package com.facebook.react.bridge; + +import android.util.Pair; + +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.WeakHashMap; + +/** + * Thread-safe Set based on the WeakHashMap. + * + * Doesn't implement the `iterator` method because it's tricky to support modifications + * to the collection while somebody is using an `Iterator` to iterate over it. + * + * Instead, it provides an `iterate` method for traversing the collection. Any add/remove operations + * that occur during iteration are postponed until the iteration has completed. + */ +public class SynchronizedWeakHashSet { + private WeakHashMap mMap = new WeakHashMap<>(); + private Queue> mPendingOperations = new ArrayDeque<>(); + private boolean mIterating; + + public boolean contains(T item) { + synchronized (mMap) { + return mMap.containsKey(item); + } + } + + public void add(T item) { + synchronized (mMap) { + if (mIterating) { + mPendingOperations.add(new Pair<>(item, Command.ADD)); + } else { + mMap.put(item, null); + } + } + } + + public void remove(T item) { + synchronized (mMap) { + if (mIterating) { + mPendingOperations.add(new Pair<>(item, Command.REMOVE)); + } else { + mMap.remove(item); + } + } + } + + public void iterate(Iteration iterated) { + synchronized (mMap) { + // Protection from modification during iteration on the same thread + mIterating = true; + for (T listener: mMap.keySet()) { + iterated.iterate(listener); + } + mIterating = false; + + while (!mPendingOperations.isEmpty()) { + Pair pair = mPendingOperations.poll(); + switch (pair.second) { + case ADD: + mMap.put(pair.first, null); + break; + case REMOVE: + mMap.remove(pair.first); + break; + default: + throw new AssertionException("Unsupported command" + pair.second); + } + } + } + } + + public interface Iteration { + void iterate(T item); + } + + private enum Command { + ADD, + REMOVE + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java index 5d010e0ed007db..c3cde5478efb13 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java @@ -17,7 +17,6 @@ import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.react.fabric.mounting.ViewPool; import com.facebook.react.fabric.mounting.mountitems.BatchMountItem; -import com.facebook.react.fabric.mounting.mountitems.CreateMountItem; import com.facebook.react.fabric.mounting.mountitems.DeleteMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.InsertMountItem; @@ -94,7 +93,6 @@ private static void loadClasses() { FabricUIManager.class.getClass(); GuardedFrameCallback.class.getClass(); BatchMountItem.class.getClass(); - CreateMountItem.class.getClass(); DeleteMountItem.class.getClass(); DispatchCommandMountItem.class.getClass(); InsertMountItem.class.getClass(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index fff1caf805b325..828cb2c64785ed 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -20,7 +20,6 @@ import android.support.annotation.UiThread; import android.view.View; import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.ThreadConfined; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.LifecycleEventListener; @@ -39,7 +38,6 @@ import com.facebook.react.fabric.jsi.FabricSoLoader; import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.react.fabric.mounting.mountitems.BatchMountItem; -import com.facebook.react.fabric.mounting.mountitems.CreateMountItem; import com.facebook.react.fabric.mounting.mountitems.DeleteMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.InsertMountItem; @@ -57,6 +55,7 @@ import com.facebook.react.uimanager.ViewManagerRegistry; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.systrace.Systrace; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -69,6 +68,9 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { private static final String TAG = FabricUIManager.class.getSimpleName(); private static final Map sComponentNames = new HashMap<>(); + private static final int FRAME_TIME_MS = 16; + private static final int MAX_TIME_IN_FRAME_FOR_NON_BATCHED_OPERATIONS_MS = 8; + private static final int PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY = 250; static { FabricSoLoader.staticInit(); @@ -77,6 +79,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { sComponentNames.put("View", "RCTView"); sComponentNames.put("Image", "RCTImageView"); sComponentNames.put("ScrollView", "RCTScrollView"); + sComponentNames.put("Slider", "RCTSlider"); sComponentNames.put("ReactPerformanceLoggerFlag", "ReactPerformanceLoggerFlag"); sComponentNames.put("Paragraph", "RCTText"); sComponentNames.put("Text", "RCText"); @@ -100,13 +103,15 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { private List mMountItems = new ArrayList<>(); @GuardedBy("mPreMountItemsLock") - private List mPreMountItems = new ArrayList<>(); + private ArrayDeque mPreMountItems = + new ArrayDeque<>(PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY); @ThreadConfined(UI) private final DispatchUIFrameCallback mDispatchUIFrameCallback; @ThreadConfined(UI) private boolean mIsMountingEnabled = true; + private long mRunStartTime = 0l; private long mBatchedExecutionTime = 0l; private long mNonBatchedExecutionTime = 0l; @@ -114,8 +119,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { private long mCommitStartTime = 0l; private long mLayoutTime = 0l; private long mFinishTransactionTime = 0l; - private int mLastWidthMeasureSpec = 0; - private int mLastHeightMeasureSpec = 0; + private long mFinishTransactionCPPTime = 0l; public FabricUIManager( ReactApplicationContext reactContext, @@ -157,22 +161,7 @@ public void removeRootView(int reactRootTag) { mMountingManager.removeRootView(reactRootTag); mReactContextForRootTag.remove(reactRootTag); } - - @DoNotStrip - @SuppressWarnings("unused") - private MountItem createMountItem( - String componentName, int reactRootTag, int reactTag, boolean isVirtual) { - String component = sComponentNames.get(componentName); - if (component == null) { - throw new IllegalArgumentException("Unable to find component with name " + componentName); - } - ThemedReactContext reactContext = mReactContextForRootTag.get(reactRootTag); - if (reactContext == null) { - throw new IllegalArgumentException("Unable to find ReactContext for root: " + reactRootTag); - } - return new CreateMountItem(reactContext, component, reactTag, isVirtual); - } - + @Override public void initialize() { mEventDispatcher.registerEventEmitter(FABRIC, new FabricEventEmitter(this)); @@ -188,17 +177,22 @@ public void onCatalystInstanceDestroy() { } @DoNotStrip - private void preallocateView(final int rootTag, final String componentName) { + private void preallocateView( + int rootTag, + int reactTag, + final String componentName, + ReadableMap props, + boolean isLayoutable) { if (UiThreadUtil.isOnUiThread()) { // There is no reason to allocate views ahead of time on the main thread. return; } + + ThemedReactContext context = mReactContextForRootTag.get(rootTag); + String component = sComponentNames.get(componentName); synchronized (mPreMountItemsLock) { - ThemedReactContext context = - Assertions.assertNotNull(mReactContextForRootTag.get(rootTag)); - String component = sComponentNames.get(componentName); - Assertions.assertNotNull(component); - mPreMountItems.add(new PreAllocateViewMountItem(context, rootTag, component)); + mPreMountItems.add( + new PreAllocateViewMountItem(context, rootTag, reactTag, component, props, isLayoutable)); } } @@ -275,7 +269,7 @@ private long measure( @Override public void synchronouslyUpdateViewOnUIThread(int reactTag, ReadableMap props) { long time = SystemClock.uptimeMillis(); - scheduleMountItems(updatePropsMountItem(reactTag, props), time, time, time); + scheduleMountItems(updatePropsMountItem(reactTag, props), time, 0, time, time); } /** @@ -288,11 +282,13 @@ private void scheduleMountItems( final MountItem mountItems, long commitStartTime, long layoutTime, - long finishTransactionStartTime) { + long finishTransactionStartTime, + long finishTransactionEndTime) { // TODO T31905686: support multithreading mCommitStartTime = commitStartTime; mLayoutTime = layoutTime; + mFinishTransactionCPPTime = finishTransactionEndTime - finishTransactionStartTime; mFinishTransactionTime = SystemClock.uptimeMillis() - finishTransactionStartTime; mDispatchViewUpdatesTime = SystemClock.uptimeMillis(); synchronized (mMountItemsLock) { @@ -300,58 +296,79 @@ private void scheduleMountItems( } if (UiThreadUtil.isOnUiThread()) { - flushMountItems(); + dispatchMountItems(); } } @UiThread - private void flushMountItems() { - if (!mIsMountingEnabled) { - FLog.w( - ReactConstants.TAG, - "Not flushing pending UI operations because of previously thrown Exception"); - return; - } + private void dispatchMountItems() { + mRunStartTime = SystemClock.uptimeMillis(); - try { - List preMountItemsToDispatch; - synchronized (mPreMountItemsLock) { - preMountItemsToDispatch = mPreMountItems; - mPreMountItems = new ArrayList<>(); + List mountItemsToDispatch; + synchronized (mMountItemsLock) { + if (mMountItems.isEmpty()) { + return; } + mountItemsToDispatch = mMountItems; + mMountItems = new ArrayList<>(); + } - mRunStartTime = SystemClock.uptimeMillis(); - List mountItemsToDispatch; - synchronized (mMountItemsLock) { - mountItemsToDispatch = mMountItems; - mMountItems = new ArrayList<>(); + // If there are MountItems to dispatch, we make sure all the "pre mount items" are executed + ArrayDeque mPreMountItemsToDispatch = null; + synchronized (mPreMountItemsLock) { + if (!mPreMountItems.isEmpty()) { + mPreMountItemsToDispatch = mPreMountItems; + mPreMountItems = new ArrayDeque<>(PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY); } - - long nonBatchedExecutionStartTime = SystemClock.uptimeMillis(); + } + if (mPreMountItemsToDispatch != null) { Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "FabricUIManager::premountViews (" + preMountItemsToDispatch.size() + " batches)"); - for (MountItem mountItem : preMountItemsToDispatch) { - mountItem.execute(mMountingManager); + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "FabricUIManager::mountViews preMountItems to execute: " + + mPreMountItemsToDispatch.size()); + + while (!mPreMountItemsToDispatch.isEmpty()) { + mPreMountItemsToDispatch.pollFirst().execute(mMountingManager); } - mNonBatchedExecutionTime = SystemClock.uptimeMillis() - nonBatchedExecutionStartTime; + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "FabricUIManager::mountViews (" + mountItemsToDispatch.size() + " batches)"); + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "FabricUIManager::mountViews mountItems to execute: " + mountItemsToDispatch.size()); + + long batchedExecutionStartTime = SystemClock.uptimeMillis(); + for (MountItem mountItem : mountItemsToDispatch) { + mountItem.execute(mMountingManager); + } + mBatchedExecutionTime = SystemClock.uptimeMillis() - batchedExecutionStartTime; + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } - long batchedExecutionStartTime = SystemClock.uptimeMillis(); - for (MountItem mountItem : mountItemsToDispatch) { - mountItem.execute(mMountingManager); + @UiThread + private void dispatchPreMountItems(long frameTimeNanos) { + long nonBatchedExecutionStartTime = SystemClock.uptimeMillis(); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricUIManager::premountViews"); + + while (true) { + long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000); + if (timeLeftInFrame < MAX_TIME_IN_FRAME_FOR_NON_BATCHED_OPERATIONS_MS) { + break; } - mBatchedExecutionTime = SystemClock.uptimeMillis() - batchedExecutionStartTime; - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } catch (Exception ex) { - FLog.e(ReactConstants.TAG, "Exception thrown when executing UIFrameGuarded", ex); - mIsMountingEnabled = false; - throw ex; + + MountItem preMountItemsToDispatch; + synchronized (mPreMountItemsLock) { + if (mPreMountItems.isEmpty()) { + break; + } + preMountItemsToDispatch = mPreMountItems.pollFirst(); + } + + preMountItemsToDispatch.execute(mMountingManager); } + mNonBatchedExecutionTime = SystemClock.uptimeMillis() - nonBatchedExecutionStartTime; + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } public void setBinding(Binding binding) { @@ -365,16 +382,12 @@ public void setBinding(Binding binding) { public void updateRootLayoutSpecs( final int rootTag, final int widthMeasureSpec, final int heightMeasureSpec) { - if (mLastWidthMeasureSpec != widthMeasureSpec || mLastHeightMeasureSpec != heightMeasureSpec) { - mLastWidthMeasureSpec = widthMeasureSpec; - mLastHeightMeasureSpec = heightMeasureSpec; - mBinding.setConstraints( - rootTag, - getMinSize(widthMeasureSpec), - getMaxSize(widthMeasureSpec), - getMinSize(heightMeasureSpec), - getMaxSize(heightMeasureSpec)); - } + mBinding.setConstraints( + rootTag, + getMinSize(widthMeasureSpec), + getMaxSize(widthMeasureSpec), + getMinSize(heightMeasureSpec), + getMaxSize(heightMeasureSpec)); } public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) { @@ -436,6 +449,7 @@ public Map getPerformanceCounters() { performanceCounters.put("BatchedExecutionTime", mBatchedExecutionTime); performanceCounters.put("NonBatchedExecutionTime", mNonBatchedExecutionTime); performanceCounters.put("FinishFabricTransactionTime", mFinishTransactionTime); + performanceCounters.put("FinishFabricTransactionCPPTime", mFinishTransactionCPPTime); return performanceCounters; } @@ -455,7 +469,11 @@ public void doFrameGuarded(long frameTimeNanos) { } try { - flushMountItems(); + + dispatchPreMountItems(frameTimeNanos); + + dispatchMountItems(); + } catch (Exception ex) { FLog.i(ReactConstants.TAG, "Exception thrown when executing UIFrameGuarded", ex); mIsMountingEnabled = false; diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/AsyncEventBeat.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/AsyncEventBeat.h index 8f07aa6a9fca6a..a87d53608b83f8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/AsyncEventBeat.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/AsyncEventBeat.h @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include "EventBeatManager.h" namespace facebook { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp index 9bb5f12bd58a17..41a64771cd7854 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp @@ -7,14 +7,13 @@ #include "EventEmitterWrapper.h" #include "ReactNativeConfigHolder.h" -#include #include #include #include #include #include -#include -#include +#include +#include #include #include #include @@ -167,30 +166,6 @@ local_ref getPlatformComponentName(const ShadowView& shadowView) { return componentName; } -local_ref createCreateMountItem( - const jni::global_ref& javaUIManager, - const ShadowViewMutation& mutation, - const Tag rootTag) { - static auto createJavaInstruction = - jni::findClassStatic(UIManagerJavaDescriptor) - ->getMethod(jstring, jint, jint, jboolean)>( - "createMountItem"); - - auto newChildShadowView = mutation.newChildShadowView; - - local_ref componentName = - getPlatformComponentName(newChildShadowView); - - jboolean isVirtual = newChildShadowView.layoutMetrics == EmptyLayoutMetrics; - - return createJavaInstruction( - javaUIManager, - componentName.get(), - rootTag, - newChildShadowView.tag, - isVirtual); -} - local_ref createUpdateEventEmitterMountItem( const jni::global_ref& javaUIManager, const ShadowViewMutation& mutation) { @@ -350,11 +325,6 @@ void Binding::schedulerDidFinishTransaction( oldChildShadowView.layoutMetrics == EmptyLayoutMetrics; switch (mutation.type) { - case ShadowViewMutation::Create: { - mountItems[position++] = - createCreateMountItem(javaUIManager_, mutation, rootTag); - break; - } case ShadowViewMutation::Remove: { if (!isVirtual) { mountItems[position++] = @@ -402,8 +372,10 @@ void Binding::schedulerDidFinishTransaction( mountItems[position++] = createInsertMountItem(javaUIManager_, mutation); - mountItems[position++] = - createUpdatePropsMountItem(javaUIManager_, mutation); + if (mutation.newChildShadowView.props->revision > 1) { + mountItems[position++] = + createUpdatePropsMountItem(javaUIManager_, mutation); + } auto updateLayoutMountItem = createUpdateLayoutMountItem(javaUIManager_, mutation); @@ -441,15 +413,18 @@ void Binding::schedulerDidFinishTransaction( static auto scheduleMountItems = jni::findClassStatic(UIManagerJavaDescriptor) - ->getMethod( + ->getMethod( "scheduleMountItems"); + long finishTransactionEndTime = getTime(); + scheduleMountItems( javaUIManager_, batch.get(), commitStartTime, layoutTime, - finishTransactionStartTime); + finishTransactionStartTime, + finishTransactionEndTime); } void Binding::setPixelDensity(float pointScaleFactor) { @@ -458,17 +433,18 @@ void Binding::setPixelDensity(float pointScaleFactor) { void Binding::schedulerDidRequestPreliminaryViewAllocation( const SurfaceId surfaceId, - const ComponentName componentName, - bool isLayoutable, - const ComponentHandle componentHandle) { - if (isLayoutable) { - static auto preallocateView = - jni::findClassStatic(UIManagerJavaDescriptor) - ->getMethod("preallocateView"); + const ShadowView &shadowView) { - preallocateView( - javaUIManager_, surfaceId, make_jstring(componentName).get()); - } + bool isLayoutableShadowNode = shadowView.layoutMetrics != EmptyLayoutMetrics; + + static auto preallocateView = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod("preallocateView"); + + local_ref readableMap = + castReadableMap(ReadableNativeMap::newObjectCxxArgs(shadowView.props->rawProps)); + preallocateView( + javaUIManager_, surfaceId, shadowView.tag, make_jstring(shadowView.componentName).get(), readableMap.get(), isLayoutableShadowNode); } void Binding::registerNatives() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.h index ad12aa53955d0f..b17e3f35223a9d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.h @@ -64,9 +64,7 @@ class Binding : public jni::HybridClass, public SchedulerDelegate { void schedulerDidRequestPreliminaryViewAllocation( const SurfaceId surfaceId, - const ComponentName componentName, - bool isLayoutable, - const ComponentHandle componentHandle); + const ShadowView &shadowView); void setPixelDensity(float pointScaleFactor); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.h index 2e49a9aa3837d5..ad6839864c6f1b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventBeatManager.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.h index 62db218133d340..ce43653eac95f6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/EventEmitterWrapper.h @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include namespace facebook { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java index 299b0f00ab8b9e..c79c7b2d0f6192 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java @@ -168,11 +168,11 @@ public void createView( ThemedReactContext themedReactContext, String componentName, int reactTag, - boolean isVirtual) { - UiThreadUtil.assertOnUiThread(); + boolean isLayoutable) { View view = null; ViewManager viewManager = null; - if (!isVirtual) { + + if (isLayoutable) { viewManager = mViewManagerRegistry.get(componentName); view = mViewPool.getOrCreateView(componentName, themedReactContext); view.setId(reactTag); @@ -271,8 +271,19 @@ public void updateLocalData(int reactTag, ReadableMap newLocalData) { } @UiThread - public void preallocateView(ThemedReactContext reactContext, String componentName) { - mViewPool.createView(reactContext, componentName); + public void preallocateView( + ThemedReactContext reactContext, + String componentName, + int reactTag, + ReadableMap props, + boolean isLayoutable) { + + if (mTagToViewState.get(reactTag) != null) return; + + createView(reactContext, componentName, reactTag, isLayoutable); + if (isLayoutable) { + updateProps(reactTag, props); + } } @UiThread diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java deleted file mode 100644 index 58e77d8b0cac50..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * - *

This source code is licensed under the MIT license found in the LICENSE file in the root - * directory of this source tree. - */ -package com.facebook.react.fabric.mounting.mountitems; - -import com.facebook.react.fabric.mounting.MountingManager; -import com.facebook.react.uimanager.ThemedReactContext; - -public class CreateMountItem implements MountItem { - - private final String mComponentName; - private final int mReactTag; - private final ThemedReactContext mThemedReactContext; - private final boolean mIsVirtual; - - public CreateMountItem( - ThemedReactContext themedReactContext, - String componentName, - int reactTag, - boolean isVirtual) { - mReactTag = reactTag; - mThemedReactContext = themedReactContext; - mComponentName = componentName; - mIsVirtual = isVirtual; - } - - @Override - public void execute(MountingManager mountingManager) { - mountingManager.createView(mThemedReactContext, mComponentName, mReactTag, mIsVirtual); - } - - public String getComponentName() { - return mComponentName; - } - - public ThemedReactContext getThemedReactContext() { - return mThemedReactContext; - } - - @Override - public String toString() { - return "CreateMountItem [" + mReactTag + "] " + mComponentName; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem.java index e07ceab98101d6..8ab88839619a5c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem.java @@ -26,7 +26,6 @@ public DispatchCommandMountItem( @Override public void execute(MountingManager mountingManager) { - UiThreadUtil.assertOnUiThread(); mountingManager.receiveCommand(mReactTag, mCommandId, mCommandArgs); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java index 858faf1fe6ea67..fc01ad8ef47f1c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java @@ -1,32 +1,38 @@ /** * Copyright (c) Facebook, Inc. and its affiliates. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. */ package com.facebook.react.fabric.mounting.mountitems; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.react.uimanager.ThemedReactContext; -/** - * {@link MountItem} that is used to pre-allocate views for JS components. - */ +/** {@link MountItem} that is used to pre-allocate views for JS components. */ public class PreAllocateViewMountItem implements MountItem { private final String mComponent; private final int mRootTag; + private final int mReactTag; + private final ReadableMap mProps; private final ThemedReactContext mContext; + private final boolean mIsLayoutable; - public PreAllocateViewMountItem(ThemedReactContext context, int rootTag, String component){ + public PreAllocateViewMountItem( + ThemedReactContext context, int rootTag, int reactTag, String component, ReadableMap props, boolean isLayoutable) { mContext = context; mComponent = component; mRootTag = rootTag; + mProps = props; + mReactTag = reactTag; + mIsLayoutable = isLayoutable; } @Override public void execute(MountingManager mountingManager) { - mountingManager.preallocateView(mContext, mComponent); + mountingManager.preallocateView(mContext, mComponent, mReactTag, mProps, mIsLayoutable); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java index 6cab32686a7cdd..f6464b870c3eff 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java @@ -77,7 +77,8 @@ public String getName() { if (ReactBuildConfig.DEBUG) { constants.put("ServerHost", getServerHost()); } - constants.put("isTesting", "true".equals(System.getProperty(IS_TESTING))); + constants.put("isTesting", "true".equals(System.getProperty(IS_TESTING)) + || isRunningScreenshotTest()); constants.put("reactNativeVersion", ReactNativeVersion.VERSION); constants.put("uiMode", uiMode()); return constants; @@ -95,4 +96,13 @@ private String getServerHost() { return AndroidInfoHelpers.getServerHost(devServerPort); } + + private Boolean isRunningScreenshotTest() { + try { + Class.forName("android.support.test.rule.ActivityTestRule"); + return true; + } catch (ClassNotFoundException ignored) { + return false; + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AccessibilityDelegateUtil.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AccessibilityDelegateUtil.java index 617773b24eb0fc..aeffe238a91ba8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AccessibilityDelegateUtil.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AccessibilityDelegateUtil.java @@ -9,6 +9,9 @@ import android.support.v4.view.AccessibilityDelegateCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat; +import android.text.SpannableString; +import android.text.style.URLSpan; import android.view.View; import com.facebook.react.R; import java.util.Locale; @@ -98,7 +101,6 @@ public static void setDelegate(final View view) { public void onInitializeAccessibilityNodeInfo( View host, AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); - setRole(info, accessibilityRole, view.getContext()); if (!(accessibilityHint == null)) { String contentDescription=(String)info.getContentDescription(); if (contentDescription != null) { @@ -108,6 +110,8 @@ public void onInitializeAccessibilityNodeInfo( info.setContentDescription(accessibilityHint); } } + + setRole(info, accessibilityRole, view.getContext()); } }); } @@ -127,6 +131,18 @@ public static void setRole(AccessibilityNodeInfoCompat nodeInfo, AccessibilityRo if (Locale.getDefault().getLanguage().equals(new Locale("en").getLanguage())) { if (role.equals(AccessibilityRole.LINK)) { nodeInfo.setRoleDescription(context.getString(R.string.link_description)); + + if (nodeInfo.getContentDescription() != null) { + SpannableString spannable = new SpannableString(nodeInfo.getContentDescription()); + spannable.setSpan(new URLSpan(""), 0, spannable.length(), 0); + nodeInfo.setContentDescription(spannable); + } + + if (nodeInfo.getText() != null) { + SpannableString spannable = new SpannableString(nodeInfo.getText()); + spannable.setSpan(new URLSpan(""), 0, spannable.length(), 0); + nodeInfo.setText(spannable); + } } if (role.equals(AccessibilityRole.SEARCH)) { nodeInfo.setRoleDescription(context.getString(R.string.search_description)); @@ -140,6 +156,12 @@ public static void setRole(AccessibilityNodeInfoCompat nodeInfo, AccessibilityRo if (role.equals(AccessibilityRole.ADJUSTABLE)) { nodeInfo.setRoleDescription(context.getString(R.string.adjustable_description)); } + if (role.equals(AccessibilityRole.HEADER)) { + nodeInfo.setRoleDescription(context.getString(R.string.header_description)); + final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo = + AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(0, 1, 0, 1, true); + nodeInfo.setCollectionItemInfo(itemInfo); + } } if (role.equals(AccessibilityRole.IMAGEBUTTON)) { nodeInfo.setClickable(true); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java index 52075e7be0d4e5..3c3244194fdc5a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java @@ -327,6 +327,9 @@ private void updateProperties() { * UIManagerModule, and will then cause the children to layout as if they can fill the window. */ static class DialogRootViewGroup extends ReactViewGroup implements RootView { + private boolean hasAdjustedSize = false; + private int viewWidth; + private int viewHeight; private final JSTouchDispatcher mJSTouchDispatcher = new JSTouchDispatcher(this); @@ -337,7 +340,14 @@ public DialogRootViewGroup(Context context) { @Override protected void onSizeChanged(final int w, final int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); + viewWidth = w; + viewHeight = h; + updateFirstChildView(); + } + + private void updateFirstChildView() { if (getChildCount() > 0) { + hasAdjustedSize = false; final int viewTag = getChildAt(0).getId(); ReactContext reactContext = getReactContext(); reactContext.runOnNativeModulesQueueThread( @@ -345,9 +355,19 @@ protected void onSizeChanged(final int w, final int h, int oldw, int oldh) { @Override public void runGuarded() { (getReactContext()).getNativeModule(UIManagerModule.class) - .updateNodeSize(viewTag, w, h); + .updateNodeSize(viewTag, viewWidth, viewHeight); } }); + } else { + hasAdjustedSize = true; + } + } + + @Override + public void addView(View child, int index, LayoutParams params) { + super.addView(child, index, params); + if (hasAdjustedSize) { + updateFirstChildView(); } } 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 84066b5d1bbf52..c287a04f0c8174 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 @@ -14,6 +14,7 @@ import android.view.ViewGroup; import android.widget.SeekBar; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNodeImpl; @@ -192,4 +193,22 @@ public Map getExportedCustomDirectEventTypeConstants() { ReactSlidingCompleteEvent.EVENT_NAME, MapBuilder.of("registrationName", "onSlidingComplete")); } -} + + @Override + public long measure( + ReactContext context, + ReadableMap localData, + ReadableMap props, + float width, + YogaMeasureMode widthMode, + float height, + YogaMeasureMode heightMode) { + SeekBar reactSlider = new ReactSlider(context, null, STYLE); + final int spec = View.MeasureSpec.makeMeasureSpec( + ViewGroup.LayoutParams.WRAP_CONTENT, + View.MeasureSpec.UNSPECIFIED); + reactSlider.measure(spec, spec); + + return YogaMeasureOutput.make(reactSlider.getMeasuredWidth(), reactSlider.getMeasuredHeight()); + } + } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index ec00cd5b30662e..5b9d5b21df52fd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -265,6 +265,9 @@ private static int parseNumericFontWeight(String fontWeightString) { protected int mTextAlign = Gravity.NO_GRAVITY; protected int mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; + protected int mJustificationMode = + (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE; + protected TextTransform mTextTransform = TextTransform.UNSET; protected float mTextShadowOffsetDx = 0; protected float mTextShadowOffsetDy = 0; @@ -357,19 +360,28 @@ public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) { @ReactProp(name = ViewProps.TEXT_ALIGN) public void setTextAlign(@Nullable String textAlign) { - if (textAlign == null || "auto".equals(textAlign)) { - mTextAlign = Gravity.NO_GRAVITY; - } else if ("left".equals(textAlign)) { - mTextAlign = Gravity.LEFT; - } else if ("right".equals(textAlign)) { - mTextAlign = Gravity.RIGHT; - } else if ("center".equals(textAlign)) { - mTextAlign = Gravity.CENTER_HORIZONTAL; - } else if ("justify".equals(textAlign)) { - // Fallback gracefully for cross-platform compat instead of error + if ("justify".equals(textAlign)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD; + } mTextAlign = Gravity.LEFT; } else { - throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; + } + + if (textAlign == null || "auto".equals(textAlign)) { + mTextAlign = Gravity.NO_GRAVITY; + } else if ("left".equals(textAlign)) { + mTextAlign = Gravity.LEFT; + } else if ("right".equals(textAlign)) { + mTextAlign = Gravity.RIGHT; + } else if ("center".equals(textAlign)) { + mTextAlign = Gravity.CENTER_HORIZONTAL; + } else { + throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + } + } markUpdated(); } 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 eaef5690304b33..5cf16db80f0fd4 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 @@ -99,14 +99,18 @@ public long measure( new StaticLayout( text, textPaint, hintWidth, alignment, 1.f, 0.f, mIncludeFontPadding); } else { - layout = + StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth) - .setAlignment(alignment) - .setLineSpacing(0.f, 1.f) - .setIncludePad(mIncludeFontPadding) - .setBreakStrategy(mTextBreakStrategy) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); + .setAlignment(alignment) + .setLineSpacing(0.f, 1.f) + .setIncludePad(mIncludeFontPadding) + .setBreakStrategy(mTextBreakStrategy) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setJustificationMode(mJustificationMode); + } + layout = builder.build(); } } else if (boring != null && (unconstrainedWidth || boring.width <= width)) { @@ -217,7 +221,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { getPadding(Spacing.END), getPadding(Spacing.BOTTOM), getTextAlign(), - mTextBreakStrategy); + mTextBreakStrategy, + mJustificationMode); 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 c31bb3c55ea52d..fd1344f0fb714d 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 @@ -26,6 +26,7 @@ public class ReactTextUpdate { private final float mPaddingBottom; private final int mTextAlign; private final int mTextBreakStrategy; + private final int mJustificationMode; /** * @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains @@ -49,7 +50,8 @@ public ReactTextUpdate( paddingEnd, paddingBottom, textAlign, - Layout.BREAK_STRATEGY_HIGH_QUALITY); + Layout.BREAK_STRATEGY_HIGH_QUALITY, + Layout.JUSTIFICATION_MODE_NONE); } public ReactTextUpdate( @@ -61,7 +63,8 @@ public ReactTextUpdate( float paddingEnd, float paddingBottom, int textAlign, - int textBreakStrategy) { + int textBreakStrategy, + int justificationMode) { mText = text; mJsEventCounter = jsEventCounter; mContainsImages = containsImages; @@ -71,6 +74,7 @@ public ReactTextUpdate( mPaddingBottom = paddingBottom; mTextAlign = textAlign; mTextBreakStrategy = textBreakStrategy; + mJustificationMode = justificationMode; } public Spannable getText() { @@ -108,4 +112,8 @@ public int getTextAlign() { public int getTextBreakStrategy() { return mTextBreakStrategy; } + + public int getJustificationMode() { + return mJustificationMode; + } } 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 7e99cb1429d8df..a804a46a94a6d5 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 @@ -72,6 +72,11 @@ public void setText(ReactTextUpdate update) { setBreakStrategy(update.getTextBreakStrategy()); } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (getJustificationMode() != update.getJustificationMode()) { + setJustificationMode(update.getJustificationMode()); + } + } } @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 eb0e9f9c0cee03..edcaf4a22cd59b 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 @@ -79,6 +79,9 @@ public Object updateLocalData( // TODO add textBreakStrategy prop into local Data int textBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY; + // TODO add justificationMode prop into local Data + int justificationMode = Layout.JUSTIFICATION_MODE_NONE; + return new ReactTextUpdate( spanned, -1, // TODO add this into local Data? @@ -88,7 +91,9 @@ public Object updateLocalData( textViewProps.getEndPadding(), textViewProps.getBottomPadding(), textViewProps.getTextAlign(), - textBreakStrategy); + textBreakStrategy, + justificationMode + ); } @Override @@ -96,6 +101,7 @@ public Object updateLocalData( return MapBuilder.of("topTextLayout", MapBuilder.of("registrationName", "onTextLayout")); } + @Override public long measure( ReactContext context, ReadableMap localData, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java index cbf49e34dc0e84..e23961d75d8b44 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java @@ -50,6 +50,8 @@ public class TextAttributeProps { protected int mTextAlign = Gravity.NO_GRAVITY; protected int mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; + protected int mJustificationMode = + (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE; protected TextTransform mTextTransform = TextTransform.UNSET; protected float mTextShadowOffsetDx = 0; @@ -204,19 +206,28 @@ public void setAllowFontScaling(boolean allowFontScaling) { } public void setTextAlign(@Nullable String textAlign) { - if (textAlign == null || "auto".equals(textAlign)) { - mTextAlign = Gravity.NO_GRAVITY; - } else if ("left".equals(textAlign)) { - mTextAlign = Gravity.LEFT; - } else if ("right".equals(textAlign)) { - mTextAlign = Gravity.RIGHT; - } else if ("center".equals(textAlign)) { - mTextAlign = Gravity.CENTER_HORIZONTAL; - } else if ("justify".equals(textAlign)) { - // Fallback gracefully for cross-platform compat instead of error + if ("justify".equals(textAlign)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD; + } mTextAlign = Gravity.LEFT; } else { - throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; + } + + if (textAlign == null || "auto".equals(textAlign)) { + mTextAlign = Gravity.NO_GRAVITY; + } else if ("left".equals(textAlign)) { + mTextAlign = Gravity.LEFT; + } else if ("right".equals(textAlign)) { + mTextAlign = Gravity.RIGHT; + } else if ("center".equals(textAlign)) { + mTextAlign = Gravity.CENTER_HORIZONTAL; + } else { + throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + } + } } 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 0b59a3c9e94060..1638d4925013ce 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 @@ -109,6 +109,13 @@ public ReactEditText(Context context) { mTextAttributes = new TextAttributes(); applyTextAttributes(); + + // Turn off hardware acceleration for Oreo (T40484798) + // see https://issuetracker.google.com/issues/67102093 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } } // After the text changes inside an EditText, TextView checks if a layout() has been requested. @@ -236,6 +243,10 @@ public void setContentSizeWatcher(ContentSizeWatcher contentSizeWatcher) { mContentSizeWatcher = contentSizeWatcher; } + public void setMostRecentEventCount(int mostRecentEventCount) { + mMostRecentEventCount = mostRecentEventCount; + } + public void setScrollWatcher(ScrollWatcher scrollWatcher) { mScrollWatcher = scrollWatcher; } 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 9cb5990ed152a1..ed352565e5f3dc 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 @@ -15,6 +15,7 @@ import android.text.Editable; import android.text.InputFilter; import android.text.InputType; +import android.text.Layout; import android.text.Spannable; import android.text.TextWatcher; import android.util.TypedValue; @@ -408,6 +409,11 @@ public void setCursorColor(ReactEditText view, @Nullable Integer color) { } catch (IllegalAccessException ex) {} } + @ReactProp(name= "mostRecentEventCount", defaultInt = 0) + public void setMostRecentEventCount(ReactEditText view, int mostRecentEventCount) { + view.setMostRecentEventCount(mostRecentEventCount); + } + @ReactProp(name = "caretHidden", defaultBoolean = false) public void setCaretHidden(ReactEditText view, boolean caretHidden) { view.setCursorVisible(!caretHidden); @@ -455,19 +461,28 @@ public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineCol @ReactProp(name = ViewProps.TEXT_ALIGN) public void setTextAlign(ReactEditText view, @Nullable String textAlign) { - if (textAlign == null || "auto".equals(textAlign)) { - view.setGravityHorizontal(Gravity.NO_GRAVITY); - } else if ("left".equals(textAlign)) { - view.setGravityHorizontal(Gravity.LEFT); - } else if ("right".equals(textAlign)) { - view.setGravityHorizontal(Gravity.RIGHT); - } else if ("center".equals(textAlign)) { - view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL); - } else if ("justify".equals(textAlign)) { - // Fallback gracefully for cross-platform compat instead of error + if ("justify".equals(textAlign)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + view.setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD); + } view.setGravityHorizontal(Gravity.LEFT); } else { - throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + view.setJustificationMode(Layout.JUSTIFICATION_MODE_NONE); + } + + if (textAlign == null || "auto".equals(textAlign)) { + view.setGravityHorizontal(Gravity.NO_GRAVITY); + } else if ("left".equals(textAlign)) { + view.setGravityHorizontal(Gravity.LEFT); + } else if ("right".equals(textAlign)) { + view.setGravityHorizontal(Gravity.RIGHT); + } else if ("center".equals(textAlign)) { + view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL); + } else { + throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + } + } } 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 53efe02e06e46d..f40a05dde07cd9 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 @@ -204,7 +204,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { getPadding(Spacing.RIGHT), getPadding(Spacing.BOTTOM), mTextAlign, - mTextBreakStrategy); + mTextBreakStrategy, + mJustificationMode); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java index 0c3d1fd271fbf2..f9682b0b1fd97b 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java @@ -98,16 +98,4 @@ public void setLogger(YogaLogger logger) { public YogaLogger getLogger() { return mLogger; } - - private native void jni_YGConfigSetHasCloneNodeFunc(long nativePointer, boolean hasClonedFunc); - - public void setOnCloneNode(YogaNodeCloneFunction cloneYogaNodeFunction) { - mYogaNodeCloneFunction = cloneYogaNodeFunction; - jni_YGConfigSetHasCloneNodeFunc(mNativePointer, cloneYogaNodeFunction != null); - } - - @DoNotStrip - private final YogaNodeJNI cloneNode(YogaNodeJNI oldNode, YogaNodeJNI parent, int childIndex) { - return (YogaNodeJNI) mYogaNodeCloneFunction.cloneNode(oldNode, parent, childIndex); - } } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java index e5201297094ae4..c01d0cec637ca9 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java @@ -89,6 +89,8 @@ public static YogaNode create(YogaConfig config) { public abstract void setPositionType(YogaPositionType positionType); + public abstract YogaWrap getWrap(); + public abstract void setWrap(YogaWrap flexWrap); public abstract YogaOverflow getOverflow(); @@ -99,6 +101,8 @@ public static YogaNode create(YogaConfig config) { public abstract void setDisplay(YogaDisplay display); + public abstract float getFlex(); + public abstract void setFlex(float flex); public abstract float getFlexGrow(); @@ -212,4 +216,6 @@ public static YogaNode create(YogaConfig config) { public abstract Object getData(); public abstract void print(); + + public abstract void setStyleInputs(float[] styleInputs, int size); } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java index f439fba6c7d877..790b23bfbd291b 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java @@ -13,7 +13,7 @@ import javax.annotation.Nullable; @DoNotStrip -public class YogaNodeJNI extends YogaNode implements Cloneable { +public class YogaNodeJNI extends YogaNode { static { SoLoader.loadLibrary("yoga"); @@ -36,10 +36,6 @@ public class YogaNodeJNI extends YogaNode implements Cloneable { private static final int PADDING = 2; private static final int BORDER = 4; - @DoNotStrip - private int mEdgeSetFlag = 0; - - private boolean mHasSetPosition = false; private final boolean mAvoidGlobalJNIRefs; @DoNotStrip @@ -120,8 +116,6 @@ public void freeNatives() { private static native void jni_YGNodeReset(long nativePointer); public void reset() { - mEdgeSetFlag = 0; - mHasSetPosition = false; mHasNewLayout = true; mWidth = YogaConstants.UNDEFINED; @@ -189,53 +183,6 @@ public boolean isReferenceBaseline() { return jni_YGNodeIsReferenceBaseline(mNativePointer); } - private static native void jni_YGNodeSetOwner(long nativePointer, long newOwnerNativePointer); - - private native long jni_YGNodeClone(long nativePointer, Object newNode, boolean avoidGlobalJNIRefs); - - @Override - public YogaNodeJNI clone() { - try { - YogaNodeJNI clonedYogaNode = (YogaNodeJNI) super.clone(); - long clonedNativePointer = jni_YGNodeClone(mNativePointer, clonedYogaNode, mAvoidGlobalJNIRefs); - - if (mChildren != null) { - for (YogaNodeJNI child : mChildren) { - jni_YGNodeSetOwner(child.mNativePointer, 0); - child.mOwner = null; - } - } - - clonedYogaNode.mNativePointer = clonedNativePointer; - clonedYogaNode.mOwner = null; - clonedYogaNode.mChildren = - mChildren != null ? (List) ((ArrayList) mChildren).clone() : null; - if (clonedYogaNode.mChildren != null) { - for (YogaNodeJNI child : clonedYogaNode.mChildren) { - child.mOwner = null; - } - } - return clonedYogaNode; - } catch (CloneNotSupportedException ex) { - // This class implements Cloneable, this should not happen - throw new RuntimeException(ex); - } - } - - public YogaNodeJNI cloneWithNewChildren() { - try { - YogaNodeJNI clonedYogaNode = (YogaNodeJNI) super.clone(); - long clonedNativePointer = jni_YGNodeClone(mNativePointer, clonedYogaNode, mAvoidGlobalJNIRefs); - clonedYogaNode.mOwner = null; - clonedYogaNode.mNativePointer = clonedNativePointer; - clonedYogaNode.clearChildren(); - return clonedYogaNode; - } catch (CloneNotSupportedException ex) { - // This class implements Cloneable, this should not happen - throw new RuntimeException(ex); - } - } - private static native void jni_YGNodeClearChildren(long nativePointer); private void clearChildren() { @@ -404,6 +351,11 @@ public void setPositionType(YogaPositionType positionType) { jni_YGNodeStyleSetPositionType(mNativePointer, positionType.intValue()); } + private static native int jni_YGNodeStyleGetFlexWrap(long nativePointer); + public YogaWrap getWrap() { + return YogaWrap.fromInt(jni_YGNodeStyleGetFlexWrap(mNativePointer)); + } + private static native void jni_YGNodeStyleSetFlexWrap(long nativePointer, int wrapType); public void setWrap(YogaWrap flexWrap) { jni_YGNodeStyleSetFlexWrap(mNativePointer, flexWrap.intValue()); @@ -429,6 +381,11 @@ public void setDisplay(YogaDisplay display) { jni_YGNodeStyleSetDisplay(mNativePointer, display.intValue()); } + private static native float jni_YGNodeStyleGetFlex(long nativePointer); + public float getFlex() { + return jni_YGNodeStyleGetFlex(mNativePointer); + } + private static native void jni_YGNodeStyleSetFlex(long nativePointer, float flex); public void setFlex(float flex) { jni_YGNodeStyleSetFlex(mNativePointer, flex); @@ -476,81 +433,61 @@ public void setFlexBasisAuto() { private static native Object jni_YGNodeStyleGetMargin(long nativePointer, int edge); public YogaValue getMargin(YogaEdge edge) { - if (!((mEdgeSetFlag & MARGIN) == MARGIN)) { - return YogaValue.UNDEFINED; - } return (YogaValue) jni_YGNodeStyleGetMargin(mNativePointer, edge.intValue()); } private static native void jni_YGNodeStyleSetMargin(long nativePointer, int edge, float margin); public void setMargin(YogaEdge edge, float margin) { - mEdgeSetFlag |= MARGIN; jni_YGNodeStyleSetMargin(mNativePointer, edge.intValue(), margin); } private static native void jni_YGNodeStyleSetMarginPercent(long nativePointer, int edge, float percent); public void setMarginPercent(YogaEdge edge, float percent) { - mEdgeSetFlag |= MARGIN; jni_YGNodeStyleSetMarginPercent(mNativePointer, edge.intValue(), percent); } private static native void jni_YGNodeStyleSetMarginAuto(long nativePointer, int edge); public void setMarginAuto(YogaEdge edge) { - mEdgeSetFlag |= MARGIN; jni_YGNodeStyleSetMarginAuto(mNativePointer, edge.intValue()); } private static native Object jni_YGNodeStyleGetPadding(long nativePointer, int edge); public YogaValue getPadding(YogaEdge edge) { - if (!((mEdgeSetFlag & PADDING) == PADDING)) { - return YogaValue.UNDEFINED; - } return (YogaValue) jni_YGNodeStyleGetPadding(mNativePointer, edge.intValue()); } private static native void jni_YGNodeStyleSetPadding(long nativePointer, int edge, float padding); public void setPadding(YogaEdge edge, float padding) { - mEdgeSetFlag |= PADDING; jni_YGNodeStyleSetPadding(mNativePointer, edge.intValue(), padding); } private static native void jni_YGNodeStyleSetPaddingPercent(long nativePointer, int edge, float percent); public void setPaddingPercent(YogaEdge edge, float percent) { - mEdgeSetFlag |= PADDING; jni_YGNodeStyleSetPaddingPercent(mNativePointer, edge.intValue(), percent); } private static native float jni_YGNodeStyleGetBorder(long nativePointer, int edge); public float getBorder(YogaEdge edge) { - if (!((mEdgeSetFlag & BORDER) == BORDER)) { - return YogaConstants.UNDEFINED; - } return jni_YGNodeStyleGetBorder(mNativePointer, edge.intValue()); } private static native void jni_YGNodeStyleSetBorder(long nativePointer, int edge, float border); public void setBorder(YogaEdge edge, float border) { - mEdgeSetFlag |= BORDER; jni_YGNodeStyleSetBorder(mNativePointer, edge.intValue(), border); } private static native Object jni_YGNodeStyleGetPosition(long nativePointer, int edge); public YogaValue getPosition(YogaEdge edge) { - if (!mHasSetPosition) { - return YogaValue.UNDEFINED; - } return (YogaValue) jni_YGNodeStyleGetPosition(mNativePointer, edge.intValue()); } private static native void jni_YGNodeStyleSetPosition(long nativePointer, int edge, float position); public void setPosition(YogaEdge edge, float position) { - mHasSetPosition = true; jni_YGNodeStyleSetPosition(mNativePointer, edge.intValue(), position); } private static native void jni_YGNodeStyleSetPositionPercent(long nativePointer, int edge, float percent); public void setPositionPercent(YogaEdge edge, float percent) { - mHasSetPosition = true; jni_YGNodeStyleSetPositionPercent(mNativePointer, edge.intValue(), percent); } @@ -803,6 +740,12 @@ public void print() { jni_YGNodePrint(mNativePointer); } + private static native void jni_YGNodeSetStyleInputs(long nativePointer, float[] styleInputsArray, int size); + + public void setStyleInputs(float[] styleInputsArray, int size) { + jni_YGNodeSetStyleInputs(mNativePointer, styleInputsArray, size); + } + /** * This method replaces the child at childIndex position with the newNode received by parameter. * This is different than calling removeChildAt and addChildAt because this method ONLY replaces diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaStyleInputs.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaStyleInputs.java new file mode 100644 index 00000000000000..0149fa83150765 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaStyleInputs.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +package com.facebook.yoga; + +import com.facebook.proguard.annotations.DoNotStrip; + +@DoNotStrip +public class YogaStyleInputs { + public static final short LAYOUT_DIRECTION = 0; + public static final short FLEX_DIRECTION = 1; + public static final short FLEX = 2; + public static final short FLEX_GROW = 3; + public static final short FLEX_SHRINK = 4; + public static final short FLEX_BASIS = 5; + public static final short FLEX_BASIS_PERCENT = 6; + public static final short FLEX_BASIS_AUTO = 7; + public static final short FLEX_WRAP = 8; + public static final short WIDTH = 9; + public static final short WIDTH_PERCENT = 10; + public static final short WIDTH_AUTO = 11; + public static final short MIN_WIDTH = 12; + public static final short MIN_WIDTH_PERCENT = 13; + public static final short MAX_WIDTH = 14; + public static final short MAX_WIDTH_PERCENT = 15; + public static final short HEIGHT = 16; + public static final short HEIGHT_PERCENT = 17; + public static final short HEIGHT_AUTO = 18; + public static final short MIN_HEIGHT = 19; + public static final short MIN_HEIGHT_PERCENT = 20; + public static final short MAX_HEIGHT = 21; + public static final short MAX_HEIGHT_PERCENT = 22; + public static final short JUSTIFY_CONTENT = 23; + public static final short ALIGN_ITEMS = 24; + public static final short ALIGN_SELF = 25; + public static final short ALIGN_CONTENT = 26; + public static final short POSITION_TYPE = 27; + public static final short ASPECT_RATIO = 28; + public static final short OVERFLOW = 29; + public static final short DISPLAY = 30; + public static final short MARGIN = 31; + public static final short MARGIN_PERCENT = 32; + public static final short MARGIN_AUTO = 33; + public static final short PADDING = 34; + public static final short PADDING_PERCENT = 35; + public static final short BORDER = 36; + public static final short POSITION = 37; + public static final short POSITION_PERCENT = 38; + public static final short IS_REFERENCE_BASELINE = 39; +} diff --git a/ReactAndroid/src/main/jni/first-party/yogajni/Android.mk b/ReactAndroid/src/main/jni/first-party/yogajni/Android.mk index 4850810df7f6bd..7fa2b376b374f9 100644 --- a/ReactAndroid/src/main/jni/first-party/yogajni/Android.mk +++ b/ReactAndroid/src/main/jni/first-party/yogajni/Android.mk @@ -10,7 +10,8 @@ include $(CLEAR_VARS) LOCAL_MODULE := yoga LOCAL_SRC_FILES := \ - jni/YGJNI.cpp + jni/YGJNI.cpp \ + jni/YGJTypes.cpp LOCAL_C_INCLUDES := $(LOCAL_PATH)/jni diff --git a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp index 8fae6d4cf6d4f2..f1d9feaabe798b 100644 --- a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp +++ b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp @@ -11,16 +11,53 @@ #include #include +#include "YGJTypes.h" + using namespace facebook::jni; using namespace std; using facebook::yoga::detail::Log; -struct JYogaNode : public JavaClass { - static constexpr auto kJavaDescriptor = "Lcom/facebook/yoga/YogaNodeJNI;"; -}; - -struct JYogaConfig : public JavaClass { - static constexpr auto kJavaDescriptor = "Lcom/facebook/yoga/YogaConfig;"; +enum YGStyleInput { + LayoutDirection, + FlexDirection, + Flex, + FlexGrow, + FlexShrink, + FlexBasis, + FlexBasisPercent, + FlexBasisAuto, + FlexWrap, + Width, + WidthPercent, + WidthAuto, + MinWidth, + MinWidthPercent, + MaxWidth, + MaxWidthPercent, + Height, + HeightPercent, + HeightAuto, + MinHeight, + MinHeightPercent, + MaxHeight, + MaxHeightPercent, + JustifyContent, + AlignItems, + AlignSelf, + AlignContent, + PositionType, + AspectRatio, + Overflow, + Display, + Margin, + MarginPercent, + MarginAuto, + Padding, + PaddingPercent, + Border, + Position, + PositionPercent, + IsReferenceBaseline, }; class PtrJNodeMap { @@ -51,23 +88,28 @@ class PtrJNodeMap { } }; -struct YGConfigContext { - global_ref* logger; - global_ref* config; - YGConfigContext() : logger(nullptr), config(nullptr) {} - ~YGConfigContext() { - delete config; - config = nullptr; - delete logger; - logger = nullptr; +struct YGNodeContext { + weak_ref* ygNodeJObjectRef{nullptr}; + int edgeSetFlag = 0; + ~YGNodeContext() { + delete ygNodeJObjectRef; } }; +const int MARGIN = 1; +const int PADDING = 2; +const int BORDER = 3; + +static inline YGNodeContext* ygNodeRefToYGNodeContext(YGNodeRef node) { + return reinterpret_cast(node->getContext()); +} + static inline local_ref YGNodeJobject( YGNodeRef node, void* layoutContext) { if (layoutContext == nullptr) { - return reinterpret_cast*>(node->getContext()) + return (reinterpret_cast*>( + ygNodeRefToYGNodeContext(node)->ygNodeJObjectRef)) ->lockLocal(); } else { return reinterpret_cast(layoutContext)->ref(node); @@ -99,6 +141,8 @@ static void YGTransferLayoutOutputsRecursive( return; } + int edgeSetFlag = ygNodeRefToYGNodeContext(root)->edgeSetFlag; + static auto widthField = obj->getClass()->getField("mWidth"); static auto heightField = obj->getClass()->getField("mHeight"); static auto leftField = obj->getClass()->getField("mLeft"); @@ -129,20 +173,11 @@ static void YGTransferLayoutOutputsRecursive( static auto borderBottomField = obj->getClass()->getField("mBorderBottom"); - static auto edgeSetFlagField = - obj->getClass()->getField("mEdgeSetFlag"); static auto hasNewLayoutField = obj->getClass()->getField("mHasNewLayout"); static auto doesLegacyStretchBehaviour = obj->getClass()->getField( "mDoesLegacyStretchFlagAffectsLayout"); - /* Those flags needs be in sync with YogaNode.java */ - const int MARGIN = 1; - const int PADDING = 2; - const int BORDER = 4; - - int hasEdgeSetFlag = (int) obj->getFieldValue(edgeSetFlagField); - obj->setFieldValue(widthField, YGNodeLayoutGetWidth(root)); obj->setFieldValue(heightField, YGNodeLayoutGetHeight(root)); obj->setFieldValue(leftField, YGNodeLayoutGetLeft(root)); @@ -151,7 +186,7 @@ static void YGTransferLayoutOutputsRecursive( doesLegacyStretchBehaviour, YGNodeLayoutGetDidLegacyStretchFlagAffectLayout(root)); - if ((hasEdgeSetFlag & MARGIN) == MARGIN) { + if ((edgeSetFlag & MARGIN) == MARGIN) { obj->setFieldValue( marginLeftField, YGNodeLayoutGetMargin(root, YGEdgeLeft)); obj->setFieldValue(marginTopField, YGNodeLayoutGetMargin(root, YGEdgeTop)); @@ -161,7 +196,7 @@ static void YGTransferLayoutOutputsRecursive( marginBottomField, YGNodeLayoutGetMargin(root, YGEdgeBottom)); } - if ((hasEdgeSetFlag & PADDING) == PADDING) { + if ((edgeSetFlag & PADDING) == PADDING) { obj->setFieldValue( paddingLeftField, YGNodeLayoutGetPadding(root, YGEdgeLeft)); obj->setFieldValue( @@ -172,7 +207,7 @@ static void YGTransferLayoutOutputsRecursive( paddingBottomField, YGNodeLayoutGetPadding(root, YGEdgeBottom)); } - if ((hasEdgeSetFlag & BORDER) == BORDER) { + if ((edgeSetFlag & BORDER) == BORDER) { obj->setFieldValue( borderLeftField, YGNodeLayoutGetBorder(root, YGEdgeLeft)); obj->setFieldValue(borderTopField, YGNodeLayoutGetBorder(root, YGEdgeTop)); @@ -209,10 +244,7 @@ static float YGJNIBaselineFunc( float height, void* layoutContext) { if (auto obj = YGNodeJobject(node, layoutContext)) { - static auto baselineFunc = - findClassStatic("com/facebook/yoga/YogaNodeJNI") - ->getMethod("baseline"); - return baselineFunc(obj, width, height); + return obj->baseline(width, height); } else { return height; } @@ -226,40 +258,6 @@ static inline YGConfigRef _jlong2YGConfigRef(jlong addr) { return reinterpret_cast(static_cast(addr)); } -static YGNodeRef YGJNIOnNodeClonedFunc( - YGNodeRef oldNode, - YGNodeRef owner, - int childIndex, - void* layoutContext) { - auto config = oldNode->getConfig(); - if (!config) { - return nullptr; - } - - static auto onNodeClonedFunc = - findClassStatic("com/facebook/yoga/YogaConfig") - ->getMethod( - local_ref, local_ref, jint)>("cloneNode"); - - auto context = reinterpret_cast(YGConfigGetContext(config)); - auto javaConfig = context->config; - - auto newNode = onNodeClonedFunc( - javaConfig->get(), - YGNodeJobject(oldNode, layoutContext), - YGNodeJobject(owner, layoutContext), - childIndex); - - static auto replaceChild = - findClassStatic("com/facebook/yoga/YogaNodeJNI") - ->getMethod, jint)>("replaceChild"); - - jlong newNodeNativePointer = - replaceChild(YGNodeJobject(owner, layoutContext), newNode, childIndex); - - return _jlong2YGNodeRef(newNodeNativePointer); -} - static YGSize YGJNIMeasureFunc( YGNodeRef node, float width, @@ -268,13 +266,9 @@ static YGSize YGJNIMeasureFunc( YGMeasureMode heightMode, void* layoutContext) { if (auto obj = YGNodeJobject(node, layoutContext)) { - static auto measureFunc = - findClassStatic("com/facebook/yoga/YogaNodeJNI") - ->getMethod("measure"); - YGTransferLayoutDirection(node, obj); const auto measureResult = - measureFunc(obj, width, widthMode, height, heightMode); + obj->measure(width, widthMode, height, heightMode); static_assert( sizeof(measureResult) == 8, @@ -300,10 +294,6 @@ static YGSize YGJNIMeasureFunc( } } -struct JYogaLogLevel : public JavaClass { - static constexpr auto kJavaDescriptor = "Lcom/facebook/yoga/YogaLogLevel;"; -}; - static int YGJNILogFunc( const YGConfigRef config, const YGNodeRef node, @@ -315,32 +305,30 @@ static int YGJNILogFunc( std::vector buffer(1 + result); vsnprintf(buffer.data(), buffer.size(), format, args); - static auto logFunc = - findClassStatic("com/facebook/yoga/YogaLogger") - ->getMethod, local_ref, jstring)>("log"); - - static auto logLevelFromInt = - JYogaLogLevel::javaClassStatic() - ->getStaticMethod("fromInt"); - - if (auto obj = YGNodeJobject(node, layoutContext)) { - auto jlogger = - reinterpret_cast*>(YGConfigGetContext(config)); - logFunc( - jlogger->get(), - obj, - logLevelFromInt( - JYogaLogLevel::javaClassStatic(), static_cast(level)), - Environment::current()->NewStringUTF(buffer.data())); + auto jloggerPtr = + static_cast*>(YGConfigGetContext(config)); + if (jloggerPtr != nullptr) { + if (auto obj = YGNodeJobject(node, layoutContext)) { + (*jloggerPtr) + ->log( + obj, + JYogaLogLevel::fromInt(level), + Environment::current()->NewStringUTF(buffer.data())); + } } return result; } +YGNodeContext* createYGNodeContext(alias_ref thiz) { + YGNodeContext* ygNodeContext = new YGNodeContext(); + ygNodeContext->ygNodeJObjectRef = new weak_ref(make_weak(thiz)); + return ygNodeContext; +} + jlong jni_YGNodeNew(alias_ref thiz) { const YGNodeRef node = YGNodeNew(); - node->setContext(new weak_ref(make_weak(thiz))); + node->setContext(createYGNodeContext(thiz)); node->setPrintFunc(YGPrint); return reinterpret_cast(node); } @@ -350,32 +338,10 @@ jlong jni_YGNodeNewWithConfig( jlong configPointer, jboolean avoidGlobalJNIRefs) { const YGNodeRef node = YGNodeNewWithConfig(_jlong2YGConfigRef(configPointer)); - if (!avoidGlobalJNIRefs) { - node->setContext(new weak_ref(make_weak(thiz))); - } + node->setContext(createYGNodeContext(avoidGlobalJNIRefs ? nullptr : thiz)); return reinterpret_cast(node); } -void jni_YGNodeSetOwner(jlong nativePointer, jlong newOwnerNativePointer) { - const YGNodeRef node = _jlong2YGNodeRef(nativePointer); - const YGNodeRef newOwnerNode = _jlong2YGNodeRef(newOwnerNativePointer); - - node->setOwner(newOwnerNode); -} - -jlong jni_YGNodeClone( - alias_ref thiz, - jlong nativePointer, - alias_ref clonedJavaObject, - jboolean avoidGlobalJNIRefs) { - const YGNodeRef clonedYogaNode = YGNodeClone(_jlong2YGNodeRef(nativePointer)); - if (!avoidGlobalJNIRefs) { - clonedYogaNode->setContext( - new weak_ref(make_weak(clonedJavaObject))); - } - return reinterpret_cast(clonedYogaNode); -} - void jni_YGNodeFree(alias_ref, jlong nativePointer) { if (nativePointer == 0) { return; @@ -383,7 +349,7 @@ void jni_YGNodeFree(alias_ref, jlong nativePointer) { const YGNodeRef node = _jlong2YGNodeRef(nativePointer); auto context = node->getContext(); if (context != nullptr) { - delete reinterpret_cast*>(node->getContext()); + delete reinterpret_cast(node->getContext()); } YGNodeFree(node); } @@ -486,14 +452,6 @@ void jni_YGNodeCopyStyle(jlong dstNativePointer, jlong srcNativePointer) { _jlong2YGNodeRef(dstNativePointer), _jlong2YGNodeRef(srcNativePointer)); } -struct JYogaValue : public JavaClass { - constexpr static auto kJavaDescriptor = "Lcom/facebook/yoga/YogaValue;"; - - static local_ref create(YGValue value) { - return newInstance(value.value, static_cast(value.unit)); - } -}; - #define YG_NODE_JNI_STYLE_PROP(javatype, type, name) \ javatype jni_YGNodeStyleGet##name(jlong nativePointer) { \ return (javatype) YGNodeStyleGet##name(_jlong2YGNodeRef(nativePointer)); \ @@ -582,6 +540,9 @@ YG_NODE_JNI_STYLE_PROP(jint, YGWrap, FlexWrap); YG_NODE_JNI_STYLE_PROP(jint, YGOverflow, Overflow); YG_NODE_JNI_STYLE_PROP(jint, YGDisplay, Display); +jfloat jni_YGNodeStyleGetFlex(jlong nativePointer) { + return YGNodeStyleGetFlex(_jlong2YGNodeRef(nativePointer)); +} void jni_YGNodeStyleSetFlex(jlong nativePointer, jfloat value) { YGNodeStyleSetFlex( _jlong2YGNodeRef(nativePointer), static_cast(value)); @@ -591,9 +552,6 @@ YG_NODE_JNI_STYLE_PROP(jfloat, float, FlexShrink); YG_NODE_JNI_STYLE_UNIT_PROP_AUTO(FlexBasis); YG_NODE_JNI_STYLE_EDGE_UNIT_PROP(Position); -YG_NODE_JNI_STYLE_EDGE_UNIT_PROP_AUTO(Margin); -YG_NODE_JNI_STYLE_EDGE_UNIT_PROP(Padding); -YG_NODE_JNI_STYLE_EDGE_PROP(jfloat, float, Border); YG_NODE_JNI_STYLE_UNIT_PROP_AUTO(Width); YG_NODE_JNI_STYLE_UNIT_PROP(MinWidth); @@ -611,10 +569,9 @@ jlong jni_YGConfigNew(alias_ref) { void jni_YGConfigFree(alias_ref, jlong nativePointer) { const YGConfigRef config = _jlong2YGConfigRef(nativePointer); - auto context = reinterpret_cast(YGConfigGetContext(config)); - if (context) { - delete context; - } + // unique_ptr will destruct the underlying global_ref, if present. + auto context = std::unique_ptr>{ + static_cast*>(YGConfigGetContext(config))}; YGConfigFree(config); } @@ -668,148 +625,385 @@ void jni_YGConfigSetUseLegacyStretchBehaviour( YGConfigSetUseLegacyStretchBehaviour(config, useLegacyStretchBehaviour); } -void jni_YGConfigSetHasCloneNodeFunc( - alias_ref thiz, - jlong nativePointer, - jboolean hasCloneNodeFunc) { - const YGConfigRef config = _jlong2YGConfigRef(nativePointer); - auto context = reinterpret_cast(YGConfigGetContext(config)); - if (context && context->config) { - delete context->config; - context->config = nullptr; - } - - if (hasCloneNodeFunc) { - if (!context) { - context = new YGConfigContext(); - YGConfigSetContext(config, context); - } - context->config = new global_ref(make_global(thiz)); - config->setCloneNodeCallback(YGJNIOnNodeClonedFunc); - } else { - config->setCloneNodeCallback(nullptr); - } -} - void jni_YGConfigSetLogger( alias_ref, jlong nativePointer, alias_ref logger) { const YGConfigRef config = _jlong2YGConfigRef(nativePointer); - auto context = reinterpret_cast(YGConfigGetContext(config)); - if (context && context->logger) { - delete context->logger; - context->logger = nullptr; - } + auto context = + reinterpret_cast*>(YGConfigGetContext(config)); if (logger) { - if (!context) { - context = new YGConfigContext(); + if (context == nullptr) { + context = new global_ref{}; YGConfigSetContext(config, context); } - context->logger = new global_ref(make_global(logger)); + + *context = make_global(static_ref_cast(logger)); config->setLogger(YGJNILogFunc); } else { + if (context != nullptr) { + delete context; + YGConfigSetContext(config, nullptr); + } config->setLogger(nullptr); } } +static void YGNodeSetStyleInputs( + const YGNodeRef node, + float* styleInputs, + int size) { + const auto end = styleInputs + size; + while (styleInputs < end) { + auto styleInputKey = static_cast((int) *styleInputs++); + switch (styleInputKey) { + case LayoutDirection: + YGNodeStyleSetDirection(node, static_cast(*styleInputs++)); + break; + case FlexDirection: + YGNodeStyleSetFlexDirection( + node, static_cast(*styleInputs++)); + break; + case Flex: + YGNodeStyleSetFlex(node, *styleInputs++); + break; + case FlexGrow: + YGNodeStyleSetFlexGrow(node, *styleInputs++); + break; + case FlexShrink: + YGNodeStyleSetFlexShrink(node, *styleInputs++); + break; + case FlexBasis: + YGNodeStyleSetFlexBasis(node, *styleInputs++); + break; + case FlexBasisPercent: + YGNodeStyleSetFlexBasisPercent(node, *styleInputs++); + break; + case FlexBasisAuto: + YGNodeStyleSetFlexBasisAuto(node); + break; + case FlexWrap: + YGNodeStyleSetFlexWrap(node, static_cast(*styleInputs++)); + break; + case Width: + YGNodeStyleSetWidth(node, *styleInputs++); + break; + case WidthPercent: + YGNodeStyleSetWidthPercent(node, *styleInputs++); + break; + case WidthAuto: + YGNodeStyleSetWidthAuto(node); + break; + case MinWidth: + YGNodeStyleSetMinWidth(node, *styleInputs++); + break; + case MinWidthPercent: + YGNodeStyleSetMinWidthPercent(node, *styleInputs++); + break; + case MaxWidth: + YGNodeStyleSetMaxWidth(node, *styleInputs++); + break; + case MaxWidthPercent: + YGNodeStyleSetMaxWidthPercent(node, *styleInputs++); + break; + case Height: + YGNodeStyleSetHeight(node, *styleInputs++); + break; + case HeightPercent: + YGNodeStyleSetHeightPercent(node, *styleInputs++); + break; + case HeightAuto: + YGNodeStyleSetHeightAuto(node); + break; + case MinHeight: + YGNodeStyleSetMinHeight(node, *styleInputs++); + break; + case MinHeightPercent: + YGNodeStyleSetMinHeightPercent(node, *styleInputs++); + break; + case MaxHeight: + YGNodeStyleSetMaxHeight(node, *styleInputs++); + break; + case MaxHeightPercent: + YGNodeStyleSetMaxHeightPercent(node, *styleInputs++); + break; + case JustifyContent: + YGNodeStyleSetJustifyContent( + node, static_cast(*styleInputs++)); + break; + case AlignItems: + YGNodeStyleSetAlignItems(node, static_cast(*styleInputs++)); + break; + case AlignSelf: + YGNodeStyleSetAlignSelf(node, static_cast(*styleInputs++)); + break; + case AlignContent: + YGNodeStyleSetAlignContent(node, static_cast(*styleInputs++)); + break; + case PositionType: + YGNodeStyleSetPositionType( + node, static_cast(*styleInputs++)); + break; + case AspectRatio: + YGNodeStyleSetAspectRatio(node, *styleInputs++); + break; + case Overflow: + YGNodeStyleSetOverflow(node, static_cast(*styleInputs++)); + break; + case Display: + YGNodeStyleSetDisplay(node, static_cast(*styleInputs++)); + break; + case Margin: { + auto edge = static_cast(*styleInputs++); + float marginValue = *styleInputs++; + ygNodeRefToYGNodeContext(node)->edgeSetFlag |= MARGIN; + YGNodeStyleSetMargin(node, edge, marginValue); + break; + } + case MarginPercent: { + auto edge = static_cast(*styleInputs++); + float marginPercent = *styleInputs++; + ygNodeRefToYGNodeContext(node)->edgeSetFlag |= MARGIN; + YGNodeStyleSetMarginPercent(node, edge, marginPercent); + break; + } + case MarginAuto: { + ygNodeRefToYGNodeContext(node)->edgeSetFlag |= MARGIN; + YGNodeStyleSetMarginAuto(node, static_cast(*styleInputs++)); + break; + } + case Padding: { + auto edge = static_cast(*styleInputs++); + float paddingValue = *styleInputs++; + ygNodeRefToYGNodeContext(node)->edgeSetFlag |= PADDING; + YGNodeStyleSetPadding(node, edge, paddingValue); + break; + } + case PaddingPercent: { + auto edge = static_cast(*styleInputs++); + float paddingPercent = *styleInputs++; + ygNodeRefToYGNodeContext(node)->edgeSetFlag |= PADDING; + YGNodeStyleSetPaddingPercent(node, edge, paddingPercent); + break; + } + case Border: { + auto edge = static_cast(*styleInputs++); + float borderValue = *styleInputs++; + ygNodeRefToYGNodeContext(node)->edgeSetFlag |= BORDER; + YGNodeStyleSetBorder(node, edge, borderValue); + break; + } + case Position: { + auto edge = static_cast(*styleInputs++); + float positionValue = *styleInputs++; + YGNodeStyleSetPosition(node, edge, positionValue); + break; + } + case PositionPercent: { + auto edge = static_cast(*styleInputs++); + float positionPercent = *styleInputs++; + YGNodeStyleSetPositionPercent(node, edge, positionPercent); + break; + } + case IsReferenceBaseline: { + YGNodeSetIsReferenceBaseline(node, *styleInputs++ == 1 ? true : false); + break; + } + default: + break; + } + } +} + +void jni_YGNodeSetStyleInputs( + alias_ref thiz, + jlong nativePointer, + alias_ref styleInputs, + jint size) { + float result[size]; + styleInputs->getRegion(0, size, result); + YGNodeSetStyleInputs(_jlong2YGNodeRef(nativePointer), result, size); +} + jint jni_YGNodeGetInstanceCount() { return YGNodeGetInstanceCount(); } +local_ref jni_YGNodeStyleGetMargin( + alias_ref, + jlong nativePointer, + jint edge) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + int edgeSetFlag = ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag; + if ((edgeSetFlag & MARGIN) != MARGIN) { + return JYogaValue::create(YGValueUndefined); + } + return JYogaValue::create( + YGNodeStyleGetMargin(yogaNodeRef, static_cast(edge))); +} + +void jni_YGNodeStyleSetMargin(jlong nativePointer, jint edge, jfloat margin) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag |= MARGIN; + YGNodeStyleSetMargin( + yogaNodeRef, static_cast(edge), static_cast(margin)); +} + +void jni_YGNodeStyleSetMarginPercent( + jlong nativePointer, + jint edge, + jfloat percent) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag |= MARGIN; + YGNodeStyleSetMarginPercent( + yogaNodeRef, static_cast(edge), static_cast(percent)); +} + +void jni_YGNodeStyleSetMarginAuto(jlong nativePointer, jint edge) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag |= MARGIN; + YGNodeStyleSetMarginAuto(yogaNodeRef, static_cast(edge)); +} + +local_ref jni_YGNodeStyleGetPadding( + alias_ref, + jlong nativePointer, + jint edge) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + int edgeSetFlag = ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag; + if ((edgeSetFlag & PADDING) != PADDING) { + return JYogaValue::create(YGValueUndefined); + } + return JYogaValue::create( + YGNodeStyleGetPadding(yogaNodeRef, static_cast(edge))); +} + +void jni_YGNodeStyleSetPadding(jlong nativePointer, jint edge, jfloat padding) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag |= PADDING; + YGNodeStyleSetPadding( + yogaNodeRef, static_cast(edge), static_cast(padding)); +} + +void jni_YGNodeStyleSetPaddingPercent( + jlong nativePointer, + jint edge, + jfloat percent) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag |= PADDING; + YGNodeStyleSetPaddingPercent( + yogaNodeRef, static_cast(edge), static_cast(percent)); +} + +jfloat jni_YGNodeStyleGetBorder(jlong nativePointer, jint edge) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + int edgeSetFlag = ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag; + if ((edgeSetFlag & BORDER) != BORDER) { + return (jfloat) YGUndefined; + } + return (jfloat) YGNodeStyleGetBorder(yogaNodeRef, static_cast(edge)); +} + +void jni_YGNodeStyleSetBorder(jlong nativePointer, jint edge, jfloat border) { + YGNodeRef yogaNodeRef = _jlong2YGNodeRef(nativePointer); + ygNodeRefToYGNodeContext(yogaNodeRef)->edgeSetFlag |= BORDER; + YGNodeStyleSetBorder( + yogaNodeRef, static_cast(edge), static_cast(border)); +} + #define YGMakeNativeMethod(name) makeNativeMethod(#name, name) -#define YGMakeCriticalNativeMethod(name) makeCriticalNativeMethod_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(#name, name) +#define YGMakeCriticalNativeMethod(name) \ + makeCriticalNativeMethod_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(#name, name) jint JNI_OnLoad(JavaVM* vm, void*) { return initialize(vm, [] { - registerNatives( - "com/facebook/yoga/YogaNodeJNI", - { - YGMakeNativeMethod(jni_YGNodeNew), - YGMakeNativeMethod(jni_YGNodeNewWithConfig), - YGMakeNativeMethod(jni_YGNodeFree), - YGMakeCriticalNativeMethod(jni_YGNodeReset), - YGMakeCriticalNativeMethod(jni_YGNodeClearChildren), - YGMakeCriticalNativeMethod(jni_YGNodeInsertChild), - YGMakeCriticalNativeMethod(jni_YGNodeRemoveChild), - YGMakeCriticalNativeMethod(jni_YGNodeSetIsReferenceBaseline), - YGMakeCriticalNativeMethod(jni_YGNodeIsReferenceBaseline), - YGMakeNativeMethod(jni_YGNodeCalculateLayout), - YGMakeCriticalNativeMethod(jni_YGNodeMarkDirty), - YGMakeCriticalNativeMethod( - jni_YGNodeMarkDirtyAndPropogateToDescendants), - YGMakeCriticalNativeMethod(jni_YGNodeIsDirty), - YGMakeCriticalNativeMethod(jni_YGNodeSetHasMeasureFunc), - YGMakeCriticalNativeMethod(jni_YGNodeSetHasBaselineFunc), - YGMakeCriticalNativeMethod(jni_YGNodeCopyStyle), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetDirection), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetDirection), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlexDirection), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexDirection), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetJustifyContent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetJustifyContent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAlignItems), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAlignItems), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAlignSelf), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAlignSelf), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAlignContent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAlignContent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetPositionType), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPositionType), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexWrap), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetOverflow), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetOverflow), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetDisplay), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetDisplay), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlex), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlexGrow), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexGrow), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlexShrink), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexShrink), - YGMakeNativeMethod(jni_YGNodeStyleGetFlexBasis), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexBasis), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexBasisPercent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexBasisAuto), - YGMakeNativeMethod(jni_YGNodeStyleGetMargin), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMargin), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMarginPercent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMarginAuto), - YGMakeNativeMethod(jni_YGNodeStyleGetPadding), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPadding), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPaddingPercent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetBorder), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetBorder), - YGMakeNativeMethod(jni_YGNodeStyleGetPosition), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPosition), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPositionPercent), - YGMakeNativeMethod(jni_YGNodeStyleGetWidth), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetWidth), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetWidthPercent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetWidthAuto), - YGMakeNativeMethod(jni_YGNodeStyleGetHeight), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetHeight), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetHeightPercent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetHeightAuto), - YGMakeNativeMethod(jni_YGNodeStyleGetMinWidth), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinWidth), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinWidthPercent), - YGMakeNativeMethod(jni_YGNodeStyleGetMinHeight), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinHeight), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinHeightPercent), - YGMakeNativeMethod(jni_YGNodeStyleGetMaxWidth), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxWidth), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxWidthPercent), - YGMakeNativeMethod(jni_YGNodeStyleGetMaxHeight), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxHeight), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxHeightPercent), - YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAspectRatio), - YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAspectRatio), - YGMakeCriticalNativeMethod(jni_YGNodeGetInstanceCount), - YGMakeCriticalNativeMethod(jni_YGNodePrint), - YGMakeNativeMethod(jni_YGNodeClone), - YGMakeCriticalNativeMethod(jni_YGNodeSetOwner), - }); + JYogaNode::javaClassStatic()->registerNatives({ + YGMakeNativeMethod(jni_YGNodeNew), + YGMakeNativeMethod(jni_YGNodeNewWithConfig), + YGMakeNativeMethod(jni_YGNodeFree), + YGMakeCriticalNativeMethod(jni_YGNodeReset), + YGMakeCriticalNativeMethod(jni_YGNodeClearChildren), + YGMakeCriticalNativeMethod(jni_YGNodeInsertChild), + YGMakeCriticalNativeMethod(jni_YGNodeRemoveChild), + YGMakeCriticalNativeMethod(jni_YGNodeSetIsReferenceBaseline), + YGMakeCriticalNativeMethod(jni_YGNodeIsReferenceBaseline), + YGMakeNativeMethod(jni_YGNodeCalculateLayout), + YGMakeCriticalNativeMethod(jni_YGNodeMarkDirty), + YGMakeCriticalNativeMethod( + jni_YGNodeMarkDirtyAndPropogateToDescendants), + YGMakeCriticalNativeMethod(jni_YGNodeIsDirty), + YGMakeCriticalNativeMethod(jni_YGNodeSetHasMeasureFunc), + YGMakeCriticalNativeMethod(jni_YGNodeSetHasBaselineFunc), + YGMakeCriticalNativeMethod(jni_YGNodeCopyStyle), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetDirection), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetDirection), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlexDirection), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexDirection), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetJustifyContent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetJustifyContent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAlignItems), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAlignItems), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAlignSelf), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAlignSelf), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAlignContent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAlignContent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetPositionType), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPositionType), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlexWrap), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexWrap), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetOverflow), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetOverflow), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetDisplay), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetDisplay), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlex), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlex), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlexGrow), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexGrow), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetFlexShrink), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexShrink), + YGMakeNativeMethod(jni_YGNodeStyleGetFlexBasis), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexBasis), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexBasisPercent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetFlexBasisAuto), + YGMakeNativeMethod(jni_YGNodeStyleGetMargin), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMargin), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMarginPercent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMarginAuto), + YGMakeNativeMethod(jni_YGNodeStyleGetPadding), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPadding), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPaddingPercent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetBorder), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetBorder), + YGMakeNativeMethod(jni_YGNodeStyleGetPosition), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPosition), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetPositionPercent), + YGMakeNativeMethod(jni_YGNodeStyleGetWidth), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetWidth), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetWidthPercent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetWidthAuto), + YGMakeNativeMethod(jni_YGNodeStyleGetHeight), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetHeight), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetHeightPercent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetHeightAuto), + YGMakeNativeMethod(jni_YGNodeStyleGetMinWidth), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinWidth), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinWidthPercent), + YGMakeNativeMethod(jni_YGNodeStyleGetMinHeight), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinHeight), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMinHeightPercent), + YGMakeNativeMethod(jni_YGNodeStyleGetMaxWidth), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxWidth), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxWidthPercent), + YGMakeNativeMethod(jni_YGNodeStyleGetMaxHeight), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxHeight), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetMaxHeightPercent), + YGMakeCriticalNativeMethod(jni_YGNodeStyleGetAspectRatio), + YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAspectRatio), + YGMakeCriticalNativeMethod(jni_YGNodeGetInstanceCount), + YGMakeCriticalNativeMethod(jni_YGNodePrint), + YGMakeNativeMethod(jni_YGNodeSetStyleInputs), + }); registerNatives( "com/facebook/yoga/YogaConfig", { @@ -821,7 +1015,6 @@ jint JNI_OnLoad(JavaVM* vm, void*) { YGMakeNativeMethod(jni_YGConfigSetPointScaleFactor), YGMakeNativeMethod(jni_YGConfigSetUseLegacyStretchBehaviour), YGMakeNativeMethod(jni_YGConfigSetLogger), - YGMakeNativeMethod(jni_YGConfigSetHasCloneNodeFunc), YGMakeNativeMethod( jni_YGConfigSetShouldDiffLayoutWithoutLegacyStretchBehaviour), }); diff --git a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJTypes.cpp b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJTypes.cpp new file mode 100644 index 00000000000000..6be244958b827d --- /dev/null +++ b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJTypes.cpp @@ -0,0 +1,44 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include "YGJTypes.h" + +using facebook::jni::alias_ref; +using facebook::jni::local_ref; + +jfloat JYogaNode::baseline(jfloat width, jfloat height) { + static auto javaMethod = + javaClassLocal()->getMethod("baseline"); + return javaMethod(self(), width, height); +} + +jlong JYogaNode::measure( + jfloat width, + jint widthMode, + jfloat height, + jint heightMode) { + static auto javaMethod = + javaClassLocal()->getMethod("measure"); + return javaMethod(self(), width, widthMode, height, heightMode); +} + +facebook::jni::local_ref JYogaLogLevel::fromInt(jint logLevel) { + static auto javaMethod = + javaClassStatic()->getStaticMethod(jint)>( + "fromInt"); + return javaMethod(javaClassStatic(), logLevel); +} + +void JYogaLogger::log( + facebook::jni::alias_ref node, + facebook::jni::alias_ref logLevel, + jstring message) { + static auto javaMethod = + javaClassLocal() + ->getMethod, alias_ref, jstring)>("log"); + javaMethod(self(), node, logLevel, message); +} diff --git a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJTypes.h b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJTypes.h new file mode 100644 index 00000000000000..5681e81e1bbee7 --- /dev/null +++ b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJTypes.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include +#include + +struct JYogaNode : public facebook::jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/facebook/yoga/YogaNodeJNI;"; + + jfloat baseline(jfloat width, jfloat height); + jlong measure(jfloat width, jint widthMode, jfloat height, jint heightMode); +}; + +struct JYogaConfig : public facebook::jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/facebook/yoga/YogaConfig;"; +}; + +struct JYogaLogLevel : public facebook::jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/facebook/yoga/YogaLogLevel;"; + + static facebook::jni::local_ref fromInt(jint); +}; + +struct JYogaLogger : public facebook::jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/facebook/yoga/YogaLogger"; + + void log( + facebook::jni::alias_ref, + facebook::jni::alias_ref, + jstring); +}; + +struct JYogaValue : public facebook::jni::JavaClass { + constexpr static auto kJavaDescriptor = "Lcom/facebook/yoga/YogaValue;"; + + static facebook::jni::local_ref create(YGValue value) { + return newInstance(value.value, static_cast(value.unit)); + } +}; diff --git a/ReactAndroid/src/main/jni/react/jni/CxxModuleWrapper.cpp b/ReactAndroid/src/main/jni/react/jni/CxxModuleWrapper.cpp index 4da868a2f291c4..8ad7bacc2b2b36 100644 --- a/ReactAndroid/src/main/jni/react/jni/CxxModuleWrapper.cpp +++ b/ReactAndroid/src/main/jni/react/jni/CxxModuleWrapper.cpp @@ -5,6 +5,8 @@ #include "CxxModuleWrapper.h" +#include + #include #include diff --git a/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp b/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp index 632c2201aee19f..93e166ad065f28 100644 --- a/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp +++ b/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp @@ -5,6 +5,8 @@ #include "JavaModuleWrapper.h" +#include + #include #include #include diff --git a/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp b/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp index 9be87fc9957c05..3c1dc410550dc5 100644 --- a/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp +++ b/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp @@ -9,6 +9,8 @@ #include #endif +#include + #include #include diff --git a/ReactAndroid/src/main/jni/react/jni/ModuleRegistryBuilder.cpp b/ReactAndroid/src/main/jni/react/jni/ModuleRegistryBuilder.cpp index 12a7f5a68234ae..5d1995596240de 100644 --- a/ReactAndroid/src/main/jni/react/jni/ModuleRegistryBuilder.cpp +++ b/ReactAndroid/src/main/jni/react/jni/ModuleRegistryBuilder.cpp @@ -5,6 +5,8 @@ #include "ModuleRegistryBuilder.h" +#include + #include #include diff --git a/ReactAndroid/src/main/res/views/uimanager/values/strings.xml b/ReactAndroid/src/main/res/views/uimanager/values/strings.xml index 60c9e805311896..d180d2a703b5f4 100644 --- a/ReactAndroid/src/main/res/views/uimanager/values/strings.xml +++ b/ReactAndroid/src/main/res/views/uimanager/values/strings.xml @@ -20,4 +20,8 @@ name="adjustable_description" translatable="false" >Adjustable + Heading diff --git a/ReactAndroid/src/test/java/com/facebook/react/CompositeReactPackageTest.java b/ReactAndroid/src/test/java/com/facebook/react/CompositeReactPackageTest.java index 643d003e5175be..788d935547aec8 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/CompositeReactPackageTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/CompositeReactPackageTest.java @@ -34,7 +34,7 @@ import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class CompositeReactPackageTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java index 9debe317bb8c18..99975aeb61e1a7 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java @@ -52,7 +52,7 @@ @PrepareForTest({Arguments.class, SystemClock.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class RootViewTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java index 91037f899c4f81..b0ea8c01b44e74 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java @@ -50,7 +50,7 @@ */ @PrepareForTest({Arguments.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class NativeAnimatedNodeTraversalTest { private static long FRAME_LEN_NANOS = 1000000000L / 60L; diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java index e48e7b20b37199..a5b61034179005 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java @@ -27,7 +27,7 @@ * Tests for {@link BaseJavaModule} and {@link JavaModuleWrapper} */ @PrepareForTest({ReadableNativeArray.class, SoLoader.class}) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) @RunWith(RobolectricTestRunner.class) public class BaseJavaModuleTest { diff --git a/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java b/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java index 3b4a8a209baa82..77f4c852b64e1d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java @@ -26,7 +26,7 @@ @PrepareForTest({ JSDebuggerWebSocketClient.class }) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class JSDebuggerWebSocketClientTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/blob/BlobModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/blob/BlobModuleTest.java index 6aeac0445c7a5b..582209827df289 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/blob/BlobModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/blob/BlobModuleTest.java @@ -41,7 +41,7 @@ @PrepareForTest({Arguments.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) @Config(manifest = Config.NONE) public class BlobModuleTest { diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.java index c220c6c7638dab..455f728ebd40f9 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.java @@ -26,7 +26,7 @@ import static org.mockito.Mockito.mock; @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ImageStoreManagerTest { @Test diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java index aa8fc484ff90c7..677f8fc3647be0 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java @@ -27,7 +27,7 @@ @SuppressLint({"ClipboardManager", "DeprecatedClass"}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ClipboardModuleTest { private static final String TEST_CONTENT = "test"; diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java index d551959eed502c..48acb7265b93db 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java @@ -29,7 +29,7 @@ import static org.junit.Assert.assertNotNull; @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class DialogModuleTest { private ActivityController mActivityController; 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 cdbcb8908c057f..f22c56f72dc664 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 @@ -67,7 +67,7 @@ OkHttpClient.Builder.class, OkHttpCallUtil.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class NetworkingModuleTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java index fc160f006c40bb..7c8f84d7249f68 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java @@ -33,7 +33,7 @@ ReactCookieJarContainer.class }) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactCookieJarContainerTest { 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 index 90484e1591fadd..63b48e89fca657 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java @@ -41,7 +41,7 @@ @PrepareForTest({Arguments.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ShareModuleTest { private Activity mActivity; diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java index 8aa09ae99c5a72..cb9683b65e156f 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java @@ -55,7 +55,7 @@ * Tests for {@link AsyncStorageModule}. */ @PrepareForTest({Arguments.class}) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.json.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*", "org.json.*"}) @RunWith(RobolectricTestRunner.class) public class AsyncStorageModuleTest { 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 e1c663507dc710..a7a18cde6c9135 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 @@ -38,7 +38,7 @@ // DISABLED, BROKEN https://circleci.com/gh/facebook/react-native/12068 // t=13905097 @PrepareForTest({Arguments.class, SystemClock.class, ReactChoreographer.class}) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) @RunWith(RobolectricTestRunner.class) public class TimingModuleTest { diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java index 48e63ea4e2a1e6..60485e9a227a22 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java @@ -42,7 +42,7 @@ @PrepareForTest({PixelUtil.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class LayoutPropertyApplicatorTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterSpecTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterSpecTest.java index 3e6f354e8bce32..d18baaaf01865e 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterSpecTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterSpecTest.java @@ -25,7 +25,7 @@ * Test that verifies that spec of methods annotated with @ReactProp is correct */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactPropAnnotationSetterSpecTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterTest.java index c3545c386b24f6..d2fb9bbc76cbf0 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterTest.java @@ -36,7 +36,7 @@ * annotations. */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactPropAnnotationSetterTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java index 5c46a7fcdf5922..cfa7be3bc0b96a 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java @@ -32,7 +32,7 @@ * Verifies that prop constants are generated properly based on {@code ReactProp} annotation. */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactPropConstantsTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java index 2b5701122091a1..dd36766d7f3e77 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java @@ -32,7 +32,7 @@ * of properties to be updated. */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactPropForShadowNodeSetterTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java index 736c23fc752076..2ae60bf9cfca3d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java @@ -23,7 +23,7 @@ * correct */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactPropForShadowNodeSpecTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/SimpleViewPropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/SimpleViewPropertyTest.java index 73d8992037b0b9..dc67c8e72ab8f9 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/SimpleViewPropertyTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/SimpleViewPropertyTest.java @@ -35,7 +35,7 @@ * Verify {@link View} view property being applied properly by {@link SimpleViewManager} */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class SimpleViewPropertyTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java index a9db7c27b7a9fa..ddc4ced9326e3d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java @@ -27,7 +27,7 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class UIManagerModuleConstantsTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java index 1922984a4a63ea..3190c692f5a451 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java @@ -58,7 +58,7 @@ */ @PrepareForTest({Arguments.class, ReactChoreographer.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class UIManagerModuleTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java index 12e5dc2e967864..6bcf6263d51d71 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java @@ -20,7 +20,7 @@ import static org.fest.assertions.api.Assertions.assertThat; @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ImageResizeModeTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java index 31299f46485c31..95f4bd371d0d00 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java @@ -42,7 +42,7 @@ * by {@link ReactImageManager}. */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactImagePropertyTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/slider/ReactSliderPropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/slider/ReactSliderPropertyTest.java index 321f79c81a418d..ddae7def15b00d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/slider/ReactSliderPropertyTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/slider/ReactSliderPropertyTest.java @@ -29,7 +29,7 @@ * Verify {@link SeekBar} view property being applied properly by {@link ReactSliderManager} */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactSliderPropertyTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/text/CustomLineHeightSpanTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/text/CustomLineHeightSpanTest.java index 219143edfca99a..062ef8d0b90ad3 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/text/CustomLineHeightSpanTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/text/CustomLineHeightSpanTest.java @@ -16,7 +16,7 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class CustomLineHeightSpanTest { @Test diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java index 7938bbffd2625f..e8543d22f5ad6b 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java @@ -15,6 +15,7 @@ import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.text.Layout; import android.text.Spanned; import android.text.TextUtils; import android.text.style.AbsoluteSizeSpan; @@ -56,7 +57,7 @@ */ @PrepareForTest({Arguments.class, ReactChoreographer.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactTextTest { @Rule @@ -419,6 +420,21 @@ public void testMaxLinesApplied() { assertThat(textView.getEllipsize()).isEqualTo(TextUtils.TruncateAt.END); } + @TargetApi(Build.VERSION_CODES.O) + @Test + public void testTextAlignJustifyApplied() { + UIManagerModule uiManager = getUIManagerModule(); + + ReactRootView rootView = createText( + uiManager, + JavaOnlyMap.of("textAlign", "justify"), + JavaOnlyMap.of(ReactRawTextShadowNode.PROP_TEXT, "test text")); + + TextView textView = (TextView) rootView.getChildAt(0); + assertThat(textView.getText().toString()).isEqualTo("test text"); + assertThat(textView.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD); + } + /** * Make sure TextView has exactly one span and that span has given type. */ diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java index 50fc92e218d3ed..f6495494c21980 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java @@ -9,8 +9,10 @@ import android.content.res.ColorStateList; import android.graphics.Color; +import android.os.Build; import android.text.InputType; import android.text.InputFilter; +import android.text.Layout; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.inputmethod.EditorInfo; @@ -42,7 +44,7 @@ * Verify {@link EditText} view property being applied properly by {@link ReactTextInputManager} */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ReactTextInputPropertyTest { @Rule @@ -344,6 +346,10 @@ public void testTextAlign() { assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_HORIZONTAL); mManager.updateProperties(view, buildStyles("textAlign", null)); assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mManager.updateProperties(view, buildStyles("textAlign", "justify")); + assertThat(view.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD); + } // TextAlignVertical mManager.updateProperties(view, buildStyles("textAlignVertical", "top")); diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java index db2bd13d1ad9d4..d7426650e12180 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java @@ -45,7 +45,7 @@ */ @PrepareForTest({Arguments.class, ReactChoreographer.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class TextInputTest { @Rule diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java index 9a813694e8b587..925b7ea3cc2179 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java @@ -22,7 +22,7 @@ * Based on Fresco's DrawableUtilsTest (https://github.com/facebook/fresco). */ @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) public class ColorUtilTest { @Rule diff --git a/ReactCommon/better/mutex.h b/ReactCommon/better/mutex.h index 94fa07fb392cb1..87a70ebbff3fce 100644 --- a/ReactCommon/better/mutex.h +++ b/ReactCommon/better/mutex.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include namespace facebook { diff --git a/ReactCommon/better/string.h b/ReactCommon/better/string.h deleted file mode 100644 index 1b8add7ef741d3..00000000000000 --- a/ReactCommon/better/string.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#ifdef BETTER_USE_FOLLY_CONTAINERS - -#include - -#else - -#include - -#endif - -namespace facebook { -namespace better { - -#ifdef BETTER_USE_FOLLY_CONTAINERS - -using string = folly::fbstring; - -#else - -using string = std::string; - -#endif - -} // namespace better -} // namespace facebook diff --git a/ReactCommon/better/vector.h b/ReactCommon/better/vector.h deleted file mode 100644 index f5f13058f0663e..00000000000000 --- a/ReactCommon/better/vector.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#ifdef BETTER_USE_FOLLY_CONTAINERS - -#include - -#else - -#include - -#endif - -namespace facebook { -namespace better { - -#ifdef BETTER_USE_FOLLY_CONTAINERS - -template -using vector = folly::fbvector; - -#else - -template -using vector = std::vector; - -#endif - -} // namespace better -} // namespace facebook diff --git a/ReactCommon/cxxreact/JSBigString.cpp b/ReactCommon/cxxreact/JSBigString.cpp index e26a81008bd249..0a05714669c59f 100644 --- a/ReactCommon/cxxreact/JSBigString.cpp +++ b/ReactCommon/cxxreact/JSBigString.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include #include diff --git a/ReactCommon/cxxreact/JSIndexedRAMBundle.cpp b/ReactCommon/cxxreact/JSIndexedRAMBundle.cpp index 237b9550f08225..6c2e7e87c7eb26 100644 --- a/ReactCommon/cxxreact/JSIndexedRAMBundle.cpp +++ b/ReactCommon/cxxreact/JSIndexedRAMBundle.cpp @@ -5,6 +5,8 @@ #include "JSIndexedRAMBundle.h" +#include + #include namespace facebook { diff --git a/ReactCommon/fabric/attributedstring/AttributedString.cpp b/ReactCommon/fabric/attributedstring/AttributedString.cpp index 5a85cb28d7e80c..67d2e78258a337 100644 --- a/ReactCommon/fabric/attributedstring/AttributedString.cpp +++ b/ReactCommon/fabric/attributedstring/AttributedString.cpp @@ -34,11 +34,21 @@ bool Fragment::operator!=(const Fragment &rhs) const { void AttributedString::appendFragment(const Fragment &fragment) { ensureUnsealed(); + + if (fragment.string.empty()) { + return; + } + fragments_.push_back(fragment); } void AttributedString::prependFragment(const Fragment &fragment) { ensureUnsealed(); + + if (fragment.string.empty()) { + return; + } + fragments_.insert(fragments_.begin(), fragment); } @@ -72,6 +82,10 @@ std::string AttributedString::getString() const { return string; } +bool AttributedString::isEmpty() const { + return fragments_.empty(); +} + bool AttributedString::operator==(const AttributedString &rhs) const { return fragments_ == rhs.fragments_; } diff --git a/ReactCommon/fabric/attributedstring/AttributedString.h b/ReactCommon/fabric/attributedstring/AttributedString.h index 2178e62f820b54..6fc1abc25333c3 100644 --- a/ReactCommon/fabric/attributedstring/AttributedString.h +++ b/ReactCommon/fabric/attributedstring/AttributedString.h @@ -69,6 +69,11 @@ class AttributedString : public Sealable, public DebugStringConvertible { */ std::string getString() const; + /* + * Returns `true` if the string is empty (has no any fragments). + */ + bool isEmpty() const; + bool operator==(const AttributedString &rhs) const; bool operator!=(const AttributedString &rhs) const; diff --git a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewProps.cpp b/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewProps.cpp deleted file mode 100644 index 6ff45aef622263..00000000000000 --- a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewProps.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -ActivityIndicatorViewProps::ActivityIndicatorViewProps( - const ActivityIndicatorViewProps &sourceProps, - const RawProps &rawProps) - : ViewProps(sourceProps, rawProps), - animating(convertRawProp(rawProps, "animating", sourceProps.animating)), - color(convertRawProp(rawProps, "color", sourceProps.color)), - hidesWhenStopped(convertRawProp( - rawProps, - "hidesWhenStopped", - sourceProps.hidesWhenStopped)), - size(convertRawProp(rawProps, "size", sourceProps.size)) {} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewProps.h b/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewProps.h deleted file mode 100644 index 8a8f7395b11e07..00000000000000 --- a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewProps.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -// TODO (T28334063): Consider for codegen. -class ActivityIndicatorViewProps final : public ViewProps { - public: - ActivityIndicatorViewProps() = default; - ActivityIndicatorViewProps( - const ActivityIndicatorViewProps &sourceProps, - const RawProps &rawProps); - -#pragma mark - Props - - const bool animating{true}; - const SharedColor color{colorFromComponents( - {153 / 255.0, 153 / 255.0, 153 / 255.0, 1.0})}; // #999999 - const bool hidesWhenStopped{true}; - const ActivityIndicatorViewSize size{ActivityIndicatorViewSize::Small}; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewShadowNode.h b/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewShadowNode.h deleted file mode 100644 index ac768dd6d15941..00000000000000 --- a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewShadowNode.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include - -namespace facebook { -namespace react { - -extern const char ActivityIndicatorViewComponentName[]; - -/* - * `ShadowNode` for component. - */ -using ActivityIndicatorViewShadowNode = ConcreteViewShadowNode< - ActivityIndicatorViewComponentName, - ActivityIndicatorViewProps>; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/activityindicator/tests/ActivityIndicatorViewTest.cpp b/ReactCommon/fabric/components/activityindicator/tests/ActivityIndicatorViewTest.cpp deleted file mode 100644 index 05b6e147c22cf9..00000000000000 --- a/ReactCommon/fabric/components/activityindicator/tests/ActivityIndicatorViewTest.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include - -#include - -TEST(ActivityIndicatorViewTest, testSomething) { - // TODO -} diff --git a/ReactCommon/fabric/components/image/ImageComponentDescriptor.h b/ReactCommon/fabric/components/image/ImageComponentDescriptor.h index f7168dcde4286d..2a9babcf8bd240 100644 --- a/ReactCommon/fabric/components/image/ImageComponentDescriptor.h +++ b/ReactCommon/fabric/components/image/ImageComponentDescriptor.h @@ -22,14 +22,22 @@ class ImageComponentDescriptor final : public ConcreteComponentDescriptor { public: ImageComponentDescriptor( - SharedEventDispatcher eventDispatcher, + EventDispatcher::Shared eventDispatcher, const SharedContextContainer &contextContainer) : ConcreteComponentDescriptor(eventDispatcher), + // TODO (39486757): implement image manager on Android, currently Android does + // not have an ImageManager so this will crash +#ifndef ANDROID imageManager_( contextContainer ? contextContainer->getInstance( "ImageManager") - : nullptr) {} + : nullptr) { + } +#else + imageManager_(nullptr) { + } +#endif void adopt(UnsharedShadowNode shadowNode) const override { ConcreteComponentDescriptor::adopt(shadowNode); diff --git a/ReactCommon/fabric/components/image/ImageShadowNode.cpp b/ReactCommon/fabric/components/image/ImageShadowNode.cpp index 5421515f8a3a70..39660911e4226f 100644 --- a/ReactCommon/fabric/components/image/ImageShadowNode.cpp +++ b/ReactCommon/fabric/components/image/ImageShadowNode.cpp @@ -48,7 +48,9 @@ ImageSource ImageShadowNode::getImageSource() const { auto sources = getProps()->sources; if (sources.size() == 0) { - return {.type = ImageSource::Type::Invalid}; + return { + /* .type = */ ImageSource::Type::Invalid, + }; } if (sources.size() == 1) { diff --git a/ReactCommon/fabric/components/image/conversions.h b/ReactCommon/fabric/components/image/conversions.h index 015c8f01d63b9f..27b4960fa97a62 100644 --- a/ReactCommon/fabric/components/image/conversions.h +++ b/ReactCommon/fabric/components/image/conversions.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -16,12 +17,15 @@ namespace react { inline void fromRawValue(const RawValue &value, ImageSource &result) { if (value.hasType()) { - result = {.type = ImageSource::Type::Remote, .uri = (std::string)value}; + result = { + /* .type = */ ImageSource::Type::Remote, + /* .uri = */ (std::string)value, + }; return; } - if (value.hasType>()) { - auto items = (std::unordered_map)value; + if (value.hasType>()) { + auto items = (better::map)value; result = {}; result.type = ImageSource::Type::Remote; diff --git a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewComponentDescriptor.h b/ReactCommon/fabric/components/root/RootComponentDescriptor.h similarity index 63% rename from ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewComponentDescriptor.h rename to ReactCommon/fabric/components/root/RootComponentDescriptor.h index 7dbe425337f335..47a5c9471b5ced 100644 --- a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewComponentDescriptor.h +++ b/ReactCommon/fabric/components/root/RootComponentDescriptor.h @@ -7,14 +7,13 @@ #pragma once -#include +#include #include namespace facebook { namespace react { -using ActivityIndicatorViewComponentDescriptor = - ConcreteComponentDescriptor; +using RootComponentDescriptor = ConcreteComponentDescriptor; } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/root/RootProps.cpp b/ReactCommon/fabric/components/root/RootProps.cpp index d0efb194000d21..134dba48b5f619 100644 --- a/ReactCommon/fabric/components/root/RootProps.cpp +++ b/ReactCommon/fabric/components/root/RootProps.cpp @@ -32,6 +32,11 @@ static YGStyle yogaStyleFromLayoutConstraints( return yogaStyle; } +RootProps::RootProps(const RootProps &sourceProps, const RawProps &rawProps) { + // `RootProps` cannot be constructed from `RawProps`. + assert(false); +} + RootProps::RootProps( const RootProps &sourceProps, const LayoutConstraints &layoutConstraints, diff --git a/ReactCommon/fabric/components/root/RootProps.h b/ReactCommon/fabric/components/root/RootProps.h index 1ef3068eaff62b..bdeaa7014d0172 100644 --- a/ReactCommon/fabric/components/root/RootProps.h +++ b/ReactCommon/fabric/components/root/RootProps.h @@ -23,6 +23,7 @@ using SharedRootProps = std::shared_ptr; class RootProps final : public ViewProps { public: RootProps() = default; + RootProps(const RootProps &sourceProps, const RawProps &rawProps); RootProps( const RootProps &sourceProps, const LayoutConstraints &layoutConstraints, diff --git a/ReactCommon/fabric/components/root/RootShadowNode.cpp b/ReactCommon/fabric/components/root/RootShadowNode.cpp index 30d817bc6d2e1b..85a11ea021b256 100644 --- a/ReactCommon/fabric/components/root/RootShadowNode.cpp +++ b/ReactCommon/fabric/components/root/RootShadowNode.cpp @@ -31,7 +31,12 @@ UnsharedRootShadowNode RootShadowNode::clone( auto props = std::make_shared( *getProps(), layoutConstraints, layoutContext); auto newRootShadowNode = std::make_shared( - *this, ShadowNodeFragment{.props = props}); + *this, + ShadowNodeFragment{ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + }); return newRootShadowNode; } @@ -39,29 +44,37 @@ UnsharedRootShadowNode RootShadowNode::clone( const SharedShadowNode &oldShadowNode, const SharedShadowNode &newShadowNode) const { std::vector> ancestors; - oldShadowNode->constructAncestorPath(*this, ancestors); - if (ancestors.size() == 0) { + if (!oldShadowNode->constructAncestorPath(*this, ancestors)) { return UnsharedRootShadowNode{nullptr}; } auto oldChild = oldShadowNode; auto newChild = newShadowNode; - SharedShadowNodeUnsharedList sharedChildren; - for (const auto &ancestor : ancestors) { - auto children = ancestor.get().getChildren(); + auto oldParent = ancestor.get().shared_from_this(); + + auto children = oldParent->getChildren(); std::replace(children.begin(), children.end(), oldChild, newChild); - sharedChildren = std::make_shared(children); + auto sharedChildren = std::make_shared(children); + auto newParent = oldParent->clone({ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ ShadowNodeFragment::propsPlaceholder(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ sharedChildren, + }); + + newParent->replaceChild(oldChild, newChild); - oldChild = ancestor.get().shared_from_this(); - newChild = oldChild->clone(ShadowNodeFragment{.children = sharedChildren}); + oldChild = oldParent; + newChild = newParent; } - return std::make_shared( - *this, ShadowNodeFragment{.children = sharedChildren}); + return std::const_pointer_cast( + std::static_pointer_cast(newChild)); } } // namespace react diff --git a/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.h b/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.h index 91d419a0b60486..7ee44bfc0f0d5a 100644 --- a/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.h +++ b/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.h @@ -10,7 +10,7 @@ #include #include -#include +#include #include namespace facebook { diff --git a/ReactCommon/fabric/components/slider/BUCK b/ReactCommon/fabric/components/slider/BUCK index e8af90f57c5ad3..a2fca619786aaa 100644 --- a/ReactCommon/fabric/components/slider/BUCK +++ b/ReactCommon/fabric/components/slider/BUCK @@ -6,6 +6,7 @@ load( "fb_xplat_cxx_test", "get_apple_compiler_flags", "get_apple_inspector_flags", + "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob", @@ -17,7 +18,10 @@ rn_xplat_cxx_library( name = "slider", srcs = glob( ["**/*.cpp"], - exclude = glob(["tests/**/*.cpp"]), + exclude = glob([ + "tests/**/*.cpp", + "platform/**/*.cpp", + ]), ), headers = [], header_namespace = "", @@ -33,8 +37,38 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], + fbandroid_deps = [ + react_native_target("jni/react/jni:jni"), + ], + fbandroid_exported_headers = subdir_glob( + [ + ("", "*.h"), + ("platform/android", "*.h"), + ], + prefix = "react/components/slider", + ), + fbandroid_headers = glob( + ["platform/android/*.h"], + ), + fbandroid_srcs = glob( + ["platform/android/*.cpp"], + ), fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), + force_static = True, + ios_exported_headers = subdir_glob( + [ + ("", "*.h"), + ("platform/ios", "*.h"), + ], + prefix = "react/components/slider", + ), + ios_headers = glob( + ["platform/ios/*.h"], + ), + ios_srcs = glob( + ["platform/ios/*.cpp"], + ), macosx_tests_override = [], platforms = (ANDROID, APPLE), preprocessor_flags = [ @@ -56,6 +90,8 @@ rn_xplat_cxx_library( react_native_xplat_target("fabric/components/view:view"), react_native_xplat_target("fabric/graphics:graphics"), react_native_xplat_target("fabric/imagemanager:imagemanager"), + react_native_xplat_target("fabric/uimanager:uimanager"), + "fbsource//xplat/js:generated_components-rncore", ], ) diff --git a/ReactCommon/fabric/components/slider/SliderComponentDescriptor.h b/ReactCommon/fabric/components/slider/SliderComponentDescriptor.h index c692d14e8402f1..8265249bc12f0b 100644 --- a/ReactCommon/fabric/components/slider/SliderComponentDescriptor.h +++ b/ReactCommon/fabric/components/slider/SliderComponentDescriptor.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -20,14 +21,25 @@ class SliderComponentDescriptor final : public ConcreteComponentDescriptor { public: SliderComponentDescriptor( - SharedEventDispatcher eventDispatcher, + EventDispatcher::Shared eventDispatcher, const SharedContextContainer &contextContainer) : ConcreteComponentDescriptor(eventDispatcher), + // TODO (39486757): implement image manager on Android, currently Android does + // not have an ImageManager so this will crash +#ifndef ANDROID imageManager_( contextContainer ? contextContainer->getInstance( "ImageManager") - : nullptr) {} + : nullptr), +#else + imageManager_(nullptr), +#endif + measurementsManager_( + SliderMeasurementsManager::shouldMeasureSlider() + ? std::make_shared(contextContainer) + : nullptr) { + } void adopt(UnsharedShadowNode shadowNode) const override { ConcreteComponentDescriptor::adopt(shadowNode); @@ -39,10 +51,21 @@ class SliderComponentDescriptor final // `SliderShadowNode` uses `ImageManager` to initiate image loading and // communicate the loading state and results to mounting layer. sliderShadowNode->setImageManager(imageManager_); + + if (measurementsManager_) { + // `SliderShadowNode` uses `SliderMeasurementsManager` to + // provide measurements to Yoga. + sliderShadowNode->setSliderMeasurementsManager(measurementsManager_); + + // All `SliderShadowNode`s must have leaf Yoga nodes with properly + // setup measure function. + sliderShadowNode->enableMeasurement(); + } } private: const SharedImageManager imageManager_; + const std::shared_ptr measurementsManager_; }; } // namespace react diff --git a/ReactCommon/fabric/components/slider/SliderEventEmitter.cpp b/ReactCommon/fabric/components/slider/SliderEventEmitter.cpp deleted file mode 100644 index 8d03b6a2075d91..00000000000000 --- a/ReactCommon/fabric/components/slider/SliderEventEmitter.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include "SliderEventEmitter.h" - -namespace facebook { -namespace react { - -void SliderEventEmitter::onValueChange(float value) const { - dispatchEvent("valueChange", [value](jsi::Runtime &runtime) { - auto payload = jsi::Object(runtime); - payload.setProperty(runtime, "value", value); - return payload; - }); -} - -void SliderEventEmitter::onSlidingComplete(float value) const { - dispatchEvent("slidingComplete", [value](jsi::Runtime &runtime) { - auto payload = jsi::Object(runtime); - payload.setProperty(runtime, "value", value); - return payload; - }); -} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/slider/SliderProps.cpp b/ReactCommon/fabric/components/slider/SliderProps.cpp deleted file mode 100644 index cbdb029f99dcc3..00000000000000 --- a/ReactCommon/fabric/components/slider/SliderProps.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -SliderProps::SliderProps( - const SliderProps &sourceProps, - const RawProps &rawProps) - : ViewProps(sourceProps, rawProps), - value(convertRawProp(rawProps, "value", sourceProps.value, value)), - minimumValue(convertRawProp( - rawProps, - "minimumValue", - sourceProps.minimumValue, - minimumValue)), - maximumValue(convertRawProp( - rawProps, - "maximumValue", - sourceProps.maximumValue, - maximumValue)), - step(convertRawProp(rawProps, "step", sourceProps.step, step)), - disabled( - convertRawProp(rawProps, "disabled", sourceProps.disabled, disabled)), - minimumTrackTintColor(convertRawProp( - rawProps, - "minimumTrackTintColor", - sourceProps.minimumTrackTintColor, - minimumTrackTintColor)), - maximumTrackTintColor(convertRawProp( - rawProps, - "maximumTrackTintColor", - sourceProps.maximumTrackTintColor, - maximumTrackTintColor)), - thumbTintColor(convertRawProp( - rawProps, - "thumbTintColor", - sourceProps.thumbTintColor, - thumbTintColor)), - trackImage(convertRawProp( - rawProps, - "trackImage", - sourceProps.trackImage, - trackImage)), - minimumTrackImage(convertRawProp( - rawProps, - "minimumTrackImage", - sourceProps.minimumTrackImage, - minimumTrackImage)), - maximumTrackImage(convertRawProp( - rawProps, - "maximumTrackImage", - sourceProps.maximumTrackImage, - maximumTrackImage)), - thumbImage(convertRawProp( - rawProps, - "thumbImage", - sourceProps.thumbImage, - thumbImage)) {} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/slider/SliderProps.h b/ReactCommon/fabric/components/slider/SliderProps.h deleted file mode 100644 index 8ddee4b6cc008b..00000000000000 --- a/ReactCommon/fabric/components/slider/SliderProps.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include - -namespace facebook { -namespace react { - -// TODO (T28334063): Consider for codegen. -class SliderProps final : public ViewProps { - public: - SliderProps() = default; - SliderProps(const SliderProps &sourceProps, const RawProps &rawProps); - -#pragma mark - Props - - const float value{0}; - const float minimumValue{0}; - const float maximumValue{1}; - const float step{0}; - const bool disabled{false}; - const SharedColor minimumTrackTintColor{}; - const SharedColor maximumTrackTintColor{}; - - // Android only - const SharedColor thumbTintColor; - - // iOS only - const ImageSource trackImage{}; - const ImageSource minimumTrackImage{}; - const ImageSource maximumTrackImage{}; - const ImageSource thumbImage{}; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/slider/SliderShadowNode.cpp b/ReactCommon/fabric/components/slider/SliderShadowNode.cpp index 5726bc2fb25357..a5fd45cc7b98a2 100644 --- a/ReactCommon/fabric/components/slider/SliderShadowNode.cpp +++ b/ReactCommon/fabric/components/slider/SliderShadowNode.cpp @@ -21,6 +21,12 @@ void SliderShadowNode::setImageManager(const SharedImageManager &imageManager) { imageManager_ = imageManager; } +void SliderShadowNode::setSliderMeasurementsManager( + const std::shared_ptr &measurementsManager) { + ensureUnsealed(); + measurementsManager_ = measurementsManager; +} + void SliderShadowNode::updateLocalData() { const auto &newTrackImageSource = getTrackImageSource(); const auto &newMinimumTrackImageSource = getMinimumTrackImageSource(); @@ -86,6 +92,14 @@ ImageSource SliderShadowNode::getThumbImageSource() const { #pragma mark - LayoutableShadowNode +Size SliderShadowNode::measure(LayoutConstraints layoutConstraints) const { + if (SliderMeasurementsManager::shouldMeasureSlider()) { + return measurementsManager_->measure(layoutConstraints); + } + + return {}; +} + void SliderShadowNode::layout(LayoutContext layoutContext) { updateLocalData(); ConcreteViewShadowNode::layout(layoutContext); diff --git a/ReactCommon/fabric/components/slider/SliderShadowNode.h b/ReactCommon/fabric/components/slider/SliderShadowNode.h index 27ffdb043026c4..61a63017e2d753 100644 --- a/ReactCommon/fabric/components/slider/SliderShadowNode.h +++ b/ReactCommon/fabric/components/slider/SliderShadowNode.h @@ -7,8 +7,9 @@ #pragma once -#include -#include +#import +#import +#include #include #include #include @@ -31,8 +32,13 @@ class SliderShadowNode final : public ConcreteViewShadowNode< // Associates a shared `ImageManager` with the node. void setImageManager(const SharedImageManager &imageManager); + // Associates a shared `SliderMeasurementsManager` with the node. + void setSliderMeasurementsManager( + const std::shared_ptr &measurementsManager); + #pragma mark - LayoutableShadowNode + Size measure(LayoutConstraints layoutConstraints) const override; void layout(LayoutContext layoutContext) override; private: @@ -45,6 +51,7 @@ class SliderShadowNode final : public ConcreteViewShadowNode< ImageSource getThumbImageSource() const; SharedImageManager imageManager_; + std::shared_ptr measurementsManager_; }; } // namespace react diff --git a/ReactCommon/fabric/components/slider/platform/android/SliderMeasurementsManager.cpp b/ReactCommon/fabric/components/slider/platform/android/SliderMeasurementsManager.cpp new file mode 100644 index 00000000000000..9f7cf72cc042c9 --- /dev/null +++ b/ReactCommon/fabric/components/slider/platform/android/SliderMeasurementsManager.cpp @@ -0,0 +1,68 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "SliderMeasurementsManager.h" + +#include +#include +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +Size SliderMeasurementsManager::measure( + LayoutConstraints layoutConstraints) const { + { + std::lock_guard lock(mutex_); + if (hasBeenMeasured_) { + return cachedMeasurement_; + } + } + + const jni::global_ref &fabricUIManager = + contextContainer_->getInstance>( + "FabricUIManager"); + + static auto measure = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("measure"); + + auto minimumSize = layoutConstraints.minimumSize; + auto maximumSize = layoutConstraints.maximumSize; + int minWidth = (int)minimumSize.width; + int minHeight = (int)minimumSize.height; + int maxWidth = (int)maximumSize.width; + int maxHeight = (int)maximumSize.height; + + local_ref componentName = make_jstring("RCTSlider"); + + auto measurement = yogaMeassureToSize(measure( + fabricUIManager, + componentName.get(), + nullptr, + nullptr, + minWidth, + maxWidth, + minHeight, + maxHeight)); + + std::lock_guard lock(mutex_); + cachedMeasurement_ = measurement; + return measurement; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/slider/platform/android/SliderMeasurementsManager.h b/ReactCommon/fabric/components/slider/platform/android/SliderMeasurementsManager.h new file mode 100644 index 00000000000000..d0515264ac5c39 --- /dev/null +++ b/ReactCommon/fabric/components/slider/platform/android/SliderMeasurementsManager.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +/** + * Class that manages slider measurements across platforms. + * On iOS it is a noop, since the height is passed in from JS on iOS only. + */ +class SliderMeasurementsManager { + public: + SliderMeasurementsManager(const SharedContextContainer &contextContainer) + : contextContainer_(contextContainer) {} + + static inline bool shouldMeasureSlider() { + return true; + } + + Size measure(LayoutConstraints layoutConstraints) const; + + private: + const SharedContextContainer contextContainer_; + mutable std::mutex mutex_; + mutable bool hasBeenMeasured_ = false; + mutable Size cachedMeasurement_{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/slider/platform/ios/SliderMeasurementsManager.cpp b/ReactCommon/fabric/components/slider/platform/ios/SliderMeasurementsManager.cpp new file mode 100644 index 00000000000000..8d7fc170631933 --- /dev/null +++ b/ReactCommon/fabric/components/slider/platform/ios/SliderMeasurementsManager.cpp @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "SliderMeasurementsManager.h" + +namespace facebook { +namespace react { + +Size SliderMeasurementsManager::measure( + LayoutConstraints layoutConstraints) const { + assert(false); // should never reach this point + return {}; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/slider/platform/ios/SliderMeasurementsManager.h b/ReactCommon/fabric/components/slider/platform/ios/SliderMeasurementsManager.h new file mode 100644 index 00000000000000..2571886edf29b3 --- /dev/null +++ b/ReactCommon/fabric/components/slider/platform/ios/SliderMeasurementsManager.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +/** + * Class that manages slider measurements across platforms. + * On iOS it is a noop, since the height is passed in from JS on iOS only. + */ +class SliderMeasurementsManager { + public: + SliderMeasurementsManager(const SharedContextContainer &contextContainer) {} + + static inline bool shouldMeasureSlider() { + return false; + } + + Size measure(LayoutConstraints layoutConstraints) const; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h b/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h index f87fffdad2e078..499490db685a5c 100644 --- a/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h +++ b/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h @@ -25,7 +25,7 @@ class ParagraphComponentDescriptor final : public ConcreteComponentDescriptor { public: ParagraphComponentDescriptor( - SharedEventDispatcher eventDispatcher, + EventDispatcher::Shared eventDispatcher, const SharedContextContainer &contextContainer) : ConcreteComponentDescriptor(eventDispatcher) { // Every single `ParagraphShadowNode` will have a reference to @@ -54,6 +54,7 @@ class ParagraphComponentDescriptor final } } + protected: void adopt(UnsharedShadowNode shadowNode) const override { ConcreteComponentDescriptor::adopt(shadowNode); diff --git a/ReactCommon/fabric/components/text/paragraph/ParagraphMeasurementCache.h b/ReactCommon/fabric/components/text/paragraph/ParagraphMeasurementCache.h index 6c4f2a89059a62..68dcb686314180 100644 --- a/ReactCommon/fabric/components/text/paragraph/ParagraphMeasurementCache.h +++ b/ReactCommon/fabric/components/text/paragraph/ParagraphMeasurementCache.h @@ -7,47 +7,21 @@ #pragma once -#include - #include #include #include +#include namespace facebook { namespace react { + using ParagraphMeasurementCacheKey = std::tuple; using ParagraphMeasurementCacheValue = Size; - -class ParagraphMeasurementCache { - public: - ParagraphMeasurementCache() : cache_{256} {} - - bool exists(const ParagraphMeasurementCacheKey &key) const { - std::lock_guard lock(mutex_); - return cache_.exists(key); - } - - ParagraphMeasurementCacheValue get( - const ParagraphMeasurementCacheKey &key) const { - std::lock_guard lock(mutex_); - return cache_.get(key); - } - - void set( - const ParagraphMeasurementCacheKey &key, - const ParagraphMeasurementCacheValue &value) const { - std::lock_guard lock(mutex_); - cache_.set(key, value); - } - - private: - mutable folly::EvictingCacheMap< - ParagraphMeasurementCacheKey, - ParagraphMeasurementCacheValue> - cache_; - mutable std::mutex mutex_; -}; +using ParagraphMeasurementCache = SimpleThreadSafeCache< + ParagraphMeasurementCacheKey, + ParagraphMeasurementCacheValue, + 256>; } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp b/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp index 68def552b09cc3..028652ca5cbcc7 100644 --- a/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp +++ b/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp @@ -59,29 +59,27 @@ void ParagraphShadowNode::updateLocalDataIfNeeded() { Size ParagraphShadowNode::measure(LayoutConstraints layoutConstraints) const { AttributedString attributedString = getAttributedString(); - const ParagraphAttributes attributes = getProps()->paragraphAttributes; - auto makeMeasurements = [&] { - return textLayoutManager_->measure( - attributedString, getProps()->paragraphAttributes, layoutConstraints); - }; + if (attributedString.isEmpty()) { + return {0, 0}; + } + + const ParagraphAttributes paragraphAttributes = + getProps()->paragraphAttributes; // Cache results of this function so we don't need to call measure() - // repeatedly - if (measureCache_ != nullptr) { - ParagraphMeasurementCacheKey cacheKey = - std::make_tuple(attributedString, attributes, layoutConstraints); - if (measureCache_->exists(cacheKey)) { - return measureCache_->get(cacheKey); - } - - auto measuredSize = makeMeasurements(); - measureCache_->set(cacheKey, measuredSize); - - return measuredSize; + // repeatedly. + if (measureCache_) { + return measureCache_->get( + ParagraphMeasurementCacheKey{attributedString, paragraphAttributes, layoutConstraints}, + [&](const ParagraphMeasurementCacheKey &key) { + return textLayoutManager_->measure( + attributedString, paragraphAttributes, layoutConstraints); + }); } - return makeMeasurements(); + return textLayoutManager_->measure( + attributedString, paragraphAttributes, layoutConstraints); } void ParagraphShadowNode::layout(LayoutContext layoutContext) { diff --git a/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h b/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h index 584afe8ec9eeeb..f1be7bbff09fec 100644 --- a/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h +++ b/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h @@ -28,11 +28,13 @@ namespace react { template < const char *concreteComponentName, typename ViewPropsT = ViewProps, - typename ViewEventEmitterT = ViewEventEmitter> + typename ViewEventEmitterT = ViewEventEmitter, + typename... Ts> class ConcreteViewShadowNode : public ConcreteShadowNode< concreteComponentName, ViewPropsT, - ViewEventEmitterT>, + ViewEventEmitterT, + Ts...>, public AccessibleShadowNode, public YogaLayoutableShadowNode { static_assert( @@ -46,14 +48,17 @@ class ConcreteViewShadowNode : public ConcreteShadowNode< "ViewPropsT must be a descendant of AccessibilityProps"); public: - using BaseShadowNode = - ConcreteShadowNode; + using BaseShadowNode = ConcreteShadowNode< + concreteComponentName, + ViewPropsT, + ViewEventEmitterT, + Ts...>; using ConcreteViewProps = ViewPropsT; ConcreteViewShadowNode( const ShadowNodeFragment &fragment, - const ShadowNodeCloneFunction &cloneFunction) - : BaseShadowNode(fragment, cloneFunction), + const ComponentDescriptor &componentDescriptor) + : BaseShadowNode(fragment, componentDescriptor), AccessibleShadowNode( std::static_pointer_cast(fragment.props)), YogaLayoutableShadowNode() { diff --git a/ReactCommon/fabric/components/view/TouchEventEmitter.h b/ReactCommon/fabric/components/view/TouchEventEmitter.h index 00471e5da77f89..9edfd51c76e4dc 100644 --- a/ReactCommon/fabric/components/view/TouchEventEmitter.h +++ b/ReactCommon/fabric/components/view/TouchEventEmitter.h @@ -6,9 +6,9 @@ */ #pragma once +#include #include #include -#include namespace facebook { namespace react { diff --git a/ReactCommon/fabric/components/view/ViewProps.cpp b/ReactCommon/fabric/components/view/ViewProps.cpp index db11821b83a331..f2dc4572f86877 100644 --- a/ReactCommon/fabric/components/view/ViewProps.cpp +++ b/ReactCommon/fabric/components/view/ViewProps.cpp @@ -79,21 +79,26 @@ ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps) BorderMetrics ViewProps::resolveBorderMetrics(bool isRTL) const { auto borderWidths = CascadedBorderWidths{ - .left = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeLeft]), - .top = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeTop]), - .right = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeRight]), - .bottom = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeBottom]), - .start = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeStart]), - .end = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeEnd]), - .horizontal = - optionalFloatFromYogaValue(yogaStyle.border[YGEdgeHorizontal]), - .vertical = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeVertical]), - .all = optionalFloatFromYogaValue(yogaStyle.border[YGEdgeAll])}; + /* .left = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeLeft]), + /* .top = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeTop]), + /* .right = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeRight]), + /* .bottom = */ + optionalFloatFromYogaValue(yogaStyle.border[YGEdgeBottom]), + /* .start = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeStart]), + /* .end = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeEnd]), + /* .horizontal = */ + optionalFloatFromYogaValue(yogaStyle.border[YGEdgeHorizontal]), + /* .vertical = */ + optionalFloatFromYogaValue(yogaStyle.border[YGEdgeVertical]), + /* .all = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeAll]), + }; - return {.borderColors = borderColors.resolve(isRTL, {}), - .borderWidths = borderWidths.resolve(isRTL, 0), - .borderRadii = borderRadii.resolve(isRTL, 0), - .borderStyles = borderStyles.resolve(isRTL, BorderStyle::Solid)}; + return { + /* .borderColors = */ borderColors.resolve(isRTL, {}), + /* .borderWidths = */ borderWidths.resolve(isRTL, 0), + /* .borderRadii = */ borderRadii.resolve(isRTL, 0), + /* .borderStyles = */ borderStyles.resolve(isRTL, BorderStyle::Solid), + }; } #pragma mark - DebugStringConvertible diff --git a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp index f8ba55d976d81c..a900be33ca1989 100644 --- a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp +++ b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp @@ -7,6 +7,8 @@ #include "AccessibilityProps.h" +#include + #include #include #include diff --git a/ReactCommon/fabric/components/view/conversions.h b/ReactCommon/fabric/components/view/conversions.h index 46d99ff1324760..a4fe368f6a7cf1 100644 --- a/ReactCommon/fabric/components/view/conversions.h +++ b/ReactCommon/fabric/components/view/conversions.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -361,8 +362,7 @@ inline void fromRawValue(const RawValue &value, Transform &result) { auto configurations = (std::vector)value; for (const auto &configuration : configurations) { - auto configurationPair = - (std::unordered_map)configuration; + auto configurationPair = (better::map)configuration; auto pair = configurationPair.begin(); auto operation = pair->first; auto ¶meters = pair->second; diff --git a/ReactCommon/fabric/components/view/primitives.h b/ReactCommon/fabric/components/view/primitives.h index 04967251093b83..d7900c2323d167 100644 --- a/ReactCommon/fabric/components/view/primitives.h +++ b/ReactCommon/fabric/components/view/primitives.h @@ -204,11 +204,13 @@ struct CascadedRectangleEdges { const auto verticalOrAllOrDefault = vertical.value_or(all.value_or(defaults)); - return Counterpart{ - .left = left.value_or(leading.value_or(horizontalOrAllOrDefault)), - .right = right.value_or(trailing.value_or(horizontalOrAllOrDefault)), - .top = top.value_or(verticalOrAllOrDefault), - .bottom = bottom.value_or(verticalOrAllOrDefault)}; + return { + /* .left = */ left.value_or(leading.value_or(horizontalOrAllOrDefault)), + /* .right = */ + right.value_or(trailing.value_or(horizontalOrAllOrDefault)), + /* .top = */ top.value_or(verticalOrAllOrDefault), + /* .bottom = */ bottom.value_or(verticalOrAllOrDefault), + }; } bool operator==(const CascadedRectangleEdges &rhs) const { @@ -260,15 +262,16 @@ struct CascadedRectangleCorners { const auto bottomLeading = isRTL ? bottomEnd : bottomStart; const auto bottomTrailing = isRTL ? bottomStart : bottomEnd; - return Counterpart{ - .topLeft = - topLeft.value_or(topLeading.value_or(all.value_or(defaults))), - .topRight = - topRight.value_or(topTrailing.value_or(all.value_or(defaults))), - .bottomLeft = - bottomLeft.value_or(topLeading.value_or(all.value_or(defaults))), - .bottomRight = - bottomRight.value_or(topTrailing.value_or(all.value_or(defaults)))}; + return { + /* .topLeft = */ topLeft.value_or( + topLeading.value_or(all.value_or(defaults))), + /* .topRight = */ + topRight.value_or(topTrailing.value_or(all.value_or(defaults))), + /* .bottomLeft = */ + bottomLeft.value_or(topLeading.value_or(all.value_or(defaults))), + /* .bottomRight = */ + bottomRight.value_or(topTrailing.value_or(all.value_or(defaults))), + }; } bool operator==(const CascadedRectangleCorners &rhs) const { diff --git a/ReactCommon/fabric/core/BUCK b/ReactCommon/fabric/core/BUCK index 81ef44668cfcf8..27456f16061242 100644 --- a/ReactCommon/fabric/core/BUCK +++ b/ReactCommon/fabric/core/BUCK @@ -22,6 +22,7 @@ rn_xplat_cxx_library( ("componentdescriptor", "*.h"), ("layout", "*.h"), ("shadownode", "*.h"), + ("state", "*.h"), ], prefix = "react/core", ), @@ -47,10 +48,11 @@ rn_xplat_cxx_library( "fbsource//xplat/folly:headers_only", "fbsource//xplat/folly:memory", "fbsource//xplat/folly:molly", + "fbsource//xplat/jsi:JSIDynamic", + "fbsource//xplat/jsi:jsi", "fbsource//xplat/third-party/glog:glog", react_native_xplat_target("utils:utils"), react_native_xplat_target("fabric/debug:debug"), - react_native_xplat_target("fabric/events:events"), react_native_xplat_target("fabric/graphics:graphics"), ], ) diff --git a/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h b/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h index 6eb19fbacda19d..dc6c5a719c2971 100644 --- a/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h +++ b/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h @@ -9,6 +9,8 @@ #include #include +#include +#include namespace facebook { namespace react { @@ -77,6 +79,20 @@ class ComponentDescriptor { virtual SharedEventEmitter createEventEmitter( SharedEventTarget eventTarget, const Tag &tag) const = 0; + + /* + * Create an initial State object that represents (and contains) an initial + * State's data which can be constructed based on initial Props. + */ + virtual State::Shared createInitialState(const SharedProps &props) const = 0; + + /* + * Creates a new State object that represents (and contains) a new version of + * State's data. + */ + virtual State::Shared createState( + const State::Shared &previousState, + const StateData::Shared &data) const = 0; }; } // namespace react diff --git a/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h b/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h index 7a52cb2d286029..be189ed8e01d8e 100644 --- a/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h +++ b/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h @@ -11,10 +11,13 @@ #include #include +#include #include #include #include -#include +#include +#include +#include namespace facebook { namespace react { @@ -31,14 +34,18 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { "ShadowNodeT must be a descendant of ShadowNode"); using SharedShadowNodeT = std::shared_ptr; + + public: + using ConcreteShadowNode = ShadowNodeT; using ConcreteProps = typename ShadowNodeT::ConcreteProps; using SharedConcreteProps = typename ShadowNodeT::SharedConcreteProps; using ConcreteEventEmitter = typename ShadowNodeT::ConcreteEventEmitter; using SharedConcreteEventEmitter = typename ShadowNodeT::SharedConcreteEventEmitter; + using ConcreteState = typename ShadowNodeT::ConcreteState; + using ConcreteStateData = typename ShadowNodeT::ConcreteState::Data; - public: - ConcreteComponentDescriptor(SharedEventDispatcher eventDispatcher) + ConcreteComponentDescriptor(EventDispatcher::Shared eventDispatcher) : eventDispatcher_(eventDispatcher) {} ComponentHandle getComponentHandle() const override { @@ -55,8 +62,7 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { assert(std::dynamic_pointer_cast( fragment.eventEmitter)); - auto shadowNode = - std::make_shared(fragment, getCloneFunction()); + auto shadowNode = std::make_shared(fragment, *this); adopt(shadowNode); @@ -95,6 +101,32 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { std::move(eventTarget), tag, eventDispatcher_); } + virtual State::Shared createInitialState( + const SharedProps &props) const override { + if (std::is_same::value) { + // Default case: Returning `null` for nodes that don't use `State`. + return nullptr; + } + + return std::make_shared( + ConcreteShadowNode::initialStateData( + std::static_pointer_cast(props)), + std::make_shared(eventDispatcher_)); + } + + virtual State::Shared createState( + const State::Shared &previousState, + const StateData::Shared &data) const override { + if (std::is_same::value) { + // Default case: Returning `null` for nodes that don't use `State`. + return nullptr; + } + + return std::make_shared( + std::move(*std::static_pointer_cast(data)), + *std::static_pointer_cast(previousState)); + } + protected: virtual void adopt(UnsharedShadowNode shadowNode) const { // Default implementation does nothing. @@ -102,21 +134,7 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { } private: - mutable SharedEventDispatcher eventDispatcher_{nullptr}; - - mutable ShadowNodeCloneFunction cloneFunction_; - - ShadowNodeCloneFunction getCloneFunction() const { - if (!cloneFunction_) { - cloneFunction_ = [this]( - const ShadowNode &shadowNode, - const ShadowNodeFragment &fragment) { - return this->cloneShadowNode(shadowNode, fragment); - }; - } - - return cloneFunction_; - } + mutable EventDispatcher::Shared eventDispatcher_{nullptr}; }; } // namespace react diff --git a/ReactCommon/fabric/events/BatchedEventQueue.cpp b/ReactCommon/fabric/core/events/BatchedEventQueue.cpp similarity index 75% rename from ReactCommon/fabric/events/BatchedEventQueue.cpp rename to ReactCommon/fabric/core/events/BatchedEventQueue.cpp index 415958e70e00f8..d7b411f058b4be 100644 --- a/ReactCommon/fabric/events/BatchedEventQueue.cpp +++ b/ReactCommon/fabric/core/events/BatchedEventQueue.cpp @@ -10,8 +10,9 @@ namespace facebook { namespace react { -void BatchedEventQueue::enqueueEvent(const RawEvent &rawEvent) const { - EventQueue::enqueueEvent(rawEvent); +void BatchedEventQueue::onEnqueue() const { + EventQueue::onEnqueue(); + eventBeat_->request(); } diff --git a/ReactCommon/fabric/events/BatchedEventQueue.h b/ReactCommon/fabric/core/events/BatchedEventQueue.h similarity index 83% rename from ReactCommon/fabric/events/BatchedEventQueue.h rename to ReactCommon/fabric/core/events/BatchedEventQueue.h index c73490a52c4c92..285133c8042076 100644 --- a/ReactCommon/fabric/events/BatchedEventQueue.h +++ b/ReactCommon/fabric/core/events/BatchedEventQueue.h @@ -7,7 +7,7 @@ #pragma once -#include +#include namespace facebook { namespace react { @@ -20,7 +20,7 @@ class BatchedEventQueue final : public EventQueue { public: using EventQueue::EventQueue; - void enqueueEvent(const RawEvent &rawEvent) const override; + void onEnqueue() const override; }; } // namespace react diff --git a/ReactCommon/fabric/events/EventBeat.cpp b/ReactCommon/fabric/core/events/EventBeat.cpp similarity index 100% rename from ReactCommon/fabric/events/EventBeat.cpp rename to ReactCommon/fabric/core/events/EventBeat.cpp diff --git a/ReactCommon/fabric/events/EventBeat.h b/ReactCommon/fabric/core/events/EventBeat.h similarity index 100% rename from ReactCommon/fabric/events/EventBeat.h rename to ReactCommon/fabric/core/events/EventBeat.h diff --git a/ReactCommon/fabric/events/EventBeatBasedExecutor.cpp b/ReactCommon/fabric/core/events/EventBeatBasedExecutor.cpp similarity index 88% rename from ReactCommon/fabric/events/EventBeatBasedExecutor.cpp rename to ReactCommon/fabric/core/events/EventBeatBasedExecutor.cpp index 2f09d0bb5902e3..d9b630c4f9b040 100644 --- a/ReactCommon/fabric/events/EventBeatBasedExecutor.cpp +++ b/ReactCommon/fabric/core/events/EventBeatBasedExecutor.cpp @@ -9,8 +9,6 @@ #include "EventBeatBasedExecutor.h" -#include - namespace facebook { namespace react { @@ -27,15 +25,19 @@ EventBeatBasedExecutor::EventBeatBasedExecutor( void EventBeatBasedExecutor::operator()(Routine routine, Mode mode) const { if (mode == Mode::Asynchronous) { - execute({.routine = std::move(routine)}); + execute({ + /* .routine = */ std::move(routine), + }); return; } std::mutex mutex; mutex.lock(); - execute({.routine = std::move(routine), - .callback = [&mutex]() { mutex.unlock(); }}); + execute({ + /* .routine = */ std::move(routine), + /* .callback = */ [&mutex]() { mutex.unlock(); }, + }); mutex.lock(); } diff --git a/ReactCommon/fabric/events/EventBeatBasedExecutor.h b/ReactCommon/fabric/core/events/EventBeatBasedExecutor.h similarity index 96% rename from ReactCommon/fabric/events/EventBeatBasedExecutor.h rename to ReactCommon/fabric/core/events/EventBeatBasedExecutor.h index 7ef5a2d74ceec7..ea281ae731cb15 100644 --- a/ReactCommon/fabric/events/EventBeatBasedExecutor.h +++ b/ReactCommon/fabric/core/events/EventBeatBasedExecutor.h @@ -11,7 +11,7 @@ #include #include -#include +#include namespace facebook { namespace react { diff --git a/ReactCommon/fabric/events/EventDispatcher.cpp b/ReactCommon/fabric/core/events/EventDispatcher.cpp similarity index 69% rename from ReactCommon/fabric/events/EventDispatcher.cpp rename to ReactCommon/fabric/core/events/EventDispatcher.cpp index 737b8a7a91d289..ea08e6d8bb7ecb 100644 --- a/ReactCommon/fabric/events/EventDispatcher.cpp +++ b/ReactCommon/fabric/core/events/EventDispatcher.cpp @@ -7,7 +7,10 @@ #include "EventDispatcher.h" +#include + #include "BatchedEventQueue.h" +#include "RawEvent.h" #include "UnbatchedEventQueue.h" #define REACT_FABRIC_SYNC_EVENT_DISPATCHING_DISABLED @@ -17,32 +20,43 @@ namespace react { EventDispatcher::EventDispatcher( const EventPipe &eventPipe, + const StatePipe &statePipe, const EventBeatFactory &synchonousEventBeatFactory, const EventBeatFactory &asynchonousEventBeatFactory) { // Synchronous/Unbatched eventQueues_[(int)EventPriority::SynchronousUnbatched] = std::make_unique( - eventPipe, synchonousEventBeatFactory()); + eventPipe, statePipe, synchonousEventBeatFactory()); // Synchronous/Batched eventQueues_[(int)EventPriority::SynchronousBatched] = std::make_unique( - eventPipe, synchonousEventBeatFactory()); + eventPipe, statePipe, synchonousEventBeatFactory()); // Asynchronous/Unbatched eventQueues_[(int)EventPriority::AsynchronousUnbatched] = std::make_unique( - eventPipe, asynchonousEventBeatFactory()); + eventPipe, statePipe, asynchonousEventBeatFactory()); // Asynchronous/Batched eventQueues_[(int)EventPriority::AsynchronousBatched] = std::make_unique( - eventPipe, asynchonousEventBeatFactory()); + eventPipe, statePipe, asynchonousEventBeatFactory()); } void EventDispatcher::dispatchEvent( const RawEvent &rawEvent, EventPriority priority) const { + getEventQueue(priority).enqueueEvent(std::move(rawEvent)); +} + +void EventDispatcher::dispatchStateUpdate( + StateUpdate &&stateUpdate, + EventPriority priority) const { + getEventQueue(priority).enqueueStateUpdate(std::move(stateUpdate)); +} + +const EventQueue &EventDispatcher::getEventQueue(EventPriority priority) const { #ifdef REACT_FABRIC_SYNC_EVENT_DISPATCHING_DISABLED // Synchronous dispatch works, but JavaScript interop layer does not have // proper synchonization yet and it crashes. @@ -55,7 +69,7 @@ void EventDispatcher::dispatchEvent( } #endif - eventQueues_[(int)priority]->enqueueEvent(rawEvent); + return *eventQueues_[(int)priority]; } } // namespace react diff --git a/ReactCommon/fabric/events/EventDispatcher.h b/ReactCommon/fabric/core/events/EventDispatcher.h similarity index 59% rename from ReactCommon/fabric/events/EventDispatcher.h rename to ReactCommon/fabric/core/events/EventDispatcher.h index 897376b1e15ada..026ed66b68e69f 100644 --- a/ReactCommon/fabric/events/EventDispatcher.h +++ b/ReactCommon/fabric/core/events/EventDispatcher.h @@ -6,19 +6,20 @@ */ #pragma once +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace facebook { namespace react { -class EventDispatcher; -using SharedEventDispatcher = std::shared_ptr; -using WeakEventDispatcher = std::weak_ptr; +class RawEvent; +class StateUpdate; /* * Represents event-delivery infrastructure. @@ -26,8 +27,12 @@ using WeakEventDispatcher = std::weak_ptr; */ class EventDispatcher { public: + using Shared = std::shared_ptr; + using Weak = std::weak_ptr; + EventDispatcher( const EventPipe &eventPipe, + const StatePipe &statePipe, const EventBeatFactory &synchonousEventBeatFactory, const EventBeatFactory &asynchonousEventBeatFactory); @@ -36,7 +41,15 @@ class EventDispatcher { */ void dispatchEvent(const RawEvent &rawEvent, EventPriority priority) const; + /* + * Dispatches a state update with given priority. + */ + void dispatchStateUpdate(StateUpdate &&stateUpdate, EventPriority priority) + const; + private: + const EventQueue &getEventQueue(EventPriority priority) const; + std::array, 4> eventQueues_; }; diff --git a/ReactCommon/fabric/events/EventEmitter.cpp b/ReactCommon/fabric/core/events/EventEmitter.cpp similarity index 98% rename from ReactCommon/fabric/events/EventEmitter.cpp rename to ReactCommon/fabric/core/events/EventEmitter.cpp index e5a8eeecba4c51..ac3e826cf67688 100644 --- a/ReactCommon/fabric/events/EventEmitter.cpp +++ b/ReactCommon/fabric/core/events/EventEmitter.cpp @@ -45,7 +45,7 @@ ValueFactory EventEmitter::defaultPayloadFactory() { EventEmitter::EventEmitter( SharedEventTarget eventTarget, Tag tag, - WeakEventDispatcher eventDispatcher) + EventDispatcher::Weak eventDispatcher) : eventTarget_(std::move(eventTarget)), eventDispatcher_(std::move(eventDispatcher)) {} diff --git a/ReactCommon/fabric/events/EventEmitter.h b/ReactCommon/fabric/core/events/EventEmitter.h similarity index 92% rename from ReactCommon/fabric/events/EventEmitter.h rename to ReactCommon/fabric/core/events/EventEmitter.h index 3e06c81e456c0a..f3ce76ca4f1f0e 100644 --- a/ReactCommon/fabric/events/EventEmitter.h +++ b/ReactCommon/fabric/core/events/EventEmitter.h @@ -10,8 +10,9 @@ #include #include -#include -#include +#include +#include +#include namespace facebook { namespace react { @@ -41,7 +42,7 @@ class EventEmitter { EventEmitter( SharedEventTarget eventTarget, Tag tag, - WeakEventDispatcher eventDispatcher); + EventDispatcher::Weak eventDispatcher); virtual ~EventEmitter() = default; @@ -83,7 +84,7 @@ class EventEmitter { void toggleEventTargetOwnership_() const; mutable SharedEventTarget eventTarget_; - WeakEventDispatcher eventDispatcher_; + EventDispatcher::Weak eventDispatcher_; mutable int enableCounter_{0}; mutable bool isEnabled_{false}; }; diff --git a/ReactCommon/fabric/events/primitives.h b/ReactCommon/fabric/core/events/EventHandler.h similarity index 56% rename from ReactCommon/fabric/events/primitives.h rename to ReactCommon/fabric/core/events/EventHandler.h index 3f6edc0bdae134..d35131f0a4eed5 100644 --- a/ReactCommon/fabric/events/primitives.h +++ b/ReactCommon/fabric/core/events/EventHandler.h @@ -7,26 +7,11 @@ #pragma once -#include -#include - -#include +#include namespace facebook { namespace react { -enum class EventPriority : int { - SynchronousUnbatched, - SynchronousBatched, - AsynchronousUnbatched, - AsynchronousBatched, - - Sync = SynchronousUnbatched, - Work = SynchronousBatched, - Interactive = AsynchronousUnbatched, - Deferred = AsynchronousBatched -}; - /* * We need this types only to ensure type-safety when we deal with them. * Conceptually, they are opaque pointers to some types that derived from those @@ -41,13 +26,5 @@ struct EventHandler { }; using UniqueEventHandler = std::unique_ptr; -using ValueFactory = std::function; - -using EventPipe = std::function; - } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/events/EventPipe.h b/ReactCommon/fabric/core/events/EventPipe.h new file mode 100644 index 00000000000000..9190fdcd7e0fdd --- /dev/null +++ b/ReactCommon/fabric/core/events/EventPipe.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace facebook { +namespace react { + +using EventPipe = std::function; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/events/EventPriority.h b/ReactCommon/fabric/core/events/EventPriority.h new file mode 100644 index 00000000000000..e59bbcf63d6509 --- /dev/null +++ b/ReactCommon/fabric/core/events/EventPriority.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace facebook { +namespace react { + +enum class EventPriority : int { + SynchronousUnbatched, + SynchronousBatched, + AsynchronousUnbatched, + AsynchronousBatched, + + Sync = SynchronousUnbatched, + Work = SynchronousBatched, + Interactive = AsynchronousUnbatched, + Deferred = AsynchronousBatched +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/events/EventQueue.cpp b/ReactCommon/fabric/core/events/EventQueue.cpp similarity index 55% rename from ReactCommon/fabric/events/EventQueue.cpp rename to ReactCommon/fabric/core/events/EventQueue.cpp index 3eb775549f2c89..ad3d29a69c99c5 100644 --- a/ReactCommon/fabric/events/EventQueue.cpp +++ b/ReactCommon/fabric/core/events/EventQueue.cpp @@ -14,29 +14,54 @@ namespace react { EventQueue::EventQueue( EventPipe eventPipe, + StatePipe statePipe, std::unique_ptr eventBeat) - : eventPipe_(std::move(eventPipe)), eventBeat_(std::move(eventBeat)) { + : eventPipe_(std::move(eventPipe)), + statePipe_(std::move(statePipe)), + eventBeat_(std::move(eventBeat)) { eventBeat_->setBeatCallback( std::bind(&EventQueue::onBeat, this, std::placeholders::_1)); } void EventQueue::enqueueEvent(const RawEvent &rawEvent) const { - std::lock_guard lock(queueMutex_); - queue_.push_back(rawEvent); + { + std::lock_guard lock(queueMutex_); + eventQueue_.push_back(rawEvent); + } + + onEnqueue(); +} + +void EventQueue::enqueueStateUpdate(const StateUpdate &stateUpdate) const { + { + std::lock_guard lock(queueMutex_); + stateUpdateQueue_.push_back(stateUpdate); + } + + onEnqueue(); +} + +void EventQueue::onEnqueue() const { + // Default implementation does nothing. } void EventQueue::onBeat(jsi::Runtime &runtime) const { + flushEvents(runtime); + flushStateUpdates(); +} + +void EventQueue::flushEvents(jsi::Runtime &runtime) const { std::vector queue; { std::lock_guard lock(queueMutex_); - if (queue_.size() == 0) { + if (eventQueue_.size() == 0) { return; } - queue = std::move(queue_); - queue_.clear(); + queue = std::move(eventQueue_); + eventQueue_.clear(); } { @@ -65,5 +90,25 @@ void EventQueue::onBeat(jsi::Runtime &runtime) const { } } +void EventQueue::flushStateUpdates() const { + std::vector stateUpdateQueue; + + { + std::lock_guard lock(queueMutex_); + + if (stateUpdateQueue_.size() == 0) { + return; + } + + stateUpdateQueue = std::move(stateUpdateQueue_); + stateUpdateQueue_.clear(); + } + + for (const auto &stateUpdate : stateUpdateQueue) { + auto pair = stateUpdate(); + statePipe_(pair.second, pair.first); + } +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/events/EventQueue.h b/ReactCommon/fabric/core/events/EventQueue.h new file mode 100644 index 00000000000000..d9b0d4e70a5284 --- /dev/null +++ b/ReactCommon/fabric/core/events/EventQueue.h @@ -0,0 +1,70 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +/* + * Event Queue synchronized with given Event Beat and dispatching event + * using given Event Pipe. + */ +class EventQueue { + public: + EventQueue( + EventPipe eventPipe, + StatePipe statePipe, + std::unique_ptr eventBeat); + virtual ~EventQueue() = default; + + /* + * Enqueues and (probably later) dispatch a given event. + * Can be called on any thread. + */ + void enqueueEvent(const RawEvent &rawEvent) const; + + /* + * Enqueues and (probably later) dispatch a given state update. + * Can be called on any thread. + */ + void enqueueStateUpdate(const StateUpdate &stateUpdate) const; + + protected: + /* + * Called on any enqueue operation. + * Override in subclasses to trigger beat `request` and/or beat `induce`. + * Default implementation does nothing. + */ + virtual void onEnqueue() const; + void onBeat(jsi::Runtime &runtime) const; + + void flushEvents(jsi::Runtime &runtime) const; + void flushStateUpdates() const; + + const EventPipe eventPipe_; + const StatePipe statePipe_; + const std::unique_ptr eventBeat_; + // Thread-safe, protected by `queueMutex_`. + mutable std::vector eventQueue_; + mutable std::vector stateUpdateQueue_; + mutable std::mutex queueMutex_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/events/EventTarget.cpp b/ReactCommon/fabric/core/events/EventTarget.cpp similarity index 100% rename from ReactCommon/fabric/events/EventTarget.cpp rename to ReactCommon/fabric/core/events/EventTarget.cpp diff --git a/ReactCommon/fabric/events/EventTarget.h b/ReactCommon/fabric/core/events/EventTarget.h similarity index 100% rename from ReactCommon/fabric/events/EventTarget.h rename to ReactCommon/fabric/core/events/EventTarget.h diff --git a/ReactCommon/fabric/events/RawEvent.cpp b/ReactCommon/fabric/core/events/RawEvent.cpp similarity index 100% rename from ReactCommon/fabric/events/RawEvent.cpp rename to ReactCommon/fabric/core/events/RawEvent.cpp diff --git a/ReactCommon/fabric/events/RawEvent.h b/ReactCommon/fabric/core/events/RawEvent.h similarity index 86% rename from ReactCommon/fabric/events/RawEvent.h rename to ReactCommon/fabric/core/events/RawEvent.h index bcf4fc5fa17e8a..4305d78ee44581 100644 --- a/ReactCommon/fabric/events/RawEvent.h +++ b/ReactCommon/fabric/core/events/RawEvent.h @@ -7,10 +7,10 @@ #pragma once #include +#include -#include -#include -#include +#include +#include namespace facebook { namespace react { diff --git a/ReactCommon/fabric/events/UnbatchedEventQueue.cpp b/ReactCommon/fabric/core/events/UnbatchedEventQueue.cpp similarity index 76% rename from ReactCommon/fabric/events/UnbatchedEventQueue.cpp rename to ReactCommon/fabric/core/events/UnbatchedEventQueue.cpp index 9c3b20670c8bed..80dd5cca2375dc 100644 --- a/ReactCommon/fabric/events/UnbatchedEventQueue.cpp +++ b/ReactCommon/fabric/core/events/UnbatchedEventQueue.cpp @@ -10,8 +10,8 @@ namespace facebook { namespace react { -void UnbatchedEventQueue::enqueueEvent(const RawEvent &rawEvent) const { - EventQueue::enqueueEvent(rawEvent); +void UnbatchedEventQueue::onEnqueue() const { + EventQueue::onEnqueue(); eventBeat_->request(); eventBeat_->induce(); diff --git a/ReactCommon/fabric/events/UnbatchedEventQueue.h b/ReactCommon/fabric/core/events/UnbatchedEventQueue.h similarity index 83% rename from ReactCommon/fabric/events/UnbatchedEventQueue.h rename to ReactCommon/fabric/core/events/UnbatchedEventQueue.h index 46f2392e928aaf..f708de10db8ba3 100644 --- a/ReactCommon/fabric/events/UnbatchedEventQueue.h +++ b/ReactCommon/fabric/core/events/UnbatchedEventQueue.h @@ -7,7 +7,7 @@ #pragma once -#include +#include namespace facebook { namespace react { @@ -20,7 +20,7 @@ class UnbatchedEventQueue final : public EventQueue { public: using EventQueue::EventQueue; - void enqueueEvent(const RawEvent &rawEvent) const override; + void onEnqueue() const override; }; } // namespace react diff --git a/ReactCommon/fabric/components/activityindicator/primitives.h b/ReactCommon/fabric/core/events/ValueFactory.h similarity index 71% rename from ReactCommon/fabric/components/activityindicator/primitives.h rename to ReactCommon/fabric/core/events/ValueFactory.h index ba9b2606e1d152..f055eabae46a8b 100644 --- a/ReactCommon/fabric/components/activityindicator/primitives.h +++ b/ReactCommon/fabric/core/events/ValueFactory.h @@ -7,13 +7,14 @@ #pragma once +#include + +#include + namespace facebook { namespace react { -enum class ActivityIndicatorViewSize { - Large, - Small, -}; +using ValueFactory = std::function; } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/layout/LayoutConstraints.cpp b/ReactCommon/fabric/core/layout/LayoutConstraints.cpp new file mode 100644 index 00000000000000..c9e2d9c3129a46 --- /dev/null +++ b/ReactCommon/fabric/core/layout/LayoutConstraints.cpp @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "LayoutConstraints.h" + +#include + +namespace facebook { +namespace react { + +Size LayoutConstraints::clamp(const Size &size) const { + return { + std::max(minimumSize.width, std::min(maximumSize.width, size.width)), + std::max(minimumSize.height, std::min(maximumSize.height, size.height))}; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/layout/LayoutConstraints.h b/ReactCommon/fabric/core/layout/LayoutConstraints.h index d4f79bf5b53578..0742a834e9e947 100644 --- a/ReactCommon/fabric/core/layout/LayoutConstraints.h +++ b/ReactCommon/fabric/core/layout/LayoutConstraints.h @@ -21,6 +21,12 @@ struct LayoutConstraints { Size minimumSize{0, 0}; Size maximumSize{kFloatUndefined, kFloatUndefined}; LayoutDirection layoutDirection{LayoutDirection::Undefined}; + + /* + * Clamps the provided `Size` between the `minimumSize` and `maximumSize` + * bounds of this `LayoutConstraints`. + */ + Size clamp(const Size &size) const; }; inline bool operator==( diff --git a/ReactCommon/fabric/core/layout/LayoutMetrics.h b/ReactCommon/fabric/core/layout/LayoutMetrics.h index b7ee04a46fcace..6cc7b51b596eb9 100644 --- a/ReactCommon/fabric/core/layout/LayoutMetrics.h +++ b/ReactCommon/fabric/core/layout/LayoutMetrics.h @@ -57,7 +57,10 @@ struct LayoutMetrics { * Represents some undefined, not-yet-computed or meaningless value of * `LayoutMetrics` type. */ -static const LayoutMetrics EmptyLayoutMetrics = {.frame = {.size = {-1, -1}}}; +static const LayoutMetrics EmptyLayoutMetrics = {/* .frame = */ { + /* .origin = */ {0, 0}, + /* .size = */ {-1, -1}, +}}; } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/primitives/RawProps.h b/ReactCommon/fabric/core/primitives/RawProps.h index 6feed6afa69b2c..0e1b52c11b1d23 100644 --- a/ReactCommon/fabric/core/primitives/RawProps.h +++ b/ReactCommon/fabric/core/primitives/RawProps.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -46,7 +47,7 @@ class RawProps { #ifdef ANDROID dynamic_(dynamic), #endif - map_((std::unordered_map)RawValue(dynamic)) { + map_((better::map)RawValue(dynamic)) { } /* @@ -90,7 +91,7 @@ class RawProps { const folly::dynamic dynamic_; #endif - const std::unordered_map map_; + const better::map map_; }; } // namespace react diff --git a/ReactCommon/fabric/core/primitives/RawValue.h b/ReactCommon/fabric/core/primitives/RawValue.h index c7acfa9bcee0dd..14a3bcacdea464 100644 --- a/ReactCommon/fabric/core/primitives/RawValue.h +++ b/ReactCommon/fabric/core/primitives/RawValue.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -180,7 +181,7 @@ class RawValue { template static bool checkValueType( const folly::dynamic &dynamic, - std::unordered_map *type) noexcept { + better::map *type) noexcept { if (!dynamic.isObject()) { return false; } @@ -249,11 +250,11 @@ class RawValue { } template - static std::unordered_map castValue( + static better::map castValue( const folly::dynamic &dynamic, - std::unordered_map *type) noexcept { + better::map *type) noexcept { assert(dynamic.isObject()); - auto result = std::unordered_map{}; + auto result = better::map{}; for (const auto &item : dynamic.items()) { assert(item.first.isString()); result[item.first.getString()] = castValue(item.second, (T *)nullptr); diff --git a/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h b/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h index c85abc748a6818..ef975302152c13 100644 --- a/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ConcreteShadowNode.h @@ -7,8 +7,10 @@ #pragma once +#include #include #include +#include namespace facebook { namespace react { @@ -22,7 +24,8 @@ namespace react { template < const char *concreteComponentName, typename PropsT, - typename EventEmitterT = EventEmitter> + typename EventEmitterT = EventEmitter, + typename StateDataT = StateData> class ConcreteShadowNode : public ShadowNode { static_assert( std::is_base_of::value, @@ -36,6 +39,8 @@ class ConcreteShadowNode : public ShadowNode { using ConcreteEventEmitter = EventEmitterT; using SharedConcreteEventEmitter = std::shared_ptr; using SharedConcreteShadowNode = std::shared_ptr; + using ConcreteState = ConcreteState; + using ConcreteStateData = StateDataT; static ComponentName Name() { return ComponentName(concreteComponentName); @@ -60,6 +65,10 @@ class ConcreteShadowNode : public ShadowNode { return defaultSharedProps; } + static ConcreteStateData initialStateData(const SharedConcreteProps &props) { + return {}; + } + ComponentName getComponentName() const override { return ComponentName(concreteComponentName); } @@ -73,6 +82,10 @@ class ConcreteShadowNode : public ShadowNode { return std::static_pointer_cast(props_); } + const typename ConcreteState::Shared getState() const { + return std::static_pointer_cast(state_); + } + /* * Returns subset of children that are inherited from `SpecificShadowNodeT`. */ diff --git a/ReactCommon/fabric/core/shadownode/Props.cpp b/ReactCommon/fabric/core/shadownode/Props.cpp index d56726f7bd55bd..d3969cdcb80238 100644 --- a/ReactCommon/fabric/core/shadownode/Props.cpp +++ b/ReactCommon/fabric/core/shadownode/Props.cpp @@ -14,7 +14,8 @@ namespace facebook { namespace react { Props::Props(const Props &sourceProps, const RawProps &rawProps) - : nativeId(convertRawProp(rawProps, "nativeID", sourceProps.nativeId)) + : nativeId(convertRawProp(rawProps, "nativeID", sourceProps.nativeId)), + revision(sourceProps.revision + 1) #ifdef ANDROID , rawProps((folly::dynamic)rawProps) diff --git a/ReactCommon/fabric/core/shadownode/Props.h b/ReactCommon/fabric/core/shadownode/Props.h index 840ef84ade9660..56362dddf87d5d 100644 --- a/ReactCommon/fabric/core/shadownode/Props.h +++ b/ReactCommon/fabric/core/shadownode/Props.h @@ -31,6 +31,15 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible { const std::string nativeId; + /* + * Special value that represents generation number of `Props` object, which + * increases when the object was constructed with some source `Props` object. + * Default props objects (that was constructed using default constructor) have + * revision equals `0`. + * The value might be used for optimization purposes. + */ + const int revision{0}; + #ifdef ANDROID const folly::dynamic rawProps = folly::dynamic::object(); #endif diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp index 07c5b9dd447790..019237129ffb75 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -26,13 +27,16 @@ SharedShadowNodeSharedList ShadowNode::emptySharedShadowNodeSharedList() { ShadowNode::ShadowNode( const ShadowNodeFragment &fragment, - const ShadowNodeCloneFunction &cloneFunction) + const ComponentDescriptor &componentDescriptor) : tag_(fragment.tag), rootTag_(fragment.rootTag), props_(fragment.props), eventEmitter_(fragment.eventEmitter), - children_(fragment.children ?: emptySharedShadowNodeSharedList()), - cloneFunction_(cloneFunction), + children_( + fragment.children ? fragment.children + : emptySharedShadowNodeSharedList()), + state_(fragment.state), + componentDescriptor_(componentDescriptor), childrenAreShared_(true), revision_(1) { assert(props_); @@ -42,13 +46,21 @@ ShadowNode::ShadowNode( ShadowNode::ShadowNode( const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment) - : tag_(fragment.tag ?: sourceShadowNode.tag_), - rootTag_(fragment.rootTag ?: sourceShadowNode.rootTag_), - props_(fragment.props ?: sourceShadowNode.props_), - eventEmitter_(fragment.eventEmitter ?: sourceShadowNode.eventEmitter_), - children_(fragment.children ?: sourceShadowNode.children_), - localData_(fragment.localData ?: sourceShadowNode.localData_), - cloneFunction_(sourceShadowNode.cloneFunction_), + : tag_(fragment.tag ? fragment.tag : sourceShadowNode.tag_), + rootTag_(fragment.rootTag ? fragment.rootTag : sourceShadowNode.rootTag_), + props_(fragment.props ? fragment.props : sourceShadowNode.props_), + eventEmitter_( + fragment.eventEmitter ? fragment.eventEmitter + : sourceShadowNode.eventEmitter_), + children_( + fragment.children ? fragment.children : sourceShadowNode.children_), + localData_( + fragment.localData ? fragment.localData + : sourceShadowNode.localData_), + state_( + fragment.state ? fragment.state + : sourceShadowNode.getCommitedState()), + componentDescriptor_(sourceShadowNode.componentDescriptor_), childrenAreShared_(true), revision_(sourceShadowNode.revision_ + 1) { assert(props_); @@ -56,8 +68,7 @@ ShadowNode::ShadowNode( } UnsharedShadowNode ShadowNode::clone(const ShadowNodeFragment &fragment) const { - assert(cloneFunction_); - return cloneFunction_(*this, fragment); + return componentDescriptor_.cloneShadowNode(*this, fragment); } #pragma mark - Getters @@ -82,6 +93,15 @@ Tag ShadowNode::getRootTag() const { return rootTag_; } +const State::Shared &ShadowNode::getState() const { + return state_; +} + +const State::Shared &ShadowNode::getCommitedState() const { + return state_ ? state_->getCommitedState() + : ShadowNodeFragment::statePlaceholder(); +} + SharedLocalData ShadowNode::getLocalData() const { return localData_; } @@ -148,6 +168,9 @@ void ShadowNode::cloneChildrenIfShared() { void ShadowNode::setMounted(bool mounted) const { eventEmitter_->setEnabled(mounted); + if (mounted && state_) { + state_->commit(*this); + } } bool ShadowNode::constructAncestorPath( diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.h b/ReactCommon/fabric/core/shadownode/ShadowNode.h index ea12b5f72ad4ee..0f9e2875901890 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.h @@ -11,34 +11,36 @@ #include #include +#include #include #include #include #include +#include #include -#include namespace facebook { namespace react { +class ComponentDescriptor; struct ShadowNodeFragment; class ShadowNode; using SharedShadowNode = std::shared_ptr; +using WeakShadowNode = std::weak_ptr; using UnsharedShadowNode = std::shared_ptr; using SharedShadowNodeList = std::vector; using SharedShadowNodeSharedList = std::shared_ptr; using SharedShadowNodeUnsharedList = std::shared_ptr; -using ShadowNodeCloneFunction = std::function; - class ShadowNode : public virtual Sealable, public virtual DebugStringConvertible, public std::enable_shared_from_this { public: + using Shared = std::shared_ptr; + using Weak = std::weak_ptr; + static SharedShadowNodeSharedList emptySharedShadowNodeSharedList(); #pragma mark - Constructors @@ -48,7 +50,7 @@ class ShadowNode : public virtual Sealable, */ ShadowNode( const ShadowNodeFragment &fragment, - const ShadowNodeCloneFunction &cloneFunction); + const ComponentDescriptor &componentDescriptor); /* * Creates a Shadow Node via cloning given `sourceShadowNode` and @@ -76,6 +78,17 @@ class ShadowNode : public virtual Sealable, Tag getTag() const; Tag getRootTag() const; + /* + * Returns a state associated with the particular node. + */ + const State::Shared &getState() const; + + /* + * Returns a momentary value of currently committed state associated with a + * family of nodes which this node belongs to. + */ + const State::Shared &getCommitedState() const; + /* * Returns a local data associated with the node. * `LocalData` object might be used for data exchange between native view and @@ -139,6 +152,7 @@ class ShadowNode : public virtual Sealable, SharedEventEmitter eventEmitter_; SharedShadowNodeSharedList children_; SharedLocalData localData_; + State::Shared state_; private: /* @@ -148,10 +162,10 @@ class ShadowNode : public virtual Sealable, void cloneChildrenIfShared(); /* - * A reference to a cloning function that understands how to clone - * the specific type of ShadowNode. + * A reference to a concrete `ComponentDescriptor` that manages nodes of this + * type. */ - ShadowNodeCloneFunction cloneFunction_; + const ComponentDescriptor &componentDescriptor_; /* * Indicates that `children` list is shared between nodes and need diff --git a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp index 9ebd9006fac390..18bce2d4558a37 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp +++ b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp @@ -38,5 +38,10 @@ SharedLocalData &ShadowNodeFragment::localDataPlaceholder() { return instance; } +State::Shared &ShadowNodeFragment::statePlaceholder() { + static auto &instance = *new State::Shared(); + return instance; +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h index 9db4cd45c2b4dc..1c77b09572cb2c 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h @@ -7,11 +7,12 @@ #pragma once +#include #include #include #include #include -#include +#include namespace facebook { namespace react { @@ -20,7 +21,7 @@ namespace react { * An object which supposed to be used as a parameter specifying a shape * of created or cloned ShadowNode. * Note: Most of the fields are `const &` references (essentially just raw - * pointers) which means that the Fragment does not copy/store them or + * pointers) which means that the Fragment does not copy/store them nor * retain ownership of them. */ struct ShadowNodeFragment { @@ -30,6 +31,7 @@ struct ShadowNodeFragment { const SharedEventEmitter &eventEmitter = eventEmitterPlaceholder(); const SharedShadowNodeSharedList &children = childrenPlaceholder(); const SharedLocalData &localData = localDataPlaceholder(); + const State::Shared &state = statePlaceholder(); static Tag tagPlaceholder(); static Tag surfaceIdPlaceholder(); @@ -37,6 +39,7 @@ struct ShadowNodeFragment { static SharedEventEmitter &eventEmitterPlaceholder(); static SharedShadowNodeSharedList &childrenPlaceholder(); static SharedLocalData &localDataPlaceholder(); + static State::Shared &statePlaceholder(); }; } // namespace react diff --git a/ReactCommon/fabric/core/state/ConcreteState.h b/ReactCommon/fabric/core/state/ConcreteState.h new file mode 100644 index 00000000000000..3cb378835bccfe --- /dev/null +++ b/ReactCommon/fabric/core/state/ConcreteState.h @@ -0,0 +1,88 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook { +namespace react { + +/* + * Concrete and only template implementation of State interface. + * State wraps an arbitrary data type and provides an interface to initiate a + * state update transaction. A data object does not need to be copyable but + * needs to be moveable. + */ +template +class ConcreteState : public State { + public: + using Shared = std::shared_ptr; + using Data = DataT; + + ConcreteState(Data &&data, StateCoordinator::Shared stateCoordinator) + : State(std::move(stateCoordinator)), data_(std::move(data)) {} + + ConcreteState(Data &&data, const ConcreteState &other) + : State(other.stateCoordinator_), data_(std::move(data)) {} + + /* + * Returns stored data. + */ + const Data &getData() const { + return data_; + } + + /* + * Initiate a state update process with given new data and priority. + * This is a simplified convenience version of the method that receives a + * function for cases where a new value of data does not depend on an old + * value. + */ + void updateState( + Data &&newData, + EventPriority priority = EventPriority::SynchronousUnbatched) const { + updateState( + [data = std::move(newData)](const Data &oldData) mutable -> Data && { + return std::move(data); + }); + } + + /* + * Initiate a state update process with a given function (that transforms an + * old data value to a new one) and priority. The update function can be + * called from any thread any moment later. The function can be called only + * once or not called at all (in the case where the node was already unmounted + * and updating makes no sense). The state update operation might fail in case + * of conflict. + */ + void updateState( + std::function callback, + EventPriority priority = EventPriority::AsynchronousBatched) const { + stateCoordinator_->dispatchRawState( + {[stateCoordinator = stateCoordinator_, + callback = std::move( + callback)]() -> std::pair { + auto target = stateCoordinator->getTarget(); + auto oldState = target.getShadowNode().getState(); + auto oldData = std::static_pointer_cast(oldState) + ->getData(); + auto newData = std::make_shared(callback(oldData)); + return {std::move(target), std::move(newData)}; + }}, + priority); + } + + private: + DataT data_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/state/State.cpp b/ReactCommon/fabric/core/state/State.cpp new file mode 100644 index 00000000000000..fb9740bfed1e1d --- /dev/null +++ b/ReactCommon/fabric/core/state/State.cpp @@ -0,0 +1,30 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "State.h" + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +State::State(StateCoordinator::Shared stateCoordinator) + : stateCoordinator_(std::move(stateCoordinator)){}; + +void State::commit(const ShadowNode &shadowNode) const { + stateCoordinator_->setTarget(StateTarget{shadowNode}); +} + +const State::Shared &State::getCommitedState() const { + return stateCoordinator_->getTarget().getShadowNode().getState(); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/state/State.h b/ReactCommon/fabric/core/state/State.h new file mode 100644 index 00000000000000..73769c020302b0 --- /dev/null +++ b/ReactCommon/fabric/core/state/State.h @@ -0,0 +1,48 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook { +namespace react { + +class ShadowNode; + +/* + * An abstract interface of State. + * State is used to control and continuously advance a single vision of some + * state (arbitrary data) associated with a family of shadow nodes. + */ +class State { + public: + using Shared = std::shared_ptr; + + State(StateCoordinator::Shared stateCoordinator); + virtual ~State() = default; + + protected: + StateCoordinator::Shared stateCoordinator_; + + private: + friend class ShadowNode; + friend class StateCoordinator; + + /* + * Must be used by `ShadowNode` *only*. + */ + void commit(const ShadowNode &shadowNode) const; + + /* + * Must be used by `ShadowNode` *only*. + */ + const State::Shared &getCommitedState() const; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/state/StateCoordinator.cpp b/ReactCommon/fabric/core/state/StateCoordinator.cpp new file mode 100644 index 00000000000000..b8493a44a4703e --- /dev/null +++ b/ReactCommon/fabric/core/state/StateCoordinator.cpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "StateCoordinator.h" + +#include +#include +#include + +namespace facebook { +namespace react { + +StateCoordinator::StateCoordinator(EventDispatcher::Weak eventDispatcher) + : eventDispatcher_(eventDispatcher) {} + +const StateTarget &StateCoordinator::getTarget() const { + std::shared_lock lock(mutex_); + return target_; +} + +void StateCoordinator::setTarget(StateTarget &&target) const { + std::unique_lock lock(mutex_); + target_ = std::move(target); +} + +void StateCoordinator::dispatchRawState( + StateUpdate &&stateUpdate, + EventPriority priority) const { + auto eventDispatcher = eventDispatcher_.lock(); + if (!eventDispatcher || !target_) { + return; + } + + eventDispatcher->dispatchStateUpdate(std::move(stateUpdate), priority); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/state/StateCoordinator.h b/ReactCommon/fabric/core/state/StateCoordinator.h new file mode 100644 index 00000000000000..4abb37069a9eee --- /dev/null +++ b/ReactCommon/fabric/core/state/StateCoordinator.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +class ShadowNode; + +/* + * Coordinates a vision of the same state values between shadow nodes from + * the same family. + */ +class StateCoordinator { + public: + using Shared = std::shared_ptr; + + StateCoordinator(EventDispatcher::Weak eventDispatcher); + + /* + * Dispatches a state update with given priority. + */ + void dispatchRawState(StateUpdate &&stateUpdate, EventPriority priority) + const; + + /* + * Sets and gets a state target. + */ + const StateTarget &getTarget() const; + void setTarget(StateTarget &&target) const; + + private: + EventDispatcher::Weak eventDispatcher_; + mutable StateTarget target_{}; // Protected by `mutex_`. + mutable better::shared_mutex mutex_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/slider/SliderEventEmitter.h b/ReactCommon/fabric/core/state/StateData.h similarity index 54% rename from ReactCommon/fabric/components/slider/SliderEventEmitter.h rename to ReactCommon/fabric/core/state/StateData.h index 9394df7f939a39..f9ef2523dd142b 100644 --- a/ReactCommon/fabric/components/slider/SliderEventEmitter.h +++ b/ReactCommon/fabric/core/state/StateData.h @@ -4,19 +4,20 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ + #pragma once -#include +#include namespace facebook { namespace react { -class SliderEventEmitter : public ViewEventEmitter { - public: - using ViewEventEmitter::ViewEventEmitter; - - void onValueChange(float value) const; - void onSlidingComplete(float value) const; +/* + * Dummy type that is used as a placeholder for state data for nodes that + * don't have a state. + */ +struct StateData { + using Shared = std::shared_ptr; }; } // namespace react diff --git a/ReactCommon/fabric/core/state/StatePipe.h b/ReactCommon/fabric/core/state/StatePipe.h new file mode 100644 index 00000000000000..2d0961f15c641c --- /dev/null +++ b/ReactCommon/fabric/core/state/StatePipe.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include + +namespace facebook { +namespace react { + +using StatePipe = std::function< + void(const StateData::Shared &data, const StateTarget &target)>; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/state/StateTarget.cpp b/ReactCommon/fabric/core/state/StateTarget.cpp new file mode 100644 index 00000000000000..d3446b9f1a9762 --- /dev/null +++ b/ReactCommon/fabric/core/state/StateTarget.cpp @@ -0,0 +1,28 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "StateTarget.h" +#include + +namespace facebook { +namespace react { + +StateTarget::StateTarget() : shadowNode_(nullptr) {} + +StateTarget::StateTarget(const ShadowNode &shadowNode) + : shadowNode_(shadowNode.shared_from_this()) {} + +StateTarget::operator bool() const { + return (bool)shadowNode_; +} + +const ShadowNode &StateTarget::getShadowNode() const { + return *std::static_pointer_cast(shadowNode_); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/state/StateTarget.h b/ReactCommon/fabric/core/state/StateTarget.h new file mode 100644 index 00000000000000..0accbcaa23a1bc --- /dev/null +++ b/ReactCommon/fabric/core/state/StateTarget.h @@ -0,0 +1,57 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook { +namespace react { + +class ShadowNode; + +/* + * Represents an entity that receives state update. + * Practically, just a wrapper around shared a pointer to ShadowNode. We need + * this mostly to avoid circular dependency problems. + */ +class StateTarget { + public: + /* + * Creates an empty target. + */ + StateTarget(); + + /* + * Creates a target which points to a given `ShadowNode`. + */ + StateTarget(const ShadowNode &shadowNode); + + /* + * Copyable and moveable. + */ + StateTarget(const StateTarget &other) = default; + StateTarget &operator=(const StateTarget &other) = default; + StateTarget(StateTarget &&other) noexcept = default; + StateTarget &operator=(StateTarget &&other) = default; + + /* + * Returns `true` is the target is not empty. + */ + operator bool() const; + + /* + * Returns a reference to a stored `ShadowNode`. + */ + const ShadowNode &getShadowNode() const; + + private: + std::shared_ptr shadowNode_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewShadowNode.cpp b/ReactCommon/fabric/core/state/StateUpdate.cpp similarity index 68% rename from ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewShadowNode.cpp rename to ReactCommon/fabric/core/state/StateUpdate.cpp index 09bb4700032b09..8e3e8897ef7d5a 100644 --- a/ReactCommon/fabric/components/activityindicator/ActivityIndicatorViewShadowNode.cpp +++ b/ReactCommon/fabric/core/state/StateUpdate.cpp @@ -5,12 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -#include "ActivityIndicatorViewShadowNode.h" +#include "StateUpdate.h" namespace facebook { namespace react { -const char ActivityIndicatorViewComponentName[] = "ActivityIndicatorView"; +std::pair StateUpdate::operator()() const { + return callback_(); +} } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/state/StateUpdate.h b/ReactCommon/fabric/core/state/StateUpdate.h new file mode 100644 index 00000000000000..e309774da0eadb --- /dev/null +++ b/ReactCommon/fabric/core/state/StateUpdate.h @@ -0,0 +1,34 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include + +namespace facebook { +namespace react { + +/* + * Carries some logic and additional information about state update transaction. + */ +class StateUpdate { + public: + std::pair operator()() const; + + /* + * The current implementation simply uses `std::function` inside that captures + * everything which is needed to perform state update. That will be probably + * changed in the future. + */ + std::function()> callback_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/tests/ComponentDescriptorTest.cpp b/ReactCommon/fabric/core/tests/ComponentDescriptorTest.cpp index 91c5b5ef8ee316..b19344dc636c2c 100644 --- a/ReactCommon/fabric/core/tests/ComponentDescriptorTest.cpp +++ b/ReactCommon/fabric/core/tests/ComponentDescriptorTest.cpp @@ -22,11 +22,12 @@ TEST(ComponentDescriptorTest, createShadowNode) { const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc")); SharedProps props = descriptor->cloneProps(nullptr, raw); - SharedShadowNode node = descriptor->createShadowNode( - ShadowNodeFragment{.tag = 9, - .rootTag = 1, - .props = props, - .eventEmitter = descriptor->createEventEmitter(0, 9)}); + SharedShadowNode node = descriptor->createShadowNode(ShadowNodeFragment{ + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ descriptor->createEventEmitter(0, 9), + }); ASSERT_EQ(node->getComponentHandle(), TestShadowNode::Handle()); ASSERT_STREQ( @@ -43,11 +44,12 @@ TEST(ComponentDescriptorTest, cloneShadowNode) { const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc")); SharedProps props = descriptor->cloneProps(nullptr, raw); - SharedShadowNode node = descriptor->createShadowNode( - ShadowNodeFragment{.tag = 9, - .rootTag = 1, - .props = props, - .eventEmitter = descriptor->createEventEmitter(0, 9)}); + SharedShadowNode node = descriptor->createShadowNode(ShadowNodeFragment{ + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ descriptor->createEventEmitter(0, 9), + }); SharedShadowNode cloned = descriptor->cloneShadowNode(*node, {}); ASSERT_STREQ(cloned->getComponentName().c_str(), "Test"); @@ -62,21 +64,24 @@ TEST(ComponentDescriptorTest, appendChild) { const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc")); SharedProps props = descriptor->cloneProps(nullptr, raw); - SharedShadowNode node1 = descriptor->createShadowNode( - ShadowNodeFragment{.tag = 1, - .rootTag = 1, - .props = props, - .eventEmitter = descriptor->createEventEmitter(0, 1)}); - SharedShadowNode node2 = descriptor->createShadowNode( - ShadowNodeFragment{.tag = 2, - .rootTag = 1, - .props = props, - .eventEmitter = descriptor->createEventEmitter(0, 2)}); - SharedShadowNode node3 = descriptor->createShadowNode( - ShadowNodeFragment{.tag = 3, - .rootTag = 1, - .props = props, - .eventEmitter = descriptor->createEventEmitter(0, 3)}); + SharedShadowNode node1 = descriptor->createShadowNode(ShadowNodeFragment{ + /* .tag = */ 1, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ descriptor->createEventEmitter(0, 1), + }); + SharedShadowNode node2 = descriptor->createShadowNode(ShadowNodeFragment{ + /* .tag = */ 2, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ descriptor->createEventEmitter(0, 2), + }); + SharedShadowNode node3 = descriptor->createShadowNode(ShadowNodeFragment{ + /* .tag = */ 3, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ descriptor->createEventEmitter(0, 3), + }); descriptor->appendChild(node1, node2); descriptor->appendChild(node1, node3); diff --git a/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp b/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp index 9b53762670cdfa..3e314a285041fd 100644 --- a/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp +++ b/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp @@ -27,13 +27,16 @@ TEST(ShadowNodeTest, handleProps) { } TEST(ShadowNodeTest, handleShadowNodeCreation) { + auto componentDescriptor = TestComponentDescriptor(nullptr); auto node = std::make_shared( ShadowNodeFragment{ - .tag = 9, - .rootTag = 1, - .props = std::make_shared(), - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ std::make_shared(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); ASSERT_FALSE(node->getSealed()); ASSERT_STREQ(node->getComponentName().c_str(), "Test"); @@ -50,13 +53,16 @@ TEST(ShadowNodeTest, handleShadowNodeCreation) { } TEST(ShadowNodeTest, handleShadowNodeSimpleCloning) { + auto componentDescriptor = TestComponentDescriptor(nullptr); auto node = std::make_shared( ShadowNodeFragment{ - .tag = 9, - .rootTag = 1, - .props = std::make_shared(), - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ std::make_shared(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto node2 = std::make_shared(*node, ShadowNodeFragment{}); ASSERT_STREQ(node->getComponentName().c_str(), "Test"); @@ -66,28 +72,35 @@ TEST(ShadowNodeTest, handleShadowNodeSimpleCloning) { } TEST(ShadowNodeTest, handleShadowNodeMutation) { + auto componentDescriptor = TestComponentDescriptor(nullptr); auto props = std::make_shared(); auto node1 = std::make_shared( ShadowNodeFragment{ - .tag = 1, - .rootTag = 1, - .props = std::make_shared(), - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 1, + /* .rootTag = */ 1, + /* .props = */ std::make_shared(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto node2 = std::make_shared( ShadowNodeFragment{ - .tag = 2, - .rootTag = 1, - .props = std::make_shared(), - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 2, + /* .rootTag = */ 1, + /* .props = */ std::make_shared(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto node3 = std::make_shared( ShadowNodeFragment{ - .tag = 3, - .rootTag = 1, - .props = std::make_shared(), - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 3, + /* .rootTag = */ 1, + /* .props = */ std::make_shared(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); node1->appendChild(node2); node1->appendChild(node3); @@ -118,44 +131,35 @@ TEST(ShadowNodeTest, handleShadowNodeMutation) { } TEST(ShadowNodeTest, handleCloneFunction) { - auto firstNode = std::make_shared( - ShadowNodeFragment{ - .tag = 9, - .rootTag = 1, - .props = std::make_shared(), - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + auto componentDescriptor = TestComponentDescriptor(nullptr); - // The shadow node is not clonable if `cloneFunction` is not provided, - ASSERT_DEATH_IF_SUPPORTED(firstNode->clone({}), "cloneFunction_"); - - auto secondNode = std::make_shared( + auto firstNode = std::make_shared( ShadowNodeFragment{ - .tag = 9, - .rootTag = 1, - .props = std::make_shared(), - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - [](const ShadowNode &shadowNode, const ShadowNodeFragment &fragment) { - return std::make_shared(shadowNode, fragment); - }); + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ std::make_shared(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); - auto secondNodeClone = secondNode->clone({}); + auto firstNodeClone = firstNode->clone({}); // Those two nodes are *not* same. - ASSERT_NE(secondNode, secondNodeClone); + ASSERT_NE(firstNode, firstNodeClone); // `secondNodeClone` is an instance of `TestShadowNode`. ASSERT_NE( - std::dynamic_pointer_cast(secondNodeClone), - nullptr); + std::dynamic_pointer_cast(firstNodeClone), nullptr); // Both nodes have same content. - ASSERT_EQ(secondNode->getTag(), secondNodeClone->getTag()); - ASSERT_EQ(secondNode->getRootTag(), secondNodeClone->getRootTag()); - ASSERT_EQ(secondNode->getProps(), secondNodeClone->getProps()); + ASSERT_EQ(firstNode->getTag(), firstNodeClone->getTag()); + ASSERT_EQ(firstNode->getRootTag(), firstNodeClone->getRootTag()); + ASSERT_EQ(firstNode->getProps(), firstNodeClone->getProps()); } TEST(ShadowNodeTest, handleLocalData) { + auto componentDescriptor = TestComponentDescriptor(nullptr); auto localData42 = std::make_shared(); localData42->setNumber(42); @@ -167,25 +171,31 @@ TEST(ShadowNodeTest, handleLocalData) { auto props = std::make_shared(); auto firstNode = std::make_shared( ShadowNodeFragment{ - .tag = 9, - .rootTag = 1, - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto secondNode = std::make_shared( ShadowNodeFragment{ - .tag = 9, - .rootTag = 1, - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto thirdNode = std::make_shared( ShadowNodeFragment{ - .tag = 9, - .rootTag = 1, - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ 9, + /* .rootTag = */ 1, + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); firstNode->setLocalData(localData42); secondNode->setLocalData(localData42); @@ -216,51 +226,90 @@ TEST(ShadowNodeTest, handleBacktracking) { * */ + auto componentDescriptor = TestComponentDescriptor(nullptr); auto props = std::make_shared(); auto nodeAA = std::make_shared( ShadowNodeFragment{ - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto nodeABA = std::make_shared( ShadowNodeFragment{ - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto nodeABB = std::make_shared( ShadowNodeFragment{ - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto nodeABC = std::make_shared( ShadowNodeFragment{ - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto nodeABChildren = std::make_shared>( std::vector{nodeABA, nodeABB, nodeABC}); auto nodeAB = std::make_shared( - ShadowNodeFragment{.props = props, .children = nodeABChildren}, nullptr); + ShadowNodeFragment{ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ nodeABChildren, + }, + componentDescriptor); auto nodeAC = std::make_shared( ShadowNodeFragment{ - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); auto nodeAChildren = std::make_shared>( std::vector{nodeAA, nodeAB, nodeAC}); auto nodeA = std::make_shared( - ShadowNodeFragment{.props = props, .children = nodeAChildren}, nullptr); + ShadowNodeFragment{ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ nodeAChildren, + }, + componentDescriptor); auto nodeZ = std::make_shared( ShadowNodeFragment{ - .props = props, - .children = ShadowNode::emptySharedShadowNodeSharedList()}, - nullptr); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }, + componentDescriptor); std::vector> ancestors = {}; diff --git a/ReactCommon/fabric/events/BUCK b/ReactCommon/fabric/events/BUCK deleted file mode 100644 index e8271c4e8692b6..00000000000000 --- a/ReactCommon/fabric/events/BUCK +++ /dev/null @@ -1,80 +0,0 @@ -load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_debug_preprocessor_flags") -load( - "//tools/build_defs/oss:rn_defs.bzl", - "ANDROID", - "APPLE", - "fb_xplat_cxx_test", - "get_apple_compiler_flags", - "get_apple_inspector_flags", - "react_native_xplat_target", - "rn_xplat_cxx_library", - "subdir_glob", -) - -APPLE_COMPILER_FLAGS = get_apple_compiler_flags() - -rn_xplat_cxx_library( - name = "events", - srcs = glob( - ["**/*.cpp"], - exclude = glob(["tests/**/*.cpp"]), - ), - headers = glob( - ["**/*.h"], - exclude = glob(["tests/**/*.h"]), - ), - header_namespace = "", - exported_headers = subdir_glob( - [ - ("", "*.h"), - ], - prefix = "react/events", - ), - compiler_flags = [ - "-fexceptions", - "-frtti", - "-std=c++14", - "-Wall", - ], - fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), - force_static = True, - macosx_tests_override = [], - platforms = (ANDROID, APPLE), - preprocessor_flags = [ - "-DLOG_TAG=\"ReactNative\"", - # Systraces are temporary disabled. - # "-DWITH_FBSYSTRACE=1", - ], - tests = [":tests"], - visibility = ["PUBLIC"], - deps = [ - "fbsource//xplat/fbsystrace:fbsystrace", - "fbsource//xplat/folly:headers_only", - "fbsource//xplat/folly:memory", - "fbsource//xplat/folly:molly", - "fbsource//xplat/jsi:JSIDynamic", - "fbsource//xplat/jsi:jsi", - "fbsource//xplat/third-party/glog:glog", - react_native_xplat_target("fabric/debug:debug"), - ], -) - -fb_xplat_cxx_test( - name = "tests", - srcs = glob(["tests/**/*.cpp"]), - headers = glob(["tests/**/*.h"]), - compiler_flags = [ - "-fexceptions", - "-frtti", - "-std=c++14", - "-Wall", - ], - contacts = ["oncall+react_native@xmail.facebook.com"], - platforms = (ANDROID, APPLE), - deps = [ - "fbsource//xplat/folly:molly", - "fbsource//xplat/third-party/gmock:gtest", - ":events", - ], -) diff --git a/ReactCommon/fabric/events/EventQueue.h b/ReactCommon/fabric/events/EventQueue.h deleted file mode 100644 index 1a322beddfc022..00000000000000 --- a/ReactCommon/fabric/events/EventQueue.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -/* - * Event Queue synchronized with given Event Beat and dispatching event - * using given Event Pipe. - */ -class EventQueue { - public: - EventQueue(EventPipe eventPipe, std::unique_ptr eventBeat); - virtual ~EventQueue() = default; - - /* - * Enqueues and (probably later) dispatch a given event. - * Can be called on any thread. - */ - virtual void enqueueEvent(const RawEvent &rawEvent) const; - - protected: - void onBeat(jsi::Runtime &runtime) const; - - const EventPipe eventPipe_; - const std::unique_ptr eventBeat_; - // Thread-safe, protected by `queueMutex_`. - mutable std::vector queue_; - mutable std::mutex queueMutex_; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/events/tests/EventsTest.cpp b/ReactCommon/fabric/events/tests/EventsTest.cpp deleted file mode 100644 index c6fe25b8413c95..00000000000000 --- a/ReactCommon/fabric/events/tests/EventsTest.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include - -#include - -TEST(EventsTest, testSomething) { - // TODO -} diff --git a/ReactCommon/fabric/graphics/conversions.h b/ReactCommon/fabric/graphics/conversions.h index 4cd7e456d4d37f..2b8e645d754360 100644 --- a/ReactCommon/fabric/graphics/conversions.h +++ b/ReactCommon/fabric/graphics/conversions.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -70,8 +71,8 @@ inline std::string toString(const SharedColor &value) { #pragma mark - Geometry inline void fromRawValue(const RawValue &value, Point &result) { - if (value.hasType>()) { - auto map = (std::unordered_map)value; + if (value.hasType>()) { + auto map = (better::map)value; result = {map.at("x"), map.at("y")}; return; } @@ -87,8 +88,8 @@ inline void fromRawValue(const RawValue &value, Point &result) { } inline void fromRawValue(const RawValue &value, Size &result) { - if (value.hasType>()) { - auto map = (std::unordered_map)value; + if (value.hasType>()) { + auto map = (better::map)value; result = {map.at("width"), map.at("height")}; return; } @@ -109,8 +110,8 @@ inline void fromRawValue(const RawValue &value, EdgeInsets &result) { result = {number, number, number, number}; } - if (value.hasType>()) { - auto map = (std::unordered_map)value; + if (value.hasType>()) { + auto map = (better::map)value; result = {map.at("top"), map.at("left"), map.at("bottom"), map.at("right")}; return; } @@ -131,8 +132,8 @@ inline void fromRawValue(const RawValue &value, CornerInsets &result) { result = {number, number, number, number}; } - if (value.hasType>()) { - auto map = (std::unordered_map)value; + if (value.hasType>()) { + auto map = (better::map)value; result = {map.at("topLeft"), map.at("topRight"), map.at("bottomLeft"), diff --git a/ReactCommon/fabric/imagemanager/ImageRequest.h b/ReactCommon/fabric/imagemanager/ImageRequest.h index 1f2ae5ed8a2b1b..d955ce70cf765b 100644 --- a/ReactCommon/fabric/imagemanager/ImageRequest.h +++ b/ReactCommon/fabric/imagemanager/ImageRequest.h @@ -24,11 +24,6 @@ namespace react { */ class ImageRequest final { public: - /* - * The exception which is thrown when `ImageRequest` is being deallocated - * if the future is not ready yet. - */ - class ImageNoLongerNeededException; /* * The default constructor diff --git a/ReactCommon/fabric/imagemanager/platform/ios/ImageRequest.cpp b/ReactCommon/fabric/imagemanager/platform/ios/ImageRequest.cpp index 81f00b726dee20..40020f7b6e202d 100644 --- a/ReactCommon/fabric/imagemanager/platform/ios/ImageRequest.cpp +++ b/ReactCommon/fabric/imagemanager/platform/ios/ImageRequest.cpp @@ -10,12 +10,6 @@ namespace facebook { namespace react { -class ImageRequest::ImageNoLongerNeededException : public std::logic_error { - public: - ImageNoLongerNeededException() - : std::logic_error("Image no longer needed.") {} -}; - ImageRequest::ImageRequest(const ImageSource &imageSource) : imageSource_(imageSource) { coordinator_ = std::make_shared(); diff --git a/ReactCommon/fabric/mounting/BUCK b/ReactCommon/fabric/mounting/BUCK index 31c5798c126643..674ad5b5611acb 100644 --- a/ReactCommon/fabric/mounting/BUCK +++ b/ReactCommon/fabric/mounting/BUCK @@ -57,7 +57,6 @@ rn_xplat_cxx_library( react_native_xplat_target("better:better"), react_native_xplat_target("fabric/core:core"), react_native_xplat_target("fabric/debug:debug"), - react_native_xplat_target("fabric/events:events"), ], ) diff --git a/ReactCommon/fabric/mounting/Differentiator.cpp b/ReactCommon/fabric/mounting/Differentiator.cpp index f0f5622567d63f..62bf19d1229503 100644 --- a/ReactCommon/fabric/mounting/Differentiator.cpp +++ b/ReactCommon/fabric/mounting/Differentiator.cpp @@ -39,7 +39,7 @@ static void sliceChildShadowNodeViewPairsRecursively( *childShadowNode); } else { shadowView.layoutMetrics.frame.origin += layoutOffset; - pairList.push_back({shadowView, *childShadowNode}); + pairList.push_back({shadowView, childShadowNode.get()}); } } } @@ -98,9 +98,9 @@ static void calculateShadowViewMutations( } const auto oldGrandChildPairs = - sliceChildShadowNodeViewPairs(oldChildPair.shadowNode); + sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); const auto newGrandChildPairs = - sliceChildShadowNodeViewPairs(newChildPair.shadowNode); + sliceChildShadowNodeViewPairs(*newChildPair.shadowNode); calculateShadowViewMutations( *(newGrandChildPairs.size() ? &downwardMutations : &destructiveDownwardMutations), @@ -116,7 +116,7 @@ static void calculateShadowViewMutations( const auto &newChildPair = newChildPairs[index]; insertMutations.push_back(ShadowViewMutation::InsertMutation( - parentShadowView, newChildPair.shadowView, index)); + parentShadowView, newChildPair.shadowView, index)); insertedPairs.insert({newChildPair.shadowView.tag, newChildPair}); } @@ -145,7 +145,7 @@ static void calculateShadowViewMutations( calculateShadowViewMutations( destructiveDownwardMutations, oldChildPair.shadowView, - sliceChildShadowNodeViewPairs(oldChildPair.shadowNode), + sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode), {}); } else { // The old view *was* (re)inserted. @@ -154,9 +154,9 @@ static void calculateShadowViewMutations( const auto &newChildPair = it->second; if (newChildPair.shadowView != oldChildPair.shadowView) { const auto oldGrandChildPairs = - sliceChildShadowNodeViewPairs(oldChildPair.shadowNode); + sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); const auto newGrandChildPairs = - sliceChildShadowNodeViewPairs(newChildPair.shadowNode); + sliceChildShadowNodeViewPairs(*newChildPair.shadowNode); calculateShadowViewMutations( *(newGrandChildPairs.size() ? &downwardMutations : &destructiveDownwardMutations), @@ -191,7 +191,7 @@ static void calculateShadowViewMutations( downwardMutations, newChildPair.shadowView, {}, - sliceChildShadowNodeViewPairs(newChildPair.shadowNode)); + sliceChildShadowNodeViewPairs(*newChildPair.shadowNode)); } // All mutations in an optimal order: diff --git a/ReactCommon/fabric/mounting/ShadowView.cpp b/ReactCommon/fabric/mounting/ShadowView.cpp index 1120745c7f6d83..0cd8eee83101e5 100644 --- a/ReactCommon/fabric/mounting/ShadowView.cpp +++ b/ReactCommon/fabric/mounting/ShadowView.cpp @@ -24,7 +24,8 @@ ShadowView::ShadowView(const ShadowNode &shadowNode) props(shadowNode.getProps()), eventEmitter(shadowNode.getEventEmitter()), layoutMetrics(layoutMetricsFromShadowNode(shadowNode)), - localData(shadowNode.getLocalData()) {} + localData(shadowNode.getLocalData()), + state(shadowNode.getState()) {} bool ShadowView::operator==(const ShadowView &rhs) const { return std::tie( @@ -33,14 +34,16 @@ bool ShadowView::operator==(const ShadowView &rhs) const { this->props, this->eventEmitter, this->layoutMetrics, - this->localData) == + this->localData, + this->state) == std::tie( rhs.tag, rhs.componentName, rhs.props, rhs.eventEmitter, rhs.layoutMetrics, - rhs.localData); + rhs.localData, + rhs.state); } bool ShadowView::operator!=(const ShadowView &rhs) const { @@ -48,7 +51,7 @@ bool ShadowView::operator!=(const ShadowView &rhs) const { } bool ShadowViewNodePair::operator==(const ShadowViewNodePair &rhs) const { - return &this->shadowNode == &rhs.shadowNode; + return this->shadowNode == rhs.shadowNode; } bool ShadowViewNodePair::operator!=(const ShadowViewNodePair &rhs) const { diff --git a/ReactCommon/fabric/mounting/ShadowView.h b/ReactCommon/fabric/mounting/ShadowView.h index 8f242effa1df7e..f343c5aac1f41b 100644 --- a/ReactCommon/fabric/mounting/ShadowView.h +++ b/ReactCommon/fabric/mounting/ShadowView.h @@ -6,12 +6,12 @@ #pragma once #include +#include #include #include #include #include #include -#include namespace facebook { namespace react { @@ -23,6 +23,8 @@ struct ShadowView final { ShadowView() = default; ShadowView(const ShadowView &shadowView) = default; + ~ShadowView(){}; + /* * Constructs a `ShadowView` from given `ShadowNode`. */ @@ -40,14 +42,15 @@ struct ShadowView final { SharedEventEmitter eventEmitter = {}; LayoutMetrics layoutMetrics = EmptyLayoutMetrics; SharedLocalData localData = {}; + State::Shared state = {}; }; /* * Describes pair of a `ShadowView` and a `ShadowNode`. */ struct ShadowViewNodePair final { - const ShadowView shadowView; - const ShadowNode &shadowNode; + ShadowView shadowView; + ShadowNode const *shadowNode; /* * The stored pointer to `ShadowNode` represents an indentity of the pair. diff --git a/ReactCommon/fabric/mounting/ShadowViewMutation.cpp b/ReactCommon/fabric/mounting/ShadowViewMutation.cpp index 023107a5fa95e0..e73e05ceea8cce 100644 --- a/ReactCommon/fabric/mounting/ShadowViewMutation.cpp +++ b/ReactCommon/fabric/mounting/ShadowViewMutation.cpp @@ -11,32 +11,49 @@ namespace facebook { namespace react { ShadowViewMutation ShadowViewMutation::CreateMutation(ShadowView shadowView) { - return ShadowViewMutation{ - .type = Create, .newChildShadowView = shadowView, .index = -1}; + return { + /* .type = */ Create, + /* .parentShadowView = */ {}, + /* .oldChildShadowView = */ {}, + /* .newChildShadowView = */ shadowView, + /* .index = */ -1, + }; } ShadowViewMutation ShadowViewMutation::DeleteMutation(ShadowView shadowView) { - return {.type = Delete, .oldChildShadowView = shadowView, .index = -1}; + return { + /* .type = */ Delete, + /* .parentShadowView = */ {}, + /* .oldChildShadowView = */ shadowView, + /* .newChildShadowView = */ {}, + /* .index = */ -1, + }; } ShadowViewMutation ShadowViewMutation::InsertMutation( ShadowView parentShadowView, ShadowView childShadowView, int index) { - return {.type = Insert, - .parentShadowView = parentShadowView, - .newChildShadowView = childShadowView, - .index = index}; + return { + /* .type = */ Insert, + /* .parentShadowView = */ parentShadowView, + /* .oldChildShadowView = */ {}, + /* .newChildShadowView = */ childShadowView, + /* .index = */ index, + }; } ShadowViewMutation ShadowViewMutation::RemoveMutation( ShadowView parentShadowView, ShadowView childShadowView, int index) { - return {.type = Remove, - .parentShadowView = parentShadowView, - .oldChildShadowView = childShadowView, - .index = index}; + return { + /* .type = */ Remove, + /* .parentShadowView = */ parentShadowView, + /* .oldChildShadowView = */ childShadowView, + /* .newChildShadowView = */ {}, + /* .index = */ index, + }; } ShadowViewMutation ShadowViewMutation::UpdateMutation( @@ -44,11 +61,13 @@ ShadowViewMutation ShadowViewMutation::UpdateMutation( ShadowView oldChildShadowView, ShadowView newChildShadowView, int index) { - return {.type = Update, - .parentShadowView = parentShadowView, - .oldChildShadowView = oldChildShadowView, - .newChildShadowView = newChildShadowView, - .index = index}; + return { + /* .type = */ Update, + /* .parentShadowView = */ parentShadowView, + /* .oldChildShadowView = */ oldChildShadowView, + /* .newChildShadowView = */ newChildShadowView, + /* .index = */ index, + }; } } // namespace react diff --git a/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp b/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp index 5721126a08c883..26e65789eb5fcb 100644 --- a/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp +++ b/ReactCommon/fabric/sample/SampleComponentDescriptorFactor.cpp @@ -16,7 +16,7 @@ namespace react { * This is a sample implementation. Each app should provide its own. */ ComponentRegistryFactory getDefaultComponentRegistryFactory() { - return [](const SharedEventDispatcher &eventDispatcher, + return [](const EventDispatcher::Shared &eventDispatcher, const SharedContextContainer &contextContainer) { auto registry = std::make_shared(); return registry; diff --git a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm index 0e885b2603723c..94fed1a895d461 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm +++ b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm @@ -10,9 +10,13 @@ #import "NSTextStorage+FontScaling.h" #import "RCTAttributedTextUtils.h" +#import + using namespace facebook::react; -@implementation RCTTextLayoutManager +@implementation RCTTextLayoutManager { + SimpleThreadSafeCache, 256> _cache; +} static NSLineBreakMode RCTNSLineBreakModeFromWritingDirection( EllipsizeMode ellipsizeMode) { @@ -34,11 +38,10 @@ static NSLineBreakMode RCTNSLineBreakModeFromWritingDirection( layoutConstraints:(LayoutConstraints)layoutConstraints { CGSize maximumSize = CGSize{layoutConstraints.maximumSize.width, layoutConstraints.maximumSize.height}; - NSTextStorage *textStorage = - [self _textStorageAndLayoutManagerWithAttributesString: - RCTNSAttributedStringFromAttributedString(attributedString) - paragraphAttributes:paragraphAttributes - size:maximumSize]; + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:maximumSize]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -55,11 +58,10 @@ static NSLineBreakMode RCTNSLineBreakModeFromWritingDirection( - (void)drawAttributedString:(AttributedString)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame { - NSTextStorage *textStorage = - [self _textStorageAndLayoutManagerWithAttributesString: - RCTNSAttributedStringFromAttributedString(attributedString) - paragraphAttributes:paragraphAttributes - size:frame.size]; + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:frame.size]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -111,11 +113,10 @@ - (void)drawAttributedString:(AttributedString)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame atPoint:(CGPoint)point { - NSTextStorage *textStorage = - [self _textStorageAndLayoutManagerWithAttributesString: - RCTNSAttributedStringFromAttributedString(attributedString) - paragraphAttributes:paragraphAttributes - size:frame.size]; + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:frame.size]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -140,4 +141,14 @@ - (void)drawAttributedString:(AttributedString)attributedString return nil; } +- (NSAttributedString *)_nsAttributedStringFromAttributedString:(AttributedString)attributedString +{ + auto sharedNSAttributedString = _cache.get(attributedString, [](const AttributedString attributedString) { + return std::shared_ptr( + (__bridge_retained void *)RCTNSAttributedStringFromAttributedString(attributedString), CFRelease); + }); + + return (__bridge NSAttributedString *)sharedNSAttributedString.get(); +} + @end diff --git a/ReactCommon/fabric/uimanager/BUCK b/ReactCommon/fabric/uimanager/BUCK index a6a2f978be52e3..28f2ce82d288c0 100644 --- a/ReactCommon/fabric/uimanager/BUCK +++ b/ReactCommon/fabric/uimanager/BUCK @@ -62,7 +62,6 @@ rn_xplat_cxx_library( react_native_xplat_target("fabric/mounting:mounting"), react_native_xplat_target("fabric/core:core"), react_native_xplat_target("fabric/debug:debug"), - react_native_xplat_target("fabric/events:events"), ], ) @@ -90,5 +89,6 @@ fb_xplat_cxx_test( react_native_xplat_target("fabric/components/scrollview:scrollview"), react_native_xplat_target("fabric/components/text:text"), react_native_xplat_target("fabric/components/view:view"), + "fbsource//xplat/js:generated_components-rncore", ], ) diff --git a/ReactCommon/fabric/uimanager/ComponentDescriptorFactory.h b/ReactCommon/fabric/uimanager/ComponentDescriptorFactory.h index 94e4260f6f52da..380300ecaadd1b 100644 --- a/ReactCommon/fabric/uimanager/ComponentDescriptorFactory.h +++ b/ReactCommon/fabric/uimanager/ComponentDescriptorFactory.h @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include "ComponentDescriptorRegistry.h" @@ -25,7 +25,7 @@ namespace react { */ using ComponentRegistryFactory = std::function; ComponentRegistryFactory getDefaultComponentRegistryFactory(); diff --git a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp index 3af8cf010679e3..5dbcbda2ad2c75 100644 --- a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp +++ b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.cpp @@ -113,12 +113,13 @@ SharedShadowNode ComponentDescriptorRegistry::createNode( ComponentName componentName = componentNameByReactViewName(viewName); const SharedComponentDescriptor &componentDescriptor = (*this)[componentName]; - SharedShadowNode shadowNode = componentDescriptor->createShadowNode( - {.tag = tag, - .rootTag = rootTag, - .eventEmitter = - componentDescriptor->createEventEmitter(std::move(eventTarget), tag), - .props = componentDescriptor->cloneProps(nullptr, RawProps(props))}); + SharedShadowNode shadowNode = componentDescriptor->createShadowNode({ + /* .tag = */ tag, + /* .rootTag = */ rootTag, + /* .props = */ componentDescriptor->cloneProps(nullptr, RawProps(props)), + /* .eventEmitter = */ + componentDescriptor->createEventEmitter(std::move(eventTarget), tag), + }); return shadowNode; } diff --git a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h index 7f6614fb9c6c20..9555c063ef9bce 100644 --- a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h +++ b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h @@ -7,6 +7,7 @@ #include +#include #include namespace facebook { @@ -40,9 +41,8 @@ class ComponentDescriptorRegistry { const SharedEventTarget &eventTarget) const; private: - std::unordered_map - _registryByHandle; - std::unordered_map _registryByName; + better::map _registryByHandle; + better::map _registryByName; }; } // namespace react diff --git a/ReactCommon/fabric/uimanager/ContextContainer.h b/ReactCommon/fabric/uimanager/ContextContainer.h index 3ae07673b58ca9..3c0170342eab47 100644 --- a/ReactCommon/fabric/uimanager/ContextContainer.h +++ b/ReactCommon/fabric/uimanager/ContextContainer.h @@ -13,6 +13,8 @@ #include #include +#include + namespace facebook { namespace react { diff --git a/ReactCommon/fabric/uimanager/Scheduler.cpp b/ReactCommon/fabric/uimanager/Scheduler.cpp index c0212fd2d53431..096d33d1edac11 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.cpp +++ b/ReactCommon/fabric/uimanager/Scheduler.cpp @@ -5,6 +5,7 @@ #include "Scheduler.h" +#include #include #include @@ -45,12 +46,25 @@ Scheduler::Scheduler( uiManagerBinding->dispatchEvent(runtime, eventTarget, type, payloadFactory); }; + auto statePipe = [uiManager = &uiManagerRef]( + const StateData::Shared &data, + const StateTarget &stateTarget) { + uiManager->updateState( + stateTarget.getShadowNode().shared_from_this(), data); + }; + auto eventDispatcher = std::make_shared( - eventPipe, synchronousEventBeatFactory, asynchronousEventBeatFactory); + eventPipe, + statePipe, + synchronousEventBeatFactory, + asynchronousEventBeatFactory); componentDescriptorRegistry_ = buildRegistryFunction(eventDispatcher, contextContainer); + rootComponentDescriptor_ = + std::make_unique(eventDispatcher); + uiManagerRef.setDelegate(this); uiManagerRef.setShadowTreeRegistry(&shadowTreeRegistry_); uiManagerRef.setComponentDescriptorRegistry(componentDescriptorRegistry_); @@ -72,8 +86,8 @@ void Scheduler::startSurface( const LayoutContext &layoutContext) const { SystraceSection s("Scheduler::startSurface"); - auto shadowTree = - std::make_unique(surfaceId, layoutConstraints, layoutContext); + auto shadowTree = std::make_unique( + surfaceId, layoutConstraints, layoutContext, *rootComponentDescriptor_); shadowTree->setDelegate(this); shadowTreeRegistry_.add(std::move(shadowTree)); @@ -110,9 +124,16 @@ void Scheduler::renderTemplateToSurface( [&](const SharedRootShadowNode &oldRootShadowNode) { return std::make_shared( *oldRootShadowNode, - ShadowNodeFragment{.children = - std::make_shared( - SharedShadowNodeList{tree})}); + ShadowNodeFragment{ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ ShadowNodeFragment::propsPlaceholder(), + /* .eventEmitter = */ + ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ + std::make_shared( + SharedShadowNodeList{tree}), + }); }, commitStartTime); }); @@ -134,8 +155,15 @@ void Scheduler::stopSurface(SurfaceId surfaceId) const { return std::make_shared( *oldRootShadowNode, ShadowNodeFragment{ - .children = - ShadowNode::emptySharedShadowNodeSharedList()}); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ + ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ ShadowNodeFragment::propsPlaceholder(), + /* .eventEmitter = */ + ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ + ShadowNode::emptySharedShadowNodeSharedList(), + }); }, commitStartTime); }); @@ -190,6 +218,11 @@ void Scheduler::constraintSurfaceLayout( }); } +const ComponentDescriptor &Scheduler::getComponentDescriptor( + ComponentHandle handle) { + return componentDescriptorRegistry_->at(handle); +} + #pragma mark - Delegate void Scheduler::setDelegate(SchedulerDelegate *delegate) { @@ -228,7 +261,14 @@ void Scheduler::uiManagerDidFinishTransaction( [&](const SharedRootShadowNode &oldRootShadowNode) { return std::make_shared( *oldRootShadowNode, - ShadowNodeFragment{.children = rootChildNodes}); + ShadowNodeFragment{ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ ShadowNodeFragment::propsPlaceholder(), + /* .eventEmitter = */ + ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ rootChildNodes, + }); }, startCommitTime); }); @@ -239,15 +279,9 @@ void Scheduler::uiManagerDidCreateShadowNode( SystraceSection s("Scheduler::uiManagerDidCreateShadowNode"); if (delegate_) { - auto layoutableShadowNode = - dynamic_cast(shadowNode.get()); - auto isLayoutable = layoutableShadowNode != nullptr; - + auto shadowView = ShadowView(*shadowNode); delegate_->schedulerDidRequestPreliminaryViewAllocation( - shadowNode->getRootTag(), - shadowNode->getComponentName(), - isLayoutable, - shadowNode->getComponentHandle()); + shadowNode->getRootTag(), shadowView); } } diff --git a/ReactCommon/fabric/uimanager/Scheduler.h b/ReactCommon/fabric/uimanager/Scheduler.h index 1d2bc10f63b19b..6bad79fc14400f 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.h +++ b/ReactCommon/fabric/uimanager/Scheduler.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -67,6 +68,8 @@ class Scheduler final : public UIManagerDelegate, public ShadowTreeDelegate { const LayoutConstraints &layoutConstraints, const LayoutContext &layoutContext) const; + const ComponentDescriptor &getComponentDescriptor(ComponentHandle handle); + #pragma mark - Delegate /* @@ -97,6 +100,7 @@ class Scheduler final : public UIManagerDelegate, public ShadowTreeDelegate { private: SchedulerDelegate *delegate_; SharedComponentDescriptorRegistry componentDescriptorRegistry_; + std::unique_ptr rootComponentDescriptor_; ShadowTreeRegistry shadowTreeRegistry_; RuntimeExecutor runtimeExecutor_; std::shared_ptr uiManagerBinding_; diff --git a/ReactCommon/fabric/uimanager/SchedulerDelegate.h b/ReactCommon/fabric/uimanager/SchedulerDelegate.h index e62c31b7fa84f8..c2d81cdbd53e7b 100644 --- a/ReactCommon/fabric/uimanager/SchedulerDelegate.h +++ b/ReactCommon/fabric/uimanager/SchedulerDelegate.h @@ -35,9 +35,7 @@ class SchedulerDelegate { */ virtual void schedulerDidRequestPreliminaryViewAllocation( SurfaceId surfaceId, - ComponentName componentName, - bool isLayoutable, - ComponentHandle componentHandle) = 0; + const ShadowView &shadowView) = 0; virtual ~SchedulerDelegate() noexcept = default; }; diff --git a/ReactCommon/fabric/uimanager/ShadowTree.cpp b/ReactCommon/fabric/uimanager/ShadowTree.cpp index 1968bc9af6a0d9..87d48e6e1add4b 100644 --- a/ReactCommon/fabric/uimanager/ShadowTree.cpp +++ b/ReactCommon/fabric/uimanager/ShadowTree.cpp @@ -5,6 +5,7 @@ #include "ShadowTree.h" +#include #include #include #include @@ -79,7 +80,8 @@ static void updateMountedFlag( ShadowTree::ShadowTree( SurfaceId surfaceId, const LayoutConstraints &layoutConstraints, - const LayoutContext &layoutContext) + const LayoutContext &layoutContext, + const RootComponentDescriptor &rootComponentDescriptor) : surfaceId_(surfaceId) { const auto noopEventEmitter = std::make_shared( nullptr, -1, std::shared_ptr()); @@ -87,14 +89,13 @@ ShadowTree::ShadowTree( const auto props = std::make_shared( *RootShadowNode::defaultSharedProps(), layoutConstraints, layoutContext); - rootShadowNode_ = std::make_shared( - ShadowNodeFragment{ - .tag = surfaceId, - .rootTag = surfaceId, - .props = props, - .eventEmitter = noopEventEmitter, - }, - nullptr); + rootShadowNode_ = std::static_pointer_cast( + rootComponentDescriptor.createShadowNode(ShadowNodeFragment{ + /* .tag = */ surfaceId, + /* .rootTag = */ surfaceId, + /* .props = */ props, + /* .eventEmitter = */ noopEventEmitter, + })); } ShadowTree::~ShadowTree() { @@ -103,7 +104,13 @@ ShadowTree::~ShadowTree() { return std::make_shared( *oldRootShadowNode, ShadowNodeFragment{ - .children = ShadowNode::emptySharedShadowNodeSharedList()}); + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ ShadowNodeFragment::propsPlaceholder(), + /* .eventEmitter = */ + ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNode::emptySharedShadowNodeSharedList(), + }); }, getTime()); } diff --git a/ReactCommon/fabric/uimanager/ShadowTree.h b/ReactCommon/fabric/uimanager/ShadowTree.h index 03e9de41173567..41f328292eee63 100644 --- a/ReactCommon/fabric/uimanager/ShadowTree.h +++ b/ReactCommon/fabric/uimanager/ShadowTree.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -33,7 +34,8 @@ class ShadowTree final { ShadowTree( SurfaceId surfaceId, const LayoutConstraints &layoutConstraints, - const LayoutContext &layoutContext); + const LayoutContext &layoutContext, + const RootComponentDescriptor &rootComponentDescriptor); ~ShadowTree(); diff --git a/ReactCommon/fabric/uimanager/UIManager.cpp b/ReactCommon/fabric/uimanager/UIManager.cpp index 6a6bea81e36fd5..da321b29764e3a 100644 --- a/ReactCommon/fabric/uimanager/UIManager.cpp +++ b/ReactCommon/fabric/uimanager/UIManager.cpp @@ -18,13 +18,19 @@ SharedShadowNode UIManager::createNode( SystraceSection s("UIManager::createNode"); auto &componentDescriptor = componentDescriptorRegistry_->at(name); - - auto shadowNode = componentDescriptor.createShadowNode( - {.tag = tag, - .rootTag = surfaceId, - .eventEmitter = - componentDescriptor.createEventEmitter(std::move(eventTarget), tag), - .props = componentDescriptor.cloneProps(nullptr, rawProps)}); + const auto &props = componentDescriptor.cloneProps(nullptr, rawProps); + const auto &state = componentDescriptor.createInitialState(props); + + auto shadowNode = componentDescriptor.createShadowNode({ + /* .tag = */ tag, + /* .rootTag = */ surfaceId, + /* .props = */ props, + /* .eventEmitter = */ + componentDescriptor.createEventEmitter(std::move(eventTarget), tag), + /* .children = */ ShadowNodeFragment::childrenPlaceholder(), + /* .localData = */ ShadowNodeFragment::localDataPlaceholder(), + /* .state = */ state, + }); if (delegate_) { delegate_->uiManagerDidCreateShadowNode(shadowNode); @@ -45,10 +51,14 @@ SharedShadowNode UIManager::cloneNode( auto clonedShadowNode = componentDescriptor.cloneShadowNode( *shadowNode, { - .props = rawProps ? componentDescriptor.cloneProps( - shadowNode->getProps(), *rawProps) - : ShadowNodeFragment::propsPlaceholder(), - .children = children, + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ + rawProps ? componentDescriptor.cloneProps( + shadowNode->getProps(), *rawProps) + : ShadowNodeFragment::propsPlaceholder(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ children, }); return clonedShadowNode; @@ -85,7 +95,11 @@ void UIManager::setNativeProps( auto &componentDescriptor = componentDescriptorRegistry_->at(shadowNode->getComponentHandle()); auto props = componentDescriptor.cloneProps(shadowNode->getProps(), rawProps); - auto newShadowNode = shadowNode->clone(ShadowNodeFragment{.props = props}); + auto newShadowNode = shadowNode->clone({ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ props, + }); shadowTreeRegistry_->visit( shadowNode->getRootTag(), [&](const ShadowTree &shadowTree) { @@ -129,6 +143,35 @@ LayoutMetrics UIManager::getRelativeLayoutMetrics( *layoutableAncestorShadowNode); } +void UIManager::updateState( + const SharedShadowNode &shadowNode, + const StateData::Shared &rawStateData) const { + long startCommitTime = getTime(); + + auto &componentDescriptor = + componentDescriptorRegistry_->at(shadowNode->getComponentHandle()); + auto state = + componentDescriptor.createState(shadowNode->getState(), rawStateData); + auto newShadowNode = shadowNode->clone({ + /* .tag = */ ShadowNodeFragment::tagPlaceholder(), + /* .rootTag = */ ShadowNodeFragment::surfaceIdPlaceholder(), + /* .props = */ ShadowNodeFragment::propsPlaceholder(), + /* .eventEmitter = */ ShadowNodeFragment::eventEmitterPlaceholder(), + /* .children = */ ShadowNodeFragment::childrenPlaceholder(), + /* .localData = */ ShadowNodeFragment::localDataPlaceholder(), + /* .state = */ state, + }); + + shadowTreeRegistry_->visit( + shadowNode->getRootTag(), [&](const ShadowTree &shadowTree) { + shadowTree.tryCommit( + [&](const SharedRootShadowNode &oldRootShadowNode) { + return oldRootShadowNode->clone(shadowNode, newShadowNode); + }, + startCommitTime); + }); +} + void UIManager::setShadowTreeRegistry(ShadowTreeRegistry *shadowTreeRegistry) { shadowTreeRegistry_ = shadowTreeRegistry; } diff --git a/ReactCommon/fabric/uimanager/UIManager.h b/ReactCommon/fabric/uimanager/UIManager.h index c39be1336f16ab..02fd1eda2f31a6 100644 --- a/ReactCommon/fabric/uimanager/UIManager.h +++ b/ReactCommon/fabric/uimanager/UIManager.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,7 @@ class UIManager { private: friend class UIManagerBinding; + friend class Scheduler; SharedShadowNode createNode( Tag tag, @@ -65,6 +67,14 @@ class UIManager { const ShadowNode &shadowNode, const ShadowNode *ancestorShadowNode) const; + /* + * Creates a new shadow node with given state data, clones what's necessary + * and performs a commit. + */ + void updateState( + const SharedShadowNode &shadowNode, + const StateData::Shared &rawStateData) const; + ShadowTreeRegistry *shadowTreeRegistry_; SharedComponentDescriptorRegistry componentDescriptorRegistry_; UIManagerDelegate *delegate_; diff --git a/ReactCommon/fabric/uimanager/UITemplateProcessor.cpp b/ReactCommon/fabric/uimanager/UITemplateProcessor.cpp index 4a245fa252a016..b59c3141151fab 100644 --- a/ReactCommon/fabric/uimanager/UITemplateProcessor.cpp +++ b/ReactCommon/fabric/uimanager/UITemplateProcessor.cpp @@ -57,8 +57,10 @@ SharedShadowNode UITemplateProcessor::runCommand( componentDescriptor->appendChild(parentShadowNode, nodes[tag]); } } else if (opcode == "returnRoot") { - LOG(INFO) - << "(stop) UITemplateProcessor inject serialized 'server rendered' view tree"; + if (DEBUG_FLY) { + LOG(INFO) + << "(stop) UITemplateProcessor inject serialized 'server rendered' view tree"; + } return nodes[command[1].asInt()]; } else if (opcode == "loadNativeBool") { int registerNumber = command[1].asInt(); @@ -105,8 +107,10 @@ SharedShadowNode UITemplateProcessor::buildShadowTree( const ComponentDescriptorRegistry &componentDescriptorRegistry, const NativeModuleRegistry &nativeModuleRegistry, const std::shared_ptr reactNativeConfig) { - LOG(INFO) - << "(strt) UITemplateProcessor inject hardcoded 'server rendered' view tree"; + if (DEBUG_FLY) { + LOG(INFO) + << "(strt) UITemplateProcessor inject hardcoded 'server rendered' view tree"; + } std::string content = jsonStr; for (const auto ¶m : params.items()) { diff --git a/ReactCommon/fabric/uimanager/primitives.h b/ReactCommon/fabric/uimanager/primitives.h index 549d541f723467..c427f0428b42f9 100644 --- a/ReactCommon/fabric/uimanager/primitives.h +++ b/ReactCommon/fabric/uimanager/primitives.h @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace facebook { diff --git a/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp b/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp index a546f7660348f3..f7c2efde78bc64 100644 --- a/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp +++ b/ReactCommon/fabric/uimanager/tests/UITemplateProcessorTest.cpp @@ -13,8 +13,8 @@ using namespace facebook::react; -#include #include +#include #include #include #include @@ -30,7 +30,7 @@ namespace react { // TODO (T29441913): Codegen this app-specific implementation. ComponentRegistryFactory getDefaultComponentRegistryFactory() { - return [](const SharedEventDispatcher &eventDispatcher, + return [](const EventDispatcher::Shared &eventDispatcher, const SharedContextContainer &contextContainer) { auto registry = std::make_shared(); registry->registerComponentDescriptor( diff --git a/ReactCommon/jsi/JSCRuntime.cpp b/ReactCommon/jsi/JSCRuntime.cpp index 0313a6d13c6b68..7a941d6093faed 100644 --- a/ReactCommon/jsi/JSCRuntime.cpp +++ b/ReactCommon/jsi/JSCRuntime.cpp @@ -246,14 +246,10 @@ class JSCRuntime : public jsi::Runtime { // JSStringRef utilities namespace { std::string JSStringToSTLString(JSStringRef str) { - std::string result; size_t maxBytes = JSStringGetMaximumUTF8CStringSize(str); - result.resize(maxBytes); - size_t bytesWritten = JSStringGetUTF8CString(str, &result[0], maxBytes); - // JSStringGetUTF8CString writes the null terminator, so we want to resize - // to `bytesWritten - 1` so that `result` has the correct length. - result.resize(bytesWritten - 1); - return result; + std::vector buffer(maxBytes); + JSStringGetUTF8CString(str, buffer.data(), maxBytes); + return std::string(buffer.data()); } JSStringRef getLengthString() { diff --git a/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp b/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp index a15ceb05ca35e4..db63a0c1c6d0cb 100644 --- a/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp +++ b/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp @@ -5,6 +5,8 @@ #include "jsireact/JSINativeModules.h" +#include + #include #include diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm index 53501da88c2ff2..6fe672e823832f 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm @@ -373,14 +373,14 @@ SEL resolveMethodSelector( * - ObjC module methods will be always be called from JS thread. * They may decide to dispatch to a different queue as needed. */ -void performMethodInvocation( +jsi::Value performMethodInvocation( jsi::Runtime &runtime, NSInvocation *inv, TurboModuleMethodValueKind valueKind, const id module, - std::shared_ptr jsInvoker, - jsi::Value *result) { - *result = jsi::Value::undefined(); + std::shared_ptr jsInvoker) { + + __block void *rawResult = NULL; jsi::Runtime *rt = &runtime; void (^block)() = ^{ [inv invokeWithTarget:module]; @@ -389,33 +389,7 @@ void performMethodInvocation( return; } - void *rawResult = NULL; - [inv getReturnValue:&rawResult]; - - // TODO: Re-use value conversion logic from existing impl, if possible. - switch (valueKind) { - case BooleanKind: - *result = convertNSNumberToJSIBoolean(*rt, (__bridge NSNumber *)rawResult); - break; - case NumberKind: - *result = convertNSNumberToJSINumber(*rt, (__bridge NSNumber *)rawResult); - break; - case StringKind: - *result = convertNSStringToJSIString(*rt, (__bridge NSString *)rawResult); - break; - case ObjectKind: - *result = convertNSDictionaryToJSIObject(*rt, (__bridge NSDictionary *)rawResult); - break; - case ArrayKind: - *result = convertNSArrayToJSIArray(*rt, (__bridge NSArray *)rawResult); - break; - case FunctionKind: - throw std::runtime_error("doInvokeTurboModuleMethod: FunctionKind is not supported yet."); - case PromiseKind: - throw std::runtime_error("doInvokeTurboModuleMethod: PromiseKind wasn't handled properly."); - case VoidKind: - throw std::runtime_error("doInvokeTurboModuleMethod: VoidKind wasn't handled properly."); - } + [inv getReturnValue:(void *)&rawResult]; }; // Backward-compatibility layer for calling module methods on specific queue. @@ -444,6 +418,26 @@ void performMethodInvocation( dispatch_sync(methodQueue, block); } } + + // TODO: Re-use value conversion logic from existing impl, if possible. + switch (valueKind) { + case VoidKind: + return jsi::Value::undefined(); + case BooleanKind: + return convertNSNumberToJSIBoolean(*rt, (__bridge NSNumber *)rawResult); + case NumberKind: + return convertNSNumberToJSINumber(*rt, (__bridge NSNumber *)rawResult); + case StringKind: + return convertNSStringToJSIString(*rt, (__bridge NSString *)rawResult); + case ObjectKind: + return convertNSDictionaryToJSIObject(*rt, (__bridge NSDictionary *)rawResult); + case ArrayKind: + return convertNSArrayToJSIArray(*rt, (__bridge NSArray *)rawResult); + case FunctionKind: + throw std::runtime_error("convertInvocationResultToJSIValue: FunctionKind is not supported yet."); + case PromiseKind: + throw std::runtime_error("convertInvocationResultToJSIValue: PromiseKind wasn't handled properly."); + } } } // namespace @@ -476,14 +470,11 @@ void performMethodInvocation( [inv setArgument:(void *)&resolveBlock atIndex:count + 2]; [inv setArgument:(void *)&rejectBlock atIndex:count + 3]; // The return type becomes void in the ObjC side. - jsi::Value result; - performMethodInvocation(rt, inv, VoidKind, instance_, jsInvoker_, &result); + performMethodInvocation(rt, inv, VoidKind, instance_, jsInvoker_); }); } - jsi::Value result; - performMethodInvocation(runtime, inv, valueKind, instance_, jsInvoker_, &result); - return result; + return performMethodInvocation(runtime, inv, valueKind, instance_, jsInvoker_); } } // namespace react diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm index a5fefa050c79dc..e6111517b8920f 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm @@ -9,6 +9,7 @@ #import +#import #import #import #import @@ -230,6 +231,16 @@ - (instancetype)initWithRuntime:(jsi::Runtime *)runtime } _rctTurboModuleCache.insert({moduleName, module}); + + /** + * Broadcast that this TurboModule was created. + * + * TODO(T41180176): Investigate whether we can get rid of this after all + * TurboModules are rolled out + */ + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification + object:_bridge.parentBridge + userInfo:@{@"module": module, @"bridge": RCTNullIfNil(_bridge.parentBridge)}]; return module; } diff --git a/ReactCommon/utils/BUCK b/ReactCommon/utils/BUCK index e086a4a0ecb7a1..dd317a18e7026e 100644 --- a/ReactCommon/utils/BUCK +++ b/ReactCommon/utils/BUCK @@ -26,6 +26,10 @@ rn_xplat_cxx_library( "PUBLIC", ], deps = [ + "fbsource//xplat/folly:evicting_cache_map", + "fbsource//xplat/folly:headers_only", + "fbsource//xplat/folly:memory", + "fbsource//xplat/folly:molly", react_native_xplat_target("better:better"), ], ) diff --git a/ReactCommon/utils/SimpleThreadSafeCache.h b/ReactCommon/utils/SimpleThreadSafeCache.h new file mode 100644 index 00000000000000..2d525d91775d01 --- /dev/null +++ b/ReactCommon/utils/SimpleThreadSafeCache.h @@ -0,0 +1,74 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +/* + * Simple thread-safe LRU cache. + */ +template +class SimpleThreadSafeCache { +public: + SimpleThreadSafeCache() : map_{maxSize} {} + + /* + * Returns a value from the map with a given key. + * If the value wasn't found in the cache, constructs the value using given + * generator function, stores it inside a cache and returns it. + * Can be called from any thread. + */ + ValueT get(const KeyT &key, std::function generator) const { + std::lock_guard lock(mutex_); + auto iterator = map_.find(key); + if (iterator == map_.end()) { + auto value = generator(key); + map_.set(key, value); + return value; + } + + return iterator->second; + } + + /* + * Returns a value from the map with a given key. + * If the value wasn't found in the cache, returns empty optional. + * Can be called from any thread. + */ + better::optional get(const KeyT &key) const { + std::lock_guard lock(mutex_); + auto iterator = map_.find(key); + if (iterator == map_.end()) { + return {}; + } + + return iterator->second; + } + + /* + * Sets a key-value pair in the LRU cache. + * Can be called from any thread. + */ + void set(const KeyT &key, const ValueT &value) const { + std::lock_guard lock(mutex_); + map_.set(std::move(key), std::move(value)); + } + +private: + mutable folly::EvictingCacheMap map_; + mutable std::mutex mutex_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/yoga/yoga/YGNode.cpp b/ReactCommon/yoga/yoga/YGNode.cpp index d1601272562172..2fb4510c590032 100644 --- a/ReactCommon/yoga/yoga/YGNode.cpp +++ b/ReactCommon/yoga/yoga/YGNode.cpp @@ -5,6 +5,7 @@ * file in the root directory of this source tree. */ #include "YGNode.h" +#include #include #include "CompactValue.h" #include "Utils.h" @@ -12,6 +13,31 @@ using namespace facebook; using facebook::yoga::detail::CompactValue; +YGNode::YGNode(YGNode&& node) { + context_ = node.context_; + hasNewLayout_ = node.hasNewLayout_; + isReferenceBaseline_ = node.isReferenceBaseline_; + isDirty_ = node.isDirty_; + nodeType_ = node.nodeType_; + measureUsesContext_ = node.measureUsesContext_; + baselineUsesContext_ = node.baselineUsesContext_; + printUsesContext_ = node.printUsesContext_; + measure_ = node.measure_; + baseline_ = node.baseline_; + print_ = node.print_; + dirtied_ = node.dirtied_; + style_ = node.style_; + layout_ = node.layout_; + lineIndex_ = node.lineIndex_; + owner_ = node.owner_; + children_ = std::move(node.children_); + config_ = node.config_; + resolvedDimensions_ = node.resolvedDimensions_; + for (auto c : children_) { + c->setOwner(c); + } +} + void YGNode::print(void* printContext) { if (print_.noContext != nullptr) { if (printUsesContext_) { @@ -298,37 +324,6 @@ void YGNode::setPosition( trailing[crossAxis]); } -YGNode& YGNode::operator=(const YGNode& node) { - if (&node == this) { - return *this; - } - - for (auto child : children_) { - delete child; - } - - context_ = node.getContext(); - hasNewLayout_ = node.getHasNewLayout(); - nodeType_ = node.getNodeType(); - measureUsesContext_ = node.measureUsesContext_; - baselineUsesContext_ = node.baselineUsesContext_; - printUsesContext_ = node.printUsesContext_; - measure_ = node.measure_; - baseline_ = node.baseline_; - print_ = node.print_; - dirtied_ = node.getDirtied(); - style_ = node.style_; - layout_ = node.layout_; - lineIndex_ = node.getLineIndex(); - owner_ = node.getOwner(); - children_ = node.getChildren(); - config_ = node.getConfig(); - isDirty_ = node.isDirty(); - resolvedDimensions_ = node.getResolvedDimensions(); - - return *this; -} - YGValue YGNode::marginLeadingValue(const YGFlexDirection axis) const { if (YGFlexDirectionIsRow(axis) && !style_.margin[YGEdgeStart].isUndefined()) { return style_.margin[YGEdgeStart]; @@ -571,3 +566,22 @@ bool YGNode::isLayoutTreeEqualToNode(const YGNode& node) const { } return isLayoutTreeEqual; } + +void YGNode::reset() { + YGAssertWithNode( + this, + children_.size() == 0, + "Cannot reset a node which still has children attached"); + YGAssertWithNode( + this, owner_ == nullptr, "Cannot reset a node still attached to a owner"); + + clearChildren(); + + auto config = getConfig(); + *this = YGNode{}; + if (config->useWebDefaults) { + setStyleFlexDirection(YGFlexDirectionRow); + setStyleAlignContent(YGAlignStretch); + } + setConfig(config); +} diff --git a/ReactCommon/yoga/yoga/YGNode.h b/ReactCommon/yoga/yoga/YGNode.h index cf2367284a9981..b5ff98a5c02116 100644 --- a/ReactCommon/yoga/yoga/YGNode.h +++ b/ReactCommon/yoga/yoga/YGNode.h @@ -55,6 +55,13 @@ struct YGNode { void setMeasureFunc(decltype(measure_)); void setBaselineFunc(decltype(baseline_)); + // DANGER DANGER DANGER! + // If the the node assigned to has children, we'd either have to deallocate + // them (potentially incorrect) or ignore them (danger of leaks). Only ever + // use this after checking that there are no children. + // DO NOT CHANGE THE VISIBILITY OF THIS METHOD! + YGNode& operator=(YGNode&&) = default; + public: YGNode() : hasNewLayout_{true}, @@ -66,8 +73,16 @@ struct YGNode { printUsesContext_{false} {} ~YGNode() = default; // cleanup of owner/children relationships in YGNodeFree explicit YGNode(const YGConfigRef newConfig) : config_(newConfig){}; + + YGNode(YGNode&&); + + // Does not expose true value semantics, as children are not cloned eagerly. + // Should we remove this? YGNode(const YGNode& node) = default; - YGNode& operator=(const YGNode& node); + + // assignment means potential leaks of existing children, or alternatively + // freeing unowned memory, double free, or freeing stack memory. + YGNode& operator=(const YGNode&) = delete; // Getters void* getContext() const { @@ -342,4 +357,5 @@ struct YGNode { bool isNodeFlexible(); bool didUseLegacyFlag(); bool isLayoutTreeEqualToNode(const YGNode& node) const; + void reset(); }; diff --git a/ReactCommon/yoga/yoga/Yoga.cpp b/ReactCommon/yoga/yoga/Yoga.cpp index bbdf724e9b6eed..94d9a602a3fdc4 100644 --- a/ReactCommon/yoga/yoga/Yoga.cpp +++ b/ReactCommon/yoga/yoga/Yoga.cpp @@ -28,6 +28,15 @@ __forceinline const float fmaxf(const float a, const float b) { using namespace facebook::yoga; using detail::Log; +namespace { +size_t usedMeasureCacheEntries = YG_MAX_CACHED_RESULT_COUNT; +} + +void YGSetUsedCachedEntries(size_t n) { + usedMeasureCacheEntries = + n == 0 || n > YG_MAX_CACHED_RESULT_COUNT ? YG_MAX_CACHED_RESULT_COUNT : n; +} + #ifdef ANDROID static int YGAndroidLog( const YGConfigRef config, @@ -323,25 +332,8 @@ void YGNodeFreeRecursive(const YGNodeRef root) { return YGNodeFreeRecursiveWithCleanupFunc(root, nullptr); } -void YGNodeReset(const YGNodeRef node) { - YGAssertWithNode( - node, - YGNodeGetChildCount(node) == 0, - "Cannot reset a node which still has children attached"); - YGAssertWithNode( - node, - node->getOwner() == nullptr, - "Cannot reset a node still attached to a owner"); - - node->clearChildren(); - - const YGConfigRef config = node->getConfig(); - *node = YGNode(); - if (config->useWebDefaults) { - node->setStyleFlexDirection(YGFlexDirectionRow); - node->setStyleAlignContent(YGAlignStretch); - } - node->setConfig(config); +void YGNodeReset(YGNodeRef node) { + node->reset(); } int32_t YGNodeGetInstanceCount(void) { @@ -3888,7 +3880,7 @@ bool YGLayoutNodeInternal( layoutMarkerData.maxMeasureCache = layout->nextCachedMeasurementsIndex + 1; } - if (layout->nextCachedMeasurementsIndex == YG_MAX_CACHED_RESULT_COUNT) { + if (layout->nextCachedMeasurementsIndex == usedMeasureCacheEntries) { if (gPrintChanges) { Log::log(node, YGLogLevelVerbose, nullptr, "Out of cache entries!\n"); } diff --git a/ReactCommon/yoga/yoga/Yoga.h b/ReactCommon/yoga/yoga/Yoga.h index 753a2f9f83266e..7132a934f4ee48 100644 --- a/ReactCommon/yoga/yoga/Yoga.h +++ b/ReactCommon/yoga/yoga/Yoga.h @@ -411,6 +411,8 @@ WIN_EXPORT float YGRoundValueToPixelGrid( const bool forceCeil, const bool forceFloor); +void YGSetUsedCachedEntries(size_t); + YG_EXTERN_C_END #ifdef __cplusplus diff --git a/bots/dangerfile.js b/bots/dangerfile.js index 8ced0c3b39e134..20fa98bfbe76b0 100644 --- a/bots/dangerfile.js +++ b/bots/dangerfile.js @@ -64,7 +64,7 @@ const correctlyFormattedChangelog = changelogRegex.test(danger.github.pr.body); // Provides advice if a changelog is missing const changelogInstructions = - 'A changelog entry has the following format: [`[CATEGORY] [TYPE] - Message`](http://facebook.github.io/react-native/docs/contributing#changelog).'; + 'A changelog entry has the following format: `[CATEGORY] [TYPE] - Message`.\n\n

CATEGORY may be:\n\n- General\n- iOS\n- Android\n\nTYPE may be:\n\n- Added, for new features.\n- Changed, for changes in existing functionality.\n- Deprecated, for soon-to-be removed features.\n- Removed, for now removed features.\n- Fixed, for any bug fixes.\n- Security, in case of vulnerabilities.\n\nMESSAGE may answer "what and why" on a feature level. Use this to briefly tell React Native users about notable changes.
'; if (!includesChangelog) { const title = ':clipboard: Missing Changelog'; const idea = diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000000000..3ed6cd49c14fb6 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,90 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +'use strict'; + +module.exports = { + 'transform': { + '^.+\\.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp)$': '/jest/assetFileTransformer.js', + '.*': './jest/preprocessor.js', + }, + 'setupFiles': [ + './jest/setup.js', + ], + 'timers': 'fake', + 'moduleNameMapper': { + '^React$': '/Libraries/react-native/React.js', + }, + 'testRegex': '/__tests__/.*-test\\.js$', + 'testPathIgnorePatterns': [ + '/node_modules/', + '/template', + 'Libraries/Renderer', + 'RNTester/e2e', + ], + 'haste': { + 'defaultPlatform': 'ios', + 'hasteImplModulePath': '/jest/hasteImpl.js', + 'providesModuleNodeModules': [ + 'react-native', + ], + 'platforms': [ + 'ios', + 'android', + ], + }, + 'modulePathIgnorePatterns': [ + '/node_modules/(?!react|fbjs|react-native|react-transform-hmr|core-js|promise)/', + 'node_modules/react/node_modules/fbjs/', + 'node_modules/react/lib/ReactDOM.js', + 'node_modules/fbjs/lib/Map.js', + 'node_modules/fbjs/lib/Promise.js', + 'node_modules/fbjs/lib/fetch.js', + 'node_modules/fbjs/lib/ErrorUtils.js', + 'node_modules/fbjs/lib/URI.js', + 'node_modules/fbjs/lib/Deferred.js', + 'node_modules/fbjs/lib/PromiseMap.js', + 'node_modules/fbjs/lib/UserAgent.js', + 'node_modules/fbjs/lib/areEqual.js', + 'node_modules/fbjs/lib/base62.js', + 'node_modules/fbjs/lib/crc32.js', + 'node_modules/fbjs/lib/everyObject.js', + 'node_modules/fbjs/lib/fetchWithRetries.js', + 'node_modules/fbjs/lib/filterObject.js', + 'node_modules/fbjs/lib/flattenArray.js', + 'node_modules/fbjs/lib/forEachObject.js', + 'node_modules/fbjs/lib/isEmpty.js', + 'node_modules/fbjs/lib/removeFromArray.js', + 'node_modules/fbjs/lib/resolveImmediate.js', + 'node_modules/fbjs/lib/someObject.js', + 'node_modules/fbjs/lib/sprintf.js', + 'node_modules/fbjs/lib/xhrSimpleDataSerializer.js', + 'node_modules/jest-cli', + 'node_modules/react/dist', + 'node_modules/fbjs/.*/__mocks__/', + 'node_modules/fbjs/node_modules/', + ], + 'unmockedModulePathPatterns': [ + 'node_modules/react/', + 'Libraries/Renderer', + 'promise', + 'source-map', + 'fastpath', + 'denodeify', + 'fbjs', + ], + 'testEnvironment': 'node', + 'collectCoverageFrom': [ + 'Libraries/**/*.js', + ], + 'coveragePathIgnorePatterns': [ + '/__tests__/', + '/vendor/', + '/Libraries/react-native/', + ], +}; diff --git a/jest/setup.js b/jest/setup.js index 9a5291a88f87e3..4bc9a7ad04954b 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -43,28 +43,6 @@ jest .mock('RefreshControl', () => jest.requireMock('RefreshControlMock')) .mock('ScrollView', () => jest.requireMock('ScrollViewMock')) .mock('ActivityIndicator', () => mockComponent('ActivityIndicator')) - .mock('ListView', () => jest.requireMock('ListViewMock')) - .mock('ListViewDataSource', () => { - const DataSource = jest.requireActual('ListViewDataSource'); - DataSource.prototype.toJSON = function() { - function ListViewDataSource(dataBlob) { - this.items = 0; - // Ensure this doesn't throw. - try { - Object.keys(dataBlob).forEach(key => { - this.items += - dataBlob[key] && - (dataBlob[key].length || dataBlob[key].size || 0); - }); - } catch (e) { - this.items = 'unknown'; - } - } - - return new ListViewDataSource(this._dataBlob); - }; - return DataSource; - }) .mock('AnimatedImplementation', () => { const AnimatedImplementation = jest.requireActual('AnimatedImplementation'); const oldCreate = AnimatedImplementation.createAnimatedComponent; diff --git a/package.json b/package.json index 88a2ed42cf512a..be67df5eef1b7b 100644 --- a/package.json +++ b/package.json @@ -18,86 +18,6 @@ "jsxBracketSameLine": true, "parser": "flow" }, - "jest": { - "transform": { - "^.+\\.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp)$": "/jest/assetFileTransformer.js", - ".*": "./jest/preprocessor.js" - }, - "setupFiles": [ - "./jest/setup.js" - ], - "timers": "fake", - "moduleNameMapper": { - "^React$": "/Libraries/react-native/React.js" - }, - "testRegex": "/__tests__/.*-test\\.js$", - "testPathIgnorePatterns": [ - "/node_modules/", - "/template", - "Libraries/Renderer", - "RNTester/e2e" - ], - "haste": { - "defaultPlatform": "ios", - "hasteImplModulePath": "/jest/hasteImpl.js", - "providesModuleNodeModules": [ - "react-native" - ], - "platforms": [ - "ios", - "android" - ] - }, - "modulePathIgnorePatterns": [ - "/node_modules/(?!react|fbjs|react-native|react-transform-hmr|core-js|promise)/", - "node_modules/react/node_modules/fbjs/", - "node_modules/react/lib/ReactDOM.js", - "node_modules/fbjs/lib/Map.js", - "node_modules/fbjs/lib/Promise.js", - "node_modules/fbjs/lib/fetch.js", - "node_modules/fbjs/lib/ErrorUtils.js", - "node_modules/fbjs/lib/URI.js", - "node_modules/fbjs/lib/Deferred.js", - "node_modules/fbjs/lib/PromiseMap.js", - "node_modules/fbjs/lib/UserAgent.js", - "node_modules/fbjs/lib/areEqual.js", - "node_modules/fbjs/lib/base62.js", - "node_modules/fbjs/lib/crc32.js", - "node_modules/fbjs/lib/everyObject.js", - "node_modules/fbjs/lib/fetchWithRetries.js", - "node_modules/fbjs/lib/filterObject.js", - "node_modules/fbjs/lib/flattenArray.js", - "node_modules/fbjs/lib/forEachObject.js", - "node_modules/fbjs/lib/isEmpty.js", - "node_modules/fbjs/lib/removeFromArray.js", - "node_modules/fbjs/lib/resolveImmediate.js", - "node_modules/fbjs/lib/someObject.js", - "node_modules/fbjs/lib/sprintf.js", - "node_modules/fbjs/lib/xhrSimpleDataSerializer.js", - "node_modules/jest-cli", - "node_modules/react/dist", - "node_modules/fbjs/.*/__mocks__/", - "node_modules/fbjs/node_modules/" - ], - "unmockedModulePathPatterns": [ - "node_modules/react/", - "Libraries/Renderer", - "promise", - "source-map", - "fastpath", - "denodeify", - "fbjs" - ], - "testEnvironment": "node", - "collectCoverageFrom": [ - "Libraries/**/*.js" - ], - "coveragePathIgnorePatterns": [ - "/__tests__/", - "/vendor/", - "/Libraries/react-native/" - ] - }, "jest-junit": { "outputDirectory": "reports/junit", "outputName": "js-test-results.xml" @@ -145,10 +65,10 @@ "prettier": "prettier --write \"./**/*.js\" \"./**/*.md\"", "format-check": "prettier --list-different \"./**/*.js\" \"./**/*.md\"", "docker-setup-android": "docker pull reactnativecommunity/react-native-android", - "docker-build-android": "docker build -t reactnativeci/android -f ContainerShip/Dockerfile.android .", - "test-android-run-instrumentation": "docker run --cap-add=SYS_ADMIN -it reactnativeci/android bash ContainerShip/scripts/run-android-docker-instrumentation-tests.sh", - "test-android-run-unit": "docker run --cap-add=SYS_ADMIN -it reactnativeci/android bash ContainerShip/scripts/run-android-docker-unit-tests.sh", - "test-android-run-e2e": "docker run --privileged -it reactnativeci/android bash ContainerShip/scripts/run-ci-e2e-tests.sh --android --js", + "docker-build-android": "docker build -t reactnativeci/android -f .circleci/Dockerfiles/Dockerfile.android .", + "test-android-run-instrumentation": "docker run --cap-add=SYS_ADMIN -it reactnativeci/android bash .circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh", + "test-android-run-unit": "docker run --cap-add=SYS_ADMIN -it reactnativeci/android bash .circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh", + "test-android-run-e2e": "docker run --privileged -it reactnativeci/android bash .circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh --android --js", "test-android-all": "yarn run docker-build-android && yarn run test-android-run-unit && yarn run test-android-run-instrumentation && yarn run test-android-run-e2e", "test-android-instrumentation": "yarn run docker-build-android && yarn run test-android-run-instrumentation", "test-android-unit": "yarn run docker-build-android && yarn run test-android-run-unit", @@ -157,7 +77,7 @@ "test-ios-e2e": "detox test -c ios.sim.release" }, "peerDependencies": { - "react": "16.8.1" + "react": "16.8.3" }, "dependencies": { "@babel/runtime": "^7.0.0", @@ -169,7 +89,7 @@ "escape-string-regexp": "^1.0.5", "event-target-shim": "^1.0.5", "fbjs": "^1.0.0", - "fbjs-scripts": "^1.0.0", + "fbjs-scripts": "^1.1.0", "invariant": "^2.2.4", "metro-babel-register": "0.52.0", "metro-react-native-babel-transformer": "0.52.0", @@ -196,21 +116,24 @@ "eslint": "5.1.0", "eslint-config-fb-strict": "22.1.0", "eslint-config-fbjs": "2.0.1", + "eslint-plugin-babel": "5.1.0", "eslint-plugin-eslint-comments": "^3.0.1", "eslint-plugin-flowtype": "2.43.0", "eslint-plugin-jest": "21.8.0", + "eslint-plugin-jsx-a11y": "6.0.3", "eslint-plugin-prettier": "2.6.0", "eslint-plugin-react": "7.8.2", "eslint-plugin-react-hooks": "^1.0.1", "eslint-plugin-react-native": "3.5.0", + "eslint-plugin-relay": "0.0.28", "flow-bin": "^0.93.0", "jest": "24.1.0", "jest-junit": "6.3.0", "jscodeshift": "^0.6.2", "prettier": "1.13.6", - "react": "16.8.1", + "react": "16.8.3", "react-native-dummy": "0.2.0", - "react-test-renderer": "16.8.1", + "react-test-renderer": "16.8.3", "shelljs": "^0.7.8", "ws": "^6.1.4" }, diff --git a/packages/eslint-config-react-native-community/README.md b/packages/eslint-config-react-native-community/README.md new file mode 100644 index 00000000000000..20423bd14bc2a0 --- /dev/null +++ b/packages/eslint-config-react-native-community/README.md @@ -0,0 +1,19 @@ +# eslint-config-react-native-community + +## Installation + +``` +yarn add --dev eslint @react-native-community/eslint-config +``` + +*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* + +## Usage + +Add to your eslint config (`.eslintrc`, or `eslintConfig` field in `package.json`): + +```json +{ + "extends": "@react-native-community" +} +``` diff --git a/packages/eslint-config-react-native-community/index.js b/packages/eslint-config-react-native-community/index.js new file mode 100644 index 00000000000000..8a188681e5f990 --- /dev/null +++ b/packages/eslint-config-react-native-community/index.js @@ -0,0 +1,270 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +module.exports = { + parser: 'babel-eslint', + + env: { + es6: true, + }, + + plugins: [ + 'eslint-comments', + 'flowtype', + 'prettier', + 'react', + 'react-hooks', + 'react-native', + 'jest', + ], + + // Map from global var to bool specifying if it can be redefined + globals: { + __DEV__: true, + __dirname: false, + __fbBatchedBridgeConfig: false, + alert: false, + cancelAnimationFrame: false, + cancelIdleCallback: false, + clearImmediate: true, + clearInterval: false, + clearTimeout: false, + console: false, + document: false, + escape: false, + Event: false, + EventTarget: false, + exports: false, + fetch: false, + FormData: false, + global: false, + Map: true, + module: false, + navigator: false, + process: false, + Promise: true, + requestAnimationFrame: true, + requestIdleCallback: true, + require: false, + Set: true, + setImmediate: true, + setInterval: false, + setTimeout: false, + window: false, + XMLHttpRequest: false, + }, + + rules: { + // General + 'comma-dangle': [1, 'always-multiline'], // allow or disallow trailing commas + 'no-cond-assign': 1, // disallow assignment in conditional expressions + 'no-console': 0, // disallow use of console (off by default in the node environment) + 'no-const-assign': 2, // disallow assignment to const-declared variables + 'no-constant-condition': 0, // disallow use of constant expressions in conditions + 'no-control-regex': 1, // disallow control characters in regular expressions + 'no-debugger': 1, // disallow use of debugger + 'no-dupe-class-members': 2, // Disallow duplicate name in class members + 'no-dupe-keys': 2, // disallow duplicate keys when creating object literals + 'no-empty': 0, // disallow empty statements + 'no-ex-assign': 1, // disallow assigning to the exception in a catch block + 'no-extra-boolean-cast': 1, // disallow double-negation boolean casts in a boolean context + 'no-extra-parens': 0, // disallow unnecessary parentheses (off by default) + 'no-extra-semi': 1, // disallow unnecessary semicolons + 'no-func-assign': 1, // disallow overwriting functions written as function declarations + 'no-inner-declarations': 0, // disallow function or variable declarations in nested blocks + 'no-invalid-regexp': 1, // disallow invalid regular expression strings in the RegExp constructor + 'no-negated-in-lhs': 1, // disallow negation of the left operand of an in expression + 'no-obj-calls': 1, // disallow the use of object properties of the global object (Math and JSON) as functions + 'no-regex-spaces': 1, // disallow multiple spaces in a regular expression literal + 'no-reserved-keys': 0, // disallow reserved words being used as object literal keys (off by default) + 'no-sparse-arrays': 1, // disallow sparse arrays + 'no-unreachable': 2, // disallow unreachable statements after a return, throw, continue, or break statement + 'use-isnan': 1, // disallow comparisons with the value NaN + 'valid-jsdoc': 0, // Ensure JSDoc comments are valid (off by default) + 'valid-typeof': 1, // Ensure that the results of typeof are compared against a valid string + + // Best Practices + // These are rules designed to prevent you from making mistakes. They either prescribe a better way of doing something or help you avoid footguns. + + 'block-scoped-var': 0, // treat var statements as if they were block scoped (off by default) + complexity: 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) + 'consistent-return': 0, // require return statements to either always or never specify values + curly: 1, // specify curly brace conventions for all control statements + 'default-case': 0, // require default case in switch statements (off by default) + 'dot-notation': 1, // encourages use of dot notation whenever possible + eqeqeq: [1, 'allow-null'], // require the use of === and !== + 'guard-for-in': 0, // make sure for-in loops have an if statement (off by default) + 'no-alert': 1, // disallow the use of alert, confirm, and prompt + 'no-caller': 1, // disallow use of arguments.caller or arguments.callee + 'no-div-regex': 1, // disallow division operators explicitly at beginning of regular expression (off by default) + 'no-else-return': 0, // disallow else after a return in an if (off by default) + 'no-eq-null': 0, // disallow comparisons to null without a type-checking operator (off by default) + 'no-eval': 2, // disallow use of eval() + 'no-extend-native': 1, // disallow adding to native types + 'no-extra-bind': 1, // disallow unnecessary function binding + 'no-fallthrough': 1, // disallow fallthrough of case statements + 'no-floating-decimal': 1, // disallow the use of leading or trailing decimal points in numeric literals (off by default) + 'no-implied-eval': 1, // disallow use of eval()-like methods + 'no-labels': 1, // disallow use of labeled statements + 'no-iterator': 1, // disallow usage of __iterator__ property + 'no-lone-blocks': 1, // disallow unnecessary nested blocks + 'no-loop-func': 0, // disallow creation of functions within loops + 'no-multi-str': 0, // disallow use of multiline strings + 'no-native-reassign': 0, // disallow reassignments of native objects + 'no-new': 1, // disallow use of new operator when not part of the assignment or comparison + 'no-new-func': 2, // disallow use of new operator for Function object + 'no-new-wrappers': 1, // disallows creating new instances of String,Number, and Boolean + 'no-octal': 1, // disallow use of octal literals + 'no-octal-escape': 1, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; + 'no-proto': 1, // disallow usage of __proto__ property + 'no-redeclare': 0, // disallow declaring the same variable more then once + 'no-return-assign': 1, // disallow use of assignment in return statement + 'no-script-url': 1, // disallow use of javascript: urls. + 'no-self-compare': 1, // disallow comparisons where both sides are exactly the same (off by default) + 'no-sequences': 1, // disallow use of comma operator + 'no-unused-expressions': 0, // disallow usage of expressions in statement position + 'no-void': 1, // disallow use of void operator (off by default) + 'no-warning-comments': 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default) + 'no-with': 1, // disallow use of the with statement + radix: 1, // require use of the second argument for parseInt() (off by default) + 'semi-spacing': 1, // require a space after a semi-colon + 'vars-on-top': 0, // requires to declare all vars on top of their containing scope (off by default) + 'wrap-iife': 0, // require immediate function invocation to be wrapped in parentheses (off by default) + yoda: 1, // require or disallow Yoda conditions + + // Variables + // These rules have to do with variable declarations. + + 'no-catch-shadow': 1, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) + 'no-delete-var': 1, // disallow deletion of variables + 'no-label-var': 1, // disallow labels that share a name with a variable + 'no-shadow': 1, // disallow declaration of variables already declared in the outer scope + 'no-shadow-restricted-names': 1, // disallow shadowing of names such as arguments + 'no-undef': 2, // disallow use of undeclared variables unless mentioned in a /*global */ block + 'no-undefined': 0, // disallow use of undefined variable (off by default) + 'no-undef-init': 1, // disallow use of undefined when initializing variables + 'no-unused-vars': [ + 1, + {vars: 'all', args: 'none', ignoreRestSiblings: true}, + ], // disallow declaration of variables that are not used in the code + 'no-use-before-define': 0, // disallow use of variables before they are defined + + // Node.js + // These rules are specific to JavaScript running on Node.js. + + 'handle-callback-err': 1, // enforces error handling in callbacks (off by default) (on by default in the node environment) + 'no-mixed-requires': 1, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment) + 'no-new-require': 1, // disallow use of new operator with the require function (off by default) (on by default in the node environment) + 'no-path-concat': 1, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment) + 'no-process-exit': 0, // disallow process.exit() (on by default in the node environment) + 'no-restricted-modules': 1, // restrict usage of specified node modules (off by default) + 'no-sync': 0, // disallow use of synchronous methods (off by default) + + // ESLint Comments Plugin + // The following rules are made available via `eslint-plugin-eslint-comments` + 'eslint-comments/no-aggregating-enable': 1, // disallows eslint-enable comments for multiple eslint-disable comments + 'eslint-comments/no-unlimited-disable': 1, // disallows eslint-disable comments without rule names + 'eslint-comments/no-unused-disable': 1, // disallow disables that don't cover any errors + 'eslint-comments/no-unused-enable': 1, // // disallow enables that don't enable anything or enable rules that weren't disabled + + // Flow Plugin + // The following rules are made available via `eslint-plugin-flowtype` + 'flowtype/define-flow-type': 1, + 'flowtype/use-flow-type': 1, + + // Prettier Plugin + // https://github.com/prettier/eslint-plugin-prettier + 'prettier/prettier': [2, 'fb', '@format'], + + // Stylistic Issues + // These rules are purely matters of style and are quite subjective. + + 'key-spacing': 0, + 'keyword-spacing': 1, // enforce spacing before and after keywords + 'jsx-quotes': [1, 'prefer-double'], // enforces the usage of double quotes for all JSX attribute values which doesn’t contain a double quote + 'comma-spacing': 0, + 'no-multi-spaces': 0, + 'brace-style': 0, // enforce one true brace style (off by default) + camelcase: 0, // require camel case names + 'consistent-this': 1, // enforces consistent naming when capturing the current execution context (off by default) + 'eol-last': 1, // enforce newline at the end of file, with no multiple empty lines + 'func-names': 0, // require function expressions to have a name (off by default) + 'func-style': 0, // enforces use of function declarations or expressions (off by default) + 'new-cap': 0, // require a capital letter for constructors + 'new-parens': 1, // disallow the omission of parentheses when invoking a constructor with no arguments + 'no-nested-ternary': 0, // disallow nested ternary expressions (off by default) + 'no-array-constructor': 1, // disallow use of the Array constructor + 'no-empty-character-class': 1, // disallow the use of empty character classes in regular expressions + 'no-lonely-if': 0, // disallow if as the only statement in an else block (off by default) + 'no-new-object': 1, // disallow use of the Object constructor + 'no-spaced-func': 1, // disallow space between function identifier and application + 'no-ternary': 0, // disallow the use of ternary operators (off by default) + 'no-trailing-spaces': 1, // disallow trailing whitespace at the end of lines + 'no-underscore-dangle': 0, // disallow dangling underscores in identifiers + 'no-mixed-spaces-and-tabs': 1, // disallow mixed spaces and tabs for indentation + quotes: [1, 'single', 'avoid-escape'], // specify whether double or single quotes should be used + 'quote-props': 0, // require quotes around object literal property names (off by default) + semi: 1, // require or disallow use of semicolons instead of ASI + 'sort-vars': 0, // sort variables within the same declaration block (off by default) + 'space-in-brackets': 0, // require or disallow spaces inside brackets (off by default) + 'space-in-parens': 0, // require or disallow spaces inside parentheses (off by default) + 'space-infix-ops': 1, // require spaces around operators + 'space-unary-ops': [1, {words: true, nonwords: false}], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default) + 'max-nested-callbacks': 0, // specify the maximum depth callbacks can be nested (off by default) + 'one-var': 0, // allow just one var statement per function (off by default) + 'wrap-regex': 0, // require regex literals to be wrapped in parentheses (off by default) + + // Legacy + // The following rules are included for compatibility with JSHint and JSLint. While the names of the rules may not match up with the JSHint/JSLint counterpart, the functionality is the same. + + 'max-depth': 0, // specify the maximum depth that blocks can be nested (off by default) + 'max-len': 0, // specify the maximum length of a line in your program (off by default) + 'max-params': 0, // limits the number of parameters that can be used in the function declaration. (off by default) + 'max-statements': 0, // specify the maximum number of statement allowed in a function (off by default) + 'no-bitwise': 1, // disallow use of bitwise operators (off by default) + 'no-plusplus': 0, // disallow use of unary operators, ++ and -- (off by default) + + // React Plugin + // The following rules are made available via `eslint-plugin-react`. + + 'react/display-name': 0, + 'react/jsx-boolean-value': 0, + 'react/jsx-no-comment-textnodes': 1, + 'react/jsx-no-duplicate-props': 2, + 'react/jsx-no-undef': 2, + 'react/jsx-sort-props': 0, + 'react/jsx-uses-react': 1, + 'react/jsx-uses-vars': 1, + 'react/no-did-mount-set-state': 1, + 'react/no-did-update-set-state': 1, + 'react/no-multi-comp': 0, + 'react/no-string-refs': 1, + 'react/no-unknown-property': 0, + 'react/prop-types': 0, + 'react/react-in-jsx-scope': 1, + 'react/self-closing-comp': 1, + 'react/wrap-multilines': 0, + + // React-Hooks Plugin + // The following rules are made available via `eslint-plugin-react-hooks` + 'react-hooks/rules-of-hooks': 'error', + + // React-Native Plugin + // The following rules are made available via `eslint-plugin-react-native` + + 'react-native/no-inline-styles': 1, + + // Jest Plugin + // The following rules are made available via `eslint-plugin-jest`. + 'jest/no-disabled-tests': 1, + 'jest/no-focused-tests': 1, + 'jest/no-identical-title': 1, + 'jest/valid-expect': 1, + }, +}; diff --git a/packages/eslint-config-react-native-community/package.json b/packages/eslint-config-react-native-community/package.json new file mode 100644 index 00000000000000..1e22001212e426 --- /dev/null +++ b/packages/eslint-config-react-native-community/package.json @@ -0,0 +1,25 @@ +{ + "name": "@react-native-community/eslint-config", + "version": "0.0.0", + "description": "ESLint config for React Native", + "main": "index.js", + "repository": { + "type": "git", + "url": "git@github.com:facebook/react-native.git" + }, + "peerDependencies": { + "eslint": ">=5" + }, + "devDependencies": { + "babel-eslint": "9.0.0", + "eslint": "5.1.0", + "eslint-plugin-eslint-comments": "^3.0.1", + "eslint-plugin-flowtype": "2.43.0", + "eslint-plugin-jest": "21.8.0", + "eslint-plugin-prettier": "2.6.0", + "eslint-plugin-react": "7.8.2", + "eslint-plugin-react-hooks": "^1.0.1", + "eslint-plugin-react-native": "3.5.0", + "prettier": "1.13.6" + } +} diff --git a/packages/eslint-config-react-native-community/yarn.lock b/packages/eslint-config-react-native-community/yarn.lock new file mode 100644 index 00000000000000..a864efd86b9de8 --- /dev/null +++ b/packages/eslint-config-react-native-community/yarn.lock @@ -0,0 +1,1144 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/generator@^7.2.2": + version "7.3.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.3.tgz#185962ade59a52e00ca2bdfcfd1d58e528d4e39e" + integrity sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A== + dependencies: + "@babel/types" "^7.3.3" + jsesc "^2.5.1" + lodash "^4.17.11" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" + integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3": + version "7.3.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.3.tgz#092d450db02bdb6ccb1ca8ffd47d8774a91aef87" + integrity sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg== + +"@babel/template@^7.1.0": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" + integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.2.2" + "@babel/types" "^7.2.2" + +"@babel/traverse@^7.0.0": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" + integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.2.2" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/parser" "^7.2.3" + "@babel/types" "^7.2.2" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.10" + +"@babel/types@^7.0.0", "@babel/types@^7.2.2", "@babel/types@^7.3.3": + version "7.3.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.3.tgz#6c44d1cdac2a7625b624216657d5bc6c107ab436" + integrity sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ== + dependencies: + esutils "^2.0.2" + lodash "^4.17.11" + to-fast-properties "^2.0.0" + +acorn-jsx@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" + integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== + +acorn@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.0.tgz#b0a3be31752c97a0f7013c5f4903b71a05db6818" + integrity sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw== + +ajv-keywords@^3.0.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d" + integrity sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw== + +ajv@^6.0.1, ajv@^6.5.0: + version "6.9.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.2.tgz#4927adb83e7f48e5a32b45729744c71ec39c9c7b" + integrity sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-eslint@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-9.0.0.tgz#7d9445f81ed9f60aff38115f838970df9f2b6220" + integrity sha512-itv1MwE3TMbY0QtNfeL7wzak1mV47Uy+n6HtSOO4Xd7rvmO+tsGQSgyOEEgo6Y2vHZKZphaoelNeSVj4vkLA1g== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + eslint-scope "3.7.1" + eslint-visitor-keys "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + 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@^2.0.0, chalk@^2.1.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +doctrine@^2.0.2, doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +es-abstract@^1.10.0, es-abstract@^1.7.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-plugin-eslint-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.1.tgz#32ff0afba8a48e17073817e6d03fbc5622f735b7" + integrity sha512-GZDKhOFqJLKlaABX+kdoLskcTINMrVOWxGca54KcFb1QCPd0CLmqgAMRxkkUfGSmN+5NJUMGh7NGccIMcWPSfQ== + dependencies: + escape-string-regexp "^1.0.5" + ignore "^5.0.5" + +eslint-plugin-flowtype@2.43.0: + version "2.43.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.43.0.tgz#47cdac5f01cda53f1c3e8477f0c83fee66a1606e" + integrity sha512-gFlHd3b6L28Mm8nbNoK6YtWgJ6STngf8GY3RA5DfeUYU3mRYJcHZ4c70bsBoERnCZy+nGHyus+aJ4jkimSnu5g== + dependencies: + lodash "^4.15.0" + +eslint-plugin-jest@21.8.0: + version "21.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.8.0.tgz#4f3155e2898c1efb0ce548f3e084e07db5e21c07" + integrity sha512-u8+tOrVHHAv9SetdzCghLaBrHyaBLx+6surC+A+VfLy7CfXixTvQnJmD/Ym8mDE6e7wPDZWWgsqb3FGAWh7NTw== + +eslint-plugin-prettier@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz#33e4e228bdb06142d03c560ce04ec23f6c767dd7" + integrity sha512-floiaI4F7hRkTrFe8V2ItOK97QYrX75DjmdzmVITZoAP6Cn06oEDPQRsO6MlHEP/u2SxI3xQ52Kpjw6j5WGfeQ== + dependencies: + fast-diff "^1.1.1" + jest-docblock "^21.0.0" + +eslint-plugin-react-hooks@^1.0.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.3.0.tgz#d73f1a61d8dd6a14f15159ab1c6e8e3aeabb6da8" + integrity sha512-hjgyNq0sfDXaLkXHkmo3vRh+p+42lwQIU3r56hVoomMYRMToJ2D/PGhwL2EPyB5ZEMlbLsRm3s5v4gm5FIjlvg== + +eslint-plugin-react-native-globals@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2" + integrity sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g== + +eslint-plugin-react-native@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-3.5.0.tgz#78a5d6aadb11f85d1b114488d1fddfa2bf4fc1fb" + integrity sha512-L0qwiBQbG3MVMQk4XVCyyX+9nqJjr9YUmqpJ98Gb+uAmD+xlxT33rDL9ZBQgzMzBxDcUW6WiPcll8j9uXpyLyQ== + dependencies: + eslint-plugin-react-native-globals "^0.1.1" + +eslint-plugin-react@7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.8.2.tgz#e95c9c47fece55d2303d1a67c9d01b930b88a51d" + integrity sha512-H3ne8ob4Bn6NXSN9N9twsn7t8dyHT5bF/ibQepxIHi6JiPIdC2gXlfYvZYucbdrWio4FxBq7Z4mSauQP+qmMkQ== + dependencies: + doctrine "^2.0.2" + has "^1.0.1" + jsx-ast-utils "^2.0.1" + prop-types "^15.6.0" + +eslint-scope@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" + integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" + integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== + +eslint-visitor-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" + integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== + +eslint@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.1.0.tgz#2ed611f1ce163c0fb99e1e0cda5af8f662dff645" + integrity sha512-DyH6JsoA1KzA5+OSWFjg56DFJT+sDLO0yokaPZ9qY0UEmYrPA1gEX/G1MnVkmRDsksG4H1foIVz2ZXXM3hHYvw== + dependencies: + ajv "^6.5.0" + babel-code-frame "^6.26.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^3.1.0" + doctrine "^2.1.0" + eslint-scope "^4.0.0" + eslint-utils "^1.3.1" + eslint-visitor-keys "^1.0.0" + espree "^4.0.0" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.7.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^5.2.0" + is-resolvable "^1.1.0" + js-yaml "^3.11.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.5" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + regexpp "^1.1.0" + require-uncached "^1.0.3" + semver "^5.5.0" + string.prototype.matchall "^2.0.0" + strip-ansi "^4.0.0" + strip-json-comments "^2.0.1" + table "^4.0.3" + text-table "^0.2.0" + +espree@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f" + integrity sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w== + dependencies: + acorn "^6.0.2" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +external-editor@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-diff@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +flat-cache@^1.2.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" + integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== + dependencies: + circular-json "^0.3.1" + graceful-fs "^4.1.2" + rimraf "~2.6.2" + write "^0.2.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob@^7.1.2, glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0, globals@^11.7.0: + version "11.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" + integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw== + +graceful-fs@^4.1.2: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +iconv-lite@^0.4.17: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^3.3.3: + version "3.3.10" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" + integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== + +ignore@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.5.tgz#c663c548d6ce186fb33616a8ccb5d46e56bdbbf9" + integrity sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inquirer@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.2.0.tgz#db350c2b73daca77ff1243962e9f22f099685726" + integrity sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ== + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.1.0" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^5.5.2" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" + +is-resolvable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +jest-docblock@^21.0.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" + integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + +js-yaml@^3.11.0: + version "3.12.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" + integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +jsx-ast-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" + integrity sha1-6AGxs5mF4g//yHtA43SAgOLcrH8= + dependencies: + array-includes "^3.0.3" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-keys@^1.0.12: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" + integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@1.13.6: + version "1.13.6" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.6.tgz#00ae0b777ad92f81a9e7a1df2f0470b6dab0cb44" + integrity sha512-p5eqCNiohWZN++7aJXUVj0JgLqHCPLf9GLIcLBHGNWs4Y9FJOPs6+KNO2WT0udJIQJTbeZFrJkjzjcb8fkAYYQ== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prop-types@^15.6.0: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +react-is@^16.8.1: + version "16.8.3" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.3.tgz#4ad8b029c2a718fc0cfc746c8d4e1b7221e5387d" + integrity sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA== + +regexp.prototype.flags@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz#6b30724e306a27833eeb171b66ac8890ba37e41c" + integrity sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA== + dependencies: + define-properties "^1.1.2" + +regexpp@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" + integrity sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw== + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + dependencies: + is-promise "^2.1.0" + +rxjs@^5.5.2: + version "5.5.12" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" + integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== + dependencies: + symbol-observable "1.0.1" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== + dependencies: + is-fullwidth-code-point "^2.0.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string.prototype.matchall@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz#2af8fe3d2d6dc53ca2a59bd376b089c3c152b3c8" + integrity sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q== + dependencies: + define-properties "^1.1.2" + es-abstract "^1.10.0" + function-bind "^1.1.1" + has-symbols "^1.0.0" + regexp.prototype.flags "^1.2.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +symbol-observable@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= + +table@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" + integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg== + dependencies: + ajv "^6.0.1" + ajv-keywords "^3.0.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= + dependencies: + mkdirp "^0.5.1" diff --git a/packages/react-native-codegen/BUCK b/packages/react-native-codegen/BUCK index 8586931f3fc7c0..38e5f4c9c0d420 100644 --- a/packages/react-native-codegen/BUCK +++ b/packages/react-native-codegen/BUCK @@ -44,35 +44,47 @@ fb_native.sh_binary( ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_BOOLEAN_PROP", + fixture_name = "INTERFACE_ONLY", ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_STRING_PROP", + fixture_name = "BOOLEAN_PROP", ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_INTEGER_PROPS", + fixture_name = "STRING_PROP", ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_FLOAT_PROPS", + fixture_name = "INTEGER_PROPS", ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_COLOR_PROP", + fixture_name = "FLOAT_PROPS", ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_ENUM_PROP", + fixture_name = "COLOR_PROP", ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_EVENT_PROPS", + fixture_name = "IMAGE_PROP", ) rn_codegen_test( - fixture_name = "SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS", + fixture_name = "MULTI_NATIVE_PROP", +) + +rn_codegen_test( + fixture_name = "ENUM_PROP", +) + +rn_codegen_test( + fixture_name = "EVENT_PROPS", +) + +rn_codegen_test( + fixture_name = "EVENT_NESTED_OBJECT_PROPS", ) rn_codegen_test( @@ -99,14 +111,17 @@ fb_xplat_cxx_binary( ], visibility = ["PUBLIC"], deps = [ - ":generated_components-SINGLE_COMPONENT_WITH_BOOLEAN_PROP", - ":generated_components-SINGLE_COMPONENT_WITH_COLOR_PROP", - ":generated_components-SINGLE_COMPONENT_WITH_ENUM_PROP", - ":generated_components-SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_EVENT_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_FLOAT_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_INTEGER_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_STRING_PROP", + ":generated_components-BOOLEAN_PROP", + ":generated_components-COLOR_PROP", + ":generated_components-ENUM_PROP", + ":generated_components-EVENT_NESTED_OBJECT_PROPS", + ":generated_components-EVENT_PROPS", + ":generated_components-FLOAT_PROPS", + ":generated_components-IMAGE_PROP", + ":generated_components-INTEGER_PROPS", + ":generated_components-INTERFACE_ONLY", + ":generated_components-MULTI_NATIVE_PROP", + ":generated_components-STRING_PROP", ":generated_components-TWO_COMPONENTS_DIFFERENT_FILES", ":generated_components-TWO_COMPONENTS_SAME_FILE", ], @@ -131,14 +146,17 @@ rn_xplat_cxx_library( "PUBLIC", ], deps = [ - ":generated_components-SINGLE_COMPONENT_WITH_BOOLEAN_PROP", - ":generated_components-SINGLE_COMPONENT_WITH_COLOR_PROP", - ":generated_components-SINGLE_COMPONENT_WITH_ENUM_PROP", - ":generated_components-SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_EVENT_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_FLOAT_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_INTEGER_PROPS", - ":generated_components-SINGLE_COMPONENT_WITH_STRING_PROP", + ":generated_components-BOOLEAN_PROP", + ":generated_components-COLOR_PROP", + ":generated_components-ENUM_PROP", + ":generated_components-EVENT_NESTED_OBJECT_PROPS", + ":generated_components-EVENT_PROPS", + ":generated_components-FLOAT_PROPS", + ":generated_components-IMAGE_PROP", + ":generated_components-INTEGER_PROPS", + ":generated_components-INTERFACE_ONLY", + ":generated_components-MULTI_NATIVE_PROP", + ":generated_components-STRING_PROP", ":generated_components-TWO_COMPONENTS_DIFFERENT_FILES", ":generated_components-TWO_COMPONENTS_SAME_FILE", ], diff --git a/packages/react-native-codegen/DEFS.bzl b/packages/react-native-codegen/DEFS.bzl index aa9a6af9bddff8..8f3ad2e3476c7b 100644 --- a/packages/react-native-codegen/DEFS.bzl +++ b/packages/react-native-codegen/DEFS.bzl @@ -129,6 +129,8 @@ def rn_codegen( react_native_xplat_target("fabric/debug:debug"), react_native_xplat_target("fabric/core:core"), react_native_xplat_target("fabric/graphics:graphics"), + react_native_xplat_target("fabric/components/image:image"), + react_native_xplat_target("fabric/imagemanager:imagemanager"), react_native_xplat_target("fabric/components/view:view"), ], ) diff --git a/packages/react-native-codegen/buck_tests/emptyFile.cpp b/packages/react-native-codegen/buck_tests/emptyFile.cpp index 0c1b12ca7c4c2d..d08daa8d04647a 100644 --- a/packages/react-native-codegen/buck_tests/emptyFile.cpp +++ b/packages/react-native-codegen/buck_tests/emptyFile.cpp @@ -1,11 +1,14 @@ -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import #import #import diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 0d4c6a014a88d0..ee2d763f29eecc 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -64,7 +64,7 @@ type PropTypeTypeAnnotation = |}> | $ReadOnly<{| type: 'NativePrimitiveTypeAnnotation', - name: 'ColorPrimitive', + name: 'ColorPrimitive' | 'ImageSourcePrimitive', |}>; export type PropTypeShape = $ReadOnly<{| @@ -87,6 +87,7 @@ export type EventTypeShape = $ReadOnly<{| |}>; export type ComponentShape = $ReadOnly<{| + interfaceOnly?: boolean, extendsProps: $ReadOnlyArray<{| type: 'ReactNativeBuiltInType', knownTypeName: 'ReactNativeCoreViewProps', diff --git a/packages/react-native-codegen/src/generators/GenerateComponentDescriptorH.js b/packages/react-native-codegen/src/generators/GenerateComponentDescriptorH.js index c4e1e5a316bb93..24f85fee9a193f 100644 --- a/packages/react-native-codegen/src/generators/GenerateComponentDescriptorH.js +++ b/packages/react-native-codegen/src/generators/GenerateComponentDescriptorH.js @@ -55,6 +55,9 @@ module.exports = { return Object.keys(components) .map(componentName => { + if (components[componentName].interfaceOnly === true) { + return; + } return componentTemplate.replace(/::_CLASSNAME_::/g, componentName); }) .join('\n'); diff --git a/packages/react-native-codegen/src/generators/GeneratePropsCpp.js b/packages/react-native-codegen/src/generators/GeneratePropsCpp.js index 162e9820c5d582..3a17aa0477b8c2 100644 --- a/packages/react-native-codegen/src/generators/GeneratePropsCpp.js +++ b/packages/react-native-codegen/src/generators/GeneratePropsCpp.js @@ -24,7 +24,7 @@ const template = ` */ #include -#include +::_IMPORTS_:: namespace facebook { namespace react { @@ -79,9 +79,36 @@ function getClassExtendString(component): string { return extendString; } +function getImports(component): Set { + const imports: Set = new Set(); + component.props.forEach(prop => { + const typeAnnotation = prop.typeAnnotation; + + if (typeAnnotation.type === 'NativePrimitiveTypeAnnotation') { + switch (typeAnnotation.name) { + case 'ColorPrimitive': + return; + case 'ImageSourcePrimitive': + imports.add('#include '); + return; + default: + (typeAnnotation.name: empty); + throw new Error( + `Invalid NativePrimitiveTypeAnnotation name, got ${prop.name}`, + ); + } + } + }); + + return imports; +} + module.exports = { generate(libraryName: string, schema: SchemaType): FilesOutput { const fileName = 'Props.cpp'; + const allImports: Set = new Set([ + '#include ', + ]); const componentProps = Object.keys(schema.modules) .map(moduleName => { @@ -99,6 +126,9 @@ module.exports = { const propsString = generatePropsString(component); const extendString = getClassExtendString(component); + const imports = getImports(component); + imports.forEach(allImports.add, allImports); + const replacedTemplate = componentTemplate .replace(/::_CLASSNAME_::/g, newName) .replace('::_EXTEND_CLASSES_::', extendString) @@ -113,7 +143,15 @@ module.exports = { const replacedTemplate = template .replace(/::_COMPONENT_CLASSES_::/g, componentProps) - .replace('::_LIBRARY_::', libraryName); + .replace('::_LIBRARY_::', libraryName) + .replace( + '::_IMPORTS_::', + + Array.from(allImports) + .sort() + .join('\n') + .trim(), + ); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/GeneratePropsH.js b/packages/react-native-codegen/src/generators/GeneratePropsH.js index 3c9e0c85e3aa3c..191efadb92e132 100644 --- a/packages/react-native-codegen/src/generators/GeneratePropsH.js +++ b/packages/react-native-codegen/src/generators/GeneratePropsH.js @@ -27,6 +27,7 @@ const template = ` * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once ::_IMPORTS_:: @@ -106,6 +107,8 @@ function getNativeTypeFromAnnotation(componentName: string, prop): string { switch (typeAnnotation.name) { case 'ColorPrimitive': return 'SharedColor'; + case 'ImageSourcePrimitive': + return 'ImageSource'; default: (typeAnnotation.name: empty); throw new Error('Receieved unknown NativePrimitiveTypeAnnotation'); @@ -136,14 +139,16 @@ function convertDefaultTypeToString(componentName: string, prop): string { switch (typeAnnotation.name) { case 'ColorPrimitive': return ''; + case 'ImageSourcePrimitive': + return ''; default: (typeAnnotation.name: empty); throw new Error('Receieved unknown NativePrimitiveTypeAnnotation'); } case 'StringEnumTypeAnnotation': - return `${getEnumName(componentName, prop.name)}::${ - typeAnnotation.default - }`; + return `${getEnumName(componentName, prop.name)}::${upperCaseFirst( + typeAnnotation.default, + )}`; default: (typeAnnotation: empty); throw new Error('Receieved invalid typeAnnotation'); @@ -239,6 +244,9 @@ function getImports(component): Set { case 'ColorPrimitive': imports.add('#include '); return; + case 'ImageSourcePrimitive': + imports.add('#include '); + return; default: (typeAnnotation.name: empty); throw new Error( diff --git a/packages/react-native-codegen/src/generators/GenerateShadowNodeCpp.js b/packages/react-native-codegen/src/generators/GenerateShadowNodeCpp.js index 6b3467ee1ab1fc..0210aaa2239cd0 100644 --- a/packages/react-native-codegen/src/generators/GenerateShadowNodeCpp.js +++ b/packages/react-native-codegen/src/generators/GenerateShadowNodeCpp.js @@ -52,6 +52,9 @@ module.exports = { return Object.keys(components) .map(componentName => { + if (components[componentName].interfaceOnly === true) { + return; + } const replacedTemplate = componentTemplate.replace( /::_CLASSNAME_::/g, componentName, diff --git a/packages/react-native-codegen/src/generators/GenerateShadowNodeH.js b/packages/react-native-codegen/src/generators/GenerateShadowNodeH.js index f365e490d1422d..59501c99c85abe 100644 --- a/packages/react-native-codegen/src/generators/GenerateShadowNodeH.js +++ b/packages/react-native-codegen/src/generators/GenerateShadowNodeH.js @@ -65,6 +65,10 @@ module.exports = { return Object.keys(components) .map(componentName => { const component = components[componentName]; + if (component.interfaceOnly === true) { + return; + } + const hasEvents = component.events.length > 0; if (hasEvents) { diff --git a/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js b/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js index 6e111765a9abc9..f19f54c18a96bc 100644 --- a/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js +++ b/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js @@ -51,6 +51,9 @@ function getReactDiffProcessValue(prop) { switch (typeAnnotation.name) { case 'ColorPrimitive': return j.template.expression`${nativeTypesString}.ColorPrimitive`; + case 'ImageSourcePrimitive': + return j.template + .expression`${nativeTypesString}.ImageSourcePrimitive`; default: (typeAnnotation.name: empty); throw new Error('Receieved unknown NativePrimitiveTypeAnnotation'); diff --git a/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js index 0cbc3662c2844d..bfbfa7ab23228f 100644 --- a/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js @@ -12,7 +12,55 @@ import type {SchemaType} from '../../CodegenSchema.js'; -const SINGLE_COMPONENT_WITH_BOOLEAN_PROP: SchemaType = { +const INTERFACE_ONLY: SchemaType = { + modules: { + Switch: { + components: { + InterfaceOnlyComponent: { + interfaceOnly: true, + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [ + { + name: 'onChange', + optional: true, + bubblingType: 'bubble', + typeAnnotation: { + type: 'EventTypeAnnotation', + argument: { + type: 'ObjectTypeAnnotation', + properties: [ + { + type: 'BooleanTypeAnnotation', + name: 'value', + optional: false, + }, + ], + }, + }, + }, + ], + props: [ + { + name: 'accessibilityHint', + optional: true, + typeAnnotation: { + type: 'StringTypeAnnotation', + default: '', + }, + }, + ], + }, + }, + }, + }, +}; + +const BOOLEAN_PROP: SchemaType = { modules: { Switch: { components: { @@ -40,7 +88,7 @@ const SINGLE_COMPONENT_WITH_BOOLEAN_PROP: SchemaType = { }, }; -const SINGLE_COMPONENT_WITH_STRING_PROP: SchemaType = { +const STRING_PROP: SchemaType = { modules: { Switch: { components: { @@ -68,7 +116,7 @@ const SINGLE_COMPONENT_WITH_STRING_PROP: SchemaType = { }, }; -const SINGLE_COMPONENT_WITH_INTEGER_PROPS: SchemaType = { +const INTEGER_PROPS: SchemaType = { modules: { Switch: { components: { @@ -112,7 +160,7 @@ const SINGLE_COMPONENT_WITH_INTEGER_PROPS: SchemaType = { }, }; -const SINGLE_COMPONENT_WITH_FLOAT_PROPS: SchemaType = { +const FLOAT_PROPS: SchemaType = { modules: { Switch: { components: { @@ -172,7 +220,7 @@ const SINGLE_COMPONENT_WITH_FLOAT_PROPS: SchemaType = { }, }; -const SINGLE_COMPONENT_WITH_COLOR_PROP: SchemaType = { +const COLOR_PROP: SchemaType = { modules: { Switch: { components: { @@ -200,7 +248,79 @@ const SINGLE_COMPONENT_WITH_COLOR_PROP: SchemaType = { }, }; -const SINGLE_COMPONENT_WITH_ENUM_PROP: SchemaType = { +const IMAGE_PROP: SchemaType = { + modules: { + Slider: { + components: { + ImagePropNativeComponent: { + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [], + props: [ + { + name: 'thumbImage', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ImageSourcePrimitive', + }, + }, + ], + }, + }, + }, + }, +}; + +const MULTI_NATIVE_PROP: SchemaType = { + modules: { + Slider: { + components: { + ImageColorPropNativeComponent: { + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [], + props: [ + { + name: 'thumbImage', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ImageSourcePrimitive', + }, + }, + { + name: 'color', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ColorPrimitive', + }, + }, + { + name: 'thumbTintColor', + optional: true, + typeAnnotation: { + type: 'NativePrimitiveTypeAnnotation', + name: 'ColorPrimitive', + }, + }, + ], + }, + }, + }, + }, +}; + +const ENUM_PROP: SchemaType = { modules: { Switch: { components: { @@ -218,7 +338,7 @@ const SINGLE_COMPONENT_WITH_ENUM_PROP: SchemaType = { optional: true, typeAnnotation: { type: 'StringEnumTypeAnnotation', - default: 'Center', + default: 'center', options: [ { name: 'top', @@ -239,7 +359,7 @@ const SINGLE_COMPONENT_WITH_ENUM_PROP: SchemaType = { }, }; -const SINGLE_COMPONENT_WITH_EVENT_PROPS: SchemaType = { +const EVENT_PROPS: SchemaType = { modules: { Switch: { components: { @@ -336,7 +456,7 @@ const SINGLE_COMPONENT_WITH_EVENT_PROPS: SchemaType = { }, }; -const SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS: SchemaType = { +const EVENT_NESTED_OBJECT_PROPS: SchemaType = { modules: { Switch: { components: { @@ -508,14 +628,17 @@ const TWO_COMPONENTS_DIFFERENT_FILES: SchemaType = { }; module.exports = { - SINGLE_COMPONENT_WITH_BOOLEAN_PROP, - SINGLE_COMPONENT_WITH_STRING_PROP, - SINGLE_COMPONENT_WITH_INTEGER_PROPS, - SINGLE_COMPONENT_WITH_FLOAT_PROPS, - SINGLE_COMPONENT_WITH_COLOR_PROP, - SINGLE_COMPONENT_WITH_ENUM_PROP, - SINGLE_COMPONENT_WITH_EVENT_PROPS, - SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS, + INTERFACE_ONLY, + BOOLEAN_PROP, + STRING_PROP, + INTEGER_PROPS, + FLOAT_PROPS, + COLOR_PROP, + IMAGE_PROP, + MULTI_NATIVE_PROP, + ENUM_PROP, + EVENT_PROPS, + EVENT_NESTED_OBJECT_PROPS, TWO_COMPONENTS_SAME_FILE, TWO_COMPONENTS_DIFFERENT_FILES, }; diff --git a/packages/react-native-codegen/src/generators/__tests__/GenerateComponentDescriptorH-test.js b/packages/react-native-codegen/src/generators/__tests__/GenerateComponentDescriptorH-test.js index 3f1c30605b5c76..f9c1d1d264ca23 100644 --- a/packages/react-native-codegen/src/generators/__tests__/GenerateComponentDescriptorH-test.js +++ b/packages/react-native-codegen/src/generators/__tests__/GenerateComponentDescriptorH-test.js @@ -14,79 +14,58 @@ const generator = require('../GenerateComponentDescriptorH.js'); const { - SINGLE_COMPONENT_WITH_BOOLEAN_PROP, - SINGLE_COMPONENT_WITH_STRING_PROP, - SINGLE_COMPONENT_WITH_INTEGER_PROPS, - SINGLE_COMPONENT_WITH_FLOAT_PROPS, - SINGLE_COMPONENT_WITH_COLOR_PROP, - SINGLE_COMPONENT_WITH_ENUM_PROP, - SINGLE_COMPONENT_WITH_EVENT_PROPS, + BOOLEAN_PROP, + STRING_PROP, + INTEGER_PROPS, + FLOAT_PROPS, + COLOR_PROP, + IMAGE_PROP, + MULTI_NATIVE_PROP, + ENUM_PROP, + EVENT_PROPS, TWO_COMPONENTS_SAME_FILE, TWO_COMPONENTS_DIFFERENT_FILES, } = require('../__test_fixtures__/fixtures.js'); describe('GenerateComponentDescriptorH', () => { it('can generate a single boolean prop', () => { - expect( - generator.generate( - 'SINGLE_COMPONENT_WITH_BOOLEAN_PROP', - SINGLE_COMPONENT_WITH_BOOLEAN_PROP, - ), - ).toMatchSnapshot(); + expect(generator.generate('BOOLEAN_PROP', BOOLEAN_PROP)).toMatchSnapshot(); }); it('can generate a single string prop', () => { - expect( - generator.generate( - 'SINGLE_COMPONENT_WITH_STRING_PROPS', - SINGLE_COMPONENT_WITH_STRING_PROP, - ), - ).toMatchSnapshot(); + expect(generator.generate('STRING_PROPS', STRING_PROP)).toMatchSnapshot(); }); it('can generate integer props', () => { expect( - generator.generate( - 'SINGLE_COMPONENT_WITH_INTEGER_PROPS', - SINGLE_COMPONENT_WITH_INTEGER_PROPS, - ), + generator.generate('INTEGER_PROPS', INTEGER_PROPS), ).toMatchSnapshot(); }); it('can generate float props', () => { - expect( - generator.generate( - 'SINGLE_COMPONENT_WITH_FLOAT_PROPS', - SINGLE_COMPONENT_WITH_FLOAT_PROPS, - ), - ).toMatchSnapshot(); + expect(generator.generate('FLOAT_PROPS', FLOAT_PROPS)).toMatchSnapshot(); }); it('can generate a single native primitive prop', () => { + expect(generator.generate('COLOR_PROP', COLOR_PROP)).toMatchSnapshot(); + }); + + it('can generate a native primitive image prop', () => { + expect(generator.generate('IMAGE_PROP', IMAGE_PROP)).toMatchSnapshot(); + }); + + it('can generate multiple native props', () => { expect( - generator.generate( - 'SINGLE_COMPONENT_WITH_COLOR_PROP', - SINGLE_COMPONENT_WITH_COLOR_PROP, - ), + generator.generate('MULTI_NATIVE_PROP', MULTI_NATIVE_PROP), ).toMatchSnapshot(); }); it('can generate enum props', () => { - expect( - generator.generate( - 'SINGLE_COMPONENT_WITH_ENUM_PROP', - SINGLE_COMPONENT_WITH_ENUM_PROP, - ), - ).toMatchSnapshot(); + expect(generator.generate('ENUM_PROP', ENUM_PROP)).toMatchSnapshot(); }); it('can generate events', () => { - expect( - generator.generate( - 'SINGLE_COMPONENT_WITH_EVENT_PROPS', - SINGLE_COMPONENT_WITH_EVENT_PROPS, - ), - ).toMatchSnapshot(); + expect(generator.generate('EVENT_PROPS', EVENT_PROPS)).toMatchSnapshot(); }); it('supports two components from same module', () => { diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateComponentDescriptorH-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateComponentDescriptorH-test.js.snap index 5be8e789959230..75dd517cf1550f 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateComponentDescriptorH-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateComponentDescriptorH-test.js.snap @@ -1,5 +1,31 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`GenerateComponentDescriptorH can generate a native primitive image prop 1`] = ` +Map { + "ComponentDescriptors.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +using ImagePropNativeComponentComponentDescriptor = ConcreteComponentDescriptor; + +} // namespace react +} // namespace facebook +", +} +`; + exports[`GenerateComponentDescriptorH can generate a single boolean prop 1`] = ` Map { "ComponentDescriptors.h" => " @@ -12,7 +38,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -38,7 +64,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -64,7 +90,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -90,7 +116,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -116,7 +142,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -142,7 +168,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -168,7 +194,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -182,6 +208,32 @@ using IntegerPropNativeComponentComponentDescriptor = ConcreteComponentDescripto } `; +exports[`GenerateComponentDescriptorH can generate multiple native props 1`] = ` +Map { + "ComponentDescriptors.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +using ImageColorPropNativeComponentComponentDescriptor = ConcreteComponentDescriptor; + +} // namespace react +} // namespace facebook +", +} +`; + exports[`GenerateComponentDescriptorH supports two components from different modules 1`] = ` Map { "ComponentDescriptors.h" => " diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap index 2c837d567dbc7d..b55e9f5bac4aca 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_BOOLEAN_PROP 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture BOOLEAN_PROP 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -10,7 +10,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -23,7 +23,7 @@ namespace react { } `; -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_COLOR_PROP 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture COLOR_PROP 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -33,7 +33,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -46,7 +46,7 @@ namespace react { } `; -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_ENUM_PROP 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture ENUM_PROP 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -56,7 +56,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -69,7 +69,7 @@ namespace react { } `; -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture EVENT_NESTED_OBJECT_PROPS 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -79,7 +79,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -110,7 +110,7 @@ location.setProperty(runtime, \\"y\\", event.location.y); } `; -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_EVENT_PROPS 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture EVENT_PROPS 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -120,7 +120,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -149,7 +149,7 @@ void EventsNativeComponentEventEmitter::onEventDirect(EventsNativeComponentOnEve } `; -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_FLOAT_PROPS 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture FLOAT_PROPS 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -159,7 +159,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -172,7 +172,7 @@ namespace react { } `; -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_INTEGER_PROPS 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture IMAGE_PROP 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -182,7 +182,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -195,7 +195,7 @@ namespace react { } `; -exports[`GenerateEventEmitterCpp can generate fixture SINGLE_COMPONENT_WITH_STRING_PROP 1`] = ` +exports[`GenerateEventEmitterCpp can generate fixture INTEGER_PROPS 1`] = ` Map { "EventEmitters.cpp" => " /** @@ -205,7 +205,82 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include + +namespace facebook { +namespace react { + + + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterCpp can generate fixture INTERFACE_ONLY 1`] = ` +Map { + "EventEmitters.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook { +namespace react { + +void InterfaceOnlyComponentEventEmitter::onChange(InterfaceOnlyComponentOnChangeStruct event) const { + dispatchEvent(\\"change\\", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, \\"value\\", event.value); + return payload; + }); +} + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterCpp can generate fixture MULTI_NATIVE_PROP 1`] = ` +Map { + "EventEmitters.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook { +namespace react { + + + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterCpp can generate fixture STRING_PROP 1`] = ` +Map { + "EventEmitters.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include namespace facebook { namespace react { diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap index 54993c07da63af..33bf6f768ff9ca 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_BOOLEAN_PROP 1`] = ` +exports[`GenerateEventEmitterH can generate fixture BOOLEAN_PROP 1`] = ` Map { "EventEmitters.h" => " /** @@ -24,7 +24,7 @@ namespace react { } `; -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_COLOR_PROP 1`] = ` +exports[`GenerateEventEmitterH can generate fixture COLOR_PROP 1`] = ` Map { "EventEmitters.h" => " /** @@ -48,7 +48,7 @@ namespace react { } `; -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_ENUM_PROP 1`] = ` +exports[`GenerateEventEmitterH can generate fixture ENUM_PROP 1`] = ` Map { "EventEmitters.h" => " /** @@ -72,7 +72,7 @@ namespace react { } `; -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS 1`] = ` +exports[`GenerateEventEmitterH can generate fixture EVENT_NESTED_OBJECT_PROPS 1`] = ` Map { "EventEmitters.h" => " /** @@ -115,7 +115,7 @@ class EventsNestedObjectNativeComponentEventEmitter : public ViewEventEmitter { } `; -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_EVENT_PROPS 1`] = ` +exports[`GenerateEventEmitterH can generate fixture EVENT_PROPS 1`] = ` Map { "EventEmitters.h" => " /** @@ -157,7 +157,7 @@ void onEventDirect(EventsNativeComponentOnEventDirectStruct value) const; } `; -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_FLOAT_PROPS 1`] = ` +exports[`GenerateEventEmitterH can generate fixture FLOAT_PROPS 1`] = ` Map { "EventEmitters.h" => " /** @@ -181,7 +181,7 @@ namespace react { } `; -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_INTEGER_PROPS 1`] = ` +exports[`GenerateEventEmitterH can generate fixture IMAGE_PROP 1`] = ` Map { "EventEmitters.h" => " /** @@ -205,7 +205,88 @@ namespace react { } `; -exports[`GenerateEventEmitterH can generate fixture SINGLE_COMPONENT_WITH_STRING_PROP 1`] = ` +exports[`GenerateEventEmitterH can generate fixture INTEGER_PROPS 1`] = ` +Map { + "EventEmitters.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once + +#include + +namespace facebook { +namespace react { + + + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterH can generate fixture INTERFACE_ONLY 1`] = ` +Map { + "EventEmitters.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once + +#include + +namespace facebook { +namespace react { + +struct InterfaceOnlyComponentOnChangeStruct { + bool value; +}; + +class InterfaceOnlyComponentEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + void onChange(InterfaceOnlyComponentOnChangeStruct value) const; +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterH can generate fixture MULTI_NATIVE_PROP 1`] = ` +Map { + "EventEmitters.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once + +#include + +namespace facebook { +namespace react { + + + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterH can generate fixture STRING_PROP 1`] = ` Map { "EventEmitters.h" => " /** diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap index aa6299148297cd..dfd4d724ba6feb 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_BOOLEAN_PROP 1`] = ` +exports[`GeneratePropsCpp can generate fixture BOOLEAN_PROP 1`] = ` Map { "Props.cpp" => " /** @@ -10,7 +10,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include #include namespace facebook { @@ -29,7 +29,7 @@ BooleanPropNativeComponentProps::BooleanPropNativeComponentProps( } `; -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_COLOR_PROP 1`] = ` +exports[`GeneratePropsCpp can generate fixture COLOR_PROP 1`] = ` Map { "Props.cpp" => " /** @@ -39,7 +39,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include #include namespace facebook { @@ -58,7 +58,7 @@ ColorPropNativeComponentProps::ColorPropNativeComponentProps( } `; -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_ENUM_PROP 1`] = ` +exports[`GeneratePropsCpp can generate fixture ENUM_PROP 1`] = ` Map { "Props.cpp" => " /** @@ -68,7 +68,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include #include namespace facebook { @@ -87,7 +87,7 @@ EnumPropsNativeComponentProps::EnumPropsNativeComponentProps( } `; -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS 1`] = ` +exports[`GeneratePropsCpp can generate fixture EVENT_NESTED_OBJECT_PROPS 1`] = ` Map { "Props.cpp" => " /** @@ -97,7 +97,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include #include namespace facebook { @@ -116,7 +116,7 @@ EventsNestedObjectNativeComponentProps::EventsNestedObjectNativeComponentProps( } `; -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_EVENT_PROPS 1`] = ` +exports[`GeneratePropsCpp can generate fixture EVENT_PROPS 1`] = ` Map { "Props.cpp" => " /** @@ -126,7 +126,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include #include namespace facebook { @@ -145,7 +145,7 @@ EventsNativeComponentProps::EventsNativeComponentProps( } `; -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_FLOAT_PROPS 1`] = ` +exports[`GeneratePropsCpp can generate fixture FLOAT_PROPS 1`] = ` Map { "Props.cpp" => " /** @@ -155,7 +155,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include #include namespace facebook { @@ -178,7 +178,7 @@ blurRadius5(convertRawProp(rawProps, \\"blurRadius5\\", sourceProps.blurRadius5, } `; -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_INTEGER_PROPS 1`] = ` +exports[`GeneratePropsCpp can generate fixture IMAGE_PROP 1`] = ` Map { "Props.cpp" => " /** @@ -188,7 +188,37 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include +#include +#include + +namespace facebook { +namespace react { + +ImagePropNativeComponentProps::ImagePropNativeComponentProps( + const ImagePropNativeComponentProps &sourceProps, + const RawProps &rawProps): ViewProps(sourceProps, rawProps), + + thumbImage(convertRawProp(rawProps, \\"thumbImage\\", sourceProps.thumbImage, thumbImage)) + {} + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GeneratePropsCpp can generate fixture INTEGER_PROPS 1`] = ` +Map { + "Props.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include #include namespace facebook { @@ -209,7 +239,68 @@ progress3(convertRawProp(rawProps, \\"progress3\\", sourceProps.progress3, progr } `; -exports[`GeneratePropsCpp can generate fixture SINGLE_COMPONENT_WITH_STRING_PROP 1`] = ` +exports[`GeneratePropsCpp can generate fixture INTERFACE_ONLY 1`] = ` +Map { + "Props.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +namespace facebook { +namespace react { + +InterfaceOnlyComponentProps::InterfaceOnlyComponentProps( + const InterfaceOnlyComponentProps &sourceProps, + const RawProps &rawProps): ViewProps(sourceProps, rawProps), + + accessibilityHint(convertRawProp(rawProps, \\"accessibilityHint\\", sourceProps.accessibilityHint, accessibilityHint)) + {} + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GeneratePropsCpp can generate fixture MULTI_NATIVE_PROP 1`] = ` +Map { + "Props.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +namespace facebook { +namespace react { + +ImageColorPropNativeComponentProps::ImageColorPropNativeComponentProps( + const ImageColorPropNativeComponentProps &sourceProps, + const RawProps &rawProps): ViewProps(sourceProps, rawProps), + + thumbImage(convertRawProp(rawProps, \\"thumbImage\\", sourceProps.thumbImage, thumbImage)), +color(convertRawProp(rawProps, \\"color\\", sourceProps.color, color)), +thumbTintColor(convertRawProp(rawProps, \\"thumbTintColor\\", sourceProps.thumbTintColor, thumbTintColor)) + {} + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GeneratePropsCpp can generate fixture STRING_PROP 1`] = ` Map { "Props.cpp" => " /** @@ -219,7 +310,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include #include namespace facebook { diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap index 543bfd58999fc6..f2a0f78497522c 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_BOOLEAN_PROP 1`] = ` +exports[`GeneratePropsH can generate fixture BOOLEAN_PROP 1`] = ` Map { "Props.h" => " /** @@ -9,6 +9,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include @@ -31,7 +32,7 @@ class BooleanPropNativeComponentProps final : public ViewProps { } `; -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_COLOR_PROP 1`] = ` +exports[`GeneratePropsH can generate fixture COLOR_PROP 1`] = ` Map { "Props.h" => " /** @@ -40,6 +41,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include #include @@ -63,7 +65,7 @@ class ColorPropNativeComponentProps final : public ViewProps { } `; -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_ENUM_PROP 1`] = ` +exports[`GeneratePropsH can generate fixture ENUM_PROP 1`] = ` Map { "Props.h" => " /** @@ -72,6 +74,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include @@ -112,7 +115,7 @@ class EnumPropsNativeComponentProps final : public ViewProps { } `; -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS 1`] = ` +exports[`GeneratePropsH can generate fixture EVENT_NESTED_OBJECT_PROPS 1`] = ` Map { "Props.h" => " /** @@ -121,6 +124,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include @@ -143,7 +147,7 @@ class EventsNestedObjectNativeComponentProps final : public ViewProps { } `; -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_EVENT_PROPS 1`] = ` +exports[`GeneratePropsH can generate fixture EVENT_PROPS 1`] = ` Map { "Props.h" => " /** @@ -152,6 +156,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include @@ -174,7 +179,7 @@ class EventsNativeComponentProps final : public ViewProps { } `; -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_FLOAT_PROPS 1`] = ` +exports[`GeneratePropsH can generate fixture FLOAT_PROPS 1`] = ` Map { "Props.h" => " /** @@ -183,6 +188,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include @@ -209,7 +215,7 @@ const Float blurRadius5{1.0}; } `; -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_INTEGER_PROPS 1`] = ` +exports[`GeneratePropsH can generate fixture IMAGE_PROP 1`] = ` Map { "Props.h" => " /** @@ -218,6 +224,40 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +class ImagePropNativeComponentProps final : public ViewProps { + public: + ImagePropNativeComponentProps() = default; + ImagePropNativeComponentProps(const ImagePropNativeComponentProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + const ImageSource thumbImage{}; +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GeneratePropsH can generate fixture INTEGER_PROPS 1`] = ` +Map { + "Props.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once #include @@ -242,7 +282,75 @@ const int progress3{10}; } `; -exports[`GeneratePropsH can generate fixture SINGLE_COMPONENT_WITH_STRING_PROP 1`] = ` +exports[`GeneratePropsH can generate fixture INTERFACE_ONLY 1`] = ` +Map { + "Props.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once + +#include + +namespace facebook { +namespace react { + +class InterfaceOnlyComponentProps final : public ViewProps { + public: + InterfaceOnlyComponentProps() = default; + InterfaceOnlyComponentProps(const InterfaceOnlyComponentProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + const std::string accessibilityHint{\\"\\"}; +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GeneratePropsH can generate fixture MULTI_NATIVE_PROP 1`] = ` +Map { + "Props.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class ImageColorPropNativeComponentProps final : public ViewProps { + public: + ImageColorPropNativeComponentProps() = default; + ImageColorPropNativeComponentProps(const ImageColorPropNativeComponentProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + const ImageSource thumbImage{}; +const SharedColor color{}; +const SharedColor thumbTintColor{}; +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GeneratePropsH can generate fixture STRING_PROP 1`] = ` Map { "Props.h" => " /** @@ -251,6 +359,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include @@ -282,6 +391,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include @@ -323,6 +433,7 @@ Map { * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +#pragma once #include diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap index 739463f7c52f61..64a9a6f5f18d13 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_BOOLEAN_PROP 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture BOOLEAN_PROP 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -10,7 +10,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -23,7 +23,7 @@ extern const char BooleanPropNativeComponentComponentName[] = \\"BooleanPropNati } `; -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_COLOR_PROP 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture COLOR_PROP 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -33,7 +33,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -46,7 +46,7 @@ extern const char ColorPropNativeComponentComponentName[] = \\"ColorPropNativeCo } `; -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_ENUM_PROP 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture ENUM_PROP 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -56,7 +56,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -69,7 +69,7 @@ extern const char EnumPropsNativeComponentComponentName[] = \\"EnumPropsNativeCo } `; -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture EVENT_NESTED_OBJECT_PROPS 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -79,7 +79,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -92,7 +92,7 @@ extern const char EventsNestedObjectNativeComponentComponentName[] = \\"EventsNe } `; -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_EVENT_PROPS 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture EVENT_PROPS 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -102,7 +102,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -115,7 +115,7 @@ extern const char EventsNativeComponentComponentName[] = \\"EventsNativeComponen } `; -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_FLOAT_PROPS 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture FLOAT_PROPS 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -125,7 +125,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { @@ -138,7 +138,7 @@ extern const char FloatPropNativeComponentComponentName[] = \\"FloatPropNativeCo } `; -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_INTEGER_PROPS 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture IMAGE_PROP 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -148,7 +148,30 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include + +namespace facebook { +namespace react { + +extern const char ImagePropNativeComponentComponentName[] = \\"ImagePropNativeComponent\\"; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateShadowNodeCpp can generate fixture INTEGER_PROPS 1`] = ` +Map { + "ShadowNodes.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include namespace facebook { namespace react { @@ -161,7 +184,53 @@ extern const char IntegerPropNativeComponentComponentName[] = \\"IntegerPropNati } `; -exports[`GenerateShadowNodeCpp can generate fixture SINGLE_COMPONENT_WITH_STRING_PROP 1`] = ` +exports[`GenerateShadowNodeCpp can generate fixture INTERFACE_ONLY 1`] = ` +Map { + "ShadowNodes.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook { +namespace react { + + + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateShadowNodeCpp can generate fixture MULTI_NATIVE_PROP 1`] = ` +Map { + "ShadowNodes.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook { +namespace react { + +extern const char ImageColorPropNativeComponentComponentName[] = \\"ImageColorPropNativeComponent\\"; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateShadowNodeCpp can generate fixture STRING_PROP 1`] = ` Map { "ShadowNodes.cpp" => " /** @@ -171,7 +240,7 @@ Map { * LICENSE file in the root directory of this source tree. */ -#include +#include namespace facebook { namespace react { diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap index 1ad65ac1233eb7..9b40789cc623dc 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_BOOLEAN_PROP 1`] = ` +exports[`GenerateShadowNodeH can generate fixture BOOLEAN_PROP 1`] = ` Map { "ShadowNodes.h" => " /** @@ -12,7 +12,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -33,7 +33,7 @@ using BooleanPropNativeComponentShadowNode = ConcreteViewShadowNode< } `; -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_COLOR_PROP 1`] = ` +exports[`GenerateShadowNodeH can generate fixture COLOR_PROP 1`] = ` Map { "ShadowNodes.h" => " /** @@ -45,7 +45,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -66,7 +66,7 @@ using ColorPropNativeComponentShadowNode = ConcreteViewShadowNode< } `; -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_ENUM_PROP 1`] = ` +exports[`GenerateShadowNodeH can generate fixture ENUM_PROP 1`] = ` Map { "ShadowNodes.h" => " /** @@ -78,7 +78,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -99,7 +99,7 @@ using EnumPropsNativeComponentShadowNode = ConcreteViewShadowNode< } `; -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS 1`] = ` +exports[`GenerateShadowNodeH can generate fixture EVENT_NESTED_OBJECT_PROPS 1`] = ` Map { "ShadowNodes.h" => " /** @@ -111,8 +111,8 @@ Map { #pragma once -#include -#include +#include +#include #include namespace facebook { @@ -134,7 +134,7 @@ EventsNestedObjectNativeComponentEventEmitter>; } `; -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_EVENT_PROPS 1`] = ` +exports[`GenerateShadowNodeH can generate fixture EVENT_PROPS 1`] = ` Map { "ShadowNodes.h" => " /** @@ -146,8 +146,8 @@ Map { #pragma once -#include -#include +#include +#include #include namespace facebook { @@ -169,7 +169,7 @@ EventsNativeComponentEventEmitter>; } `; -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_FLOAT_PROPS 1`] = ` +exports[`GenerateShadowNodeH can generate fixture FLOAT_PROPS 1`] = ` Map { "ShadowNodes.h" => " /** @@ -181,7 +181,7 @@ Map { #pragma once -#include +#include #include namespace facebook { @@ -202,7 +202,7 @@ using FloatPropNativeComponentShadowNode = ConcreteViewShadowNode< } `; -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_INTEGER_PROPS 1`] = ` +exports[`GenerateShadowNodeH can generate fixture IMAGE_PROP 1`] = ` Map { "ShadowNodes.h" => " /** @@ -214,7 +214,40 @@ Map { #pragma once -#include +#include +#include + +namespace facebook { +namespace react { + +extern const char ImagePropNativeComponentComponentName[]; + +/* + * \`ShadowNode\` for component. + */ +using ImagePropNativeComponentShadowNode = ConcreteViewShadowNode< + ImagePropNativeComponentComponentName, + ImagePropNativeComponentProps>; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateShadowNodeH can generate fixture INTEGER_PROPS 1`] = ` +Map { + "ShadowNodes.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include #include namespace facebook { @@ -235,7 +268,66 @@ using IntegerPropNativeComponentShadowNode = ConcreteViewShadowNode< } `; -exports[`GenerateShadowNodeH can generate fixture SINGLE_COMPONENT_WITH_STRING_PROP 1`] = ` +exports[`GenerateShadowNodeH can generate fixture INTERFACE_ONLY 1`] = ` +Map { + "ShadowNodes.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + + + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateShadowNodeH can generate fixture MULTI_NATIVE_PROP 1`] = ` +Map { + "ShadowNodes.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +extern const char ImageColorPropNativeComponentComponentName[]; + +/* + * \`ShadowNode\` for component. + */ +using ImageColorPropNativeComponentShadowNode = ConcreteViewShadowNode< + ImageColorPropNativeComponentComponentName, + ImageColorPropNativeComponentProps>; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateShadowNodeH can generate fixture STRING_PROP 1`] = ` Map { "ShadowNodes.h" => " /** @@ -247,7 +339,7 @@ Map { #pragma once -#include +#include #include namespace facebook { diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap index b9451f104a5d1e..0358579cda539c 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_BOOLEAN_PROP 1`] = ` +exports[`GenerateViewConfigJs can generate fixture BOOLEAN_PROP 1`] = ` Map { "ViewConfigs.js" => " /** @@ -35,7 +35,7 @@ ReactNativeViewConfigRegistry.register( } `; -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_COLOR_PROP 1`] = ` +exports[`GenerateViewConfigJs can generate fixture COLOR_PROP 1`] = ` Map { "ViewConfigs.js" => " /** @@ -70,7 +70,7 @@ ReactNativeViewConfigRegistry.register( } `; -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_ENUM_PROP 1`] = ` +exports[`GenerateViewConfigJs can generate fixture ENUM_PROP 1`] = ` Map { "ViewConfigs.js" => " /** @@ -105,7 +105,7 @@ ReactNativeViewConfigRegistry.register( } `; -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_EVENT_NESTED_OBJECT_PROPS 1`] = ` +exports[`GenerateViewConfigJs can generate fixture EVENT_NESTED_OBJECT_PROPS 1`] = ` Map { "ViewConfigs.js" => " /** @@ -149,7 +149,7 @@ ReactNativeViewConfigRegistry.register( } `; -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_EVENT_PROPS 1`] = ` +exports[`GenerateViewConfigJs can generate fixture EVENT_PROPS 1`] = ` Map { "ViewConfigs.js" => " /** @@ -199,7 +199,7 @@ ReactNativeViewConfigRegistry.register( } `; -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_FLOAT_PROPS 1`] = ` +exports[`GenerateViewConfigJs can generate fixture FLOAT_PROPS 1`] = ` Map { "ViewConfigs.js" => " /** @@ -238,7 +238,42 @@ ReactNativeViewConfigRegistry.register( } `; -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_INTEGER_PROPS 1`] = ` +exports[`GenerateViewConfigJs can generate fixture IMAGE_PROP 1`] = ` +Map { + "ViewConfigs.js" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); + +const ImagePropNativeComponentViewConfig = { + uiViewClassName: 'ImagePropNativeComponent', + + validAttributes: { + thumbImage: require('react-native').__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.NativePrimitives.ImageSourcePrimitive, + style: ReactNativeStyleAttributes + } +}; + +ReactNativeViewConfigRegistry.register( + 'ImagePropNativeComponent', + () => ImagePropNativeComponentViewConfig, +); +", +} +`; + +exports[`GenerateViewConfigJs can generate fixture INTEGER_PROPS 1`] = ` Map { "ViewConfigs.js" => " /** @@ -275,7 +310,88 @@ ReactNativeViewConfigRegistry.register( } `; -exports[`GenerateViewConfigJs can generate fixture SINGLE_COMPONENT_WITH_STRING_PROP 1`] = ` +exports[`GenerateViewConfigJs can generate fixture INTERFACE_ONLY 1`] = ` +Map { + "ViewConfigs.js" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); + +const InterfaceOnlyComponentViewConfig = { + uiViewClassName: 'InterfaceOnlyComponent', + + bubblingEventTypes: { + onChange: { + phasedRegistrationNames: { + captured: 'onChangeCapture', + bubbled: 'onChange' + } + } + }, + + validAttributes: { + accessibilityHint: true, + style: ReactNativeStyleAttributes + } +}; + +ReactNativeViewConfigRegistry.register( + 'InterfaceOnlyComponent', + () => InterfaceOnlyComponentViewConfig, +); +", +} +`; + +exports[`GenerateViewConfigJs can generate fixture MULTI_NATIVE_PROP 1`] = ` +Map { + "ViewConfigs.js" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); + +const ImageColorPropNativeComponentViewConfig = { + uiViewClassName: 'ImageColorPropNativeComponent', + + validAttributes: { + thumbImage: require('react-native').__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.NativePrimitives.ImageSourcePrimitive, + color: require('react-native').__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.NativePrimitives.ColorPrimitive, + thumbTintColor: require('react-native').__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.NativePrimitives.ColorPrimitive, + style: ReactNativeStyleAttributes + } +}; + +ReactNativeViewConfigRegistry.register( + 'ImageColorPropNativeComponent', + () => ImageColorPropNativeComponentViewConfig, +); +", +} +`; + +exports[`GenerateViewConfigJs can generate fixture STRING_PROP 1`] = ` Map { "ViewConfigs.js" => " /** diff --git a/packages/react-native-symbolicate/BUCK b/packages/react-native-symbolicate/BUCK deleted file mode 100644 index b53b34b9bf99e8..00000000000000 --- a/packages/react-native-symbolicate/BUCK +++ /dev/null @@ -1,44 +0,0 @@ -load("@fbsource//tools/build_defs/third_party:node_defs.bzl", "nodejs_binary") -load("@fbsource//tools/build_defs/third_party:yarn_defs.bzl", "yarn_install", "yarn_workspace") - -yarn_workspace( - name = "yarn-workspace", - srcs = glob( - ["**/*.js"], - exclude = [ - "**/__fixtures__/**", - "**/__flowtests__/**", - "**/__mocks__/**", - "**/__tests__/**", - "**/node_modules/**", - "**/node_modules/.bin/**", - "**/.*", - "**/.*/**", - "**/.*/.*", - "**/*.xcodeproj/**", - "**/*.xcworkspace/**", - ], - ), - visibility = ["PUBLIC"], -) - -yarn_install( - name = "node-modules", - extra_srcs = [ - "symbolicate.js", - "Symbolication.js", - ], -) - -nodejs_binary( - name = "symbolicate", - main = [ - ":node-modules", - "symbolicate.js", - ], - node_args = [ - "--max-old-space-size=8192", - "--stack_size=10000", - ], - visibility = ["PUBLIC"], -) diff --git a/packages/react-native-symbolicate/Symbolication.js b/packages/react-native-symbolicate/Symbolication.js deleted file mode 100644 index fd340700d80440..00000000000000 --- a/packages/react-native-symbolicate/Symbolication.js +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -/* eslint-disable no-console */ - -const fs = require('fs'); - -const UNKNOWN_MODULE_IDS = { - segmentId: 0, - localId: undefined, -}; - -/* - * If the file name of a stack frame is numeric (+ ".js"), we assume it's a - * lazily injected module coming from a "random access bundle". We are using - * special source maps for these bundles, so that we can symbolicate stack - * traces for multiple injected files with a single source map. - * - * There is also a convention for callsites that are in split segments of a - * bundle, named either `seg-3.js` for segment #3 for example, or `seg-3_5.js` - * for module #5 of segment #3 of a segmented RAM bundle. - */ -function parseFileName(str) { - const modMatch = str.match(/^(\d+).js$/); - if (modMatch != null) { - return {segmentId: 0, localId: Number(modMatch[1])}; - } - const segMatch = str.match(/^seg-(\d+)(?:_(\d+))?.js$/); - if (segMatch != null) { - return { - segmentId: Number(segMatch[1]), - localId: segMatch[2] && Number(segMatch[2]), - }; - } - return UNKNOWN_MODULE_IDS; -} - -/* - * A helper function to return a mapping {line, column} object for a given input - * line and column, and optionally a module ID. - */ -function getOriginalPositionFor(lineNumber, columnNumber, moduleIds, context) { - var moduleLineOffset = 0; - var metadata = context.segments[moduleIds.segmentId]; - const {localId} = moduleIds; - if (localId != null) { - const {moduleOffsets} = metadata; - if (!moduleOffsets) { - throw new Error( - 'Module ID given for a source map that does not have ' + - 'an x_facebook_offsets field', - ); - } - if (moduleOffsets[localId] == null) { - throw new Error('Unknown module ID: ' + localId); - } - moduleLineOffset = moduleOffsets[localId]; - } - return metadata.consumer.originalPositionFor({ - line: Number(lineNumber) + moduleLineOffset, - column: Number(columnNumber), - }); -} - -function createContext(SourceMapConsumer, sourceMapContent) { - var sourceMapJson = JSON.parse(sourceMapContent.replace(/^\)\]\}'/, '')); - return { - segments: Object.entries(sourceMapJson.x_facebook_segments || {}).reduce( - (acc, seg) => { - acc[seg[0]] = { - consumer: new SourceMapConsumer(seg[1]), - moduleOffsets: seg[1].x_facebook_offsets || {}, - }; - return acc; - }, - { - '0': { - consumer: new SourceMapConsumer(sourceMapJson), - moduleOffsets: sourceMapJson.x_facebook_offsets || {}, - }, - }, - ), - }; -} - -// parse stack trace with String.replace -// replace the matched part of stack trace to symbolicated result -// sample stack trace: -// IOS: foo@4:18131, Android: bar:4:18063 -// sample stack trace with module id: -// IOS: foo@123.js:4:18131, Android: bar:123.js:4:18063 -// sample result: -// IOS: foo.js:57:foo, Android: bar.js:75:bar -function symbolicate(stackTrace, context) { - return stackTrace.replace( - /(?:([^@: \n]+)(@|:))?(?:(?:([^@: \n]+):)?(\d+):(\d+)|\[native code\])/g, - function(match, func, delimiter, fileName, line, column) { - var original = getOriginalPositionFor( - line, - column, - parseFileName(fileName || ''), - context, - ); - return original.source + ':' + original.line + ':' + original.name; - }, - ); -} - -// Taking in a map like -// trampoline offset (optional js function name) -// JS_0158_xxxxxxxxxxxxxxxxxxxxxx fe 91081 -// JS_0159_xxxxxxxxxxxxxxxxxxxxxx Ft 68651 -// JS_0160_xxxxxxxxxxxxxxxxxxxxxx value 50700 -// JS_0161_xxxxxxxxxxxxxxxxxxxxxx setGapAtCursor 0 -// JS_0162_xxxxxxxxxxxxxxxxxxxxxx (unknown) 50818 -// JS_0163_xxxxxxxxxxxxxxxxxxxxxx value 108267 - -function symbolicateProfilerMap(mapFile, context) { - return fs - .readFileSync(mapFile, 'utf8') - .split('\n') - .slice(0, -1) - .map(function(line) { - const line_list = line.split(' '); - const trampoline = line_list[0]; - const js_name = line_list[1]; - const offset = parseInt(line_list[2], 10); - - if (!offset) { - return trampoline + ' ' + trampoline; - } - - var original = getOriginalPositionFor( - 1, - offset, - UNKNOWN_MODULE_IDS, - context, - ); - - return ( - trampoline + - ' ' + - (original.name || js_name) + - '::' + - [original.source, original.line, original.column].join(':') - ); - }) - .join('\n'); -} - -function symbolicateAttribution(obj, context) { - var loc = obj.location; - var line = loc.line || 1; - var column = loc.column || loc.virtualOffset; - var file = loc.filename ? parseFileName(loc.filename) : UNKNOWN_MODULE_IDS; - var original = getOriginalPositionFor(line, column, file, context); - obj.location = { - file: original.source, - line: original.line, - column: original.column, - }; -} - -// Symbolicate chrome trace "stackFrames" section. -// Each frame in it has three fields: name, funcVirtAddr(optional), offset(optional). -// funcVirtAddr and offset are only available if trace is generated from -// hbc bundle without debug info. -function symbolicateChromeTrace(traceFile, context) { - const contentJson = JSON.parse(fs.readFileSync(traceFile, 'utf8')); - if (contentJson.stackFrames == null) { - console.error('Unable to locate `stackFrames` section in trace.'); - process.exit(1); - } - console.log( - 'Processing ' + Object.keys(contentJson.stackFrames).length + ' frames', - ); - Object.values(contentJson.stackFrames).forEach(function(entry) { - let line; - let column; - - // Function entrypoint line/column; used for symbolicating function name. - let funcLine; - let funcColumn; - - if (entry.funcVirtAddr != null && entry.offset != null) { - // Without debug information. - const funcVirtAddr = parseInt(entry.funcVirtAddr, 10); - const offsetInFunction = parseInt(entry.offset, 10); - // Main bundle always use hard-coded line value 1. - // TODO: support multiple bundle/module. - line = 1; - column = funcVirtAddr + offsetInFunction; - funcLine = 1; - funcColumn = funcVirtAddr; - } else if (entry.line != null && entry.column != null) { - // For hbc bundle with debug info, name field may already have source - // information for the bundle; we still can use babel/metro/prepack - // source map to symbolicate the bundle frame addresses further to its - // original source code. - line = entry.line; - column = entry.column; - - funcLine = entry.funcLine; - funcColumn = entry.funcColumn; - } else { - // Native frames. - return; - } - - // Symbolicate original file/line/column. - const addressOriginal = getOriginalPositionFor( - line, - column, - UNKNOWN_MODULE_IDS, - context, - ); - - let frameName = entry.name; - // Symbolicate function name. - if (funcLine != null && funcColumn != null) { - const funcOriginal = getOriginalPositionFor( - funcLine, - funcColumn, - UNKNOWN_MODULE_IDS, - context, - ); - if (funcOriginal.name != null) { - frameName = funcOriginal.name; - } - } else { - // No function line/column info. - console.warn( - 'Warning: no function prolog line/column info; name may be wrong', - ); - } - - // Output format is: funcName(file:line:column) - const sourceLocation = `(${addressOriginal.source}:${ - addressOriginal.line - }:${addressOriginal.column})`; - entry.name = frameName + sourceLocation; - }); - console.log('Writing to ' + traceFile); - fs.writeFileSync(traceFile, JSON.stringify(contentJson)); -} - -module.exports = { - createContext, - getOriginalPositionFor, - parseFileName, - symbolicate, - symbolicateProfilerMap, - symbolicateAttribution, - symbolicateChromeTrace, -}; diff --git a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.attribution.input b/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.attribution.input deleted file mode 100644 index 0d80b22250902e..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.attribution.input +++ /dev/null @@ -1,2 +0,0 @@ -{"functionId":1,"location":{"virtualOffset":22},"usage":[]} -{"functionId":2,"location":{"virtualOffset":99},"usage":[]} diff --git a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.cpuprofile b/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.cpuprofile deleted file mode 100644 index 6270b03750e43d..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.cpuprofile +++ /dev/null @@ -1,42 +0,0 @@ -{ - "stackFrames": { - "1": { - "funcVirtAddr": "0", - "offset": "0", - "name": "global+0", - "category": "JavaScript" - }, - "2": { - "funcVirtAddr": "0", - "offset": "55", - "name": "global+55", - "category": "JavaScript" - }, - "3": { - "funcVirtAddr": "67", - "offset": "16", - "name": "entryPoint+16", - "category": "JavaScript", - "parent": 2 - }, - "4": { - "funcVirtAddr": "89", - "offset": "0", - "name": "helper+0", - "category": "JavaScript", - "parent": 3 - }, - "5": { - "funcVirtAddr": "89", - "offset": "146", - "name": "helper+146", - "category": "JavaScript", - "parent": 3 - }, - "6": { - "name": "[Native]4367295792", - "category": "Native", - "parent": 5 - } - } -} diff --git a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.cpuprofile.map b/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.cpuprofile.map deleted file mode 100644 index 9b0c31c6d05564..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.cpuprofile.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": [ - "temp/bench.js" - ], - "mappings": "AACC,uDAmBU,YAnBY,gBACd,MAGU,wFAKA,WACT,iBACC,IAAD,YACU,cAAA,YAHY,eAAb;" -} diff --git a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.js.map b/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.js.map deleted file mode 100644 index 636c74a153e409..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.js.map +++ /dev/null @@ -1,2 +0,0 @@ -{"version":3,"sources":["thrower.js"],"names":["notCalled","alsoNotCalled","throws6a","Error","throws6","throws6b","throws4","obj","throws5","apply","this","throws2","throws3","throws1","throws0","t","eval","o","forEach","call","arguments","arg","err","print","stack"],"mappings":"CAAA,WAGE,SAASA,YACP,SAASC,IACPA,IAEFA,IACAD,YAIF,SAASE,WACP,MAAM,IAAIC,MAAM,wBAGlB,SAASC,UACP,MAAM,IAAID,MAAM,wBAGlB,SAASE,WACP,MAAM,IAAIF,MAAM,wBAYlB,SAASG,UACPC,IAAIC,EAAQC,MAAMC,MAAON,QAASA,QAASA,UAG7C,SAASO,UACPJ,IAAIK,IAGN,SAASC,UAELF,UAIJ,SAASG,UACPD,UAxBF,IAAIN,KACFQ,EAAS,WACPC,KAAK,eAEPC,EAAS,cACJC,QAAQC,KAAKC,UAAW,SAASC,GAAOA,QAsB/C,IACEP,UACA,MAAOQ,GACPC,MAAMD,EAAIE,QAtDd"} - diff --git a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.profmap b/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.profmap deleted file mode 100644 index f2c26d89be2157..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.profmap +++ /dev/null @@ -1,4 +0,0 @@ -JS_0000_xxxxxxxxxxxxxxxxxxxxxx (unknown) 373 -JS_0001_xxxxxxxxxxxxxxxxxxxxxx garbage 296 -JS_0002_xxxxxxxxxxxxxxxxxxxxxx name 1234 -JS_0003_xxxxxxxxxxxxxxxxxxxxxx (unknown) 5678 diff --git a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.sectioned.js.map b/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.sectioned.js.map deleted file mode 100644 index f438264a967602..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.sectioned.js.map +++ /dev/null @@ -1 +0,0 @@ -{"sections":[{"map":{"version":3,"sources":["index.js"],"sourcesContent":["import { AppRegistry } from \"react-native\";\nimport { start } from \"./nested-thrower\";\n\nAppRegistry.registerComponent(\"loglabrat\", () => {\n start();\n return null;\n});\n"],"names":["_reactNative","r","d","_nestedThrower","AppRegistry","registerComponent","start"],"mappings":"4BAAA,IAAAA,EAAAC,EAAAC,EAAA,IACAC,EAAAF,EAAAC,EAAA,IAEAE,EAAAA,YAAYC,kBAAkB,YAAa,WAEzC,OADA,EAAAF,EAAAG,SACO"},"offset":{"line":13,"column":28}},{"map":{"version":3,"sources":["nested-thrower.js"],"sourcesContent":["function start() {\n throw new Error('nope');\n}\n\nmodule.exports = {\n start\n}\n"],"names":["module","exports","start","Error"],"mappings":"4BAIAA,EAAOC,SACLC,MALF,WACE,MAAM,IAAIC,MAAM"},"offset":{"line":356,"column":28}}],"version":3,"x_facebook_offsets":[95,13,null,null,null,null,null,null,null,null,null,null,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356],"x_metro_module_paths":["node_modules/react-native/Libraries/Core/InitializeCore.js","index.js","__prelude__","node_modules/metro/src/lib/polyfills/require.js","node_modules/react-native/Libraries/polyfills/Object.es6.js","node_modules/react-native/Libraries/polyfills/console.js","node_modules/react-native/Libraries/polyfills/error-guard.js","node_modules/react-native/Libraries/polyfills/Number.es6.js","node_modules/react-native/Libraries/polyfills/String.prototype.es6.js","node_modules/react-native/Libraries/polyfills/Array.prototype.es6.js","node_modules/react-native/Libraries/polyfills/Array.es6.js","node_modules/react-native/Libraries/polyfills/Object.es7.js","node_modules/react-native/Libraries/react-native/react-native-implementation.js","node_modules/fbjs/lib/invariant.js","node_modules/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js","node_modules/react-native/Libraries/BatchedBridge/NativeModules.js","node_modules/@babel/runtime/helpers/objectWithoutProperties.js","node_modules/@babel/runtime/helpers/objectWithoutPropertiesLoose.js","node_modules/@babel/runtime/helpers/extends.js","node_modules/@babel/runtime/helpers/slicedToArray.js","node_modules/@babel/runtime/helpers/arrayWithHoles.js","node_modules/@babel/runtime/helpers/iterableToArrayLimit.js","node_modules/@babel/runtime/helpers/nonIterableRest.js","node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js","node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js","node_modules/@babel/runtime/helpers/toConsumableArray.js","node_modules/@babel/runtime/helpers/arrayWithoutHoles.js","node_modules/@babel/runtime/helpers/iterableToArray.js","node_modules/@babel/runtime/helpers/nonIterableSpread.js","node_modules/@babel/runtime/helpers/classCallCheck.js","node_modules/@babel/runtime/helpers/createClass.js","node_modules/react-native/Libraries/vendor/core/ErrorUtils.js","node_modules/react-native/Libraries/Performance/Systrace.js","node_modules/react-native/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js","node_modules/react-native/Libraries/Utilities/stringifySafe.js","node_modules/react-native/Libraries/Utilities/defineLazyObjectProperty.js","node_modules/react-native/Libraries/Promise.js","node_modules/fbjs/lib/Promise.native.js","node_modules/promise/setimmediate/es6-extensions.js","node_modules/promise/setimmediate/core.js","node_modules/promise/setimmediate/done.js","node_modules/react-native/Libraries/EventEmitter/RCTDeviceEventEmitter.js","node_modules/@babel/runtime/helpers/possibleConstructorReturn.js","node_modules/@babel/runtime/helpers/typeof.js","node_modules/@babel/runtime/helpers/assertThisInitialized.js","node_modules/@babel/runtime/helpers/getPrototypeOf.js","node_modules/@babel/runtime/helpers/get.js","node_modules/@babel/runtime/helpers/superPropBase.js","node_modules/@babel/runtime/helpers/inherits.js","node_modules/@babel/runtime/helpers/setPrototypeOf.js","node_modules/react-native/Libraries/vendor/emitter/EventEmitter.js","node_modules/react-native/Libraries/vendor/emitter/EmitterSubscription.js","node_modules/react-native/Libraries/vendor/emitter/EventSubscription.js","node_modules/react-native/Libraries/vendor/emitter/EventSubscriptionVendor.js","node_modules/fbjs/lib/emptyFunction.js","node_modules/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js","node_modules/@babel/runtime/helpers/objectSpread.js","node_modules/@babel/runtime/helpers/defineProperty.js","node_modules/react-native/Libraries/Utilities/Platform.ios.js","node_modules/react-native/Libraries/react-native/React.js","node_modules/react/index.js","node_modules/react/cjs/react.production.min.js","node_modules/object-assign/index.js","node_modules/react-native/Libraries/StyleSheet/StyleSheet.js","node_modules/react-native/Libraries/Utilities/PixelRatio.js","node_modules/react-native/Libraries/Utilities/Dimensions.js","node_modules/react-native/Libraries/Utilities/DeviceInfo.js","node_modules/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedImageStylePropTypes.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedColorPropType.js","node_modules/react-native/Libraries/Color/normalizeColor.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedLayoutPropTypes.js","node_modules/prop-types/index.js","node_modules/prop-types/factoryWithThrowingShims.js","node_modules/prop-types/lib/ReactPropTypesSecret.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedShadowPropTypesIOS.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedTransformPropTypes.js","node_modules/react-native/Libraries/Utilities/deprecatedPropType.js","node_modules/react-native/Libraries/ReactNative/UIManager.js","node_modules/react-native/Libraries/ReactNative/UIManagerProperties.js","node_modules/react-native/Libraries/Text/TextStylePropTypes.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedViewStylePropTypes.js","node_modules/react-native/Libraries/StyleSheet/processColor.js","node_modules/react-native/Libraries/StyleSheet/processTransform.js","node_modules/react-native/Libraries/Utilities/MatrixMath.js","node_modules/react-native/Libraries/Utilities/differ/sizesDiffer.js","node_modules/react-native/Libraries/StyleSheet/StyleSheetValidation.js","node_modules/react-native/Libraries/StyleSheet/flattenStyle.js","node_modules/react-native/Libraries/Components/View/View.js","node_modules/react-native/Libraries/Text/TextAncestor.js","node_modules/react-native/Libraries/Components/View/ViewNativeComponent.js","node_modules/react-native/Libraries/Renderer/shims/ReactNative.js","node_modules/react-native/Libraries/Renderer/oss/ReactNativeRenderer-prod.js","node_modules/react-native/Libraries/Core/setUpGlobals.js","node_modules/react-native/Libraries/Core/polyfillES6Collections.js","node_modules/react-native/Libraries/Utilities/PolyfillFunctions.js","node_modules/react-native/Libraries/vendor/core/_shouldPolyfillES6Collection.js","node_modules/react-native/Libraries/vendor/core/Map.js","node_modules/react-native/Libraries/vendor/core/guid.js","node_modules/fbjs/lib/isNode.js","node_modules/react-native/Libraries/vendor/core/toIterator.js","node_modules/react-native/Libraries/vendor/core/Set.js","node_modules/react-native/Libraries/Core/setUpSystrace.js","node_modules/react-native/Libraries/Core/setUpErrorHandling.js","node_modules/react-native/Libraries/Core/ExceptionsManager.js","node_modules/react-native/Libraries/Core/Devtools/parseErrorStack.js","node_modules/stacktrace-parser/index.js","node_modules/stacktrace-parser/lib/stacktrace-parser.js","node_modules/react-native/Libraries/Core/checkNativeVersion.js","node_modules/react-native/Libraries/Core/ReactNativeVersionCheck.js","node_modules/react-native/Libraries/Core/ReactNativeVersion.js","node_modules/react-native/Libraries/Core/polyfillPromise.js","node_modules/react-native/Libraries/Core/setUpRegeneratorRuntime.js","node_modules/regenerator-runtime/runtime.js","node_modules/react-native/Libraries/Core/setUpTimers.js","node_modules/react-native/Libraries/Core/Timers/JSTimers.js","node_modules/fbjs/lib/performanceNow.js","node_modules/fbjs/lib/performance.js","node_modules/fbjs/lib/ExecutionEnvironment.js","node_modules/fbjs/lib/warning.js","node_modules/react-native/Libraries/Core/setUpXHR.js","node_modules/react-native/Libraries/Network/XMLHttpRequest.js","node_modules/event-target-shim/lib/event-target.js","node_modules/event-target-shim/lib/commons.js","node_modules/event-target-shim/lib/custom-event-target.js","node_modules/event-target-shim/lib/event-wrapper.js","node_modules/react-native/Libraries/Network/RCTNetworking.ios.js","node_modules/react-native/Libraries/EventEmitter/MissingNativeEventEmitterShim.js","node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter.js","node_modules/react-native/Libraries/Network/convertRequestBody.js","node_modules/react-native/Libraries/Utilities/binaryToBase64.js","node_modules/base64-js/index.js","node_modules/react-native/Libraries/Blob/Blob.js","node_modules/react-native/Libraries/Blob/BlobManager.js","node_modules/react-native/Libraries/Blob/BlobRegistry.js","node_modules/react-native/Libraries/Network/FormData.js","node_modules/react-native/Libraries/Network/fetch.js","node_modules/react-native/Libraries/vendor/core/whatwg-fetch.js","node_modules/react-native/Libraries/WebSocket/WebSocket.js","node_modules/react-native/Libraries/WebSocket/WebSocketEvent.js","node_modules/react-native/Libraries/Blob/File.js","node_modules/react-native/Libraries/Blob/FileReader.js","node_modules/react-native/Libraries/Blob/URL.js","node_modules/react-native/Libraries/Core/setUpAlert.js","node_modules/react-native/Libraries/Alert/Alert.js","node_modules/react-native/Libraries/Alert/AlertIOS.js","node_modules/react-native/Libraries/Core/setUpGeolocation.js","node_modules/react-native/Libraries/Geolocation/Geolocation.js","node_modules/@babel/runtime/regenerator/index.js","node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime-module.js","node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js","node_modules/react-native/Libraries/Utilities/logError.js","node_modules/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js","node_modules/react-native/Libraries/Core/setUpBatchedBridge.js","node_modules/react-native/Libraries/Utilities/HeapCapture.js","node_modules/react-native/Libraries/Performance/SamplingProfiler.js","node_modules/react-native/Libraries/Utilities/RCTLog.js","node_modules/react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter.js","node_modules/react-native/Libraries/Utilities/PerformanceLogger.js","node_modules/react-native/Libraries/Utilities/infoLog.js","node_modules/react-native/Libraries/Utilities/JSDevSupportModule.js","node_modules/react-native/Libraries/Core/setUpSegmentFetcher.js","node_modules/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js","node_modules/react-native/Libraries/EventEmitter/RCTEventEmitter.js","node_modules/react-native/Libraries/Utilities/differ/deepDiffer.js","node_modules/react-native/Libraries/Components/TextInput/TextInputState.js","node_modules/scheduler/index.js","node_modules/scheduler/cjs/scheduler.production.min.js","node_modules/react-native/Libraries/ReactNative/requireNativeComponent.js","node_modules/react-native/Libraries/Renderer/shims/createReactNativeComponentClass.js","node_modules/react-native/Libraries/ReactNative/getNativeComponentAttributes.js","node_modules/react-native/Libraries/Utilities/differ/insetsDiffer.js","node_modules/react-native/Libraries/Utilities/differ/matricesDiffer.js","node_modules/react-native/Libraries/Utilities/differ/pointsDiffer.js","node_modules/react-native/Libraries/Image/resolveAssetSource.js","node_modules/react-native/Libraries/Image/AssetRegistry.js","node_modules/react-native/Libraries/Image/AssetSourceResolver.js","node_modules/react-native/local-cli/bundle/assetPathUtils.js","node_modules/react-native/Libraries/ART/ReactNativeART.js","node_modules/art/core/color.js","node_modules/react-native/Libraries/ART/ARTSerializablePath.js","node_modules/art/core/class.js","node_modules/art/core/path.js","node_modules/art/core/transform.js","node_modules/react-native/Libraries/Components/View/ReactNativeViewAttributes.js","node_modules/react-native/Libraries/vendor/core/merge.js","node_modules/react-native/Libraries/vendor/core/mergeInto.js","node_modules/react-native/Libraries/vendor/core/mergeHelpers.js","node_modules/react-native/Libraries/Components/Button.js","node_modules/react-native/Libraries/Text/Text.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedTextPropTypes.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedEdgeInsetsPropType.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedStyleSheetPropType.js","node_modules/react-native/Libraries/DeprecatedPropTypes/deprecatedCreateStrictShapeTypeChecker.js","node_modules/react-native/Libraries/Components/Touchable/Touchable.js","node_modules/react-native/Libraries/Components/Touchable/BoundingDimensions.js","node_modules/react-native/Libraries/Components/Touchable/PooledClass.js","node_modules/react-native/Libraries/Components/Touchable/Position.js","node_modules/react-native/Libraries/Components/AppleTV/TVEventHandler.js","node_modules/fbjs/lib/TouchEventUtils.js","node_modules/fbjs/lib/keyMirror.js","node_modules/nullthrows/nullthrows.js","node_modules/react-native/Libraries/Components/Touchable/TouchableNativeFeedback.ios.js","node_modules/react-native/Libraries/Components/Touchable/TouchableOpacity.js","node_modules/react-native/Libraries/Animated/src/Animated.js","node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js","node_modules/react-native/Libraries/Animated/src/AnimatedEvent.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedValue.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedInterpolation.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedNode.js","node_modules/react-native/Libraries/Animated/src/NativeAnimatedHelper.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedWithChildren.js","node_modules/react-native/Libraries/Interaction/InteractionManager.js","node_modules/react-native/Libraries/Interaction/TaskQueue.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedAddition.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedDiffClamp.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedDivision.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedModulo.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedMultiplication.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedProps.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedStyle.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedTransform.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedSubtraction.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedTracking.js","node_modules/react-native/Libraries/Animated/src/nodes/AnimatedValueXY.js","node_modules/react-native/Libraries/Animated/src/animations/DecayAnimation.js","node_modules/react-native/Libraries/Animated/src/animations/Animation.js","node_modules/react-native/Libraries/Animated/src/animations/SpringAnimation.js","node_modules/react-native/Libraries/Animated/src/SpringConfig.js","node_modules/react-native/Libraries/Animated/src/animations/TimingAnimation.js","node_modules/react-native/Libraries/Animated/src/Easing.js","node_modules/react-native/Libraries/Animated/src/bezier.js","node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js","node_modules/react-native/Libraries/Animated/src/components/AnimatedFlatList.js","node_modules/react-native/Libraries/Lists/FlatList.js","node_modules/react-native/Libraries/Lists/VirtualizedList.js","node_modules/react-native/Libraries/Interaction/Batchinator.js","node_modules/react-native/Libraries/Lists/FillRateHelper.js","node_modules/react-native/Libraries/Components/RefreshControl/RefreshControl.js","node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js","node_modules/react-native/Libraries/Components/ScrollResponder.js","node_modules/react-native/Libraries/Interaction/FrameRateLogger.js","node_modules/react-native/Libraries/Components/Keyboard/Keyboard.js","node_modules/react-native/Libraries/LayoutAnimation/LayoutAnimation.js","node_modules/react-native/Libraries/Utilities/dismissKeyboard.js","node_modules/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js","node_modules/react-native/Libraries/Components/ScrollView/InternalScrollViewType.js","node_modules/create-react-class/index.js","node_modules/create-react-class/factory.js","node_modules/create-react-class/node_modules/fbjs/lib/emptyObject.js","node_modules/create-react-class/node_modules/fbjs/lib/invariant.js","node_modules/react-native/Libraries/Components/ScrollView/processDecelerationRate.js","node_modules/react-native/Libraries/Lists/ViewabilityHelper.js","node_modules/react-native/Libraries/Lists/VirtualizeUtils.js","node_modules/react-native/Libraries/Animated/src/components/AnimatedImage.js","node_modules/react-native/Libraries/Image/Image.ios.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedImagePropType.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedImageSourcePropType.js","node_modules/react-native/Libraries/Animated/src/components/AnimatedScrollView.js","node_modules/react-native/Libraries/Animated/src/components/AnimatedSectionList.js","node_modules/react-native/Libraries/Lists/SectionList.js","node_modules/react-native/Libraries/Lists/VirtualizedSectionList.js","node_modules/react-native/Libraries/Animated/src/components/AnimatedText.js","node_modules/react-native/Libraries/Animated/src/components/AnimatedView.js","node_modules/react-native/Libraries/Renderer/shims/NativeMethodsMixin.js","node_modules/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js","node_modules/react-native/Libraries/Components/Touchable/ensurePositiveDelayProps.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedViewAccessibility.js","node_modules/react-native/Libraries/Components/CheckBox/CheckBox.ios.js","node_modules/react-native/Libraries/Components/UnimplementedViews/UnimplementedView.js","node_modules/react-native/Libraries/Components/DatePicker/DatePickerIOS.ios.js","node_modules/react-native/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.ios.js","node_modules/react-native/Libraries/Image/ImageBackground.js","node_modules/react-native/Libraries/Components/Touchable/ensureComponentIsNative.js","node_modules/react-native/Libraries/Image/ImageEditor.js","node_modules/react-native/Libraries/Image/ImageStore.js","node_modules/react-native/Libraries/Components/TextInput/InputAccessoryView.js","node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js","node_modules/react-native/Libraries/Lists/ListView/ListView.js","node_modules/react-native/Libraries/Lists/ListView/InternalListViewType.js","node_modules/react-native/Libraries/Lists/ListView/ListViewDataSource.js","node_modules/react-native/Libraries/vendor/core/isEmpty.js","node_modules/react-native/Libraries/Components/StaticRenderer.js","node_modules/react-clone-referenced-element/cloneReferencedElement.js","node_modules/react-native/Libraries/Components/MaskedView/MaskedViewIOS.ios.js","node_modules/react-native/Libraries/Modal/Modal.js","node_modules/react-native/Libraries/ReactNative/AppContainer.js","node_modules/react-native/Libraries/ReactNative/I18nManager.js","node_modules/react-native/Libraries/Components/Picker/Picker.js","node_modules/react-native/Libraries/Components/Picker/PickerAndroid.ios.js","node_modules/react-native/Libraries/Components/Picker/PickerIOS.ios.js","node_modules/react-native/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.ios.js","node_modules/react-native/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js","node_modules/react-native/Libraries/Components/SafeAreaView/SafeAreaView.js","node_modules/react-native/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js","node_modules/react-native/Libraries/Components/Slider/Slider.js","node_modules/react-native/Libraries/RCTTest/SnapshotViewIOS.ios.js","node_modules/react-native/Libraries/Components/Switch/Switch.js","node_modules/react-native/Libraries/Components/Switch/SwitchNativeComponent.js","node_modules/react-native/Libraries/Components/StatusBar/StatusBar.js","node_modules/react-native/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js","node_modules/react-native/Libraries/Experimental/SwipeableRow/SwipeableRow.js","node_modules/react-native/Libraries/Interaction/PanResponder.js","node_modules/react-native/Libraries/Interaction/TouchHistoryMath.js","node_modules/react-native/Libraries/Experimental/SwipeableRow/SwipeableListView.js","node_modules/react-native/Libraries/Experimental/SwipeableRow/SwipeableListViewDataSource.js","node_modules/react-native/Libraries/Components/TabBarIOS/TabBarIOS.ios.js","node_modules/react-native/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js","node_modules/react-native/Libraries/Components/StaticContainer.react.js","node_modules/react-native/Libraries/Components/TextInput/TextInput.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedViewPropTypes.js","node_modules/react-native/Libraries/Components/View/PlatformViewPropTypes.ios.js","node_modules/react-native/Libraries/vendor/document/selection/DocumentSelectionState.js","node_modules/react-native/Libraries/vendor/emitter/mixInEventEmitter.js","node_modules/react-native/Libraries/vendor/emitter/EventEmitterWithHolding.js","node_modules/react-native/Libraries/vendor/emitter/EventHolder.js","node_modules/fbjs/lib/keyOf.js","node_modules/react-native/Libraries/Components/ToastAndroid/ToastAndroid.ios.js","node_modules/react-native/Libraries/Components/ToolbarAndroid/ToolbarAndroid.ios.js","node_modules/react-native/Libraries/Components/Touchable/TouchableHighlight.js","node_modules/react-native/Libraries/Components/ViewPager/ViewPagerAndroid.ios.js","node_modules/react-native/Libraries/Components/WebView/WebView.ios.js","node_modules/react-native/Libraries/Linking/Linking.js","node_modules/react-native/Libraries/Components/WebView/WebViewShared.js","node_modules/escape-string-regexp/index.js","node_modules/react-native/Libraries/ActionSheetIOS/ActionSheetIOS.js","node_modules/react-native/Libraries/ReactNative/AppRegistry.js","node_modules/react-native/Libraries/BugReporting/BugReporting.js","node_modules/react-native/Libraries/BugReporting/dumpReactTree.js","node_modules/react-native/Libraries/Utilities/SceneTracker.js","node_modules/react-native/Libraries/ReactNative/renderApplication.js","node_modules/react-native/Libraries/ReactNative/ReactFabricIndicator.js","node_modules/react-native/Libraries/Utilities/BackHandler.ios.js","node_modules/react-native/Libraries/Renderer/shims/ReactFabric.js","node_modules/react-native/Libraries/Renderer/oss/ReactFabric-prod.js","node_modules/react-native/Libraries/ReactNative/FabricUIManager.js","node_modules/react-native/Libraries/AppState/AppState.js","node_modules/react-native/Libraries/Storage/AsyncStorage.js","node_modules/react-native/Libraries/CameraRoll/CameraRoll.js","node_modules/react-native/Libraries/Components/Clipboard/Clipboard.js","node_modules/react-native/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js","node_modules/react-native/Libraries/CameraRoll/ImagePickerIOS.js","node_modules/react-native/Libraries/Network/NetInfo.js","node_modules/react-native/Libraries/PushNotificationIOS/PushNotificationIOS.js","node_modules/react-native/Libraries/Settings/Settings.ios.js","node_modules/react-native/Libraries/Share/Share.js","node_modules/react-native/Libraries/Components/StatusBar/StatusBarIOS.ios.js","node_modules/react-native/Libraries/Components/TimePickerAndroid/TimePickerAndroid.ios.js","node_modules/react-native/Libraries/Vibration/Vibration.js","node_modules/react-native/Libraries/Vibration/VibrationIOS.ios.js","node_modules/react-native/Libraries/YellowBox/YellowBox.js","node_modules/react-native/Libraries/ReactNative/takeSnapshot.js","node_modules/react-native/Libraries/DeprecatedPropTypes/DeprecatedPointPropType.js","nested-thrower.js","require-node_modules/react-native/Libraries/Core/InitializeCore.js","require-index.js","source-map"]} diff --git a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.stack b/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.stack deleted file mode 100644 index fedb815fb0188c..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__fixtures__/testfile.stack +++ /dev/null @@ -1,13 +0,0 @@ -throws6@thrower.min.js:1:161 -thrower.min.js:1:488 -forEach@[native code] -o@thrower.min.js:1:464 -throws4@thrower.min.js:1:276 -eval code -eval@[native code] -t@thrower.min.js:1:420 -throws2@thrower.min.js:1:333 -throws1@thrower.min.js:1:362 -throws0@thrower.min.js:1:391 -thrower.min.js:1:506 -global code@thrower.min.js:1:534 diff --git a/packages/react-native-symbolicate/__tests__/__snapshots__/symbolicate-test.js.snap b/packages/react-native-symbolicate/__tests__/__snapshots__/symbolicate-test.js.snap deleted file mode 100644 index eb65f8814d31e0..00000000000000 --- a/packages/react-native-symbolicate/__tests__/__snapshots__/symbolicate-test.js.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`symbolicating a profiler map 1`] = ` -"JS_0000_xxxxxxxxxxxxxxxxxxxxxx throws0::thrower.js:48:11 -JS_0001_xxxxxxxxxxxxxxxxxxxxxx throws6::thrower.js:35:38 -JS_0002_xxxxxxxxxxxxxxxxxxxxxx name::thrower.js:1:0 -JS_0003_xxxxxxxxxxxxxxxxxxxxxx (unknown)::thrower.js:1:0" -`; - -exports[`symbolicating a stack trace 1`] = ` -"thrower.js:18:null -thrower.js:30:arg -null:null:null -thrower.js:30:arguments -thrower.js:35:this -eval code -null:null:null -thrower.js:27:null -thrower.js:39:throws3 -thrower.js:44:throws2 -thrower.js:49:throws1 -thrower.js:53:throws0 -global thrower.js:1:null -" -`; - -exports[`symbolicating an attribution file 1`] = ` -"{\\"functionId\\":1,\\"location\\":{\\"file\\":\\"thrower.js\\",\\"line\\":4,\\"column\\":11},\\"usage\\":[]} -{\\"functionId\\":2,\\"location\\":{\\"file\\":\\"thrower.js\\",\\"line\\":14,\\"column\\":14},\\"usage\\":[]} -" -`; - -exports[`symbolicating with a cpuprofile 1`] = `"{\\"stackFrames\\":{\\"1\\":{\\"funcVirtAddr\\":\\"0\\",\\"offset\\":\\"0\\",\\"name\\":\\"global+0(temp/bench.js:2:1)\\",\\"category\\":\\"JavaScript\\"},\\"2\\":{\\"funcVirtAddr\\":\\"0\\",\\"offset\\":\\"55\\",\\"name\\":\\"global+55(temp/bench.js:21:11)\\",\\"category\\":\\"JavaScript\\"},\\"3\\":{\\"funcVirtAddr\\":\\"67\\",\\"offset\\":\\"16\\",\\"name\\":\\"entryPoint+16(temp/bench.js:3:9)\\",\\"category\\":\\"JavaScript\\",\\"parent\\":2},\\"4\\":{\\"funcVirtAddr\\":\\"89\\",\\"offset\\":\\"0\\",\\"name\\":\\"helper+0(temp/bench.js:6:19)\\",\\"category\\":\\"JavaScript\\",\\"parent\\":3},\\"5\\":{\\"funcVirtAddr\\":\\"89\\",\\"offset\\":\\"146\\",\\"name\\":\\"helper+146(temp/bench.js:14:20)\\",\\"category\\":\\"JavaScript\\",\\"parent\\":3},\\"6\\":{\\"name\\":\\"[Native]4367295792\\",\\"category\\":\\"Native\\",\\"parent\\":5}}}"`; diff --git a/packages/react-native-symbolicate/__tests__/symbolicate-test.js b/packages/react-native-symbolicate/__tests__/symbolicate-test.js deleted file mode 100644 index 1c0184edc2147c..00000000000000 --- a/packages/react-native-symbolicate/__tests__/symbolicate-test.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails oncall+react_native - * @format - */ - -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const {spawn} = require('child_process'); - -const resolve = fileName => path.resolve(__dirname, '__fixtures__', fileName); -const read = fileName => fs.readFileSync(resolve(fileName), 'utf8'); - -const execute = (args: Array, stdin: string): Promise => - new Promise((resolvePromise, reject) => { - const stdout = []; - const output = ['Process failed with the following output:\n======\n']; - const child = spawn(process.execPath, [ - ...process.execArgv, - path.join(__dirname, '..', 'symbolicate.js'), - ...args, - ]); - child.stdout.on('data', data => { - output.push(data); - stdout.push(data); - }); - child.stderr.on('data', data => { - output.push(data); - }); - child.on('close', (code, signal) => { - if (code !== 0 || signal != null) { - output.push('======\n'); - reject(new Error(output.join(''))); - return; - } - resolvePromise(stdout.join('')); - }); - if (stdin) { - child.stdin.write(stdin); - child.stdin.end(); - } - }); - -afterAll(() => { - try { - fs.unlinkSync(resolve('testfile.temp.cpuprofile')); - } catch (e) {} -}); - -const TESTFILE_MAP = resolve('testfile.js.map'); - -test('symbolicating a stack trace', async () => - await expect( - execute([TESTFILE_MAP], read('testfile.stack')), - ).resolves.toMatchSnapshot()); - -test('symbolicating a single entry', async () => - await expect(execute([TESTFILE_MAP, '1', '161'])).resolves.toEqual( - 'thrower.js:18:null\n', - )); - -test('symbolicating a sectioned source map', async () => - await expect( - execute([resolve('testfile.sectioned.js.map'), '353.js', '1', '72']), - ).resolves.toEqual('nested-thrower.js:6:start\n')); - -test('symbolicating a profiler map', async () => - await expect( - execute([TESTFILE_MAP, resolve('testfile.profmap')]), - ).resolves.toMatchSnapshot()); - -test('symbolicating an attribution file', async () => - await expect( - execute( - [TESTFILE_MAP, '--attribution'], - read('testfile.attribution.input'), - ), - ).resolves.toMatchSnapshot()); - -test('symbolicating with a cpuprofile', async () => { - fs.copyFileSync( - resolve('testfile.cpuprofile'), - resolve('testfile.temp.cpuprofile'), - ); - - await execute([ - resolve('testfile.cpuprofile.map'), - resolve('testfile.temp.cpuprofile'), - ]); - - expect(read('testfile.temp.cpuprofile')).toMatchSnapshot(); -}); diff --git a/packages/react-native-symbolicate/package.json b/packages/react-native-symbolicate/package.json deleted file mode 100644 index 1e2648658f8d6b..00000000000000 --- a/packages/react-native-symbolicate/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "react-native-symbolicate", - "version": "0.0.2", - "description": "A tool to find the source location from JS bundles and stack traces.", - "license": "MIT", - "main": "./symbolicate.js", - "bin": "./symbolicate.js", - "repository": { - "type": "git", - "url": "git@github.com:facebook/react-native.git" - }, - "engines": { - "node": ">=8.3" - }, - "files": [ - "BUCK", - "symbolicate.js", - "Symbolication.js" - ], - "dependencies": { - "source-map": "^0.5.6", - "through2": "^2.0.1" - } -} diff --git a/packages/react-native-symbolicate/symbolicate.js b/packages/react-native-symbolicate/symbolicate.js deleted file mode 100755 index 22088042dbe981..00000000000000 --- a/packages/react-native-symbolicate/symbolicate.js +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * Symbolicates a JavaScript stack trace using a source map. - * In our first form, we read a stack trace from stdin and symbolicate it via - * the provided source map. - * In our second form, we symbolicate using an explicit line number, and - * optionally a column. - * In our third form, we symbolicate using a module ID, a line number, and - * optionally a column. - * - * See https://our.intern.facebook.com/intern/dex/symbolicating-javascript-stack-traces-for-react-native/ - * - * @format - */ - -'use strict'; - -const SourceMapConsumer = require('source-map').SourceMapConsumer; -const Symbolication = require('./Symbolication.js'); - -const fs = require('fs'); -const through2 = require('through2'); - -const argv = process.argv.slice(2); -if (argv.length < 1 || argv.length > 4) { - /* eslint no-path-concat: "off" */ - const usages = [ - 'Usage: ' + __filename + ' ', - ' ' + __filename + ' [column]', - ' ' + __filename + ' .js [column]', - ' ' + __filename + ' .profmap', - ' ' + - __filename + - ' --attribution < attribution.jsonl > symbolicated.jsonl', - ' ' + __filename + ' .cpuprofile', - ]; - console.error(usages.join('\n')); - process.exit(1); -} - -// Read the source map. -const sourceMapFileName = argv.shift(); -const content = fs.readFileSync(sourceMapFileName, 'utf8'); -const context = Symbolication.createContext(SourceMapConsumer, content); - -if (argv.length === 0) { - const read = stream => { - return new Promise(resolve => { - let data = ''; - if (stream.isTTY) { - resolve(data); - return; - } - - stream.setEncoding('utf8'); - stream.on('readable', () => { - let chunk; - while ((chunk = stream.read())) { - data += chunk; - } - }); - stream.on('end', () => { - resolve(data); - }); - }); - }; - - (async () => { - const stackTrace = await read(process.stdin); - process.stdout.write(Symbolication.symbolicate(stackTrace, context)); - })().catch(error => { - console.error(error); - }); -} else if (argv[0].endsWith('.profmap')) { - process.stdout.write(Symbolication.symbolicateProfilerMap(argv[0], context)); -} else if (argv[0] === '--attribution') { - let buffer = ''; - process.stdin - .pipe( - through2(function(data, enc, callback) { - // Take arbitrary strings, output single lines - buffer += data; - const lines = buffer.split('\n'); - for (let i = 0, e = lines.length - 1; i < e; i++) { - this.push(lines[i]); - } - buffer = lines[lines.length - 1]; - callback(); - }), - ) - .pipe( - through2.obj(function(data, enc, callback) { - // This is JSONL, so each line is a separate JSON object - const obj = JSON.parse(data); - Symbolication.symbolicateAttribution(obj, context); - this.push(JSON.stringify(obj) + '\n'); - callback(); - }), - ) - .pipe(process.stdout); -} else if (argv[0].endsWith('.cpuprofile')) { - Symbolication.symbolicateChromeTrace(argv[0], context); -} else { - // read-from-argv form. - let moduleIds, lineNumber, columnNumber; - if (argv[0].endsWith('.js')) { - moduleIds = Symbolication.parseFileName(argv[0]); - argv.shift(); - } else { - moduleIds = {segmentId: 0, localId: undefined}; - } - lineNumber = argv.shift(); - columnNumber = argv.shift() || 0; - const original = Symbolication.getOriginalPositionFor( - lineNumber, - columnNumber, - moduleIds, - context, - ); - console.log(original.source + ':' + original.line + ':' + original.name); -} diff --git a/packages/react-native-symbolicate/yarn.lock b/packages/react-native-symbolicate/yarn.lock deleted file mode 100644 index 02f92527bc997d..00000000000000 --- a/packages/react-native-symbolicate/yarn.lock +++ /dev/null @@ -1,71 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== - -readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -through2@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= diff --git a/scripts/launchPackager.command b/scripts/launchPackager.command index 7feb061ec16674..1c1278deb129a3 100755 --- a/scripts/launchPackager.command +++ b/scripts/launchPackager.command @@ -13,5 +13,7 @@ THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOUR # shellcheck source=/dev/null . "$THIS_DIR/packager.sh" -echo "Process terminated. Press to close the window" -read -r +if [[ -z "$CI" ]]; then + echo "Process terminated. Press to close the window" + read -r +fi diff --git a/scripts/react-native-xcode.sh b/scripts/react-native-xcode.sh index d684e89f8dc99d..3848f0624cdcea 100755 --- a/scripts/react-native-xcode.sh +++ b/scripts/react-native-xcode.sh @@ -63,10 +63,13 @@ cd $PROJECT_ROOT [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" # Define entry file -if [[ -s "index.ios.js" ]]; then - ENTRY_FILE=${1:-index.ios.js} -else - ENTRY_FILE=${1:-index.js} +if [[ "$ENTRY_FILE" ]]; then + # Use ENTRY_FILE defined by user + : +elif [[ -s "index.ios.js" ]]; then + ENTRY_FILE=${1:-index.ios.js} + else + ENTRY_FILE=${1:-index.js} fi if [[ -s "$HOME/.nvm/nvm.sh" ]]; then diff --git a/yarn.lock b/yarn.lock index ef247e613bc189..eb1e1f78133b07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1096,6 +1096,14 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-query@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-0.7.1.tgz#26cbb5aff64144b0a825be1846e0b16cfa00b11e" + integrity sha1-Jsu1r/ZBRLCoJb4YRuCxbPoAsR4= + dependencies: + ast-types-flow "0.0.7" + commander "^2.11.0" + arr-diff@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" @@ -1218,6 +1226,11 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-types-flow@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + ast-types@0.11.6: version "0.11.6" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.6.tgz#4e2266c2658829aef3b40cc33ad599c4e9eb89ef" @@ -1272,6 +1285,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +axobject-query@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0" + integrity sha1-YvWdvFnJ+SQnWco0mWDnov48NsA= + dependencies: + ast-types-flow "0.0.7" + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -1780,7 +1800,7 @@ combined-stream@1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.15.1, commander@^2.9.0: +commander@^2.11.0, commander@^2.15.1, commander@^2.9.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -1957,6 +1977,11 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" +damerau-levenshtein@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" + integrity sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ= + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2183,6 +2208,11 @@ electron-to-chromium@^1.3.92: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.95.tgz#79fac438813ca7f3db182a525c2ab432934f6484" integrity sha512-0JZEDKOQAE05EO/4rk3vLAE+PYFI9OLCVLAS4QAq1y+Bb2y1N6MyQJz62ynzHN/y0Ka/nO5jVJcahbCEdfiXLQ== +emoji-regex@^6.1.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" + integrity sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ== + encodeurl@~1.0.1, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2276,6 +2306,13 @@ eslint-config-fbjs@2.0.1, eslint-config-fbjs@^2.0.1: resolved "https://registry.yarnpkg.com/eslint-config-fbjs/-/eslint-config-fbjs-2.0.1.tgz#395896fd740e0e28dc1c2072e3bc982e88247df5" integrity sha512-nZ/JByixNK/8epeQqmrtNCYYMXCjHoPkJwHaHg4aZyZlS62YLttDSWYE6ISGl070V+o6dkFbDALceWaO3Po+Sw== +eslint-plugin-babel@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.1.0.tgz#9c76e476162041e50b6ba69aa4eae3bdd6a4e1c3" + integrity sha512-HBkv9Q0LU/IhNUauC8TrbhcN79Yq/+xh2bYTOcv6KMaV2tsvVphkHwDTJ9r3C6mJUnmxrtzT3DQfrWj0rOISqQ== + dependencies: + eslint-rule-composer "^0.3.0" + eslint-plugin-eslint-comments@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.0.1.tgz#baafb713584c0de7ee588710720e580bcc28cfbb" @@ -2296,6 +2333,19 @@ eslint-plugin-jest@21.8.0: resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.8.0.tgz#4f3155e2898c1efb0ce548f3e084e07db5e21c07" integrity sha512-u8+tOrVHHAv9SetdzCghLaBrHyaBLx+6surC+A+VfLy7CfXixTvQnJmD/Ym8mDE6e7wPDZWWgsqb3FGAWh7NTw== +eslint-plugin-jsx-a11y@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.3.tgz#54583d1ae442483162e040e13cc31865465100e5" + integrity sha1-VFg9GuRCSDFi4EDhPMMYZUZRAOU= + dependencies: + aria-query "^0.7.0" + array-includes "^3.0.3" + ast-types-flow "0.0.7" + axobject-query "^0.1.0" + damerau-levenshtein "^1.0.0" + emoji-regex "^6.1.0" + jsx-ast-utils "^2.0.0" + eslint-plugin-prettier@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz#33e4e228bdb06142d03c560ce04ec23f6c767dd7" @@ -2331,6 +2381,18 @@ eslint-plugin-react@7.8.2: jsx-ast-utils "^2.0.1" prop-types "^15.6.0" +eslint-plugin-relay@0.0.28: + version "0.0.28" + resolved "https://registry.yarnpkg.com/eslint-plugin-relay/-/eslint-plugin-relay-0.0.28.tgz#dd9ba7dc03fd21f3a30e053d21d90e5bf2ecff34" + integrity sha512-CvyT/WxEQmcUKE4lVqjxgioTj3zSvHnT9bvR4cISgs9j2z4J5Ojsurjcv/kWe4I6gf6L+lV6zcVuZ2LkiRIO6g== + dependencies: + graphql "^14.0.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + eslint-scope@3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" @@ -2663,10 +2725,10 @@ fbjs-css-vars@^1.0.0: resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.1.tgz#836d876e887d702f45610f5ebd2fbeef649527fc" integrity sha512-IM+v/C40MNZWqsLErc32e0TyIk/NhkkQZL0QmjBh6zi1eXv0/GeVKmKmueQX7nn9SXQBQbTUcB8zuexIF3/88w== -fbjs-scripts@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fbjs-scripts/-/fbjs-scripts-1.0.1.tgz#7d8d09d76e83308bf3b1fc7b4c9c6fd081c5ef64" - integrity sha512-x8bfX7k0z5B24Ue0YqjZq/2QxxaKZUNbkGdX//zbQDElMJFqBRrvRi8O3qds7UNNzs78jYqIYCS32Sk/wu5UJg== +fbjs-scripts@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fbjs-scripts/-/fbjs-scripts-1.1.0.tgz#d9e855aed19b572be9dfe39da70d8aece724eed9" + integrity sha512-VMCpHJd76YI2nYOfVM/d9LDAIFTH4uw4/7sAIGEgxk6kaNmirgTY9bLgpla9DTu+DvV2+ufvDxehGbl2U9bYCA== dependencies: "@babel/core" "^7.0.0" ansi-colors "^1.0.1" @@ -3031,6 +3093,13 @@ graceful-fs@^4.1.15: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== +graphql@^14.0.0: + version "14.1.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.1.1.tgz#d5d77df4b19ef41538d7215d1e7a28834619fac0" + integrity sha512-C5zDzLqvfPAgTtP8AUPIt9keDabrdRAqSWjj2OPRKrKxI9Fb65I36s1uCs1UUBFnSWTdO7hyHi7z1ZbwKMKF6Q== + dependencies: + iterall "^1.2.2" + "growl@~> 1.10.0": version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -3615,6 +3684,11 @@ istanbul-reports@^2.1.1: dependencies: handlebars "^4.1.0" +iterall@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" + integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== + jest-changed-files@^24.0.0: version "24.0.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.0.0.tgz#c02c09a8cc9ca93f513166bc773741bd39898ff7" @@ -4164,7 +4238,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jsx-ast-utils@^2.0.1: +jsx-ast-utils@^2.0.0, jsx-ast-utils@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" integrity sha1-6AGxs5mF4g//yHtA43SAgOLcrH8= @@ -5644,10 +5718,10 @@ react-devtools-core@^3.6.0: shell-quote "^1.6.1" ws "^3.3.1" -react-is@^16.8.1: - version "16.8.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.1.tgz#a80141e246eb894824fb4f2901c0c50ef31d4cdb" - integrity sha512-ioMCzVDWvCvKD8eeT+iukyWrBGrA3DiFYkXfBsVYIRdaREZuBjENG+KjrikavCLasozqRWTwFUagU/O4vPpRMA== +react-is@^16.8.3: + version "16.8.3" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.3.tgz#4ad8b029c2a718fc0cfc746c8d4e1b7221e5387d" + integrity sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA== react-native-dummy@0.2.0: version "0.2.0" @@ -5662,15 +5736,15 @@ react-proxy@^1.1.7: lodash "^4.6.1" react-deep-force-update "^1.0.0" -react-test-renderer@16.8.1: - version "16.8.1" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.1.tgz#72845ad9269be526126e97853311982f781767be" - integrity sha512-Bd21TN3+YVl6GZwav6O0T6m5UwGfOj+2+xZH5VH93ToD6M5uclN/c+R1DGX49ueG413KZPUx7Kw3sOYz2aJgfg== +react-test-renderer@16.8.3: + version "16.8.3" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.3.tgz#230006af264cc46aeef94392e04747c21839e05e" + integrity sha512-rjJGYebduKNZH0k1bUivVrRLX04JfIQ0FKJLPK10TAb06XWhfi4gTobooF9K/DEFNW98iGac3OSxkfIJUN9Mdg== dependencies: object-assign "^4.1.1" prop-types "^15.6.2" - react-is "^16.8.1" - scheduler "^0.13.1" + react-is "^16.8.3" + scheduler "^0.13.3" react-transform-hmr@^1.0.4: version "1.0.4" @@ -5680,15 +5754,15 @@ react-transform-hmr@^1.0.4: global "^4.3.0" react-proxy "^1.1.7" -react@16.8.1: - version "16.8.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.1.tgz#ae11831f6cb2a05d58603a976afc8a558e852c4a" - integrity sha512-wLw5CFGPdo7p/AgteFz7GblI2JPOos0+biSoxf1FPsGxWQZdN/pj6oToJs1crn61DL3Ln7mN86uZ4j74p31ELQ== +react@16.8.3: + version "16.8.3" + resolved "https://registry.yarnpkg.com/react/-/react-16.8.3.tgz#c6f988a2ce895375de216edcfaedd6b9a76451d9" + integrity sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.1" + scheduler "^0.13.3" read-pkg-up@^2.0.0: version "2.0.0" @@ -6088,10 +6162,10 @@ sax@~1.1.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.6.tgz#5d616be8a5e607d54e114afae55b7eaf2fcc3240" integrity sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA= -scheduler@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.1.tgz#1a217df1bfaabaf4f1b92a9127d5d732d85a9591" - integrity sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A== +scheduler@^0.13.3: + version "0.13.3" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.3.tgz#bed3c5850f62ea9c716a4d781f9daeb9b2a58896" + integrity sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1"