Compare commits

..
Author SHA1 Message Date
Nicola Corti a429f4169c Fix tests - take 4 2024-11-05 14:15:39 +00:00
Nicola Corti 05f073d346 Fix tests - take 3 2024-11-05 14:11:50 +00:00
Nicola Corti a0ca57538f Fix tests - take 2 2024-11-05 13:49:14 +00:00
Nicola Corti 292f3a4cab Fix jest tests 2024-11-05 11:49:39 +00:00
Riccardo CipolleschiandNicola Corti 68231cb949 [LOCAL] Revert React 19 to React 18.3.1 2024-11-05 11:49:39 +00:00
491 changed files with 7018 additions and 9592 deletions
+1 -6
View File
@@ -26,9 +26,6 @@
; helloworld
<PROJECT_ROOT>/packages/helloworld/ios/Pods/
; Ignore rn-tester Pods
<PROJECT_ROOT>/packages/rn-tester/Pods/
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
@@ -72,8 +69,6 @@ suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FlowFixMeEmpty
ban_spread_key_props=true
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
@@ -95,4 +90,4 @@ untyped-import
untyped-type-import
[version]
^0.253.0
^0.251.1
@@ -43,7 +43,7 @@ runs:
shell: powershell
run: |
if (-not(Test-Path -Path $Env:HERMES_WS_DIR\win64-bin\hermesc.exe)) {
choco install --no-progress cmake --version 3.14.7 --allow-downgrade
choco install --no-progress cmake --version 3.14.7
if (-not $?) { throw "Failed to install CMake" }
cd $Env:HERMES_WS_DIR\icu
@@ -3,6 +3,9 @@ description: Prepare iOS Tests
runs:
using: composite
steps:
- name: brew install xcbeautify
run: brew install xcbeautify
shell: bash
- name: Run Ruby Tests
shell: bash
run: |
+4 -4
View File
@@ -121,21 +121,21 @@ runs:
if: ${{ inputs.run-unit-tests != 'true' && inputs.run-e2e-tests == 'false' }}
shell: bash
run: |
xcodebuild build \
set -o pipefail && xcodebuild build \
-workspace packages/rn-tester/RNTesterPods.xcworkspace \
-scheme RNTester \
-sdk iphonesimulator
-sdk iphonesimulator | xcbeautify
- name: Build RNTester (E2E Tests)
shell: bash
if: ${{ inputs.run-e2e-tests == 'true' }}
run: |
xcodebuild \
set -o pipefail && xcodebuild \
-scheme "RNTester" \
-workspace packages/rn-tester/RNTesterPods.xcworkspace \
-configuration "${{ inputs.flavor }}" \
-sdk "iphonesimulator" \
-destination "generic/platform=iOS Simulator" \
-derivedDataPath "/tmp/RNTesterBuild"
-derivedDataPath "/tmp/RNTesterBuild" | xcbeautify
echo "Print path to *.app file"
find "/tmp/RNTesterBuild" -type d -name "*.app"
@@ -1,46 +0,0 @@
name: test-library-on-nightly
description: Tests a library on a nightly
inputs:
library-npm-package:
description: The library npm package to add
required: true
platform:
description: whether we want to build for iOS or Android
required: true
runs:
using: composite
steps:
- name: Create new app
shell: bash
run: |
cd /tmp
npx @react-native-community/cli init RNApp --skip-install --version nightly
- name: Add library
shell: bash
run: |
cd /tmp/RNApp
yarn add ${{ inputs.library-npm-package }}
- name: Build iOS
shell: bash
if: ${{ inputs.platform == 'ios' }}
run: |
cd /tmp/RNApp/ios
bundle install
bundle exec pod install
xcodebuild build \
-workspace RNApp.xcworkspace \
-scheme RNApp \
-sdk iphonesimulator
- name: Setup Java for Android
if: ${{ inputs.platform == 'android' }}
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'zulu'
- name: Build Android
shell: bash
if: ${{ inputs.platform == 'android' }}
run: |
cd /tmp/RNApp/android
./gradlew assembleDebug
+41 -29
View File
@@ -48,84 +48,96 @@ module.exports = async (github, context, labelWithContext) => {
switch (labelWithContext.label) {
case 'Type: Invalid':
await addComment(
`> [!CAUTION]\n` +
`> **Invalid issue**: This issue is not valid, either is not a bug in React Native, it doesn't match any of the issue template, or we can't help further with this.`,
`| :warning: | Issue is Invalid |\n` +
`| --- | --- |\n` +
`| :information_source: | This issue doesn't match any of the expected types for this repository - closing. |`,
);
await closeIssue();
return;
case 'Type: Question':
await addComment(
`> [!NOTE]\n` +
`> **Not a bug report**: This issue looks like a question. We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/).`,
);
await closeIssue();
return;
case 'Resolution: For Stack Overflow':
await addComment(
`> [!NOTE]\n` +
`> **Not a bug report**: This issue looks like a question. We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/).`,
`| :warning: | Issue is a Question |\n` +
`| --- | --- |\n` +
`| :information_source: | We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/). |`,
);
await closeIssue();
return;
case 'Type: Docs':
await addComment(
`> [!NOTE]\n` +
`> **Docs issue**: This issue looks like an issue related to our docs. Please report documentation issues in the [react-native-website](https://github.com/facebook/react-native-website/issues) repository.`,
`| :warning: | Documentation Issue |\n` +
`| --- | --- |\n` +
`| :information_source: | Please report documentation issues in the [react-native-website](https://github.com/facebook/react-native-website/issues) repository. |`,
);
await closeIssue();
return;
case 'Resolution: For Stack Overflow':
await addComment(
`| :warning: | Issue is a Question |\n` +
`| --- | --- |\n` +
`| :information_source: | We are using GitHub issues exclusively to track bugs in the core React Native library. Please try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) as it is better suited for this type of question. |`,
);
await closeIssue();
return;
case 'Type: Expo':
await addComment(
`> [!NOTE]\n` +
`> **Expo related**: It looks like your issue is related to Expo and not React Native core. Please open your issue in [Expo's repository](https://github.com/expo/expo/issues/new). If you are able to create a repro that showcases that this issue is also happening in React Native vanilla, we will be happy to re-open.`,
`| :warning: | Issue is Related to Expo |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue is related to Expo and not React Native core. Please open your issue in [Expo's repository](https://github.com/expo/expo/issues/new). If you are able to create a repro that showcases that this issue is also happening in React Native vanilla, we will be happy to re-open. |`,
);
await closeIssue();
return;
case 'Needs: Issue Template':
await addComment(
`> [!WARNING]\n` +
`> **Missing issue template**: It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a [new issue is created](https://github.com/facebook/react-native/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A&projects=&template=bug_report.yml). Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one.`,
`| :warning: | Missing Required Fields |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a [new issue is created](https://github.com/facebook/react-native/issues/new?template=bug_report.md). Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one. |`,
);
await requestAuthorFeedback();
return;
case 'Needs: Environment Info':
await addComment(
`> [!WARNING]\n` +
`> **Missing info**: It looks like your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console.`,
`| :warning: | Missing Environment Information |\n` +
`| --- | --- |\n` +
`| :information_source: | Your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console. |`,
);
await requestAuthorFeedback();
return;
case 'Newer Patch Available':
await addComment(
`> [!TIP]\n` +
`> **Newer version available**: You are on a supported minor version, but it looks like there's a newer patch available - ${labelWithContext.newestPatch}. Please [upgrade](https://reactnative.dev/docs/upgrading) to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.`,
`| :warning: | Newer Version of React Native is Available! |\n` +
`| --- | --- |\n` +
`| :information_source: | You are on a supported minor version, but it looks like there's a newer patch available - ${labelWithContext.newestPatch}. Please [upgrade](https://reactnative.dev/docs/upgrading) to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases. |`,
);
return;
case 'Needs: Version Info':
await addComment(
`> [!WARNING]\n` +
`> **Could not parse version**: We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.76.2.`,
`| :warning: | Add or Reformat Version Info |\n` +
`| --- | --- |\n` +
`| :information_source: | We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.70.2 |`,
);
await requestAuthorFeedback();
return;
case 'Needs: Repro':
await addComment(
`> [!WARNING]\n` +
`> **Missing reproducer**: We could not detect a reproducible example in your issue report. Please provide either: <br/><ul><li>If your bug is UI related: a [Snack](https://snack.expo.dev)</li><li> If your bug is build/upgrade related: a project using our [Reproducer Template](https://github.com/react-native-community/reproducer-react-native/generate)</li><li>Otherwise send us a Pull Request with the [RNTesterPlayground.js](https://github.com/facebook/react-native/blob/main/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js) edited to reproduce your bug.</li></ul>`,
`| :warning: | Missing Reproducible Example |\n` +
`| --- | --- |\n` +
`| :information_source: | We could not detect a reproducible example in your issue report. Please provide either: <br /><ul><li>If your bug is UI related: a [Snack](https://snack.expo.dev)</li><li> If your bug is build/update related: use our [Reproducer Template](https://github.com/react-native-community/reproducer-react-native/generate)</li></ul> |`,
);
await requestAuthorFeedback();
return;
case 'Type: Unsupported Version':
await addComment(
`> [!WARNING]\n` +
`> **Unsupported version**: It looks like your issue or the example you provided uses an [unsupported version of React Native](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support.`,
`| :warning: | Unsupported Version of React Native |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue or the example you provided uses an [unsupported version of React Native](https://github.com/reactwg/react-native-releases/blob/main/README.md#releases-support-policy).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support. |`,
);
await requestAuthorFeedback();
return;
case 'Type: Too Old Version':
await addComment(
`> [!CAUTION]\n` +
`> **Too old version**: It looks like your issue or the example you provided uses a [**Too Old Version of React Native**](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support.`,
`| :warning: | Too Old Version of React Native |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue or the example you provided uses a [**Too Old Version of React Native**](https://github.com/reactwg/react-native-releases/blob/main/README.md#releases-support-policy).<br/><br/>Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support. |`,
);
await closeIssue();
return;
+25 -1
View File
@@ -9,6 +9,11 @@
const NEEDS_REPRO_LABEL = 'Needs: Repro';
const NEEDS_AUTHOR_FEEDBACK_LABEL = 'Needs: Author Feedback';
const NEEDS_REPRO_HEADER = 'Missing Reproducible Example';
const NEEDS_REPRO_MESSAGE =
`| :warning: | Missing Reproducible Example |\n` +
`| --- | --- |\n` +
`| :information_source: | We could not detect a reproducible example in your issue report. Please provide either: <br /><ul><li>If your bug is UI related: a [Snack](https://snack.expo.dev)</li><li> If your bug is build/update related: use our [Reproducer Template](https://github.com/react-native-community/reproducer-react-native/generate). A reproducer needs to be in a GitHub repository under your username.</li></ul> |`;
const SKIP_ISSUES_OLDER_THAN = '2023-07-01T00:00:00Z';
module.exports = async (github, context) => {
@@ -20,6 +25,7 @@ module.exports = async (github, context) => {
const issue = await github.rest.issues.get(issueData);
const comments = await github.rest.issues.listComments(issueData);
const author = issue.data.user.login;
const issueDate = issue.data.created_at;
@@ -37,6 +43,10 @@ module.exports = async (github, context) => {
return;
}
const botComment = comments.data.find(comment =>
comment.body.includes(NEEDS_REPRO_HEADER),
);
const entities = [issue.data, ...comments.data];
// Look for Snack or a GH repo associated with the user that added an issue or comment
@@ -64,11 +74,25 @@ module.exports = async (github, context) => {
throw error;
}
}
if (!botComment) return;
await github.rest.issues.deleteComment({
...issueData,
comment_id: botComment.id,
});
} else {
await github.rest.issues.addLabels({
...issueData,
labels: [NEEDS_REPRO_LABEL, NEEDS_AUTHOR_FEEDBACK_LABEL],
});
if (botComment) return;
await github.rest.issues.createComment({
...issueData,
body: NEEDS_REPRO_MESSAGE,
});
}
};
@@ -77,7 +101,7 @@ function containsPattern(body, pattern) {
return body.search(regexp) !== -1;
}
// Prevents the bot from responding when maintainer has changed the 'Needs: Repro' label
// Prevents the bot from responding when maintainer has changed Needs: Repro the label
async function hasMaintainerChangedLabel(github, issueData, author) {
const timeline = await github.rest.issues.listEventsForTimeline(issueData);
@@ -10,6 +10,11 @@
module.exports = async (github, context) => {
const issue = context.payload.issue;
// Ignore issues using upgrade template (they use a special label)
if (issue.labels.find(label => label.name === 'Type: Upgrade Issue')) {
return;
}
const issueVersionUnparsed =
getReactNativeVersionFromIssueBodyIfExists(issue);
const issueVersion = parseVersionFromString(issueVersionUnparsed);
-6
View File
@@ -13,8 +13,6 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check nightly
run: |
TODAY=$(date "+%Y%m%d")
@@ -26,7 +24,3 @@ jobs:
else
echo 'Nightly Worked, All Good!'
fi
test-libraries:
uses: ./.github/workflows/test-libraries-on-nightlies.yml
needs: check-nightly
@@ -1,75 +0,0 @@
name: Test Libraries on Nightlies
on:
workflow_call:
jobs:
test-library-on-nightly-android:
name: "[Android] ${{ matrix.library }}"
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
library: [
"react-native-async-storage",
"react-native-blob-util",
"@react-native-clipboard/clipboard",
"@react-native-community/datetimepicker",
"react-native-gesture-handler",
"react-native-image-picker",
"react-native-linear-gradient",
"@react-native-masked-view/masked-view",
"react-native-maps",
"@react-native-community/netinfo",
"react-native-reanimated",
"react-native-svg",
"react-native-video",
"react-native-webview",
"react-native-mmkv",
"react-native-screens",
"react-native-pager-view",
"@react-native-community/slider"
]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Test ${{ inputs.library-name }}
uses: ./.github/actions/test-library-on-nightly
with:
library-npm-package: ${{ matrix.library }}
platform: android
test-library-on-nightly-ios:
name: "[iOS] ${{ matrix.library }}"
runs-on: macos-13-large
continue-on-error: true
strategy:
matrix:
library: [
"react-native-async-storage",
"react-native-blob-util",
"@react-native-clipboard/clipboard",
"@react-native-community/datetimepicker",
"react-native-gesture-handler",
"react-native-image-picker",
"react-native-linear-gradient",
"@react-native-masked-view/masked-view",
"react-native-maps",
"@react-native-community/netinfo",
"react-native-reanimated",
"react-native-svg",
"react-native-video",
"react-native-webview",
"react-native-mmkv",
"react-native-screens",
"react-native-pager-view",
"@react-native-community/slider"
]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Test ${{ inputs.library-name }}
uses: ./.github/actions/test-library-on-nightly
with:
library-npm-package: ${{ matrix.library }}
platform: ios
+1 -4
View File
@@ -124,7 +124,7 @@ vendor/
!/packages/rn-tester/Pods/__offline_mirrors_jsc__
# @react-native/codegen
/packages/react-native/React/FBReactNativeSpec/
/packages/react-native/React/FBReactNativeSpec/FBReactNativeSpec
/packages/react-native-codegen/lib
/packages/react-native-codegen/tmp/
/packages/react-native/ReactCommon/react/renderer/components/rncore/
@@ -155,6 +155,3 @@ vendor/
# CircleCI
.circleci/generated_config.yml
# Jest Integration
/jest/integration/build/
-50
View File
@@ -1,40 +1,5 @@
# Changelog
## v0.76.2
### Added
- **TypeScript** Add CodegenTypes for TS ([20b141508b](https://github.com/facebook/react-native/commit/20b141508b30324d52080c255dd3fb318718d746) by [@cipolleschi](https://github.com/cipolleschi))
### Changed
- **infra** Bump CLI to 15.0.1 ([51b98c24bd](https://github.com/facebook/react-native/commit/51b98c24bdc6369ce6fabcacaf8df2d2a706eada) by [@szymonrybczak](https://github.com/szymonrybczak))
#### iOS specific
- **TextInput** Include existing attributes in newly typed text ([557e3447f5](https://github.com/facebook/react-native/commit/557e3447f520e40a1ec0ae344126b4f2836d2e83) by [@NickGerleman](https://github.com/NickGerleman))
### Fixed
- **Hermes** Update Hermes to support Intl ([94d4bfd7c8](https://github.com/facebook/react-native/commit/94d4bfd7c80ba0d55adbff656441b55d59055bcc) by [@blakef](https://github.com/blakef))
- **infra** Skip hermes-parser under Babel for non-Flow JS code ([ff1261e7dc](https://github.com/facebook/react-native/commit/ff1261e7dc0ab7e241e1f14aa0d6fd17f2ba9328) by [@huntie](https://github.com/huntie))
- **infra** fix `semver` not being found in pnpm setups ([0def73d1a6](https://github.com/facebook/react-native/commit/0def73d1a6e398d451585032ea1213f96d84fe9c) by [@tido64](https://github.com/tido64))
- **Error Handling** Fix `setUpErrorHandling` to show early JS errors ([dac6d508af](https://github.com/facebook/react-native/commit/dac6d508afd0f919943d8053330d6314201319c3) by [@cipolleschi](https://github.com/cipolleschi))
#### Android specific
- **infra** Use absolute path when compiling appmodules.so sources ([3956955eaa](https://github.com/facebook/react-native/commit/3956955eaa3cd8c50dfe35a68a6cb8fdcac43155) by [@cortinico](https://github.com/cortinico))
- **infra** Properly handle paths with spaces in autolinking ([1f62529dc4](https://github.com/facebook/react-native/commit/1f62529dc4583af88ef06bee04c89ce6c2ef737f) by [@cortinico](https://github.com/cortinico))
- **Modal** Fix Regression - Modal content rendering below system bar on < API 30 when activity is edge-to-edge ([2cd48ef351](https://github.com/facebook/react-native/commit/2cd48ef351d10333a14091188bbe8e3bcd6a7a01) by [@alanleedev](https://github.com/alanleedev))
- **runtime** Fix timers in headless tasks on bridgeless mode ([ee7b4e2763](https://github.com/facebook/react-native/commit/ee7b4e276355146be53958b402bfb2d5af2dd1bc) by [@j-piasecki](https://github.com/j-piasecki))
#### iOS specific
- **Codegen** Properly stop generating component registration for components defined in app. ([97a4234b6e](https://github.com/facebook/react-native/commit/97a4234b6e51b3c35c82095029ef00270ad02e29) by [@cipolleschi](https://github.com/cipolleschi))
- **infra** Give apps access to Yoga headers ([e851e73c18](https://github.com/facebook/react-native/commit/e851e73c1806a7b7b898a67716be87f42ced491a) by [@cipolleschi](https://github.com/cipolleschi))
- **TextInput** Fix missing emitter attributes on iOS TextInput when controlled component value specified using `value` instead of `children` ([52cdedb40e](https://github.com/facebook/react-native/commit/52cdedb40e242c9ed280b821f8493a3872ef2b54) by [@NickGerleman](https://github.com/NickGerleman))
- **TextInput** Fix cursor moving in iOS controlled single line TextInput on Autocorrection (New Arch) ([36fd5533f6](https://github.com/facebook/react-native/commit/36fd5533f68b0f907a949db87884b58820015ba8) by [@NickGerleman](https://github.com/NickGerleman))
## v0.76.1
### Fixed
@@ -1441,21 +1406,6 @@ created on the mqt_native thread. ([c4a6bbc8fd](https://github.com/facebook/reac
- Bump activesupport to minimum 6.1.7.5 CVE-2023-38037. ([07a159f279](https://github.com/facebook/react-native/commit/07a159f279cdcbed29c9c437dec1c0b8ac2d852f) by [@lunaleaps](https://github.com/lunaleaps))
## v0.73.11
### Fixed
#### Android specific
- Suppress path adjustment when not actually drawing a border ([c9cf6d4b60](https://github.com/facebook/react-native/commit/c9cf6d4b60b6c5f717b3e5c9f3e3720e8d588707) by [@tjzel](https://github.com/tjzel))
#### iOS specific
- Stop generating dSYM path in Hermes for the framework ([e992405e87](https://github.com/facebook/react-native/commit/e992405e87) by [@cipolleschi](https://github.com/cipolleschi))
- Pin Xcodeproj to < 1.26.0 ([dfcad7c678](https://github.com/facebook/react-native/commit/dfcad7c678) by [@cipolleschi](https://github.com/cipolleschi))
## v0.73.10
### Removed
-17
View File
@@ -1,17 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
*/
declare module 'deep-equal' {
declare module.exports: (
actual: mixed,
expected: mixed,
options?: {strict: boolean},
) => boolean;
}
+39
View File
@@ -0,0 +1,39 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @format
* @oncall react_native
*/
declare module '@pkgjs/parseargs' {
declare export function parseArgs<
TOptions: {[string]: util$ParseArgsOption} = {},
>(config: {
args?: Array<string>,
options?: TOptions,
strict?: boolean,
allowPositionals?: boolean,
tokens?: false,
}): {
values: util$ParseArgsOptionsToValues<TOptions>,
positionals: Array<string>,
};
declare export function parseArgs<
TOptions: {[string]: util$ParseArgsOption} = {},
>(config: {
args?: Array<string>,
options?: TOptions,
strict?: boolean,
allowPositionals?: boolean,
tokens: true,
}): {
values: util$ParseArgsOptionsToValues<TOptions>,
positionals: Array<string>,
tokens: Array<util$ParseArgsToken>,
};
}
-20
View File
@@ -16,13 +16,6 @@ declare interface undici$Agent$Options {
}
declare module 'undici' {
declare export type RequestOptions = $ReadOnly<{
dispatcher?: Dispatcher,
method?: string,
headers?: HeadersInit,
...
}>;
declare export class Dispatcher extends events$EventEmitter {
constructor(): void;
}
@@ -30,17 +23,4 @@ declare module 'undici' {
declare export class Agent extends Dispatcher {
constructor(opts?: undici$Agent$Options): void;
}
declare export function request(
url: string | URL,
options: RequestOptions,
): Promise<{
statusCode: number,
headers: Headers,
body: {
read(): Promise<Buffer>,
...
},
...
}>;
}
-30
View File
@@ -1,30 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 crypto = require('crypto');
const fs = require('fs');
module.exports = {
getHasteName(filePath) {
if (filePath.endsWith('ReactNativeInternalFeatureFlags.js')) {
return 'ReactNativeInternalFeatureFlags';
}
return null;
},
getCacheKey() {
return crypto
.createHash('sha1')
.update(fs.readFileSync(__filename))
.digest('hex');
},
};
-28
View File
@@ -1,28 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 baseConfig = require('../../../jest.config');
const path = require('path');
module.exports = {
rootDir: path.resolve(__dirname, '../../..'),
roots: [
'<rootDir>/packages/react-native',
'<rootDir>/jest/integration/runtime',
],
moduleFileExtensions: [...baseConfig.moduleFileExtensions, 'cpp', 'h'],
// This allows running Meta-internal tests with the `-test.fb.js` suffix.
testRegex: '/__tests__/.*-itest(\\.fb)?\\.js$',
testPathIgnorePatterns: baseConfig.testPathIgnorePatterns,
transformIgnorePatterns: ['.*'],
testRunner: './jest/integration/runner/index.js',
watchPathIgnorePatterns: ['<rootDir>/jest/integration/build/'],
};
@@ -1,13 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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';
require('../../../scripts/build/babel-register').registerForMonorepo();
module.exports = require('@react-native/metro-babel-transformer');
-49
View File
@@ -1,49 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 {getDefaultConfig} = require('@react-native/metro-config');
const {mergeConfig} = require('metro-config');
const path = require('path');
const rnTesterConfig = getDefaultConfig(
path.resolve('../../../packages/rn-tester'),
);
const config = {
projectRoot: path.resolve(__dirname, '../../..'),
reporter: {
update: () => {},
},
resolver: {
blockList: /\/RendererProxy\.fb\.js$/, // Disable dependency injection for the renderer
disableHierarchicalLookup: !!process.env.JS_DIR,
sourceExts: ['fb.js', ...rnTesterConfig.resolver.sourceExts],
nodeModulesPaths: process.env.JS_DIR
? [path.join(process.env.JS_DIR, 'public', 'node_modules')]
: [],
hasteImplModulePath: path.resolve(__dirname, 'hasteImpl.js'),
},
transformer: {
// We need to wrap the default transformer so we can run it from source
// using babel-register.
babelTransformerPath: path.resolve(__dirname, 'metro-babel-transformer.js'),
},
watchFolders: process.env.JS_DIR
? [
path.join(process.env.JS_DIR, 'RKJSModules', 'vendor', 'react'),
path.join(process.env.JS_DIR, 'tools', 'metro'),
path.join(process.env.JS_DIR, 'node_modules'),
path.join(process.env.JS_DIR, 'public', 'node_modules'),
]
: [],
};
module.exports = mergeConfig(rnTesterConfig, config);
@@ -1,35 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
module.exports = function entrypointTemplate({
testPath,
setupModulePath,
}: {
testPath: string,
setupModulePath: string,
}): string {
return `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* ${'@'}generated
* @noformat
* @noflow
* @oncall react_native
*/
import {registerTest} from '${setupModulePath}';
registerTest(() => require('${testPath}'));
`;
};
-13
View File
@@ -1,13 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
require('../../../scripts/build/babel-register').registerForMonorepo();
module.exports = require('./runner');
-290
View File
@@ -1,290 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {TestSuiteResult} from '../runtime/setup';
import entrypointTemplate from './entrypoint-template';
import {spawnSync} from 'child_process';
import crypto from 'crypto';
import fs from 'fs';
// $FlowExpectedError[untyped-import]
import {formatResultsErrors} from 'jest-message-util';
import Metro from 'metro';
import nullthrows from 'nullthrows';
import os from 'os';
import path from 'path';
const BUILD_OUTPUT_PATH = path.resolve(__dirname, '..', 'build');
const ENABLE_OPTIMIZED_MODE: false = false;
const PRINT_FANTOM_OUTPUT: false = false;
function parseRNTesterCommandResult(
commandArgs: $ReadOnlyArray<string>,
result: ReturnType<typeof spawnSync>,
): {logs: string, testResult: TestSuiteResult} {
const stdout = result.stdout.toString();
const outputArray = stdout
.trim()
.split('\n')
.filter(log => !log.startsWith('Running "')); // remove AppRegistry logs.
// The last line should be the test output in JSON format
const testResultJSON = outputArray.pop();
let testResult;
try {
testResult = JSON.parse(nullthrows(testResultJSON));
} catch (error) {
throw new Error(
[
'Failed to parse test results from RN tester binary result. Full output:',
'buck2 ' + commandArgs.join(' '),
'stdout:',
stdout,
'stderr:',
result.stderr.toString(),
].join('\n'),
);
}
return {logs: outputArray.join('\n'), testResult};
}
function getBuckModeForPlatform() {
const mode = ENABLE_OPTIMIZED_MODE ? 'opt' : 'dev';
switch (os.platform()) {
case 'linux':
return `@//arvr/mode/linux/${mode}`;
case 'darwin':
return os.arch() === 'arm64'
? `@//arvr/mode/mac-arm/${mode}`
: `@//arvr/mode/mac/${mode}`;
case 'win32':
return `@//arvr/mode/win/${mode}`;
default:
throw new Error(`Unsupported platform: ${os.platform()}`);
}
}
function getShortHash(contents: string): string {
return crypto.createHash('md5').update(contents).digest('hex').slice(0, 8);
}
function generateBytecodeBundle({
sourcePath,
bytecodePath,
}: {
sourcePath: string,
bytecodePath: string,
}): void {
const hermesCompilerCommandArgs = [
'run',
getBuckModeForPlatform(),
'//xplat/hermes/tools/hermesc:hermesc',
'--',
'-emit-binary',
'-O',
'-max-diagnostic-width',
'80',
'-out',
bytecodePath,
sourcePath,
];
const hermesCompilerCommandResult = spawnSync(
'buck2',
hermesCompilerCommandArgs,
{
encoding: 'utf8',
env: {
...process.env,
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
},
},
);
if (hermesCompilerCommandResult.status !== 0) {
throw new Error(
[
'Failed to run Hermes compiler. Full output:',
'buck2 ' + hermesCompilerCommandArgs.join(' '),
'stdout:',
hermesCompilerCommandResult.stdout,
'stderr:',
hermesCompilerCommandResult.stderr,
'error:',
hermesCompilerCommandResult.error,
].join('\n'),
);
}
}
module.exports = async function runTest(
globalConfig: {...},
config: {...},
environment: {...},
runtime: {...},
testPath: string,
): mixed {
const startTime = Date.now();
const isOptimizedMode = ENABLE_OPTIMIZED_MODE;
const metroConfig = await Metro.loadConfig({
config: path.resolve(__dirname, '..', 'config', 'metro.config.js'),
});
const setupModulePath = path.resolve(__dirname, '../runtime/setup.js');
const entrypointContents = entrypointTemplate({
testPath: `${path.relative(BUILD_OUTPUT_PATH, testPath)}`,
setupModulePath: `${path.relative(BUILD_OUTPUT_PATH, setupModulePath)}`,
});
const entrypointPath = path.join(
BUILD_OUTPUT_PATH,
`${getShortHash(entrypointContents)}-${path.basename(testPath)}`,
);
const testBundlePath = entrypointPath + '.bundle';
const testJSBundlePath = testBundlePath + '.js';
const testBytecodeBundlePath = testJSBundlePath + '.hbc';
fs.mkdirSync(path.dirname(entrypointPath), {recursive: true});
fs.writeFileSync(entrypointPath, entrypointContents, 'utf8');
await Metro.runBuild(metroConfig, {
entry: entrypointPath,
out: testJSBundlePath,
platform: 'android',
minify: isOptimizedMode,
dev: !isOptimizedMode,
});
if (isOptimizedMode) {
generateBytecodeBundle({
sourcePath: testJSBundlePath,
bytecodePath: testBytecodeBundlePath,
});
}
const rnTesterCommandArgs = [
'run',
getBuckModeForPlatform(),
'//xplat/ReactNative/react-native-cxx/samples/tester:tester',
'--',
'--bundlePath',
testBundlePath,
];
const rnTesterCommandResult = spawnSync('buck2', rnTesterCommandArgs, {
encoding: 'utf8',
env: {
...process.env,
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
},
});
if (rnTesterCommandResult.status !== 0) {
throw new Error(
[
'Failed to run test in RN tester binary. Full output:',
'buck2 ' + rnTesterCommandArgs.join(' '),
'stdout:',
rnTesterCommandResult.stdout,
'stderr:',
rnTesterCommandResult.stderr,
'error:',
rnTesterCommandResult.error,
].join('\n'),
);
}
if (PRINT_FANTOM_OUTPUT) {
console.log(
[
'RN tester binary. Full output:',
'buck2 ' + rnTesterCommandArgs.join(' '),
'stdout:',
rnTesterCommandResult.stdout,
'stderr:',
rnTesterCommandResult.stderr,
'error:',
rnTesterCommandResult.error,
].join('\n'),
);
}
const rnTesterParsedOutput = parseRNTesterCommandResult(
rnTesterCommandArgs,
rnTesterCommandResult,
);
const testResultError = rnTesterParsedOutput.testResult.error;
if (testResultError) {
const error = new Error(testResultError.message);
error.stack = testResultError.stack;
throw error;
}
const endTime = Date.now();
if (process.env.SANDCASTLE == null) {
console.log(rnTesterParsedOutput.logs);
}
const testResults =
nullthrows(rnTesterParsedOutput.testResult.testResults).map(testResult => ({
ancestorTitles: [] as Array<string>,
failureDetails: [] as Array<string>,
testFilePath: testPath,
...testResult,
})) ?? [];
return {
testFilePath: testPath,
failureMessage: formatResultsErrors(
testResults,
config,
globalConfig,
testPath,
),
leaks: false,
openHandles: [],
perfStats: {
start: startTime,
end: endTime,
duration: endTime - startTime,
runtime: endTime - startTime,
slow: false,
},
snapshot: {
added: 0,
fileDeleted: false,
matched: 0,
unchecked: 0,
uncheckedKeys: [],
unmatched: 0,
updated: 0,
},
numTotalTests: testResults.length,
numPassingTests: testResults.filter(test => test.status === 'passed')
.length,
numFailingTests: testResults.filter(test => test.status === 'failed')
.length,
numPendingTests: testResults.filter(test => test.status === 'pending')
.length,
numTodoTests: 0,
skipped: false,
testResults,
};
};
-370
View File
@@ -1,370 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import deepEqual from 'deep-equal';
import nullthrows from 'nullthrows';
export type TestCaseResult = {
ancestorTitles: Array<string>,
title: string,
fullName: string,
status: 'passed' | 'failed' | 'pending',
duration: number,
failureMessages: Array<string>,
numPassingAsserts: number,
// location: string,
};
export type TestSuiteResult =
| {
testResults: Array<TestCaseResult>,
}
| {
error: {
message: string,
stack: string,
},
};
const tests: Array<{
title: string,
ancestorTitles: Array<string>,
implementation: () => mixed,
isFocused: boolean,
isSkipped: boolean,
result?: TestCaseResult,
}> = [];
const ancestorTitles: Array<string> = [];
const globalModifiers: Array<'focused' | 'skipped'> = [];
const globalDescribe = (global.describe = (
title: string,
implementation: () => mixed,
) => {
ancestorTitles.push(title);
implementation();
ancestorTitles.pop();
});
const globalIt =
(global.it =
global.test =
(title: string, implementation: () => mixed) =>
tests.push({
title,
implementation,
ancestorTitles: ancestorTitles.slice(),
isFocused:
globalModifiers.length > 0 &&
globalModifiers[globalModifiers.length - 1] === 'focused',
isSkipped:
globalModifiers.length > 0 &&
globalModifiers[globalModifiers.length - 1] === 'skipped',
}));
// $FlowExpectedError[prop-missing]
global.fdescribe = global.describe.only = (
title: string,
implementation: () => mixed,
) => {
globalModifiers.push('focused');
globalDescribe(title, implementation);
globalModifiers.pop();
};
// $FlowExpectedError[prop-missing]
global.it.only =
global.fit =
// $FlowExpectedError[prop-missing]
global.test.only =
(title: string, implementation: () => mixed) => {
globalModifiers.push('focused');
globalIt(title, implementation);
globalModifiers.pop();
};
// $FlowExpectedError[prop-missing]
global.xdescribe = global.describe.skip = (
title: string,
implementation: () => mixed,
) => {
globalModifiers.push('skipped');
globalDescribe(title, implementation);
globalModifiers.pop();
};
// $FlowExpectedError[prop-missing]
global.it.skip =
global.xit =
// $FlowExpectedError[prop-missing]
global.test.skip =
global.xtest =
(title: string, implementation: () => mixed) => {
globalModifiers.push('skipped');
globalIt(title, implementation);
globalModifiers.pop();
};
global.jest = {
fn: createMockFunction,
};
const MOCK_FN_TAG = Symbol('mock function');
function createMockFunction<TArgs: $ReadOnlyArray<mixed>, TReturn>(
initialImplementation?: (...TArgs) => TReturn,
): JestMockFn<TArgs, TReturn> {
let implementation: ?(...TArgs) => TReturn = initialImplementation;
const mock: JestMockFn<TArgs, TReturn>['mock'] = {
calls: [],
// $FlowExpectedError[incompatible-type]
lastCall: undefined,
instances: [],
contexts: [],
results: [],
};
const mockFunction = function (this: mixed, ...args: TArgs): TReturn {
let result: JestMockFn<TArgs, TReturn>['mock']['results'][number] = {
isThrow: false,
// $FlowExpectedError[incompatible-type]
value: undefined,
};
if (implementation != null) {
try {
result.value = implementation.apply(this, args);
} catch (error) {
result.isThrow = true;
result.value = error;
}
}
mock.calls.push(args);
mock.lastCall = args;
// $FlowExpectedError[incompatible-call]
mock.instances.push(new.target ? this : undefined);
mock.contexts.push(this);
mock.results.push(result);
if (result.isThrow) {
throw result.value;
}
return result.value;
};
mockFunction.mock = mock;
// $FlowExpectedError[invalid-computed-prop]
mockFunction[MOCK_FN_TAG] = true;
// $FlowExpectedError[prop-missing]
return mockFunction;
}
// flowlint unsafe-getters-setters:off
class Expect {
#received: mixed;
#isNot: boolean = false;
constructor(received: mixed) {
this.#received = received;
}
get not(): this {
this.#isNot = !this.#isNot;
return this;
}
toEqual(expected: mixed): void {
const pass = deepEqual(this.#received, expected, {strict: true});
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected${this.#maybeNotLabel()} to equal ${String(expected)} but received ${String(this.#received)}.`,
);
}
}
toBe(expected: mixed): void {
const pass = this.#received === expected;
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected${this.#maybeNotLabel()} ${String(expected)} but received ${String(this.#received)}.`,
);
}
}
toBeInstanceOf(expected: Class<mixed>): void {
const pass = this.#received instanceof expected;
if (!this.#isExpectedResult(pass)) {
throw new Error(
`expected ${String(this.#received)}${this.#maybeNotLabel()} to be an instance of ${String(expected)}`,
);
}
}
toBeCloseTo(expected: number, precision: number = 2): void {
const pass =
Math.abs(expected - Number(this.#received)) < Math.pow(10, -precision);
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
);
}
}
toBeNull(): void {
const pass = this.#received == null;
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be null`,
);
}
}
toThrow(expected?: string): void {
if (expected != null && typeof expected !== 'string') {
throw new Error(
'toThrow() implementation only accepts strings as arguments.',
);
}
let pass = false;
try {
// $FlowExpectedError[not-a-function]
this.#received();
} catch (error) {
pass = expected != null ? error.message === expected : true;
}
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
);
}
}
toHaveBeenCalled(): void {
const mock = this.#requireMock();
const pass = mock.calls.length > 0;
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called, but it was${this.#isNot ? '' : "n't"}`,
);
}
}
toHaveBeenCalledTimes(times: number): void {
const mock = this.#requireMock();
const pass = mock.calls.length === times;
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called ${times} times, but it was called ${mock.calls.length} times`,
);
}
}
#isExpectedResult(pass: boolean): boolean {
return this.#isNot ? !pass : pass;
}
#maybeNotLabel(): string {
return this.#isNot ? ' not' : '';
}
#requireMock(): JestMockFn<$ReadOnlyArray<mixed>, mixed>['mock'] {
// $FlowExpectedError[incompatible-use]
if (!this.#received?.[MOCK_FN_TAG]) {
throw new Error(
`Expected ${String(this.#received)} to be a mock function, but it wasn't`,
);
}
// $FlowExpectedError[incompatible-use]
return this.#received.mock;
}
}
global.expect = (received: mixed) => new Expect(received);
function runWithGuard(fn: () => void) {
try {
fn();
} catch (error) {
let reportedError =
error instanceof Error ? error : new Error(String(error));
reportTestSuiteResult({
error: {
message: reportedError.message,
stack: reportedError.stack,
},
});
}
}
function executeTests() {
const hasFocusedTests = tests.some(test => test.isFocused);
for (const test of tests) {
const result: TestCaseResult = {
title: test.title,
fullName: [...test.ancestorTitles, test.title].join(' '),
ancestorTitles: test.ancestorTitles,
status: 'pending',
duration: 0,
failureMessages: [],
numPassingAsserts: 0,
};
test.result = result;
if (!test.isSkipped && (!hasFocusedTests || test.isFocused)) {
let status;
let error;
const start = Date.now();
try {
test.implementation();
status = 'passed';
} catch (e) {
error = e;
status = 'failed';
}
result.status = status;
result.duration = Date.now() - start;
result.failureMessages =
status === 'failed' && error ? [error.message] : [];
}
}
reportTestSuiteResult({
testResults: tests.map(test => nullthrows(test.result)),
});
}
function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
console.log(JSON.stringify(testSuiteResult));
}
global.$$RunTests$$ = () => {
executeTests();
};
export function registerTest(setUpTest: () => void) {
runWithGuard(() => {
setUpTest();
});
}
+2 -5
View File
@@ -48,6 +48,7 @@
"@babel/preset-flow": "^7.24.7",
"@definitelytyped/dtslint": "^0.0.127",
"@jest/create-cache-key-function": "^29.6.3",
"@pkgjs/parseargs": "^0.11.0",
"@react-native/metro-babel-transformer": "0.77.0-main",
"@react-native/metro-config": "0.77.0-main",
"@tsconfig/node18": "1.0.1",
@@ -61,7 +62,6 @@
"chalk": "^4.0.0",
"clang-format": "^1.8.0",
"connect": "^3.6.5",
"deep-equal": "1.1.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-babel": "^5.3.1",
@@ -77,7 +77,7 @@
"eslint-plugin-redundant-undefined": "^0.4.0",
"eslint-plugin-relay": "^1.8.3",
"flow-api-translator": "0.24.0",
"flow-bin": "^0.253.0",
"flow-bin": "^0.251.1",
"glob": "^7.1.1",
"hermes-eslint": "0.24.0",
"hermes-transform": "0.24.0",
@@ -101,8 +101,5 @@
"supports-color": "^7.1.0",
"typescript": "5.0.4",
"ws": "^6.2.3"
},
"resolutions": {
"react-is": "18.3.1"
}
}
@@ -19,7 +19,10 @@ import isDevServerRunning from '../../utils/isDevServerRunning';
import loadMetroConfig from '../../utils/loadMetroConfig';
import * as version from '../../utils/version';
import attachKeyHandlers from './attachKeyHandlers';
import {createDevServerMiddleware, indexPageMiddleware} from './middleware';
import {
createDevServerMiddleware,
indexPageMiddleware,
} from '@react-native-community/cli-server-api';
import {createDevMiddleware} from '@react-native/dev-middleware';
import chalk from 'chalk';
import Metro from 'metro';
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<6b92b66e59525cef52902139f863f175>>
Git revision: b61aae3ccc6e2684dfbf1e2a06b0f985b459f11f
@generated SignedSource<<b5e82d2eb99e1ed4c012065a530ca78b>>
Git revision: ff343d805527223750fafb8573ee48f8e2fb0d1e
Built with --nohooks: false
Is local checkout: false
Remote URL: https://github.com/facebookexperimental/rn-chrome-devtools-frontend
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -10,30 +10,24 @@
*/
import type {JSONSerializable} from '../inspector-proxy/types';
import type {RequestOptions} from 'undici';
import {Agent, request} from 'undici';
import {Agent} from 'undici';
declare var globalThis: $FlowFixMe;
/**
* A version of `fetch` that is usable with the HTTPS server created in
* ServerUtils (which uses a self-signed certificate).
*/
export async function requestLocal(
export async function fetchLocal(
url: string,
options?: RequestOptions,
): Promise<{
statusCode: number,
headers: Headers,
bodyBuffer: Buffer,
}> {
const {
statusCode,
headers: rawHeaders,
body,
} = await request(url, {
options?: Partial<Parameters<typeof fetch>[1] & {dispatcher?: mixed}>,
): ReturnType<typeof fetch> {
return await fetch(url, {
...options,
// Use undici's `dispatcher` to make it accept self-signed certificates.
// Node's native `fetch` comes from undici and supports the same options,
// including `dispatcher` which we use to make it accept self-signed
// certificates.
dispatcher:
options?.dispatcher ??
new Agent({
@@ -42,48 +36,41 @@ export async function requestLocal(
},
}),
});
return {
statusCode,
bodyBuffer: await body.read(),
headers: new Headers(rawHeaders),
};
}
export async function fetchJson<T: JSONSerializable>(url: string): Promise<T> {
const response = await requestLocal(url);
if (response.statusCode !== 200) {
throw new Error(`HTTP ${response.statusCode}`);
const response = await fetchLocal(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
if (!response.headers.get('Content-Type')?.startsWith('application/json')) {
throw new Error('Expected Content-Type: application/json');
}
return JSON.parse(response.bodyBuffer.toString());
return response.json();
}
/**
* Change the global fetch dispatcher to allow self-signed certificates.
* This runs with Jest's `beforeAll` and `afterAll`, and restores the original dispatcher.
*/
export function withFetchSelfSignedCertsForAllTests(
fetchSpy: JestMockFn<Parameters<typeof fetch>, ReturnType<typeof fetch>>,
fetchOriginal: typeof fetch,
) {
export function withFetchSelfSignedCertsForAllTests() {
const fetchOriginal = globalThis.fetch;
const selfSignedCertDispatcher = new Agent({
connect: {
rejectUnauthorized: false,
},
});
let fetchSpy;
beforeAll(() => {
// For some reason, setting the `selfSignedCertDispatcher` with `setGlobalDispatcher` doesn't work.
// Instead of using `setGlobalDispatcher`, we'll use a spy to intercept the fetch calls and add the dispatcher.
fetchSpy.mockImplementation((url, options) =>
fetchOriginal(url, {
...options,
// $FlowFixMe[prop-missing]: dispatcher
dispatcher: options?.dispatcher ?? selfSignedCertDispatcher,
}),
);
fetchSpy = jest
.spyOn(globalThis, 'fetch')
.mockImplementation((url, options) =>
fetchOriginal(url, {
...options,
dispatcher: options?.dispatcher ?? selfSignedCertDispatcher,
}),
);
});
afterAll(() => {
@@ -27,17 +27,10 @@ export class DeviceAgent {
#ws: ?WebSocket;
#readyPromise: Promise<void>;
constructor(url: string, signal?: AbortSignal, host?: ?string) {
constructor(url: string, signal?: AbortSignal) {
const ws = new WebSocket(url, {
// The mock server uses a self-signed certificate.
rejectUnauthorized: false,
...(host != null
? {
headers: {
Host: host,
},
}
: {}),
});
this.#ws = ws;
ws.on('message', data => {
@@ -167,9 +160,8 @@ export class DeviceMock extends DeviceAgent {
export async function createDeviceMock(
url: string,
signal: AbortSignal,
host?: ?string,
): Promise<DeviceMock> {
const device = new DeviceMock(url, signal, host);
const device = new DeviceMock(url, signal);
await device.ready();
return device;
}
@@ -126,13 +126,7 @@ export async function createAndConnectTarget(
}>,
signal: AbortSignal,
page: PageFromDevice,
{
deviceId = null,
host = null,
}: $ReadOnly<{
deviceId?: ?string,
host?: ?string,
}> = {},
deviceId: ?string = null,
): Promise<{device: DeviceMock, debugger_: DebuggerMock}> {
let device;
let debugger_;
@@ -142,7 +136,6 @@ export async function createAndConnectTarget(
deviceId ?? 'device' + Date.now()
}&name=foo&app=bar`,
signal,
host,
);
device.getPages.mockImplementation(() => [page]);
@@ -33,18 +33,12 @@ jest.useRealTimers();
jest.setTimeout(10000);
const fetchOriginal = fetch;
const fetchSpy: JestMockFn<
Parameters<typeof fetch>,
ReturnType<typeof fetch>,
> = jest.spyOn(globalThis, 'fetch');
describe.each(['HTTP', 'HTTPS'])(
'inspector proxy CDP rewriting hacks over %s',
protocol => {
// Inspector proxy tests are using a self-signed certificate for HTTPS tests.
if (protocol === 'HTTPS') {
withFetchSelfSignedCertsForAllTests(fetchSpy, fetchOriginal);
withFetchSelfSignedCertsForAllTests();
}
const serverRef = withServerForEachTest({
@@ -52,7 +46,6 @@ describe.each(['HTTP', 'HTTPS'])(
projectRoot: __dirname,
secure: protocol === 'HTTPS',
});
const autoCleanup = withAbortSignalForEachTest();
afterEach(() => {
jest.clearAllMocks();
@@ -195,96 +188,6 @@ describe.each(['HTTP', 'HTTPS'])(
}
});
test("does not rewrite urls in Debugger.scriptParsed that don't match the device connection host", async () => {
serverRef.app.use('/source-map', serveStaticJson({version: 3}));
const {device, debugger_} = await createAndConnectTarget(
serverRef,
autoCleanup.signal,
{
app: 'bar-app',
id: 'page1',
title: 'bar-title',
vm: 'bar-vm',
},
{
host: '192.168.0.123:' + serverRef.port,
},
);
try {
let fetchCalledWithURL;
fetchSpy.mockImplementationOnce(async url => {
fetchCalledWithURL = url instanceof URL ? url : null;
throw new Error('Unreachable');
});
const sourceMapURL = `${protocol.toLowerCase()}://127.0.0.1:${
serverRef.port
}/source-map`;
const scriptParsedMessage = await sendFromTargetToDebugger(
device,
debugger_,
'page1',
{
method: 'Debugger.scriptParsed',
params: {
sourceMapURL,
},
},
);
expect(fetchCalledWithURL?.href).toEqual(sourceMapURL);
expect(scriptParsedMessage.params.sourceMapURL).toEqual(
`${protocol.toLowerCase()}://127.0.0.1:${serverRef.port}/source-map`,
);
} finally {
device.close();
debugger_.close();
}
});
test('does not rewrite urls in Debugger.scriptParsed that match the device connection host but are not allowlisted for rewriting', async () => {
serverRef.app.use('/source-map', serveStaticJson({version: 3}));
const {device, debugger_} = await createAndConnectTarget(
serverRef,
autoCleanup.signal,
{
app: 'bar-app',
id: 'page1',
title: 'bar-title',
vm: 'bar-vm',
},
{
host: '192.168.0.123:' + serverRef.port,
},
);
try {
let fetchCalledWithURL;
fetchSpy.mockImplementationOnce(url => {
fetchCalledWithURL = url instanceof URL ? url : null;
throw new Error('Unreachable');
});
const sourceMapURL = `${protocol.toLowerCase()}://192.168.0.123:${
serverRef.port
}/source-map`;
const scriptParsedMessage = await sendFromTargetToDebugger(
device,
debugger_,
'page1',
{
method: 'Debugger.scriptParsed',
params: {
sourceMapURL,
},
},
);
expect(fetchCalledWithURL?.href).toEqual(sourceMapURL);
expect(scriptParsedMessage.params.sourceMapURL).toEqual(
`${protocol.toLowerCase()}://192.168.0.123:${serverRef.port}/source-map`,
);
} finally {
device.close();
debugger_.close();
}
});
describe.each(['10.0.2.2', '10.0.3.2', '127.0.0.1'])(
'%s aliasing to and from localhost',
sourceHost => {
@@ -299,9 +202,6 @@ describe.each(['HTTP', 'HTTPS'])(
title: 'bar-title',
vm: 'bar-vm',
},
{
host: sourceHost + ':' + serverRef.port,
},
);
try {
const scriptParsedMessage = await sendFromTargetToDebugger(
@@ -336,9 +236,6 @@ describe.each(['HTTP', 'HTTPS'])(
title: 'bar-title',
vm: 'bar-vm',
},
{
host: sourceHost + ':' + serverRef.port,
},
);
try {
const scriptParsedMessage = await sendFromTargetToDebugger(
@@ -387,56 +284,17 @@ describe.each(['HTTP', 'HTTPS'])(
method: 'Debugger.setBreakpointByUrl',
params: {
lineNumber: 1,
urlRegex: `localhost:${serverRef.port}|example.com:2000`,
urlRegex: 'localhost:1000|localhost:2000',
},
});
expect(setBreakpointByUrlRegexMessage.params.urlRegex).toEqual(
`${sourceHost.replaceAll('.', '\\.')}:${serverRef.port}|example.com:2000`,
`${sourceHost}:1000|${sourceHost}:2000`,
);
} finally {
device.close();
debugger_.close();
}
});
describe('Network.loadNetworkResource', () => {
test('should respond with an error without forwarding to the client', async () => {
const {device, debugger_} = await createAndConnectTarget(
serverRef,
autoCleanup.signal,
{
app: 'bar-app',
id: 'page1',
title: 'bar-title',
vm: 'bar-vm',
},
{
host: sourceHost + ':' + serverRef.port,
},
);
try {
const response = await debugger_.sendAndGetResponse({
id: 1,
method: 'Network.loadNetworkResource',
params: {
url: 'http://example.com',
},
});
expect(response.result).toEqual(
expect.objectContaining({
error: {
code: -32601,
message:
'[inspector-proxy]: Page lacks nativeSourceCodeFetching capability.',
},
}),
);
} finally {
device.close();
debugger_.close();
}
});
});
},
);
@@ -450,9 +308,6 @@ describe.each(['HTTP', 'HTTPS'])(
title: 'bar-title',
vm: 'bar-vm',
},
{
host: '127.0.0.1:' + serverRef.port,
},
);
try {
const scriptParsedMessage = await sendFromTargetToDebugger(
@@ -726,33 +581,6 @@ describe.each(['HTTP', 'HTTPS'])(
}
});
});
describe('Network.loadNetworkResource', () => {
test('should forward event directly to client (does not rewrite url host)', async () => {
const {device, debugger_} = await createAndConnectTarget(
serverRef,
autoCleanup.signal,
pageDescription,
);
try {
const message = {
id: 1,
method: 'Network.loadNetworkResource',
params: {
url: `${protocol.toLowerCase()}://10.0.2.2:${serverRef.port}`,
},
};
await sendFromDebuggerToTarget(debugger_, device, 'page1', message);
expect(device.wrappedEventParsed).toBeCalledWith({
pageId: 'page1',
wrappedEvent: message,
});
} finally {
device.close();
debugger_.close();
}
});
});
});
},
);
@@ -15,7 +15,7 @@ import type {
} from '../inspector-proxy/types';
import DefaultBrowserLauncher from '../utils/DefaultBrowserLauncher';
import {fetchJson, requestLocal} from './FetchUtils';
import {fetchJson, fetchLocal} from './FetchUtils';
import {createDeviceMock} from './InspectorDeviceUtils';
import {withAbortSignalForEachTest} from './ResourceUtils';
import {withServerForEachTest} from './ServerUtils';
@@ -362,7 +362,7 @@ describe('inspector proxy HTTP API', () => {
jest.advanceTimersByTime(PAGES_POLLING_DELAY);
const response = await requestLocal(
const response = await fetchLocal(
`${serverRef.serverBaseUrl}${endpoint}`,
);
expect(response.headers.get('Content-Length')).not.toBeNull();
@@ -415,12 +415,10 @@ describe('inspector proxy HTTP API', () => {
);
openUrl.searchParams.set('target', firstPage.id);
// Request to open the debugger for the first device
const response = await requestLocal(openUrl.toString(), {
method: 'POST',
});
const response = await fetchLocal(openUrl.toString(), {method: 'POST'});
// Ensure the request was handled properly
expect(response.statusCode).toBe(200);
expect(response.status).toBe(200);
// Ensure the debugger was launched
expect(launchDebuggerSpy).toHaveBeenCalledWith(expect.any(String));
} finally {
@@ -9,7 +9,7 @@
* @oncall react_native
*/
import {requestLocal} from './FetchUtils';
import {fetchLocal} from './FetchUtils';
import {withServerForEachTest} from './ServerUtils';
jest.useRealTimers();
@@ -22,10 +22,11 @@ describe('embedder script', () => {
});
test('is always served', async () => {
const resp = await requestLocal(
const resp = await fetchLocal(
serverRef.serverBaseUrl +
'/debugger-frontend/embedder-static/embedderScript.js',
);
expect(resp.statusCode).toBe(200);
expect(resp.ok).toBeTruthy();
expect(resp.status).toBe(200);
});
});
@@ -1,49 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import getBaseUrlFromRequest from '../utils/getBaseUrlFromRequest';
test('returns a base url based on req.headers.host', () => {
expect(
getBaseUrlFromRequest(makeRequest('localhost:8081', false))?.href,
).toEqual('http://localhost:8081/');
});
test('identifies https using socket.encrypted', () => {
expect(
getBaseUrlFromRequest(makeRequest('secure.net:8443', true))?.href,
).toEqual('https://secure.net:8443/');
});
test('works with ipv6 hosts', () => {
expect(getBaseUrlFromRequest(makeRequest('[::1]:8081', false))?.href).toEqual(
'http://[::1]:8081/',
);
});
test('returns null on an invalid host header', () => {
expect(getBaseUrlFromRequest(makeRequest('local[]host', false))).toBeNull();
});
test('returns null on an empty host header', () => {
expect(getBaseUrlFromRequest(makeRequest(null, false))).toBeNull();
});
function makeRequest(
host: ?string,
encrypted: boolean,
): http$IncomingMessage<> | http$IncomingMessage<tls$TLSSocket> {
// $FlowIgnore[incompatible-return] Partial mock of request
return {
socket: encrypted ? {encrypted: true} : {},
headers: host != null ? {host} : {},
};
}
@@ -28,8 +28,11 @@ type Options = $ReadOnly<{
projectRoot: string,
/**
* The base URL to the dev server, as reachable from the machine on which
* dev-middleware is hosted. Typically `http://localhost:${metroPort}`.
* The base URL to the dev server, as addressible from the local developer
* machine. This is used in responses which return URLs to other endpoints,
* e.g. the debugger frontend and inspector proxy targets.
*
* Example: `'http://localhost:8081'`.
*/
serverBaseUrl: string,
@@ -40,7 +40,7 @@ const PAGES_POLLING_INTERVAL = 1000;
// Replace hosts appearing in the `url` and `sourceMapURL` fields of
// `Debugger.scriptParsed`, and back again in messages from the debugger,
// to account for device/debugger/proxy running on different networks.
const REWRITE_HOSTS_TO_LOCALHOST: $ReadOnlySet<string> = new Set([
const REWRITE_HOSTS_TO_LOCALHOST: Array<string> = [
// A device may retrieve a bundle through 127.0.0.1 via a (SSH) tunnel, but
// the (remote) Metro server may be on a host without an IPv4 loopback, so
// 127.0.0.1 may not be addressible locally for (e.g., for source map
@@ -51,7 +51,7 @@ const REWRITE_HOSTS_TO_LOCALHOST: $ReadOnlySet<string> = new Set([
// standard localhost alias.
'10.0.2.2',
'10.0.3.2',
]);
];
// Prefix for script URLs that are alphanumeric IDs. See comment in #processMessageFromDeviceLegacy method for
// more details.
@@ -71,18 +71,6 @@ type DebuggerConnection = {
const REACT_NATIVE_RELOADABLE_PAGE_ID = '-1';
export type DeviceOptions = $ReadOnly<{
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
deviceRelativeBaseUrl: URL,
serverRelativeBaseUrl: URL,
}>;
/**
* Device class represents single device connection to Inspector Proxy. Each device
* can have multiple inspectable pages.
@@ -137,35 +125,40 @@ export default class Device {
#connectedPageIds: Set<string> = new Set();
// A base HTTP(S) URL to this server, reachable from the device. Derived from
// the http request that created the connection.
#deviceRelativeBaseUrl: URL;
// A base HTTP(S) URL to the server, relative to this server.
#serverRelativeBaseUrl: URL;
constructor(deviceOptions: DeviceOptions) {
this.#dangerouslyConstruct(deviceOptions);
constructor(
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
) {
this.#dangerouslyConstruct(
id,
name,
app,
socket,
projectRoot,
eventReporter,
createMessageMiddleware,
);
}
#dangerouslyConstruct({
id,
name,
app,
socket,
projectRoot,
eventReporter,
createMessageMiddleware,
serverRelativeBaseUrl,
deviceRelativeBaseUrl,
}: DeviceOptions) {
#dangerouslyConstruct(
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
) {
this.#id = id;
this.#name = name;
this.#app = app;
this.#deviceSocket = socket;
this.#projectRoot = projectRoot;
this.#serverRelativeBaseUrl = serverRelativeBaseUrl;
this.#deviceRelativeBaseUrl = deviceRelativeBaseUrl;
this.#deviceEventReporter = eventReporter
? new DeviceEventReporter(eventReporter, {
deviceId: id,
@@ -245,15 +238,23 @@ export default class Device {
* This hack attempts to allow users to reload the app, either as result of a
* crash, or manually reloading, without having to restart the debugger.
*/
dangerouslyRecreateDevice(deviceOptions: DeviceOptions) {
dangerouslyRecreateDevice(
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
) {
invariant(
deviceOptions.id === this.#id,
id === this.#id,
'dangerouslyRecreateDevice() can only be used for the same device ID',
);
const oldDebugger = this.#debuggerConnection;
if (this.#app !== deviceOptions.app || this.#name !== deviceOptions.name) {
if (this.#app !== app || this.#name !== name) {
this.#deviceSocket.close();
this.#terminateDebuggerConnection();
}
@@ -268,7 +269,15 @@ export default class Device {
});
}
this.#dangerouslyConstruct(deviceOptions);
this.#dangerouslyConstruct(
id,
name,
app,
socket,
projectRoot,
eventReporter,
createMessageMiddleware,
);
}
getName(): string {
@@ -682,43 +691,25 @@ export default class Device {
) {
const params = payload.params;
if ('sourceMapURL' in params) {
const sourceMapURL = this.#tryParseHTTPURL(params.sourceMapURL);
if (sourceMapURL) {
// This URL will be used to fetch from the server, and will be
// mutated if necessary from device-relative to server-relative.
// This is not exposed to the debugger.
const serverRelativeUrl = new URL(sourceMapURL.href);
// Rewrite device-relative URLs to localhost-relative URLs for the
// debugger.
// TODO: Fix the assumption that localhost:[same port] is correct.
if (
// sourceMapURL is a device-relative url to the server.
// May or may not be reachable from the frontend.
sourceMapURL.origin === this.#deviceRelativeBaseUrl.origin &&
// For a specific set of IPs (eg 10.0.2.2) it's relatively safe to
// assume the frontend can reach the server on localhost.
// TODO: Fix the assumption that localhost:[same port] is correct
// and remove this check.
REWRITE_HOSTS_TO_LOCALHOST.has(this.#deviceRelativeBaseUrl.hostname)
) {
const debuggerRelativeURL = new URL(sourceMapURL.href);
debuggerRelativeURL.hostname = 'localhost';
serverRelativeUrl.host = this.#serverRelativeBaseUrl.host;
serverRelativeUrl.protocol = this.#serverRelativeBaseUrl.protocol;
debuggerInfo.originalSourceURLAddress =
this.#deviceRelativeBaseUrl.hostname;
payload.params.sourceMapURL = debuggerRelativeURL.href;
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
if (params.sourceMapURL.includes(hostToRewrite)) {
payload.params.sourceMapURL = params.sourceMapURL.replace(
hostToRewrite,
'localhost',
);
debuggerInfo.originalSourceURLAddress = hostToRewrite;
}
}
const sourceMapURL = this.#tryParseHTTPURL(params.sourceMapURL);
if (sourceMapURL) {
// Some debug clients do not support fetching HTTP URLs. If the
// message headed to the debug client identifies the source map with
// an HTTP URL, fetch the content here and convert the content to a
// Data URL (which is more widely supported) before passing the
// message to the debug client.
try {
const sourceMap = await this.#fetchText(serverRelativeUrl);
const sourceMap = await this.#fetchText(sourceMapURL);
payload.params.sourceMapURL =
'data:application/json;charset=utf-8;base64,' +
Buffer.from(sourceMap).toString('base64');
@@ -730,33 +721,11 @@ export default class Device {
}
}
if ('url' in params) {
const originalParamsUrl = params.url;
let serverRelativeUrl = originalParamsUrl;
const parsedUrl = this.#tryParseHTTPURL(originalParamsUrl);
// Rewrite device-relative URLs pointing to the server so that they're
// reachable from the frontend.
if (
parsedUrl &&
// url is a device-relative url to the server.
// May or may not be reachable from the frontend.
parsedUrl.origin === this.#deviceRelativeBaseUrl.origin &&
// For a specific set of IPs (eg 10.0.2.2) it's relatively safe to
// assume the frontend can reach the server on localhost.
// TODO: Fix the assumption that localhost:[same port] is correct and
// remove this check.
REWRITE_HOSTS_TO_LOCALHOST.has(this.#deviceRelativeBaseUrl.hostname)
) {
// URL is device-relative and points to the host - rewrite it to
// use localhost.
parsedUrl.hostname = 'localhost';
payload.params.url = parsedUrl.href;
debuggerInfo.originalSourceURLAddress =
this.#deviceRelativeBaseUrl.hostname;
// Determine the server-relative URL.
parsedUrl.host = this.#serverRelativeBaseUrl.host;
parsedUrl.protocol = this.#serverRelativeBaseUrl.protocol;
serverRelativeUrl = parsedUrl.href;
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
if (params.url.includes(hostToRewrite)) {
payload.params.url = params.url.replace(hostToRewrite, 'localhost');
debuggerInfo.originalSourceURLAddress = hostToRewrite;
}
}
// Chrome doesn't download source maps if URL param is not a valid
@@ -768,13 +737,9 @@ export default class Device {
debuggerInfo.prependedFilePrefix = true;
}
if ('scriptId' in params && params.scriptId != null) {
// Set a server-relative URL to locally fetch source by script ID
// on Debugger.getScriptSource.
this.#scriptIdToSourcePathMapping.set(
params.scriptId,
serverRelativeUrl,
);
// $FlowFixMe[prop-missing]
if (params.scriptId != null) {
this.#scriptIdToSourcePathMapping.set(params.scriptId, params.url);
}
}
}
@@ -834,31 +799,6 @@ export default class Device {
// Sends response to debugger via side-effect
this.#processDebuggerGetScriptSource(req, socket);
return null;
case 'Network.loadNetworkResource':
// If we're rewriting URLs (to frontend-relative), we don't want to
// pass these URLs to the device, since it may try to fetch, return a
// CDP *result* (not error) with a network failure, and CDT
// will *not* then fall back to fetching locally.
//
// Instead, take the absence of a nativeSourceCodeFetching
// capability as a signal to never pass a loadNetworkResource request
// to the device. By returning a CDP error, the frontend should fetch.
const result = {
error: {
code: -32601, // Method not found
message:
'[inspector-proxy]: Page lacks nativeSourceCodeFetching capability.',
},
};
const response = {id: req.id, result};
socket.send(JSON.stringify(response));
const pageId = this.#debuggerConnection?.pageId ?? null;
this.#deviceEventReporter?.logResponse(response, 'proxy', {
pageId,
frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
prefersFuseboxFrontend: this.#isPageFuseboxFrontend(pageId),
});
return null;
default:
return req;
}
@@ -869,48 +809,36 @@ export default class Device {
debuggerInfo: DebuggerConnection,
): CDPRequest<'Debugger.setBreakpointByUrl'> {
// If we replaced Android emulator's address to localhost we need to change it back.
const {originalSourceURLAddress, prependedFilePrefix} = debuggerInfo;
const processedReq = {...req, params: {...req.params}};
if (originalSourceURLAddress != null && processedReq.params.url != null) {
processedReq.params.url = processedReq.params.url.replace(
'localhost',
originalSourceURLAddress,
);
if (debuggerInfo.originalSourceURLAddress != null) {
const processedReq = {...req, params: {...req.params}};
if (processedReq.params.url != null) {
processedReq.params.url = processedReq.params.url.replace(
'localhost',
debuggerInfo.originalSourceURLAddress,
);
if (
processedReq.params.url &&
processedReq.params.url.startsWith(FILE_PREFIX) &&
prependedFilePrefix
) {
// Remove fake URL prefix if we modified URL in #processMessageFromDeviceLegacy.
// $FlowFixMe[incompatible-use]
processedReq.params.url = processedReq.params.url.slice(
FILE_PREFIX.length,
if (
processedReq.params.url &&
processedReq.params.url.startsWith(FILE_PREFIX) &&
debuggerInfo.prependedFilePrefix
) {
// Remove fake URL prefix if we modified URL in #processMessageFromDeviceLegacy.
// $FlowFixMe[incompatible-use]
processedReq.params.url = processedReq.params.url.slice(
FILE_PREFIX.length,
);
}
}
if (processedReq.params.urlRegex != null) {
processedReq.params.urlRegex = processedReq.params.urlRegex.replace(
/localhost/g,
// $FlowFixMe[incompatible-call]
debuggerInfo.originalSourceURLAddress,
);
}
return processedReq;
}
// Retain special case rewriting of localhost to device-relative IPs
// within regex patterns. We don't rewrite the protocol here because
// these patterns typically come from CDT reinterpreting the source URL
// `file://host/path` into the regex `host/path|file://host/path`. See:
//
// https://github.com/ChromeDevTools/devtools-frontend/blob/f913cc6d76f2e2639c05b11ba673fc880b5490dd/front_end/core/sdk/DebuggerModel.ts#L505
//
// This has always been fragile and probably unnecessary - we don't set
// `file://` source URLs. It can be removed when we drop support for
// legacy targets, if not sooner.
if (
REWRITE_HOSTS_TO_LOCALHOST.has(this.#deviceRelativeBaseUrl.hostname) &&
processedReq.params.urlRegex != null
) {
processedReq.params.urlRegex = processedReq.params.urlRegex.replaceAll(
'localhost',
// regex-escape IPv4
this.#deviceRelativeBaseUrl.hostname.replaceAll('.', '\\.'),
);
}
return processedReq;
return req;
}
#processDebuggerGetScriptSource(
@@ -953,7 +881,6 @@ export default class Device {
if (pathToSource != null) {
const httpURL = this.#tryParseHTTPURL(pathToSource);
if (httpURL) {
// URL is server-relatve, so we should be able to fetch it from here.
this.#fetchText(httpURL).then(
text => sendSuccessResponse(text),
err =>
@@ -12,7 +12,6 @@
import type {EventReporter} from '../types/EventReporter';
import type {Experiments} from '../types/Experiments';
import type {CreateCustomMessageHandlerFn} from './CustomMessageHandler';
import type {DeviceOptions} from './Device';
import type {
JsonPagesListResponse,
JsonVersionResponse,
@@ -23,7 +22,6 @@ import type {IncomingMessage, ServerResponse} from 'http';
// $FlowFixMe[cannot-resolve-module] libdef missing in RN OSS
import type {Timeout} from 'timers';
import getBaseUrlFromRequest from '../utils/getBaseUrlFromRequest';
import Device from './Device';
import nullthrows from 'nullthrows';
// Import these from node:timers to get the correct Flow types.
@@ -49,7 +47,7 @@ export interface InspectorProxyQueries {
* Returns list of page descriptions ordered by device connection order, then
* page addition order.
*/
getPageDescriptions(requestorRelativeBaseUrl: URL): Array<PageDescription>;
getPageDescriptions(): Array<PageDescription>;
}
/**
@@ -59,8 +57,8 @@ export default class InspectorProxy implements InspectorProxyQueries {
// Root of the project used for relative to absolute source path conversion.
#projectRoot: string;
// The base URL to the dev server from the dev-middleware host.
#serverBaseUrl: URL;
/** The base URL to the dev server from the developer machine. */
#serverBaseUrl: string;
// Maps device ID to Device instance.
#devices: Map<string, Device>;
@@ -83,14 +81,14 @@ export default class InspectorProxy implements InspectorProxyQueries {
customMessageHandler: ?CreateCustomMessageHandlerFn,
) {
this.#projectRoot = projectRoot;
this.#serverBaseUrl = new URL(serverBaseUrl);
this.#serverBaseUrl = serverBaseUrl;
this.#devices = new Map();
this.#eventReporter = eventReporter;
this.#experiments = experiments;
this.#customMessageHandler = customMessageHandler;
}
getPageDescriptions(requestorRelativeBaseUrl: URL): Array<PageDescription> {
getPageDescriptions(): Array<PageDescription> {
// Build list of pages from all devices.
let result: Array<PageDescription> = [];
Array.from(this.#devices.entries()).forEach(([deviceId, device]) => {
@@ -98,12 +96,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
device
.getPagesList()
.map((page: Page) =>
this.#buildPageDescription(
deviceId,
device,
page,
requestorRelativeBaseUrl,
),
this.#buildPageDescription(deviceId, device, page),
),
);
});
@@ -124,12 +117,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
pathname === PAGES_LIST_JSON_URL ||
pathname === PAGES_LIST_JSON_URL_2
) {
this.#sendJsonResponse(
response,
this.getPageDescriptions(
getBaseUrlFromRequest(request) ?? this.#serverBaseUrl,
),
);
this.#sendJsonResponse(response, this.getPageDescriptions());
} else if (pathname === PAGES_LIST_JSON_VERSION_URL) {
this.#sendJsonResponse(response, {
Browser: 'Mobile JavaScript',
@@ -155,9 +143,8 @@ export default class InspectorProxy implements InspectorProxyQueries {
deviceId: string,
device: Device,
page: Page,
requestorRelativeBaseUrl: URL,
): PageDescription {
const {host, protocol} = requestorRelativeBaseUrl;
const {host, protocol} = new URL(this.#serverBaseUrl);
const webSocketScheme = protocol === 'https:' ? 'wss' : 'ws';
const webSocketUrlWithoutProtocol = `${host}${WS_DEBUGGER_URL}?device=${deviceId}&page=${page.id}`;
@@ -226,35 +213,35 @@ export default class InspectorProxy implements InspectorProxyQueries {
const deviceName = query.name || 'Unknown';
const appName = query.app || 'Unknown';
const deviceRelativeBaseUrl =
getBaseUrlFromRequest(req) ?? this.#serverBaseUrl;
const oldDevice = this.#devices.get(deviceId);
let newDevice;
const deviceOptions: DeviceOptions = {
id: deviceId,
name: deviceName,
app: appName,
socket,
projectRoot: this.#projectRoot,
eventReporter: this.#eventReporter,
createMessageMiddleware: this.#customMessageHandler,
deviceRelativeBaseUrl,
serverRelativeBaseUrl: this.#serverBaseUrl,
};
if (oldDevice) {
oldDevice.dangerouslyRecreateDevice(deviceOptions);
oldDevice.dangerouslyRecreateDevice(
deviceId,
deviceName,
appName,
socket,
this.#projectRoot,
this.#eventReporter,
this.#customMessageHandler,
);
newDevice = oldDevice;
} else {
newDevice = new Device(deviceOptions);
newDevice = new Device(
deviceId,
deviceName,
appName,
socket,
this.#projectRoot,
this.#eventReporter,
this.#customMessageHandler,
);
}
this.#devices.set(deviceId, newDevice);
debug(
`Got new connection: name=${deviceName}, app=${appName}, device=${deviceId}, via=${deviceRelativeBaseUrl.origin}`,
`Got new connection: name=${deviceName}, app=${appName}, device=${deviceId}`,
);
socket.on('close', () => {
@@ -44,7 +44,6 @@ export type CDPClientMessage =
| CDPRequest<'Debugger.getScriptSource'>
| CDPRequest<'Debugger.scriptParsed'>
| CDPRequest<'Debugger.setBreakpointByUrl'>
| CDPRequest<'Network.loadNetworkResource'>
| CDPRequest<>;
export type CDPServerMessage =
@@ -72,14 +72,12 @@ export default function openDebuggerMiddleware({
...
} = query;
const targets = inspectorProxy
.getPageDescriptions(new URL(serverBaseUrl))
.filter(
// Only use targets with better reloading support
app =>
app.title === LEGACY_SYNTHETIC_PAGE_TITLE ||
app.reactNative.capabilities?.nativePageReloads === true,
);
const targets = inspectorProxy.getPageDescriptions().filter(
// Only use targets with better reloading support
app =>
app.title === LEGACY_SYNTHETIC_PAGE_TITLE ||
app.reactNative.capabilities?.nativePageReloads === true,
);
let target;
@@ -18,10 +18,6 @@ export interface BrowserLauncher {
* Attempt to open a debugger frontend URL in a browser app window,
* optionally returning an object to control the launched browser instance.
* The browser used should be capable of running Chrome DevTools.
*
* The provided url is based on serverBaseUrl, and therefore reachable from
* the host of dev-middleware. Implementations are responsible for rewriting
* this as necessary where the server is remote.
*/
launchDebuggerAppWindow: (url: string) => Promise<void>;
}
@@ -1,32 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
// Determine the base URL (scheme and host) used by a client to reach this
// server.
//
// TODO: Support X-Forwarded-Host, etc. for trusted proxies
export default function getBaseUrlFromRequest(
req: http$IncomingMessage<tls$TLSSocket> | http$IncomingMessage<net$Socket>,
): ?URL {
const hostHeader = req.headers.host;
if (hostHeader == null) {
return null;
}
// `encrypted` is always true for TLS sockets and undefined for net
// https://github.com/nodejs/node/issues/41863#issuecomment-1030709186
const scheme = req.socket.encrypted === true ? 'https' : 'http';
const url = `${scheme}://${req.headers.host}`;
try {
return new URL(url);
} catch {
return null;
}
}
@@ -65,11 +65,7 @@ function getWsParam({
const serverHost = new URL(devServerUrl).host;
let value;
if (wsUrl.host === serverHost) {
// Use a path-absolute (host-relative) URL if the WS server and frontend
// server are colocated. This is more robust for cases where the frontend
// may actually load through a tunnel or proxy, and the WS connection
// should therefore do the same.
//
// Use a path-absolute (host-relative) URL
// Depends on https://github.com/facebookexperimental/rn-chrome-devtools-frontend/pull/4
value = wsUrl.pathname + wsUrl.search + wsUrl.hash;
} else {
@@ -15,7 +15,6 @@ import java.math.BigInteger
import java.security.MessageDigest
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.min
import org.gradle.api.GradleException
import org.gradle.api.file.FileCollection
import org.gradle.api.initialization.Settings
@@ -160,10 +159,7 @@ abstract class ReactSettingsExtension @Inject constructor(val settings: Settings
val logger = Logging.getLogger("ReactSettingsExtension")
logger.error(message)
if (cacheJsonConfig.length() != 0L) {
logger.error(
cacheJsonConfig
.readText()
.substring(0, min(1024, cacheJsonConfig.length().toInt())))
logger.error(cacheJsonConfig.readText().substring(0, 1024))
}
cacheJsonConfig.delete()
throw GradleException(message)
-1
View File
@@ -97,7 +97,6 @@ PLATFORMS
DEPENDENCIES
activesupport (>= 6.1.7.5, < 7.1.0)
cocoapods (~> 1.13, != 1.15.1, != 1.15.0)
xcodeproj (< 1.26.0)
RUBY VERSION
ruby 3.3.0p0
+1 -1
View File
@@ -10,7 +10,7 @@ buildscript {
buildToolsVersion = "35.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
targetSdkVersion = 34
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
}
@@ -8,13 +8,13 @@
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* HelloWorldTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* HelloWorldTests.m */; };
0C80B921A6F3F58F76C31292 /* libPods-HelloWorld.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-HelloWorld.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
68D852E4B70E7C539AF156EA /* libPods-HelloWorld-HelloWorldTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CE12D37B885C99EE7D4A2086 /* libPods-HelloWorld-HelloWorldTests.a */; };
6EA01F72FAC10D00AECACF94 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0EC7AB76F90EED035707BA4E /* PrivacyInfo.xcprivacy */; };
7699B88040F8A987B510C191 /* libPods-HelloWorld-HelloWorldTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-HelloWorld-HelloWorldTests.a */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
D463203D20D2FDD34F945C74 /* libPods-HelloWorld.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82822864BCFA4BE42C6C2969 /* libPods-HelloWorld.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -39,13 +39,13 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = HelloWorld/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = HelloWorld/main.m; sourceTree = "<group>"; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = HelloWorld/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
19F6CBCC0A4E27FBF8BF4A61 /* libPods-HelloWorld-HelloWorldTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld-HelloWorldTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B4392A12AC88292D35C810B /* Pods-HelloWorld.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld.debug.xcconfig"; path = "Target Support Files/Pods-HelloWorld/Pods-HelloWorld.debug.xcconfig"; sourceTree = "<group>"; };
5709B34CF0A7D63546082F79 /* Pods-HelloWorld.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld.release.xcconfig"; path = "Target Support Files/Pods-HelloWorld/Pods-HelloWorld.release.xcconfig"; sourceTree = "<group>"; };
5B7EB9410499542E8C5724F5 /* Pods-HelloWorld-HelloWorldTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld-HelloWorldTests.debug.xcconfig"; path = "Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests.debug.xcconfig"; sourceTree = "<group>"; };
5DCACB8F33CDC322A6C60F78 /* libPods-HelloWorld.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld.a"; sourceTree = BUILT_PRODUCTS_DIR; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = HelloWorld/LaunchScreen.storyboard; sourceTree = "<group>"; };
82822864BCFA4BE42C6C2969 /* libPods-HelloWorld.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld.a"; sourceTree = BUILT_PRODUCTS_DIR; };
89C6BE57DB24E9ADA2F236DE /* Pods-HelloWorld-HelloWorldTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld-HelloWorldTests.release.xcconfig"; path = "Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests.release.xcconfig"; sourceTree = "<group>"; };
CE12D37B885C99EE7D4A2086 /* libPods-HelloWorld-HelloWorldTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld-HelloWorldTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
@@ -54,7 +54,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
68D852E4B70E7C539AF156EA /* libPods-HelloWorld-HelloWorldTests.a in Frameworks */,
7699B88040F8A987B510C191 /* libPods-HelloWorld-HelloWorldTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -62,7 +62,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D463203D20D2FDD34F945C74 /* libPods-HelloWorld.a in Frameworks */,
0C80B921A6F3F58F76C31292 /* libPods-HelloWorld.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -105,8 +105,8 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
82822864BCFA4BE42C6C2969 /* libPods-HelloWorld.a */,
CE12D37B885C99EE7D4A2086 /* libPods-HelloWorld-HelloWorldTests.a */,
5DCACB8F33CDC322A6C60F78 /* libPods-HelloWorld.a */,
19F6CBCC0A4E27FBF8BF4A61 /* libPods-HelloWorld-HelloWorldTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -165,7 +165,6 @@
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */,
A44ED3CC3037C88F69E3AF15 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -187,7 +186,6 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
D32CB2BA406E97DB62F51C6B /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -272,23 +270,6 @@
shellPath = /bin/sh;
shellScript = "set -e\n\nexport CONFIG_JSON=$(sed -e \"s|HELLOWORLD_PATH|$(realpath \"${SRCROOT}/../\")|g\" \"${SRCROOT}/../.react-native.config\")\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
A44ED3CC3037C88F69E3AF15 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -333,23 +314,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D32CB2BA406E97DB62F51C6B /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -571,17 +535,6 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
@@ -602,14 +555,8 @@
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../react-native";
OTHER_LDFLAGS = "$(inherited) ";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
USE_HERMES = true;
};
name = Debug;
};
@@ -655,17 +602,6 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
@@ -685,13 +621,8 @@
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../react-native";
OTHER_LDFLAGS = "$(inherited) ";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -8,14 +8,12 @@
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"HelloWorld";
self.dependencyProvider = [RCTAppDependencyProvider new];
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
+1
View File
@@ -89,6 +89,7 @@ export function getDefaultConfig(projectRoot: string): ConfigT {
babelTransformerPath: require.resolve(
'@react-native/metro-babel-transformer',
),
hermesParser: true,
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
-42
View File
@@ -553,48 +553,6 @@ if (global.nativeLoggingHook) {
assert: consoleAssertPolyfill,
};
// TODO(T206796580): This was copy-pasted from ExceptionsManager.js
// Delete the copy there after the c++ pipeline is rolled out everywhere.
if (global.RN$useAlwaysAvailableJSErrorHandling === true) {
let originalConsoleError = console.error;
console.reportErrorsAsExceptions = true;
function stringifySafe(arg) {
return inspect(arg, {depth: 10}).replaceAll(/\n\s*/g, ' ');
}
console.error = function (...args) {
originalConsoleError.apply(this, args);
if (!console.reportErrorsAsExceptions) {
return;
}
if (global.RN$inExceptionHandler?.()) {
return;
}
let error;
const firstArg = args[0];
if (firstArg?.stack) {
// RN$handleException will console.error this with high enough fidelity.
error = firstArg;
} else {
if (typeof firstArg === 'string' && firstArg.startsWith('Warning: ')) {
// React warnings use console.error so that a stack trace is shown, but
// we don't (currently) want these to show a redbox
return;
}
const message = args
.map(arg => (typeof arg === 'string' ? arg : stringifySafe(arg)))
.join(' ');
error = new Error(message);
error.name = 'console.error';
}
const isFatal = false;
const reportToConsole = false;
global.RN$handleException(error, isFatal, reportToConsole);
};
}
Object.defineProperty(console, '_isPolyfilled', {
value: true,
enumerable: false,
+6 -6
View File
@@ -19,12 +19,12 @@ type Fn<Args, Return> = (...Args) => Return;
* when loading a module. This will report any errors encountered before
* ExceptionsManager is configured.
*/
let _globalHandler: ErrorHandler =
global.RN$useAlwaysAvailableJSErrorHandling === true
? global.RN$handleException
: (e: mixed, isFatal: boolean) => {
throw e;
};
let _globalHandler: ErrorHandler = function onError(
e: mixed,
isFatal: boolean,
) {
throw e;
};
/**
* The particular require runtime that we are using looks for a global
@@ -23,7 +23,6 @@ type OnChangeEvent = $ReadOnly<{|
source: {url: string, ...},
x: Int32,
y: Int32,
arrayOfObjects: $ReadOnlyArray<{value: $ReadOnly<{str: string}>}>,
...
},
|}>;
@@ -161,20 +161,6 @@ void EventNestedObjectPropsNativeComponentViewEventEmitter::onChange(OnChange $e
}
location.setProperty(runtime, \\"x\\", $event.location.x);
location.setProperty(runtime, \\"y\\", $event.location.y);
auto arrayOfObjects = jsi::Array(runtime, $event.location.arrayOfObjects.size());
size_t arrayOfObjectsIndex = 0;
for (auto arrayOfObjectsValue : $event.location.arrayOfObjects) {
auto arrayOfObjectsObject = jsi::Object(runtime);
{
auto value = jsi::Object(runtime);
value.setProperty(runtime, \\"str\\", arrayOfObjectsValue.value.str);
arrayOfObjectsObject.setProperty(runtime, \\"value\\", value);
}
arrayOfObjects.setValueAtIndex(runtime, arrayOfObjectsIndex++, arrayOfObjectsObject);
}
location.setProperty(runtime, \\"arrayOfObjects\\", arrayOfObjects);
$payload.setProperty(runtime, \\"location\\", location);
}
return $payload;
@@ -199,19 +199,10 @@ class EventNestedObjectPropsNativeComponentViewEventEmitter : public ViewEventEm
std::string url;
};
struct OnChangeLocationArrayOfObjectsValue {
std::string str;
};
struct OnChangeLocationArrayOfObjects {
OnChangeLocationArrayOfObjectsValue value;
};
struct OnChangeLocation {
OnChangeLocationSource source;
int x;
int y;
std::vector<OnChangeLocationArrayOfObjects> arrayOfObjects;
};
struct OnChange {
@@ -104,7 +104,7 @@ function generateSetter(
) {
const eventChain = usingEvent
? `$event.${[...propertyParts, propertyName].join('.')}`
: [...propertyParts, propertyName].join('.');
: [propertyParts, propertyName].join('.');
return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper(
eventChain,
)});`;
@@ -157,7 +157,7 @@ function generateArraySetter(
): string {
const eventChain = usingEvent
? `$event.${[...propertyParts, propertyName].join('.')}`
: [...propertyParts, propertyName].join('.');
: [propertyParts, propertyName].join('.');
const indexVar = `${propertyName}Index`;
const innerLoopVar = `${propertyName}Value`;
return `
@@ -1717,7 +1717,6 @@ const REAL_MODULE_EXAMPLE: SchemaType = {
const CXX_ONLY_NATIVE_MODULES: SchemaType = {
modules: {
// $FlowFixMe[incompatible-type]
NativeSampleTurboModule: {
type: 'NativeModule',
aliasMap: {
@@ -265,35 +265,6 @@ export interface Spec extends TurboModule {
export default TurboModuleRegistry.getEnforcing<Spec>('MixedValuesEnumNativeModule');
`;
const NUMERIC_VALUES_ENUM_NATIVE_MODULE = `
/**
* Copyright (c) Meta Platforms, Inc. and 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';
import type {TurboModule} from '../RCTExport';
import * as TurboModuleRegistry from '../TurboModuleRegistry';
export enum SomeEnum {
NUM = 1,
NEGATIVE = -1,
SUBFACTORIAL = !5,
}
export interface Spec extends TurboModule {
+getEnums: (a: SomeEnum) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NumericValuesEnumNativeModule');
`;
const MAP_WITH_EXTRA_KEYS_NATIVE_MODULE = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -333,6 +304,5 @@ module.exports = {
TWO_NATIVE_EXTENDING_TURBO_MODULE,
EMPTY_ENUM_NATIVE_MODULE,
MIXED_VALUES_ENUM_NATIVE_MODULE,
NUMERIC_VALUES_ENUM_NATIVE_MODULE,
MAP_WITH_EXTRA_KEYS_NATIVE_MODULE,
};
@@ -782,7 +782,6 @@ export enum Quality {
}
export enum Resolution {
Corrupted = -1,
Low = 720,
High = 1080,
}
@@ -25,12 +25,6 @@ exports[`RN Codegen Flow Parser Fails with error message NATIVE_MODULES_WITH_REA
exports[`RN Codegen Flow Parser Fails with error message NATIVE_MODULES_WITH_UNNAMED_PARAMS 1`] = `"Module NativeSampleTurboModule: All function parameters must be named."`;
exports[`RN Codegen Flow Parser Fails with error message NUMERIC_VALUES_ENUM_NATIVE_MODULE 1`] = `
"Syntax error in path/NativeSampleTurboModule.js: 'true', 'false', 'string', 'number' or 'bigint' expected in enum member initializer (20:17)
SUBFACTORIAL = !5,
~~~~~~~~~~~~~~~^"
`;
exports[`RN Codegen Flow Parser Fails with error message TWO_NATIVE_EXTENDING_TURBO_MODULE 1`] = `"Module NativeSampleTurboModule: Every NativeModule spec file must declare exactly one NativeModule Flow interface. This file declares 2: 'Spec', and 'Spec2'. Please remove the extraneous Flow interface declarations."`;
exports[`RN Codegen Flow Parser Fails with error message TWO_NATIVE_MODULES_EXPORTED_WITH_DEFAULT 1`] = `"Module NativeSampleTurboModule: No Flow interfaces extending TurboModule were detected in this NativeModule spec."`;
@@ -160,10 +154,6 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`]
'type': 'EnumDeclarationWithMembers',
'memberType': 'NumberTypeAnnotation',
'members': [
{
'name': 'Corrupted',
'value': -1
},
{
'name': 'Low',
'value': 720
@@ -208,34 +208,6 @@ export default TurboModuleRegistry.getEnforcing<Spec>(
);
`;
const NUMERIC_VALUES_ENUM_NATIVE_MODULE = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export enum SomeEnum {
NUM = 1,
NEGATIVE = -1,
SUBFACTORIAL = !5,
}
export interface Spec extends TurboModule {
readonly getEnums: (a: SomeEnum) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NumericValuesEnumNativeModule',
);
`;
const MAP_WITH_EXTRA_KEYS_NATIVE_MODULE = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -271,6 +243,5 @@ module.exports = {
TWO_NATIVE_EXTENDING_TURBO_MODULE,
EMPTY_ENUM_NATIVE_MODULE,
MIXED_VALUES_ENUM_NATIVE_MODULE,
NUMERIC_VALUES_ENUM_NATIVE_MODULE,
MAP_WITH_EXTRA_KEYS_NATIVE_MODULE,
};
@@ -866,7 +866,6 @@ export enum Quality {
}
export enum Resolution {
Corrupted = -1,
Low = 720,
High = 1080,
}
@@ -16,8 +16,6 @@ exports[`RN Codegen TypeScript Parser Fails with error message NATIVE_MODULES_WI
exports[`RN Codegen TypeScript Parser Fails with error message NATIVE_MODULES_WITH_UNNAMED_PARAMS 1`] = `"Module NativeSampleTurboModule: All function parameters must be named."`;
exports[`RN Codegen TypeScript Parser Fails with error message NUMERIC_VALUES_ENUM_NATIVE_MODULE 1`] = `"Module NativeSampleTurboModule: Failed parsing the enum SomeEnum in NativeSampleTurboModule with the error: Enum values can not be mixed. They all must be either blank, number, or string values."`;
exports[`RN Codegen TypeScript Parser Fails with error message TWO_NATIVE_EXTENDING_TURBO_MODULE 1`] = `"Module NativeSampleTurboModule: Every NativeModule spec file must declare exactly one NativeModule TypeScript interface. This file declares 2: 'Spec', and 'Spec2'. Please remove the extraneous TypeScript interface declarations."`;
exports[`RN Codegen TypeScript Parser Fails with error message TWO_NATIVE_MODULES_EXPORTED_WITH_DEFAULT 1`] = `"Module NativeSampleTurboModule: No TypeScript interfaces extending TurboModule were detected in this NativeModule spec."`;
@@ -147,10 +145,6 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL
'type': 'EnumDeclarationWithMembers',
'memberType': 'NumberTypeAnnotation',
'members': [
{
'name': 'Corrupted',
'value': -1
},
{
'name': 'Low',
'value': 720
@@ -106,52 +106,6 @@ describe('TypeScript Module Parser', () => {
expect(parser).toThrow(UnnamedFunctionParamParserError);
});
it('should properly parse negative enums', () => {
const parser = () =>
parseModule(`
import type {TurboModule} from 'RCTExport';
import * as TurboModuleRegistry from 'TurboModuleRegistry';
enum MyEnum {
ZERO = 0,
POSITIVE = 1,
NEGATIVE = -1,
}
export interface Spec extends TurboModule {
useArg(arg: MyEnum): void;
}
export default TurboModuleRegistry.get<Spec>('Foo');
`);
expect(parser).not.toThrow();
expect(parser().enumMap.MyEnum.members).toEqual([
{name: 'ZERO', value: 0},
{name: 'POSITIVE', value: 1},
{name: 'NEGATIVE', value: -1},
]);
});
it('should properly parse enums', () => {
const parser = () =>
parseModule(`
import type {TurboModule} from 'RCTExport';
import * as TurboModuleRegistry from 'TurboModuleRegistry';
enum MyEnum {
ZERO = 0,
POSITIVE = 1,
}
export interface Spec extends TurboModule {
useArg(arg: MyEnum): void;
}
export default TurboModuleRegistry.get<Spec>('Foo');
`);
expect(parser).not.toThrow();
expect(parser().enumMap.MyEnum.members).toEqual([
{name: 'ZERO', value: 0},
{name: 'POSITIVE', value: 1},
]);
});
[
{nullable: false, optional: false},
{nullable: false, optional: true},
@@ -183,30 +183,12 @@ class TypeScriptParser implements Parser {
parseEnumMembersType(typeAnnotation: $FlowFixMe): NativeModuleEnumMemberType {
const enumInitializer = typeAnnotation.members[0]?.initializer;
const enumInitializerType = enumInitializer?.type;
let enumMembersType: ?NativeModuleEnumMemberType = null;
if (!enumInitializerType) {
return 'StringTypeAnnotation';
}
switch (enumInitializerType) {
case 'StringLiteral':
enumMembersType = 'StringTypeAnnotation';
break;
case 'NumericLiteral':
enumMembersType = 'NumberTypeAnnotation';
break;
case 'UnaryExpression':
if (enumInitializer.operator === '-') {
enumMembersType = 'NumberTypeAnnotation';
}
break;
default:
enumMembersType = null;
}
const enumMembersType: ?NativeModuleEnumMemberType =
!enumInitializer || enumInitializer.type === 'StringLiteral'
? 'StringTypeAnnotation'
: enumInitializer.type === 'NumericLiteral'
? 'NumberTypeAnnotation'
: null;
if (!enumMembersType) {
throw new Error(
'Enum values must be either blank, number, or string values.',
@@ -231,14 +213,9 @@ class TypeScriptParser implements Parser {
: null;
typeAnnotation.members.forEach(member => {
const isNegative =
member.initializer?.type === 'UnaryExpression' &&
member.initializer?.operator === '-';
const initializerType = isNegative
? member.initializer?.argument?.type
: member.initializer?.type;
if ((initializerType ?? 'StringLiteral') !== enumInitializerType) {
if (
(member.initializer?.type ?? 'StringLiteral') !== enumInitializerType
) {
throw new Error(
'Enum values can not be mixed. They all must be either blank, number, or string values.',
);
@@ -249,20 +226,10 @@ class TypeScriptParser implements Parser {
parseEnumMembers(
typeAnnotation: $FlowFixMe,
): $ReadOnlyArray<NativeModuleEnumMember> {
return typeAnnotation.members.map(member => {
// Handle negative values
if (member.initializer?.operator === '-') {
return {
name: member.id.name,
value: -member.initializer?.argument?.value ?? member.id.name,
};
}
return {
name: member.id.name,
value: member.initializer?.value ?? member.id.name,
};
});
return typeAnnotation.members.map(member => ({
name: member.id.name,
value: member.initializer?.value ?? member.id.name,
}));
}
isModuleInterface(node: $FlowFixMe): boolean {
@@ -24,15 +24,15 @@ public class PopupMenuPackage() : BaseReactPackage(), ViewManagerOnDemandReactPa
ModuleSpec.viewManagerSpec({ ReactPopupMenuManager() }),
)
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
override fun getModule(name: String, context: ReactApplicationContext): NativeModule? {
return null
}
protected override fun getViewManagers(reactContext: ReactApplicationContext): List<ModuleSpec> {
protected override fun getViewManagers(context: ReactApplicationContext): List<ModuleSpec> {
return viewManagersMap.values.toList()
}
override fun getViewManagerNames(reactContext: ReactApplicationContext): Collection<String> {
override fun getViewManagerNames(context: ReactApplicationContext): Collection<String> {
return viewManagersMap.keys
}
@@ -44,11 +44,6 @@
"includesGeneratedCode": true,
"android": {
"javaPackageName": "com.reactnative.osslibraryexample"
},
"ios": {
"componentProvider": {
"SampleNativeComponent": "RCTSampleNativeComponentComponentView"
}
}
}
}
@@ -22,7 +22,6 @@ export interface ActionSheetIOSOptions {
anchor?: number | undefined;
tintColor?: ColorValue | ProcessedColorValue | undefined;
cancelButtonTintColor?: ColorValue | ProcessedColorValue | undefined;
disabledButtonTintColor?: ColorValue | ProcessedColorValue | undefined;
userInterfaceStyle?: 'light' | 'dark' | undefined;
disabledButtonIndices?: number[] | undefined;
}
@@ -49,7 +49,6 @@ const ActionSheetIOS = {
+anchor?: ?number,
+tintColor?: ColorValue | ProcessedColorValue,
+cancelButtonTintColor?: ColorValue | ProcessedColorValue,
+disabledButtonTintColor?: ColorValue | ProcessedColorValue,
+userInterfaceStyle?: string,
+disabledButtonIndices?: Array<number>,
|},
@@ -65,7 +64,6 @@ const ActionSheetIOS = {
const {
tintColor,
cancelButtonTintColor,
disabledButtonTintColor,
destructiveButtonIndex,
...remainingOptions
} = options;
@@ -79,10 +77,6 @@ const ActionSheetIOS = {
const processedTintColor = processColor(tintColor);
const processedCancelButtonTintColor = processColor(cancelButtonTintColor);
const processedDisabledButtonTintColor = processColor(
disabledButtonTintColor,
);
invariant(
processedTintColor == null || typeof processedTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions tintColor',
@@ -92,11 +86,6 @@ const ActionSheetIOS = {
typeof processedCancelButtonTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions cancelButtonTintColor',
);
invariant(
processedDisabledButtonTintColor == null ||
typeof processedDisabledButtonTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions disabledButtonTintColor',
);
RCTActionSheetManager.showActionSheetWithOptions(
{
...remainingOptions,
@@ -104,8 +93,6 @@ const ActionSheetIOS = {
tintColor: processedTintColor,
// $FlowFixMe[incompatible-call]
cancelButtonTintColor: processedCancelButtonTintColor,
// $FlowFixMe[incompatible-call]
disabledButtonTintColor: processedDisabledButtonTintColor,
destructiveButtonIndices,
},
callback,
@@ -121,12 +121,10 @@ describe('Animated', () => {
await unmount(root);
expect(callback).not.toBeCalled();
await jest.runOnlyPendingTimersAsync();
expect(callback).toBeCalledWith({finished: false});
});
it('triggers callback when spring is at rest', async () => {
it('triggers callback when spring is at rest', () => {
const anim = new Animated.Value(0);
const callback = jest.fn();
Animated.spring(anim, {
@@ -134,10 +132,7 @@ describe('Animated', () => {
velocity: 0,
useNativeDriver: false,
}).start(callback);
expect(callback).not.toBeCalled();
await jest.runOnlyPendingTimersAsync();
expect(callback).toBeCalledWith({finished: true});
expect(callback).toBeCalled();
});
it('send toValue when a critically damped spring stops', () => {
@@ -165,7 +165,11 @@ export default class Animation {
const callback = this.#onEnd;
if (callback != null) {
this.#onEnd = null;
queueMicrotask(() => callback(result));
if (ReactNativeFeatureFlags.scheduleAnimatedEndCallbackInMicrotask()) {
queueMicrotask(() => callback(result));
} else {
callback(result);
}
}
}
}
@@ -8,7 +8,6 @@
* @format
*/
import type {SectionBase} from '../../Lists/SectionList';
import type {AnimatedComponentType} from '../createAnimatedComponent';
import SectionList from '../../Lists/SectionList';
@@ -17,6 +16,5 @@ import * as React from 'react';
export default (createAnimatedComponent(SectionList): AnimatedComponentType<
React.ElementConfig<typeof SectionList>,
// $FlowExpectedError[unclear-type]
SectionList<SectionBase<any>>,
React.ElementRef<typeof SectionList>,
>);
@@ -73,6 +73,8 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
const useNativePropsInFabric =
ReactNativeFeatureFlags.shouldUseSetNativePropsInFabric();
const useSetNativePropsInNativeAnimationsInFabric =
ReactNativeFeatureFlags.shouldUseSetNativePropsInNativeAnimationsInFabric();
const useAnimatedPropsLifecycle =
ReactNativeFeatureFlags.useInsertionEffectsForAnimations()
@@ -117,7 +119,12 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
if (isFabricNode) {
// Call `scheduleUpdate` to synchronise Fiber and Shadow tree.
// Must not be called in Paper.
scheduleUpdate();
if (useSetNativePropsInNativeAnimationsInFabric) {
// $FlowFixMe[incompatible-use]
instance.setNativeProps(node.__getAnimatedValue());
} else {
scheduleUpdate();
}
}
return;
}
@@ -194,7 +201,12 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
}
};
},
[node, useNativePropsInFabric, props],
[
node,
useNativePropsInFabric,
useSetNativePropsInNativeAnimationsInFabric,
props,
],
);
const callbackRef = useRefEffect<TInstance>(refEffect);
@@ -17,7 +17,6 @@
@protocol RCTComponentViewProtocol;
@class RCTRootView;
@class RCTSurfacePresenterBridgeAdapter;
@protocol RCTDependencyProvider;
NS_ASSUME_NONNULL_BEGIN
@@ -71,7 +70,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, nullable) NSString *moduleName;
@property (nonatomic, strong, nullable) NSDictionary *initialProps;
@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory;
@property (nonatomic, strong) id<RCTDependencyProvider> dependencyProvider;
/// If `automaticallyLoadReactNativeWindow` is set to `true`, the React Native window will be loaded automatically.
@property (nonatomic, assign) BOOL automaticallyLoadReactNativeWindow;
@@ -19,7 +19,6 @@
#import <react/renderer/graphics/ColorComponents.h>
#import "RCTAppDelegate+Protected.h"
#import "RCTAppSetupUtils.h"
#import "RCTDependencyProvider.h"
#if RN_DISABLE_OSS_PLUGIN_HEADER
#import <RCTTurboModulePlugin/RCTTurboModulePlugin.h>
@@ -229,14 +228,14 @@ using namespace facebook::react;
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
return RCTAppSetupDefaultModuleFromClass(moduleClass, self.dependencyProvider);
return RCTAppSetupDefaultModuleFromClass(moduleClass);
}
#pragma mark - RCTComponentViewFactoryComponentProvider
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
return self.dependencyProvider ? self.dependencyProvider.thirdPartyFabricComponents : @{};
return @{};
}
- (RCTRootViewFactory *)createRCTRootViewFactory
@@ -314,10 +313,6 @@ class RCTAppDelegateBridgelessFeatureFlags : public ReactNativeFeatureFlagsDefau
{
return true;
}
bool enableFixForViewCommandRace() override
{
return true;
}
};
- (void)_setUpFeatureFlags
@@ -25,16 +25,12 @@
#import <ReactCommon/RCTTurboModuleManager.h>
@protocol RCTDependencyProvider;
// Forward declaration to decrease compilation coupling
namespace facebook::react {
class RuntimeScheduler;
}
RCT_EXTERN id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(
Class moduleClass,
id<RCTDependencyProvider> dependencyProvider);
RCT_EXTERN id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(Class moduleClass);
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupDefaultJsExecutorFactory(
RCTBridge *bridge,
@@ -27,7 +27,13 @@
// jsinspector-modern
#import <jsinspector-modern/InspectorFlags.h>
#import "RCTDependencyProvider.h"
#if __has_include(<ReactCodegen/RCTModulesConformingToProtocolsProvider.h>)
#define USE_OSS_CODEGEN 1
#import <ReactCodegen/RCTModulesConformingToProtocolsProvider.h>
#else
// Meta internal system do not generate the RCTModulesConformingToProtocolsProvider.h file
#define USE_OSS_CODEGEN 0
#endif
void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
{
@@ -54,20 +60,22 @@ RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary
return [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(Class moduleClass, id<RCTDependencyProvider> dependencyProvider)
id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(Class moduleClass)
{
// private block used to filter out modules depending on protocol conformance
NSArray * (^extractModuleConformingToProtocol)(RCTModuleRegistry *, Protocol *) =
^NSArray *(RCTModuleRegistry *moduleRegistry, Protocol *protocol) {
NSArray<NSString *> *classNames = @[];
#if USE_OSS_CODEGEN
if (protocol == @protocol(RCTImageURLLoader)) {
classNames = dependencyProvider ? dependencyProvider.imageURLLoaderClassNames : @[];
classNames = [RCTModulesConformingToProtocolsProvider imageURLLoaderClassNames];
} else if (protocol == @protocol(RCTImageDataDecoder)) {
classNames = dependencyProvider ? dependencyProvider.imageDataDecoderClassNames : @[];
classNames = [RCTModulesConformingToProtocolsProvider imageDataDecoderClassNames];
} else if (protocol == @protocol(RCTURLRequestHandler)) {
classNames = dependencyProvider ? dependencyProvider.URLRequestHandlerClassNames : @[];
classNames = [RCTModulesConformingToProtocolsProvider URLRequestHandlerClassNames];
}
#endif
NSMutableArray *modules = [NSMutableArray new];
@@ -1,26 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTComponentViewProtocol;
NS_ASSUME_NONNULL_BEGIN
@protocol RCTDependencyProvider <NSObject>
- (NSArray<NSString *> *)imageURLLoaderClassNames;
- (NSArray<NSString *> *)imageDataDecoderClassNames;
- (NSArray<NSString *> *)URLRequestHandlerClassNames;
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
@end
NS_ASSUME_NONNULL_END
@@ -26,6 +26,7 @@
#import <React/RCTFabricSurface.h>
#import <React/RCTSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <ReactCommon/RCTContextContainerHandling.h>
#if USE_HERMES
#import <ReactCommon/RCTHermesInstance.h>
#else
@@ -34,10 +35,22 @@
#import <ReactCommon/RCTHost+Internal.h>
#import <ReactCommon/RCTHost.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <react/config/ReactNativeConfig.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
#import <react/runtime/JSRuntimeFactory.h>
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
static NSDictionary *updateInitialProps(NSDictionary *initialProps, BOOL isFabricEnabled)
{
NSMutableDictionary *mutableProps = initialProps != NULL ? [initialProps mutableCopy] : [NSMutableDictionary new];
// Hardcoding the Concurrent Root as it it not recommended to
// have the concurrentRoot turned off when Fabric is enabled.
mutableProps[kRNConcurrentRoot] = @(isFabricEnabled);
return mutableProps;
}
@implementation RCTRootViewFactoryConfiguration
- (instancetype)initWithBundleURL:(NSURL *)bundleURL newArchEnabled:(BOOL)newArchEnabled
@@ -86,8 +99,13 @@
@end
@interface RCTRootViewFactory () <RCTCxxBridgeDelegate> {
@interface RCTRootViewFactory () <RCTContextContainerHandling> {
std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
facebook::react::ContextContainer::Shared _contextContainer;
}
@end
@interface RCTRootViewFactory () <RCTCxxBridgeDelegate> {
std::shared_ptr<facebook::react::RuntimeScheduler> _runtimeScheduler;
}
@end
@@ -106,6 +124,8 @@
_configuration = configuration;
_hostDelegate = hostdelegate;
_contextContainer = std::make_shared<const facebook::react::ContextContainer>();
_reactNativeConfig = std::make_shared<const facebook::react::EmptyReactNativeConfig>();
_contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
_turboModuleManagerDelegate = turboModuleManagerDelegate;
}
return self;
@@ -138,9 +158,11 @@
}
- (UIView *)viewWithModuleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initProps
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions
{
NSDictionary *initProps = updateInitialProps(initialProperties, _configuration.fabricEnabled);
if (_configuration.bridgelessEnabled) {
// Enable TurboModule interop by default in Bridgeless mode
RCTEnableTurboModuleInterop(YES);
@@ -154,8 +176,8 @@
[[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor];
if (_configuration.customizeRootView != nil) {
_configuration.customizeRootView(surfaceHostingProxyRootView);
if (self->_configuration.customizeRootView != nil) {
self->_configuration.customizeRootView(surfaceHostingProxyRootView);
}
return surfaceHostingProxyRootView;
}
@@ -169,8 +191,8 @@
} else {
rootView = [self createRootViewWithBridge:self.bridge moduleName:moduleName initProps:initProps];
}
if (_configuration.customizeRootView != nil) {
_configuration.customizeRootView(rootView);
if (self->_configuration.customizeRootView != nil) {
self->_configuration.customizeRootView(rootView);
}
return rootView;
}
@@ -184,9 +206,11 @@
moduleName:(NSString *)moduleName
initProps:(NSDictionary *)initProps
{
BOOL enableFabric = _configuration.fabricEnabled;
BOOL enableFabric = self->_configuration.fabricEnabled;
UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, enableFabric);
rootView.backgroundColor = [UIColor systemBackgroundColor];
return rootView;
}
@@ -257,6 +281,7 @@
[reactHost setBundleURLProvider:^NSURL *() {
return [weakSelf bundleURL];
}];
[reactHost setContextContainerHandler:self];
[reactHost start];
return reactHost;
}
@@ -264,12 +289,18 @@
- (std::shared_ptr<facebook::react::JSRuntimeFactory>)createJSRuntimeFactory
{
#if USE_HERMES
return std::make_shared<facebook::react::RCTHermesInstance>(nullptr, nullptr, /* allocInOldGenBeforeTTI */ false);
return std::make_shared<facebook::react::RCTHermesInstance>(
_reactNativeConfig, nullptr, /* allocInOldGenBeforeTTI */ false);
#else
return std::make_shared<facebook::react::RCTJscInstance>();
#endif
}
- (void)didCreateContextContainer:(std::shared_ptr<facebook::react::ContextContainer>)contextContainer
{
contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
if (_configuration.extraModulesForBridge != nil) {
@@ -74,7 +74,7 @@ Pod::Spec.new do |s|
s.dependency "React-RCTImage"
s.dependency "React-CoreModules"
s.dependency "React-nativeconfig"
s.dependency "React-RCTFBReactNativeSpec"
s.dependency "ReactCodegen"
s.dependency "React-defaultsnativemodule"
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
@@ -57,7 +57,7 @@ Pod::Spec.new do |s|
s.dependency "React-Core/RCTWebSocket"
s.dependency "React-RCTNetwork"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "ReactCodegen")
add_dependency(s, "React-NativeModulesApple")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
@@ -56,7 +56,6 @@ const EventNames: Map<
['screenReaderChanged', 'touchExplorationDidChange'],
['accessibilityServiceChanged', 'accessibilityServiceDidChange'],
['invertColorsChanged', 'invertColorDidChange'],
['grayscaleChanged', 'grayscaleModeDidChange'],
])
: new Map([
['announcementFinished', 'announcementFinished'],
@@ -115,13 +114,7 @@ const AccessibilityInfo = {
*/
isGrayscaleEnabled(): Promise<boolean> {
if (Platform.OS === 'android') {
return new Promise((resolve, reject) => {
if (NativeAccessibilityInfoAndroid?.isGrayscaleEnabled != null) {
NativeAccessibilityInfoAndroid.isGrayscaleEnabled(resolve);
} else {
reject(null);
}
});
return Promise.resolve(false);
} else {
return new Promise((resolve, reject) => {
if (NativeAccessibilityManagerIOS != null) {
@@ -31,6 +31,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
pagingEnabled: true,
persistentScrollbar: true,
horizontal: true,
enableSyncOnScroll: true,
scrollEnabled: true,
scrollEventThrottle: true,
scrollPerfTag: true,
@@ -8,6 +8,10 @@
* @flow strict-local
*/
import type {
TScrollViewNativeComponentInstance,
TScrollViewNativeImperativeHandle,
} from '../../../src/private/components/useSyncOnScroll';
import type {HostInstance} from '../../Renderer/shims/ReactNativeTypes';
import type {EdgeInsetsProp} from '../../StyleSheet/EdgeInsetsPropType';
import type {PointProp} from '../../StyleSheet/PointPropType';
@@ -42,6 +46,7 @@ import StyleSheet from '../../StyleSheet/StyleSheet';
import Dimensions from '../../Utilities/Dimensions';
import dismissKeyboard from '../../Utilities/dismissKeyboard';
import Platform from '../../Utilities/Platform';
import EventEmitter from '../../vendor/emitter/EventEmitter';
import Keyboard from '../Keyboard/Keyboard';
import TextInputState from '../TextInput/TextInputState';
import processDecelerationRate from './processDecelerationRate';
@@ -147,7 +152,7 @@ export type DecelerationRateType = 'fast' | 'normal' | number;
export type ScrollResponderType = ScrollViewImperativeMethods;
type PublicScrollViewInstance = $ReadOnly<{|
...HostInstance,
...$Exact<TScrollViewNativeComponentInstance>,
...ScrollViewImperativeMethods,
|}>;
@@ -738,6 +743,10 @@ class ScrollView extends React.Component<Props, State> {
_subscriptionKeyboardDidShow: ?EventSubscription = null;
_subscriptionKeyboardDidHide: ?EventSubscription = null;
#onScrollEmitter: ?EventEmitter<{
scroll: [{x: number, y: number}],
}> = null;
state: State = {
layoutHeight: null,
};
@@ -808,6 +817,8 @@ class ScrollView extends React.Component<Props, State> {
if (this._scrollAnimatedValueAttachment) {
this._scrollAnimatedValueAttachment.detach();
}
this.#onScrollEmitter?.removeAllListeners();
}
/**
@@ -833,8 +844,9 @@ class ScrollView extends React.Component<Props, State> {
return this._innerView.nativeInstance;
};
getNativeScrollRef: () => HostInstance | null = () => {
return this._scrollView.nativeInstance;
getNativeScrollRef: () => TScrollViewNativeComponentInstance | null = () => {
const {nativeInstance} = this._scrollView;
return nativeInstance == null ? null : nativeInstance.componentRef.current;
};
/**
@@ -925,6 +937,20 @@ class ScrollView extends React.Component<Props, State> {
Commands.flashScrollIndicators(component);
};
_subscribeToOnScroll: (
callback: ({x: number, y: number}) => void,
) => EventSubscription = callback => {
let onScrollEmitter = this.#onScrollEmitter;
if (onScrollEmitter == null) {
onScrollEmitter = new EventEmitter();
this.#onScrollEmitter = onScrollEmitter;
// This is the first subscription, so make sure the native component is
// also configured to output synchronous scroll events.
this._scrollView.nativeInstance?.unstable_setEnableSyncOnScroll(true);
}
return onScrollEmitter.addListener('scroll', callback);
};
/**
* This method should be used as the callback to onFocus in a TextInputs'
* parent view. Note that any module using this mixin needs to return
@@ -1128,6 +1154,11 @@ class ScrollView extends React.Component<Props, State> {
_handleScroll = (e: ScrollEvent) => {
this._observedScrollSinceBecomingResponder = true;
this.props.onScroll && this.props.onScroll(e);
this.#onScrollEmitter?.emit('scroll', {
x: e.nativeEvent.contentOffset.x,
y: e.nativeEvent.contentOffset.y,
});
};
_handleLayout = (e: LayoutEvent) => {
@@ -1150,36 +1181,45 @@ class ScrollView extends React.Component<Props, State> {
(instance: InnerViewInstance): InnerViewInstance => instance,
);
_scrollView: RefForwarder<HostInstance, PublicScrollViewInstance | null> =
createRefForwarder(nativeInstance => {
// This is a hack. Ideally we would forwardRef to the underlying
// host component. However, since ScrollView has it's own methods that can be
// called as well, if we used the standard forwardRef then these
// methods wouldn't be accessible and thus be a breaking change.
//
// Therefore we edit ref to include ScrollView's public methods so that
// they are callable from the ref.
_scrollView: RefForwarder<
TScrollViewNativeImperativeHandle,
PublicScrollViewInstance | null,
> = createRefForwarder(nativeImperativeHandle => {
const nativeInstance = nativeImperativeHandle.componentRef.current;
if (nativeInstance == null) {
return null;
}
// $FlowFixMe[prop-missing] - Known issue with appending custom methods.
const publicInstance: PublicScrollViewInstance = Object.assign(
nativeInstance,
{
getScrollResponder: this.getScrollResponder,
getScrollableNode: this.getScrollableNode,
getInnerViewNode: this.getInnerViewNode,
getInnerViewRef: this.getInnerViewRef,
getNativeScrollRef: this.getNativeScrollRef,
scrollTo: this.scrollTo,
scrollToEnd: this.scrollToEnd,
flashScrollIndicators: this.flashScrollIndicators,
scrollResponderZoomTo: this.scrollResponderZoomTo,
scrollResponderScrollNativeHandleToKeyboard:
this.scrollResponderScrollNativeHandleToKeyboard,
},
);
// This is a hack. Ideally we would forwardRef to the underlying
// host component. However, since ScrollView has it's own methods that can be
// called as well, if we used the standard forwardRef then these
// methods wouldn't be accessible and thus be a breaking change.
//
// Therefore we edit ref to include ScrollView's public methods so that
// they are callable from the ref.
return publicInstance;
});
// $FlowFixMe[prop-missing] - Known issue with appending custom methods.
const publicInstance: PublicScrollViewInstance = Object.assign(
nativeInstance,
{
getScrollResponder: this.getScrollResponder,
getScrollableNode: this.getScrollableNode,
getInnerViewNode: this.getInnerViewNode,
getInnerViewRef: this.getInnerViewRef,
getNativeScrollRef: this.getNativeScrollRef,
scrollTo: this.scrollTo,
scrollToEnd: this.scrollToEnd,
flashScrollIndicators: this.flashScrollIndicators,
scrollResponderZoomTo: this.scrollResponderZoomTo,
// TODO: Replace unstable_subscribeToOnScroll once scrollView.addEventListener('scroll', (e: ScrollEvent) => {}, {passive: false});
unstable_subscribeToOnScroll: this._subscribeToOnScroll,
scrollResponderScrollNativeHandleToKeyboard:
this.scrollResponderScrollNativeHandleToKeyboard,
},
);
return publicInstance;
});
/**
* Warning, this may be called several times for a single keyboard opening.
@@ -1789,9 +1829,8 @@ class ScrollView extends React.Component<Props, State> {
}
const refreshControl = this.props.refreshControl;
const scrollViewRef = this._scrollView.getForwardingRef(
this.props.scrollViewRef,
);
const scrollViewRef: React.RefSetter<TScrollViewNativeImperativeHandle | null> =
this._scrollView.getForwardingRef(this.props.scrollViewRef);
if (refreshControl) {
if (Platform.OS === 'ios') {
@@ -45,6 +45,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
diff: require('../../Utilities/differ/pointsDiffer'),
},
decelerationRate: true,
enableSyncOnScroll: true, // Fabric only.
disableIntervalMomentum: true,
maintainVisibleContentPosition: true,
pagingEnabled: true,
@@ -134,6 +135,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
contentInsetAdjustmentBehavior: true,
decelerationRate: true,
endDraggingSensitivityMultiplier: true,
enableSyncOnScroll: true, // Fabric only.
directionalLockEnabled: true,
disableIntervalMomentum: true,
indicatorStyle: true,
@@ -301,6 +301,7 @@ const ScrollViewStickyHeaderWithForwardedRef: component(
const styles = StyleSheet.create({
header: {
zIndex: 10,
position: 'relative',
},
fill: {
flex: 1,
@@ -121,7 +121,6 @@ const RCTTextInputViewConfig = {
},
editable: true,
inputAccessoryViewID: true,
inputAccessoryViewButtonLabel: true,
caretHidden: true,
enablesReturnKeyAutomatically: true,
placeholderTextColor: {
@@ -266,12 +266,6 @@ type IOSProps = $ReadOnly<{|
*/
inputAccessoryViewID?: ?string,
/**
* An optional label that overrides the default input accessory view button label.
* @platform ios
*/
inputAccessoryViewButtonLabel?: ?string,
/**
* Determines the color of the keyboard.
* @platform ios
@@ -310,12 +310,6 @@ type IOSProps = $ReadOnly<{|
*/
inputAccessoryViewID?: ?string,
/**
* An optional label that overrides the default input accessory view button label.
* @platform ios
*/
inputAccessoryViewButtonLabel?: ?string,
/**
* Determines the color of the keyboard.
* @platform ios
@@ -1539,7 +1533,7 @@ function InternalTextInput(props: Props): React.Node {
// TextInput handles onBlur and onFocus events
// so omitting onBlur and onFocus pressability handlers here.
const {onBlur, onFocus, ...eventHandlers} = usePressability(config);
const {onBlur, onFocus, ...eventHandlers} = usePressability(config) || {};
let _accessibilityState;
if (
@@ -195,7 +195,8 @@ module.exports = function TouchableWithoutFeedback(props: Props): React.Node {
// BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
// adopting `Pressability`, so preserve that behavior.
const {onBlur, onFocus, ...eventHandlersWithoutBlurAndFocus} = eventHandlers;
const {onBlur, onFocus, ...eventHandlersWithoutBlurAndFocus} =
eventHandlers || {};
const elementProps: {[string]: mixed, ...} = {
...eventHandlersWithoutBlurAndFocus,
@@ -211,11 +211,4 @@ export interface ViewProps
* Used to reference react managed views from native code.
*/
nativeID?: string | undefined;
/**
* Contols whether this view, and its transitive children, are laid in a way
* consistent with web browsers ('strict'), or consistent with existing
* React Native code which may rely on incorrect behavior ('classic').
*/
experimental_layoutConformance?: 'strict' | 'classic' | undefined;
}
+4 -1
View File
@@ -177,7 +177,10 @@ function reactConsoleErrorHandler(...args) {
if (!console.reportErrorsAsExceptions) {
return;
}
if (inExceptionHandler || global.RN$inExceptionHandler?.()) {
if (
inExceptionHandler ||
(global.RN$inExceptionHandler && global.RN$inExceptionHandler())
) {
// The fundamental trick here is that are multiple entry point to logging errors:
// (see D19743075 for more background)
//
+18 -20
View File
@@ -10,26 +10,24 @@
'use strict';
if (global.RN$useAlwaysAvailableJSErrorHandling !== true) {
/**
* Sets up the console and exception handling (redbox) for React Native.
* You can use this module directly, or just require InitializeCore.
*/
const ExceptionsManager = require('./ExceptionsManager');
ExceptionsManager.installConsoleErrorReporter();
/**
* Sets up the console and exception handling (redbox) for React Native.
* You can use this module directly, or just require InitializeCore.
*/
const ExceptionsManager = require('./ExceptionsManager');
ExceptionsManager.installConsoleErrorReporter();
// Set up error handler
if (!global.__fbDisableExceptionsManager) {
const handleError = (e: mixed, isFatal: boolean) => {
try {
ExceptionsManager.handleException(e, isFatal);
} catch (ee) {
console.log('Failed to print error: ', ee.message);
throw e;
}
};
// Set up error handler
if (!global.__fbDisableExceptionsManager) {
const handleError = (e: mixed, isFatal: boolean) => {
try {
ExceptionsManager.handleException(e, isFatal);
} catch (ee) {
console.log('Failed to print error: ', ee.message);
throw e;
}
};
const ErrorUtils = require('../vendor/core/ErrorUtils');
ErrorUtils.setGlobalHandler(handleError);
}
const ErrorUtils = require('../vendor/core/ErrorUtils');
ErrorUtils.setGlobalHandler(handleError);
}
+12 -4
View File
@@ -26,10 +26,18 @@ const isEventLoopEnabled = (() => {
return false;
}
return (
ReactNativeFeatureFlags.enableBridgelessArchitecture() &&
!ReactNativeFeatureFlags.disableEventLoopOnBridgeless()
);
if (NativeReactNativeFeatureFlags.disableEventLoopOnBridgeless == null) {
// Flags not unified yet
return (
ReactNativeFeatureFlags.useModernRuntimeScheduler() &&
ReactNativeFeatureFlags.enableMicrotasks()
);
} else {
return (
ReactNativeFeatureFlags.enableBridgelessArchitecture() &&
!ReactNativeFeatureFlags.disableEventLoopOnBridgeless()
);
}
})();
// In bridgeless mode, timers are host functions installed from cpp.
@@ -133,6 +133,7 @@ let BaseImage: AbstractImageAndroid = React.forwardRef(
width: undefined,
height: undefined,
};
const defaultSource = resolveAssetSource(props.defaultSource);
const loadingIndicatorSource = resolveAssetSource(
props.loadingIndicatorSource,
);
@@ -178,6 +179,7 @@ let BaseImage: AbstractImageAndroid = React.forwardRef(
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
headers: (source?.[0]?.headers || source?.headers: ?{[string]: string}),
defaultSrc: defaultSource ? defaultSource.uri : null,
loadingIndicatorSrc: loadingIndicatorSource
? loadingIndicatorSource.uri
: null,
-2
View File
@@ -200,8 +200,6 @@ export interface ImagePropsBase
* 'center': Scale the image down so that it is completely visible,
* if bigger than the area of the view.
* The image will not be scaled up.
*
* 'none': Do not resize the image. The image will be displayed at its intrinsic size.
*/
resizeMode?: ImageResizeMode | undefined;
+1 -2
View File
@@ -19,7 +19,6 @@ import type {
} from '../StyleSheet/StyleSheet';
import type {LayoutEvent, SyntheticEvent} from '../Types/CoreEventTypes';
import typeof Image from './Image';
import type {ImageResizeMode} from './ImageResizeMode';
import type {ImageSource} from './ImageSource';
import type {ElementRef, Node, RefSetter} from 'react';
@@ -235,7 +234,7 @@ export type ImageProps = $ReadOnly<{|
*
* See https://reactnative.dev/docs/image#resizemode
*/
resizeMode?: ?ImageResizeMode,
resizeMode?: ?('cover' | 'contain' | 'stretch' | 'repeat' | 'center'),
/**
* A unique identifier for this element to be used in UI Automation
+1 -8
View File
@@ -12,8 +12,7 @@ export type ImageResizeMode =
| 'contain'
| 'stretch'
| 'repeat'
| 'center'
| 'none';
| 'center';
/**
* @see ImageResizeMode.js
@@ -47,10 +46,4 @@ export interface ImageResizeModeStatic {
* image will keep it's size and aspect ratio.
*/
repeat: ImageResizeMode;
/**
* none - The image will be displayed at its intrinsic size, which means the
* image will not be scaled up or down.
*/
none: ImageResizeMode;
}
+1 -4
View File
@@ -33,7 +33,4 @@ export type ImageResizeMode =
// Resize by stretching it to fill the entire frame of the view without
// clipping. This may change the aspect ratio of the image, distorting it.
| 'stretch'
// The image will not be resized at all.
| 'none';
| 'stretch';
@@ -50,6 +50,8 @@ export interface ImageURISource {
* its age or expiration date. If there is no existing data in the cache corresponding
* to a URL load request, no attempt is made to load the data from the originating source,
* and the load is considered to have failed.
*
* @platform ios (for `force-cache`)
*/
cache?: 'default' | 'reload' | 'force-cache' | 'only-if-cached' | undefined;
/**
+2
View File
@@ -65,6 +65,8 @@ export interface ImageURISource {
* its age or expiration date. If there is no existing data in the cache corresponding
* to a URL load request, no attempt is made to load the data from the originating source,
* and the load is considered to have failed.
*
* @platform ios (for `force-cache`)
*/
+cache?: ?('default' | 'reload' | 'force-cache' | 'only-if-cached');

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