mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
52
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10e5b03474 | ||
|
|
3efbe33ce0 | ||
|
|
4134b1c60d | ||
|
|
87ec0965a2 | ||
|
|
c6f12254d1 | ||
|
|
e9f279117e | ||
|
|
7a81fd7a8a | ||
|
|
be9b076087 | ||
|
|
db70b791ba | ||
|
|
da6d089305 | ||
|
|
1243679fe2 | ||
|
|
18ebea533d | ||
|
|
e750059d98 | ||
|
|
e7b9d70e0a | ||
|
|
13900d75b8 | ||
|
|
2f0977d8e4 | ||
|
|
00d5caee99 | ||
|
|
ea84bc666c | ||
|
|
ca908c0681 | ||
|
|
eee5d2ec3c | ||
|
|
efd57d681c | ||
|
|
bc0b5ca5df | ||
|
|
f6aae38e52 | ||
|
|
2e7e065d09 | ||
|
|
b487e65869 | ||
|
|
ff934cd249 | ||
|
|
118c18f3bb | ||
|
|
e7f943de2f | ||
|
|
8aac234ce2 | ||
|
|
f402ed17fa | ||
|
|
734730df75 | ||
|
|
50d0157f0c | ||
|
|
2e444d2b40 | ||
|
|
366270e742 | ||
|
|
469978f170 | ||
|
|
7ee7e69cdf | ||
|
|
efcfe5dcd6 | ||
|
|
b5b9e032c2 | ||
|
|
4c62d46525 | ||
|
|
0217d7e19c | ||
|
|
74f3ab7d40 | ||
|
|
949d229b5f | ||
|
|
52f09276cc | ||
|
|
5e7eb9f3a6 | ||
|
|
725527885a | ||
|
|
d19f5f97b8 | ||
|
|
9aa21b5e87 | ||
|
|
9800c8e47e | ||
|
|
34901d4861 | ||
|
|
21c9491926 | ||
|
|
9df20d414e | ||
|
|
d6f286a4a0 |
@@ -43,9 +43,6 @@ 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
|
||||
if (-not $?) { throw "Failed to install CMake" }
|
||||
|
||||
cd $Env:HERMES_WS_DIR\icu
|
||||
# If Invoke-WebRequest shows a progress bar, it will fail with
|
||||
# Win32 internal error "Access is denied" 0x5 occurred [...]
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
name: Trigger E2E Tests on Comment
|
||||
# This workflow is used to automatically trigger E2E tests when a comment is made
|
||||
# containing the text "/run-e2e-tests".
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
trigger-e2e-tests:
|
||||
name: Trigger E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/test-e2e')
|
||||
steps:
|
||||
# This is needed because of https://github.com/actions/runner-images/issues/6283
|
||||
# TL;DR: brew is not in the PATH anymore.
|
||||
- name: Setup Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Install jq
|
||||
run: brew install jq
|
||||
- name: Run E2E Tests
|
||||
run: |
|
||||
# Github does not provide the branch of a PR when a comment on a PR is made
|
||||
# So, given the issue number, which is the PR number, we can retrieve the branch with
|
||||
# a quick API call
|
||||
echo "Retrieving branch"
|
||||
BRANCH=$(curl -L \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/facebook/react-native/pulls/$PR_NUMBER | jq -r '.head.ref')
|
||||
|
||||
echo "Trigger Test All workflow for branch $BRANCH"
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/facebook/react-native/actions/workflows/test-all.yml/dispatches \
|
||||
-d "{\"ref\": \"$BRANCH\", \"inputs\": {\"run-e2e-tests\": \"true\"}}"
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.issue.number }}
|
||||
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## v0.77.0-rc.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Replace Object.hasOwn usages to fix Animated on JSC ([e996b3f346](https://github.com/facebook/react-native/commit/e996b3f346462a394012a722ce19990cdf9c3d9a) by [@robhogan](https://github.com/robhogan))
|
||||
- Remove non compliant `filename*` attribute in a FormData `content-disposition` header ([f791fb9e66](https://github.com/facebook/react-native/commit/f791fb9e660fe15bccf55029045c48f4bbcbc5cb) by [@foyarash](https://github.com/foyarash))
|
||||
- Fix "punycode is deprecated" warning by replacing `node-fetch` with native `fetch` ([881d8a720f](https://github.com/facebook/react-native/commit/881d8a720fb24241d7b2127273ca6116833bf176) by [@jbroma](https://github.com/jbroma))
|
||||
|
||||
#### Android specific
|
||||
|
||||
- Reverted removal of TurboReactPackage ([70a957452c](https://github.com/facebook/react-native/commit/70a957452c438a74787f4f752b2c274360cb2edd) by [@javache](https://github.com/javache))
|
||||
- Fix IOException in `BuildCodegenCLITask` ([9147b0753a](https://github.com/facebook/react-native/commit/9147b0753a6c3afb2480b079f91614cd7189a28a) by [@vonovak](https://github.com/vonovak))
|
||||
|
||||
## v0.77.0-rc.0
|
||||
|
||||
### Breaking
|
||||
|
||||
@@ -67,7 +67,7 @@ React Native is developed and supported by many companies and individual core co
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
React Native apps may target iOS 13.4 and Android 6.0 (API 23) or newer. You may use Windows, macOS, or Linux as your development operating system, though building and running iOS apps is limited to macOS. Tools like [Expo](https://expo.dev) can be used to work around this.
|
||||
React Native apps may target iOS 15.1 and Android 7.0 (API 24) or newer. You may use Windows, macOS, or Linux as your development operating system, though building and running iOS apps is limited to macOS. Tools like [Expo](https://expo.dev) can be used to work around this.
|
||||
|
||||
## 🎉 Building your first React Native app
|
||||
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {FantomTestConfigJsOnlyFeatureFlags} from './getFantomTestConfig';
|
||||
|
||||
module.exports = function entrypointTemplate({
|
||||
testPath,
|
||||
setupModulePath,
|
||||
featureFlagsModulePath,
|
||||
featureFlags,
|
||||
}: {
|
||||
testPath: string,
|
||||
setupModulePath: string,
|
||||
featureFlagsModulePath: string,
|
||||
featureFlags: FantomTestConfigJsOnlyFeatureFlags,
|
||||
}): string {
|
||||
return `/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
@@ -29,6 +35,17 @@ module.exports = function entrypointTemplate({
|
||||
*/
|
||||
|
||||
import {registerTest} from '${setupModulePath}';
|
||||
${
|
||||
Object.keys(featureFlags).length > 0
|
||||
? `import * as ReactNativeFeatureFlags from '${featureFlagsModulePath}';
|
||||
|
||||
ReactNativeFeatureFlags.override({
|
||||
${Object.entries(featureFlags)
|
||||
.map(([name, value]) => ` ${name}: () => ${JSON.stringify(value)},`)
|
||||
.join('\n')}
|
||||
});`
|
||||
: ''
|
||||
}
|
||||
|
||||
registerTest(() => require('${testPath}'));
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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 ReactNativeFeatureFlags from '../../../packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config';
|
||||
import fs from 'fs';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {extract, parse} from 'jest-docblock';
|
||||
|
||||
type CommonFeatureFlags = (typeof ReactNativeFeatureFlags)['common'];
|
||||
type JsOnlyFeatureFlags = (typeof ReactNativeFeatureFlags)['jsOnly'];
|
||||
|
||||
type DocblockPragmas = {[key: string]: string | string[]};
|
||||
|
||||
export type FantomTestConfigMode = 'dev' | 'opt';
|
||||
|
||||
export type FantomTestConfigCommonFeatureFlags = Partial<{
|
||||
[key in keyof CommonFeatureFlags]: CommonFeatureFlags[key]['defaultValue'],
|
||||
}>;
|
||||
|
||||
export type FantomTestConfigJsOnlyFeatureFlags = Partial<{
|
||||
[key in keyof JsOnlyFeatureFlags]: JsOnlyFeatureFlags[key]['defaultValue'],
|
||||
}>;
|
||||
|
||||
export type FantomTestConfig = {
|
||||
mode: FantomTestConfigMode,
|
||||
flags: {
|
||||
common: FantomTestConfigCommonFeatureFlags,
|
||||
jsOnly: FantomTestConfigJsOnlyFeatureFlags,
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_MODE: FantomTestConfigMode = 'dev';
|
||||
|
||||
const FANTOM_FLAG_FORMAT = /^(\w+):(\w+)$/;
|
||||
|
||||
/**
|
||||
* Extracts the Fantom configuration from the test file, specified as part of
|
||||
* the docblock comment. E.g.:
|
||||
*
|
||||
* ```
|
||||
* /**
|
||||
* * @flow strict-local
|
||||
* * @fantom_mode opt
|
||||
* * @fantom_flags commonTestFlag:true
|
||||
* * @fantom_flags jsOnlyTestFlag:true
|
||||
* *
|
||||
* ```
|
||||
*
|
||||
* The supported options are:
|
||||
* - `fantom_mode`: specifies the level of optimization to compile the test
|
||||
* with. Valid values are `dev` and `opt`.
|
||||
* - `fantom_flags`: specifies the configuration for common and JS-only feature
|
||||
* flags. They can be specified in the same pragma or in different ones, and
|
||||
* the format is `<flag_name>:<value>`.
|
||||
*/
|
||||
export default function getFantomTestConfig(
|
||||
testPath: string,
|
||||
): FantomTestConfig {
|
||||
const docblock = extract(fs.readFileSync(testPath, 'utf8'));
|
||||
const pragmas = parse(docblock) as DocblockPragmas;
|
||||
|
||||
const config: FantomTestConfig = {
|
||||
mode: DEFAULT_MODE,
|
||||
flags: {
|
||||
common: {},
|
||||
jsOnly: {},
|
||||
},
|
||||
};
|
||||
|
||||
const maybeMode = pragmas.fantom_mode;
|
||||
|
||||
if (maybeMode != null) {
|
||||
if (Array.isArray(maybeMode)) {
|
||||
throw new Error('Expected a single value for @fantom_mode');
|
||||
}
|
||||
|
||||
const mode = maybeMode;
|
||||
|
||||
if (mode === 'dev' || mode === 'opt') {
|
||||
config.mode = mode;
|
||||
} else {
|
||||
throw new Error(`Invalid Fantom mode: ${mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
const maybeRawFlagConfig = pragmas.fantom_flags;
|
||||
|
||||
if (maybeRawFlagConfig != null) {
|
||||
const rawFlagConfigs = (
|
||||
Array.isArray(maybeRawFlagConfig)
|
||||
? maybeRawFlagConfig
|
||||
: [maybeRawFlagConfig]
|
||||
).flatMap(value => value.split(/\s+/g));
|
||||
|
||||
for (const rawFlagConfig of rawFlagConfigs) {
|
||||
const matches = FANTOM_FLAG_FORMAT.exec(rawFlagConfig);
|
||||
if (matches == null) {
|
||||
throw new Error(
|
||||
`Invalid format for Fantom feature flag: ${rawFlagConfig}. Expected <flag_name>:<value>`,
|
||||
);
|
||||
}
|
||||
|
||||
const [, name, rawValue] = matches;
|
||||
|
||||
if (ReactNativeFeatureFlags.common[name]) {
|
||||
const flagConfig = ReactNativeFeatureFlags.common[name];
|
||||
const value = parseFeatureFlagValue(flagConfig.defaultValue, rawValue);
|
||||
config.flags.common[name] = value;
|
||||
} else if (ReactNativeFeatureFlags.jsOnly[name]) {
|
||||
const flagConfig = ReactNativeFeatureFlags.jsOnly[name];
|
||||
const value = parseFeatureFlagValue(flagConfig.defaultValue, rawValue);
|
||||
config.flags.jsOnly[name] = value;
|
||||
} else {
|
||||
const validKeys = Object.keys(ReactNativeFeatureFlags.common)
|
||||
.concat(Object.keys(ReactNativeFeatureFlags.jsOnly))
|
||||
.join(', ');
|
||||
|
||||
throw new Error(
|
||||
`Invalid Fantom feature flag: ${name}. Valid flags are: ${validKeys}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function parseFeatureFlagValue<T: boolean | number | string>(
|
||||
defaultValue: T,
|
||||
value: string,
|
||||
): T {
|
||||
switch (typeof defaultValue) {
|
||||
case 'boolean':
|
||||
if (value === 'true') {
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a boolean
|
||||
return true;
|
||||
} else if (value === 'false') {
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a boolean
|
||||
return false;
|
||||
} else {
|
||||
throw new Error(`Invalid value for boolean flag: ${value}`);
|
||||
}
|
||||
case 'number':
|
||||
const parsed = Number(value);
|
||||
|
||||
if (Number.isNaN(parsed)) {
|
||||
throw new Error(`Invalid value for number flag: ${value}`);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a number
|
||||
return parsed;
|
||||
case 'string':
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a string
|
||||
return value;
|
||||
default:
|
||||
throw new Error(`Unsupported feature flag type: ${typeof defaultValue}`);
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@
|
||||
import type {TestSuiteResult} from '../runtime/setup';
|
||||
|
||||
import entrypointTemplate from './entrypoint-template';
|
||||
import getFantomTestConfig from './getFantomTestConfig';
|
||||
import {
|
||||
getBuckModeForPlatform,
|
||||
getDebugInfoFromCommandResult,
|
||||
getFantomTestConfig,
|
||||
getShortHash,
|
||||
runBuck2,
|
||||
symbolicateStackTrace,
|
||||
@@ -104,10 +104,16 @@ module.exports = async function runTest(
|
||||
});
|
||||
|
||||
const setupModulePath = path.resolve(__dirname, '../runtime/setup.js');
|
||||
const featureFlagsModulePath = path.resolve(
|
||||
__dirname,
|
||||
'../../../packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js',
|
||||
);
|
||||
|
||||
const entrypointContents = entrypointTemplate({
|
||||
testPath: `${path.relative(BUILD_OUTPUT_PATH, testPath)}`,
|
||||
setupModulePath: `${path.relative(BUILD_OUTPUT_PATH, setupModulePath)}`,
|
||||
featureFlagsModulePath: `${path.relative(BUILD_OUTPUT_PATH, featureFlagsModulePath)}`,
|
||||
featureFlags: testConfig.flags.jsOnly,
|
||||
});
|
||||
|
||||
const entrypointPath = path.join(
|
||||
@@ -151,6 +157,8 @@ module.exports = async function runTest(
|
||||
'--',
|
||||
'--bundlePath',
|
||||
testBundlePath,
|
||||
'--featureFlags',
|
||||
JSON.stringify(testConfig.flags.common),
|
||||
]);
|
||||
|
||||
if (rnTesterCommandResult.status !== 0) {
|
||||
|
||||
@@ -12,60 +12,10 @@
|
||||
import {spawnSync} from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {extract, parse} from 'jest-docblock';
|
||||
import os from 'os';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {SourceMapConsumer} from 'source-map';
|
||||
|
||||
type DocblockPragmas = {[key: string]: string | string[]};
|
||||
type FantomTestMode = 'dev' | 'opt';
|
||||
type FantomTestConfig = {
|
||||
mode: FantomTestMode,
|
||||
};
|
||||
|
||||
const DEFAULT_MODE: FantomTestMode = 'dev';
|
||||
|
||||
/**
|
||||
* Extracts the Fantom configuration from the test file, specified as part of
|
||||
* the docblock comment. E.g.:
|
||||
*
|
||||
* ```
|
||||
* /**
|
||||
* * @flow strict-local
|
||||
* * @fantom mode:opt
|
||||
* *
|
||||
* ```
|
||||
*
|
||||
* So far the only supported option is `mode`, which can be 'dev' or 'opt'.
|
||||
*/
|
||||
export function getFantomTestConfig(testPath: string): FantomTestConfig {
|
||||
const docblock = extract(fs.readFileSync(testPath, 'utf8'));
|
||||
const pragmas = parse(docblock) as DocblockPragmas;
|
||||
|
||||
const config = {
|
||||
mode: DEFAULT_MODE,
|
||||
};
|
||||
|
||||
const maybeMode = pragmas.fantom_mode;
|
||||
|
||||
if (maybeMode != null) {
|
||||
if (Array.isArray(maybeMode)) {
|
||||
throw new Error('Expected a single value for @fantom_mode');
|
||||
}
|
||||
|
||||
const mode = maybeMode;
|
||||
|
||||
if (mode === 'dev' || mode === 'opt') {
|
||||
config.mode = mode;
|
||||
} else {
|
||||
throw new Error(`Invalid Fantom mode: ${mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function getBuckModeForPlatform(enableRelease: boolean = false): string {
|
||||
const mode = enableRelease ? 'opt' : 'dev';
|
||||
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* 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 {ensureMockFunction} from './mocks';
|
||||
import deepEqual from 'deep-equal';
|
||||
import {diff} from 'jest-diff';
|
||||
|
||||
class ErrorWithCustomBlame extends Error {
|
||||
// Initially 5 to ignore all the frames from Babel helpers to instantiate this
|
||||
// custom error class.
|
||||
#ignoredFrameCount: number = 5;
|
||||
#cachedProcessedStack: ?string;
|
||||
#customStack: ?string;
|
||||
|
||||
blameToPreviousFrame(): this {
|
||||
this.#cachedProcessedStack = null;
|
||||
this.#ignoredFrameCount++;
|
||||
return this;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[unsafe-getters-setters]
|
||||
get stack(): string {
|
||||
if (this.#cachedProcessedStack == null) {
|
||||
const originalStack = this.#customStack ?? super.stack;
|
||||
|
||||
const lines = originalStack.split('\n');
|
||||
const index = lines.findIndex(line =>
|
||||
/at (.*) \((.*):(\d+):(\d+)\)/.test(line),
|
||||
);
|
||||
lines.splice(index > -1 ? index : 1, this.#ignoredFrameCount);
|
||||
this.#cachedProcessedStack = lines.join('\n');
|
||||
}
|
||||
|
||||
return this.#cachedProcessedStack;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[unsafe-getters-setters]
|
||||
set stack(value: string) {
|
||||
this.#cachedProcessedStack = null;
|
||||
this.#customStack = value;
|
||||
}
|
||||
|
||||
static fromError(error: Error): ErrorWithCustomBlame {
|
||||
const errorWithCustomBlame = new ErrorWithCustomBlame(error.message);
|
||||
// In this case we're inheriting the error and we don't know if the stack
|
||||
// contains helpers that we need to ignore.
|
||||
errorWithCustomBlame.#ignoredFrameCount = 0;
|
||||
errorWithCustomBlame.stack = error.stack;
|
||||
return errorWithCustomBlame;
|
||||
}
|
||||
}
|
||||
|
||||
class Expect {
|
||||
#received: mixed;
|
||||
#isNot: boolean = false;
|
||||
|
||||
constructor(received: mixed) {
|
||||
this.#received = received;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[unsafe-getters-setters]
|
||||
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 ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} to equal:\n${
|
||||
diff(expected, this.#received, {
|
||||
contextLines: 1,
|
||||
expand: false,
|
||||
omitAnnotationLines: true,
|
||||
}) ?? 'Failed to compare outputs'
|
||||
}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBe(expected: mixed): void {
|
||||
const pass = this.#received === expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeInstanceOf(expected: Class<mixed>): void {
|
||||
const pass = this.#received instanceof expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`expected ${String(this.#received)}${this.#maybeNotLabel()} to be an instance of ${String(expected)}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
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 ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeNull(): void {
|
||||
const pass = this.#received == null;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be null`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toThrow(expected?: string): void {
|
||||
if (expected != null && typeof expected !== 'string') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
'toThrow() implementation only accepts strings as arguments.',
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
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 ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalled(): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length > 0;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called, but it was${this.#isNot ? '' : "n't"}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalledTimes(times: number): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length === times;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called ${times} times, but it was called ${mock.calls.length} times`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeGreaterThan(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received > expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be greater than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeGreaterThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received >= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be greater than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeLessThan(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received < expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be less than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeLessThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received <= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be less than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
#isExpectedResult(pass: boolean): boolean {
|
||||
return this.#isNot ? !pass : pass;
|
||||
}
|
||||
|
||||
#maybeNotLabel(): string {
|
||||
return this.#isNot ? ' not' : '';
|
||||
}
|
||||
|
||||
#requireMock(): JestMockFn<Array<mixed>, mixed>['mock'] {
|
||||
try {
|
||||
return ensureMockFunction(this.#received).mock;
|
||||
} catch (error) {
|
||||
const errorWithCustomBlame = ErrorWithCustomBlame.fromError(error);
|
||||
errorWithCustomBlame.message = `Expected ${String(this.#received)} to be a mock function, but it wasn't`;
|
||||
errorWithCustomBlame
|
||||
.blameToPreviousFrame() // ignore `ensureMockFunction`
|
||||
.blameToPreviousFrame() // ignore `requireMock`
|
||||
.blameToPreviousFrame(); // ignore `expect().[method]`
|
||||
throw errorWithCustomBlame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const expect: mixed => Expect = (received: mixed) => new Expect(received);
|
||||
|
||||
export default expect;
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export const MOCK_FN_TAG: symbol = Symbol('mock function');
|
||||
|
||||
// The type is defined this way because if we get a mixed value, we return
|
||||
// a generic mock function, and if we get a typed function, we get a typed mock.
|
||||
export const ensureMockFunction: (<TArgs: Array<mixed>, TReturn>(
|
||||
fn: (...TArgs) => TReturn,
|
||||
) => JestMockFn<TArgs, TReturn>) &
|
||||
((fn: mixed) => JestMockFn<Array<mixed>, mixed>) = fn => {
|
||||
// $FlowExpectedError[invalid-computed-prop]
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
if (typeof fn !== 'function' || !fn[MOCK_FN_TAG]) {
|
||||
throw new Error(
|
||||
`Expected ${String(fn)} to be a mock function, but it wasn't`,
|
||||
);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
// $FlowExpectedError[prop-missing]
|
||||
return fn;
|
||||
};
|
||||
|
||||
export function createMockFunction<TArgs: Array<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;
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import expect from './expect';
|
||||
import {createMockFunction} from './mocks';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
export type TestCaseResult = {
|
||||
@@ -119,263 +120,7 @@ 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 ErrorWithCustomBlame extends Error {
|
||||
// Initially 5 to ignore all the frames from Babel helpers to instantiate this
|
||||
// custom error class.
|
||||
#ignoredFrameCount: number = 5;
|
||||
#cachedProcessedStack: ?string;
|
||||
|
||||
blameToPreviousFrame(): this {
|
||||
this.#ignoredFrameCount++;
|
||||
return this;
|
||||
}
|
||||
|
||||
get stack(): string {
|
||||
if (this.#cachedProcessedStack == null) {
|
||||
const originalStack = super.stack;
|
||||
|
||||
if (originalStack == null) {
|
||||
this.#cachedProcessedStack = originalStack;
|
||||
} else {
|
||||
const lines = originalStack.split('\n');
|
||||
const index = lines.findIndex(line =>
|
||||
/at (.*) \((.*):(\d+):(\d+)\)/.test(line),
|
||||
);
|
||||
lines.splice(index > -1 ? index : 1, this.#ignoredFrameCount);
|
||||
this.#cachedProcessedStack = lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return this.#cachedProcessedStack;
|
||||
}
|
||||
|
||||
set stack(value: string) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
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 ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} to equal ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBe(expected: mixed): void {
|
||||
const pass = this.#received === expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeInstanceOf(expected: Class<mixed>): void {
|
||||
const pass = this.#received instanceof expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`expected ${String(this.#received)}${this.#maybeNotLabel()} to be an instance of ${String(expected)}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
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 ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeNull(): void {
|
||||
const pass = this.#received == null;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be null`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toThrow(expected?: string): void {
|
||||
if (expected != null && typeof expected !== 'string') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
'toThrow() implementation only accepts strings as arguments.',
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
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 ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalled(): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length > 0;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called, but it was${this.#isNot ? '' : "n't"}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalledTimes(times: number): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length === times;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called ${times} times, but it was called ${mock.calls.length} times`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeGreaterThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received >= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be greater than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeLessThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received <= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be less than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
#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 ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a mock function, but it wasn't`,
|
||||
)
|
||||
.blameToPreviousFrame()
|
||||
.blameToPreviousFrame();
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
return this.#received.mock;
|
||||
}
|
||||
}
|
||||
|
||||
global.expect = (received: mixed) => new Expect(received);
|
||||
global.expect = expect;
|
||||
|
||||
function runWithGuard(fn: () => void) {
|
||||
try {
|
||||
|
||||
@@ -559,7 +559,7 @@ if (global.nativeLoggingHook) {
|
||||
let originalConsoleError = console.error;
|
||||
console.reportErrorsAsExceptions = true;
|
||||
function stringifySafe(arg) {
|
||||
return inspect(arg, {depth: 10}).replaceAll(/\n\s*/g, ' ');
|
||||
return inspect(arg, {depth: 10}).replace(/\n\s*/g, ' ');
|
||||
}
|
||||
console.error = function (...args) {
|
||||
originalConsoleError.apply(this, args);
|
||||
|
||||
@@ -430,4 +430,5 @@ export type CompleteTypeAnnotation =
|
||||
| NativeModuleFunctionTypeAnnotation
|
||||
| NullableTypeAnnotation<NativeModuleTypeAnnotation>
|
||||
| EventEmitterTypeAnnotation
|
||||
| NativeModuleEnumDeclarationWithMembers
|
||||
| UnsafeAnyTypeAnnotation;
|
||||
|
||||
@@ -26,6 +26,7 @@ export type AnimationConfig = $ReadOnly<{
|
||||
onComplete?: ?EndCallback,
|
||||
iterations?: number,
|
||||
isLooping?: boolean,
|
||||
debugID?: ?string,
|
||||
...
|
||||
}>;
|
||||
|
||||
@@ -43,6 +44,7 @@ export default class Animation {
|
||||
__isInteraction: boolean;
|
||||
__isLooping: ?boolean;
|
||||
__iterations: number;
|
||||
__debugID: ?string;
|
||||
|
||||
constructor(config: AnimationConfig) {
|
||||
this.#useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
|
||||
@@ -51,6 +53,9 @@ export default class Animation {
|
||||
this.__isInteraction = config.isInteraction ?? !this.#useNativeDriver;
|
||||
this.__isLooping = config.isLooping;
|
||||
this.__iterations = config.iterations ?? 1;
|
||||
if (__DEV__) {
|
||||
this.__debugID = config.debugID;
|
||||
}
|
||||
}
|
||||
|
||||
start(
|
||||
|
||||
@@ -99,6 +99,7 @@ export default class TimingAnimation extends Animation {
|
||||
toValue: this._toValue,
|
||||
iterations: this.__iterations,
|
||||
platformConfig: this._platformConfig,
|
||||
debugID: __DEV__ ? this.__debugID : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -197,4 +197,6 @@ export default class AnimatedNode {
|
||||
toJSON(): mixed {
|
||||
return this.__getValue();
|
||||
}
|
||||
|
||||
__debugID: ?string = undefined;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import AnimatedWithChildren from './AnimatedWithChildren';
|
||||
|
||||
export type AnimatedValueConfig = $ReadOnly<{
|
||||
useNativeDriver: boolean,
|
||||
debugID?: string,
|
||||
}>;
|
||||
|
||||
const NativeAnimatedAPI = NativeAnimatedHelper.API;
|
||||
@@ -97,8 +98,13 @@ export default class AnimatedValue extends AnimatedWithChildren {
|
||||
this._startingValue = this._value = value;
|
||||
this._offset = 0;
|
||||
this._animation = null;
|
||||
if (config && config.useNativeDriver) {
|
||||
this.__makeNative();
|
||||
if (config) {
|
||||
if (config.useNativeDriver) {
|
||||
this.__makeNative();
|
||||
}
|
||||
if (__DEV__) {
|
||||
this.__debugID = config.debugID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +304,7 @@ export default class AnimatedValue extends AnimatedWithChildren {
|
||||
type: 'value',
|
||||
value: this._value,
|
||||
offset: this._offset,
|
||||
debugID: __DEV__ ? this.__debugID : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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
|
||||
* @fantom_flags enableFixForViewCommandRace:true
|
||||
*/
|
||||
|
||||
import '../../../Core/InitializeCore.js';
|
||||
import * as ReactNativeTester from '../../../../src/private/__tests__/ReactNativeTester';
|
||||
import TextInput from '../TextInput';
|
||||
import * as React from 'react';
|
||||
import {useEffect, useLayoutEffect, useRef} from 'react';
|
||||
|
||||
describe('TextInput', () => {
|
||||
it('creates view before dispatching view command from ref function', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<TextInput
|
||||
ref={node => {
|
||||
if (node) {
|
||||
node.focus();
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(2);
|
||||
expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`');
|
||||
expect(mountingLogs[1]).toBe(
|
||||
'dispatch command `focus` on component `AndroidTextInput`',
|
||||
);
|
||||
});
|
||||
|
||||
it('creates view before dispatching view command from useLayoutEffect', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
function Component() {
|
||||
const textInputRef = useRef<null | React.ElementRef<typeof TextInput>>(
|
||||
null,
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
textInputRef.current?.focus();
|
||||
});
|
||||
|
||||
return <TextInput ref={textInputRef} />;
|
||||
}
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
const mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(2);
|
||||
expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`');
|
||||
expect(mountingLogs[1]).toBe(
|
||||
'dispatch command `focus` on component `AndroidTextInput`',
|
||||
);
|
||||
});
|
||||
|
||||
it('creates view before dispatching view command from useEffect', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
function Component() {
|
||||
const textInputRef = useRef<null | React.ElementRef<typeof TextInput>>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
textInputRef.current?.focus();
|
||||
});
|
||||
|
||||
return <TextInput ref={textInputRef} />;
|
||||
}
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
const mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(2);
|
||||
expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`');
|
||||
expect(mountingLogs[1]).toBe(
|
||||
'dispatch command `focus` on component `AndroidTextInput`',
|
||||
);
|
||||
});
|
||||
});
|
||||
+1
@@ -7,6 +7,7 @@
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
* @fantom_flags enableAccessToHostTreeInFabric:false
|
||||
*/
|
||||
|
||||
import setUpReactFabricPublicInstanceFantomTests from './setUpReactFabricPublicInstanceFantomTests';
|
||||
|
||||
+1
-1
@@ -7,9 +7,9 @@
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
* @fantom_flags enableAccessToHostTreeInFabric:true
|
||||
*/
|
||||
|
||||
import './setUpFeatureFlags';
|
||||
import setUpReactFabricPublicInstanceFantomTests from './setUpReactFabricPublicInstanceFantomTests';
|
||||
|
||||
setUpReactFabricPublicInstanceFantomTests({isModern: true});
|
||||
|
||||
packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/__tests__/setUpFeatureFlags.js
Vendored
-16
@@ -1,16 +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 * as ReactNativeFeatureFlags from '../../../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
|
||||
ReactNativeFeatureFlags.override({
|
||||
enableAccessToHostTreeInFabric: () => true,
|
||||
});
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 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 '../../Core/InitializeCore.js';
|
||||
import * as ReactNativeTester from '../../../src/private/__tests__/ReactNativeTester';
|
||||
import View from '../../Components/View/View';
|
||||
import * as React from 'react';
|
||||
import {Suspense, startTransition} from 'react';
|
||||
|
||||
let resolveFunction: (() => void) | null = null;
|
||||
|
||||
// This is a workaround for a bug to get the demo running.
|
||||
// TODO: replace with real implementation when the bug is fixed.
|
||||
// $FlowFixMe: [missing-local-annot]
|
||||
function use(promise) {
|
||||
if (promise.status === 'fulfilled') {
|
||||
return promise.value;
|
||||
} else if (promise.status === 'rejected') {
|
||||
throw promise.reason;
|
||||
} else if (promise.status === 'pending') {
|
||||
throw promise;
|
||||
} else {
|
||||
promise.status = 'pending';
|
||||
promise.then(
|
||||
result => {
|
||||
promise.status = 'fulfilled';
|
||||
promise.value = result;
|
||||
},
|
||||
reason => {
|
||||
promise.status = 'rejected';
|
||||
promise.reason = reason;
|
||||
},
|
||||
);
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
|
||||
type SquareData = {
|
||||
color: 'red' | 'green',
|
||||
};
|
||||
|
||||
enum SquareId {
|
||||
Green = 'green-square',
|
||||
Red = 'red-square',
|
||||
}
|
||||
|
||||
async function getGreenSquareData(): Promise<SquareData> {
|
||||
await new Promise(resolve => {
|
||||
resolveFunction = resolve;
|
||||
});
|
||||
return {
|
||||
color: 'green',
|
||||
};
|
||||
}
|
||||
|
||||
async function getRedSquareData(): Promise<SquareData> {
|
||||
await new Promise(resolve => {
|
||||
resolveFunction = resolve;
|
||||
});
|
||||
return {
|
||||
color: 'red',
|
||||
};
|
||||
}
|
||||
|
||||
const cache = new Map<SquareId, SquareData>();
|
||||
|
||||
async function getData(squareId: SquareId): Promise<SquareData> {
|
||||
switch (squareId) {
|
||||
case SquareId.Green:
|
||||
return await getGreenSquareData();
|
||||
case SquareId.Red:
|
||||
return await getRedSquareData();
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchData(squareId: SquareId): Promise<SquareData> {
|
||||
const data = await getData(squareId);
|
||||
cache.set(squareId, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
function Square(props: {squareId: SquareId}) {
|
||||
let data = cache.get(props.squareId);
|
||||
if (data == null) {
|
||||
data = use(fetchData(props.squareId));
|
||||
}
|
||||
return <View key={data.color} nativeID={'square with data: ' + data.color} />;
|
||||
}
|
||||
|
||||
function GreenSquare() {
|
||||
return <Square squareId={SquareId.Green} />;
|
||||
}
|
||||
|
||||
function RedSquare() {
|
||||
return <Square squareId={SquareId.Red} />;
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <View nativeID="suspense fallback" />;
|
||||
}
|
||||
|
||||
describe('Suspense', () => {
|
||||
it('shows fallback if data is not available', () => {
|
||||
cache.clear();
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<GreenSquare />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
|
||||
let mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `suspense fallback`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).not.toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
resolveFunction?.();
|
||||
resolveFunction = null;
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: green`',
|
||||
);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<RedSquare />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `suspense fallback`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).not.toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
resolveFunction?.();
|
||||
resolveFunction = null;
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: red`',
|
||||
);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<GreenSquare />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: green`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).toBeNull();
|
||||
|
||||
root.destroy();
|
||||
});
|
||||
|
||||
// TODO(T207868872): this test only succeeds with enableFabricCompleteRootInCommitPhase enabled.
|
||||
// enableFabricCompleteRootInCommitPhase is hardcoded to true in the testing environment.
|
||||
it('shows stale data while transition is happening', () => {
|
||||
cache.clear();
|
||||
cache.set(SquareId.Green, {color: 'green'});
|
||||
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
function App(props: {color: 'red' | 'green'}) {
|
||||
return (
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{props.color === 'green' ? <GreenSquare /> : <RedSquare />}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<App color="green" />);
|
||||
});
|
||||
|
||||
let mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: green`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
startTransition(() => {
|
||||
root.render(<App color="red" />);
|
||||
});
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
// Green square is still mounted. Fallback is not shown to the user.
|
||||
expect(mountingLogs.length).toBe(0);
|
||||
|
||||
expect(resolveFunction).not.toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
resolveFunction?.();
|
||||
resolveFunction = null;
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: red`',
|
||||
);
|
||||
|
||||
root.destroy();
|
||||
});
|
||||
});
|
||||
+6
-1
@@ -12,7 +12,7 @@ import {Constructor} from '../../types/private/Utilities';
|
||||
import {AccessibilityProps} from '../Components/View/ViewAccessibility';
|
||||
import {NativeMethods} from '../../types/public/ReactNativeTypes';
|
||||
import {ColorValue, StyleProp} from '../StyleSheet/StyleSheet';
|
||||
import {TextStyle} from '../StyleSheet/StyleSheetTypes';
|
||||
import {TextStyle, ViewStyle} from '../StyleSheet/StyleSheetTypes';
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
LayoutChangeEvent,
|
||||
@@ -209,6 +209,11 @@ export interface TextProps
|
||||
* Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0).
|
||||
*/
|
||||
minimumFontScale?: number | undefined;
|
||||
|
||||
/**
|
||||
* Controls how touch events are handled. Similar to `View`'s `pointerEvents`.
|
||||
*/
|
||||
pointerEvents?: ViewStyle['pointerEvents'] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -424,6 +424,7 @@ export type AnimationConfig = $ReadOnly<{
|
||||
onComplete?: ?EndCallback,
|
||||
iterations?: number,
|
||||
isLooping?: boolean,
|
||||
debugID?: ?string,
|
||||
...
|
||||
}>;
|
||||
declare export default class Animation {
|
||||
@@ -431,6 +432,7 @@ declare export default class Animation {
|
||||
__isInteraction: boolean;
|
||||
__isLooping: ?boolean;
|
||||
__iterations: number;
|
||||
__debugID: ?string;
|
||||
constructor(config: AnimationConfig): void;
|
||||
start(
|
||||
fromValue: number,
|
||||
@@ -959,6 +961,7 @@ exports[`public API should not change unintentionally Libraries/Animated/nodes/A
|
||||
__getPlatformConfig(): ?PlatformConfig;
|
||||
__setPlatformConfig(platformConfig: ?PlatformConfig): void;
|
||||
toJSON(): mixed;
|
||||
__debugID: ?string;
|
||||
}
|
||||
"
|
||||
`;
|
||||
@@ -1107,6 +1110,7 @@ declare export default class AnimatedTransform extends AnimatedWithChildren {
|
||||
exports[`public API should not change unintentionally Libraries/Animated/nodes/AnimatedValue.js 1`] = `
|
||||
"export type AnimatedValueConfig = $ReadOnly<{
|
||||
useNativeDriver: boolean,
|
||||
debugID?: string,
|
||||
}>;
|
||||
declare export function flushValue(rootNode: AnimatedNode): void;
|
||||
declare export default class AnimatedValue extends AnimatedWithChildren {
|
||||
|
||||
@@ -468,7 +468,8 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init);
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
if (_bridge.valid) {
|
||||
RCTAssert(_methodQueue != nullptr, @"Module %@ has no methodQueue (instance: %@)", self, self.instance);
|
||||
id instance = self.instance;
|
||||
RCTAssert(_methodQueue != nullptr, @"Module %@ has no methodQueue (instance: %@)", self, instance);
|
||||
}
|
||||
return _methodQueue;
|
||||
}
|
||||
|
||||
@@ -13,20 +13,18 @@ namespace facebook::react {
|
||||
|
||||
void RCTDefaultCxxLogFunction(ReactNativeLogLevel level, const char *message)
|
||||
{
|
||||
NSString *messageString = [NSString stringWithUTF8String:message];
|
||||
|
||||
switch (level) {
|
||||
case ReactNativeLogLevelInfo:
|
||||
LOG(INFO) << message;
|
||||
RCTLogInfo(@"%@", messageString);
|
||||
RCTLogInfo(@"%@", [NSString stringWithUTF8String:message]);
|
||||
break;
|
||||
case ReactNativeLogLevelWarning:
|
||||
LOG(WARNING) << message;
|
||||
RCTLogWarn(@"%@", messageString);
|
||||
RCTLogWarn(@"%@", [NSString stringWithUTF8String:message]);
|
||||
break;
|
||||
case ReactNativeLogLevelError:
|
||||
LOG(ERROR) << message;
|
||||
RCTLogError(@"%@", messageString);
|
||||
RCTLogError(@"%@", [NSString stringWithUTF8String:message]);
|
||||
break;
|
||||
case ReactNativeLogLevelFatal:
|
||||
LOG(FATAL) << message;
|
||||
|
||||
+4
-9
@@ -189,10 +189,11 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
|
||||
UIEdgeInsets newEdgeInsets = _scrollView.contentInset;
|
||||
CGFloat inset = MAX(scrollViewLowerY - keyboardEndFrame.origin.y, 0);
|
||||
const auto &props = static_cast<const ScrollViewProps &>(*_props);
|
||||
if (isInverted) {
|
||||
newEdgeInsets.top = MAX(inset, _scrollView.contentInset.top);
|
||||
newEdgeInsets.top = MAX(inset, props.contentInset.top);
|
||||
} else {
|
||||
newEdgeInsets.bottom = MAX(inset, _scrollView.contentInset.bottom);
|
||||
newEdgeInsets.bottom = MAX(inset, props.contentInset.bottom);
|
||||
}
|
||||
|
||||
CGPoint newContentOffset = _scrollView.contentOffset;
|
||||
@@ -210,12 +211,6 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
contentDiff = keyboardEndFrame.origin.y - keyboardBeginFrame.origin.y;
|
||||
}
|
||||
} else {
|
||||
CGRect viewIntersection = CGRectIntersection(self.firstResponderFocus, keyboardEndFrame);
|
||||
|
||||
if (CGRectIsNull(viewIntersection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inner text field focused
|
||||
CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus);
|
||||
if (focusEnd > keyboardEndFrame.origin.y) {
|
||||
@@ -247,7 +242,7 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
animations:^{
|
||||
self->_scrollView.contentInset = newEdgeInsets;
|
||||
self->_scrollView.verticalScrollIndicatorInsets = newEdgeInsets;
|
||||
[self scrollToOffset:newContentOffset animated:NO];
|
||||
[self scrollTo:newContentOffset.x y:newContentOffset.y animated:NO];
|
||||
}
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
@@ -73,9 +73,8 @@ static void RCTPerformMountInstructions(
|
||||
|
||||
case ShadowViewMutation::Insert: {
|
||||
auto &newChildShadowView = mutation.newChildShadowView;
|
||||
auto &parentShadowView = mutation.parentShadowView;
|
||||
auto &newChildViewDescriptor = [registry componentViewDescriptorWithTag:newChildShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:mutation.parentTag];
|
||||
|
||||
UIView<RCTComponentViewProtocol> *newChildComponentView = newChildViewDescriptor.view;
|
||||
|
||||
@@ -94,9 +93,8 @@ static void RCTPerformMountInstructions(
|
||||
|
||||
case ShadowViewMutation::Remove: {
|
||||
auto &oldChildShadowView = mutation.oldChildShadowView;
|
||||
auto &parentShadowView = mutation.parentShadowView;
|
||||
auto &oldChildViewDescriptor = [registry componentViewDescriptorWithTag:oldChildShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:mutation.parentTag];
|
||||
[parentViewDescriptor.view unmountChildComponentView:oldChildViewDescriptor.view index:mutation.index];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -322,9 +322,8 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
|
||||
NSNumber *reactTag = rootView.reactTag;
|
||||
RCTAssert(RCTIsReactRootView(reactTag), @"View %@ with tag #%@ is not a root view", rootView, reactTag);
|
||||
|
||||
UIView *existingView = _viewRegistry[reactTag];
|
||||
RCTAssert(
|
||||
existingView == nil || existingView == rootView,
|
||||
_viewRegistry[reactTag] == nil || _viewRegistry[reactTag] == rootView,
|
||||
@"Expect all root views to have unique tag. Added %@ twice",
|
||||
reactTag);
|
||||
|
||||
|
||||
@@ -74,11 +74,7 @@ static SEL selectorForType(NSString *type)
|
||||
if (!_manager && [self isBridgeMode]) {
|
||||
_manager = [_bridge moduleForClass:_managerClass];
|
||||
} else if (!_manager && !_bridgelessViewManager) {
|
||||
_bridgelessViewManager = [_managerClass new];
|
||||
_bridgelessViewManager.bridge = _bridge;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
|
||||
object:nil
|
||||
userInfo:@{@"module" : _bridgelessViewManager}];
|
||||
_bridgelessViewManager = [_bridge moduleForClass:_managerClass];
|
||||
}
|
||||
return _manager ? _manager : _bridgelessViewManager;
|
||||
}
|
||||
|
||||
@@ -2612,6 +2612,7 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
|
||||
public fun dispatchCommand (IILcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun dispatchCommand (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun dispatchCommand (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun experimental_prefetchResource (Ljava/lang/String;IILcom/facebook/react/common/mapbuffer/ReadableMapBuffer;)V
|
||||
public fun getColor (I[Ljava/lang/String;)I
|
||||
public fun getEventDispatcher ()Lcom/facebook/react/uimanager/events/EventDispatcher;
|
||||
public fun getInspectorDataForInstance (ILandroid/view/View;)Lcom/facebook/react/bridge/ReadableMap;
|
||||
@@ -2955,6 +2956,7 @@ public abstract interface annotation class com/facebook/react/module/annotations
|
||||
public final class com/facebook/react/module/model/ReactModuleInfo {
|
||||
public static final field Companion Lcom/facebook/react/module/model/ReactModuleInfo$Companion;
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZ)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZZ)V
|
||||
public final fun canOverrideExistingModule ()Z
|
||||
public static final fun classIsTurboModule (Ljava/lang/Class;)Z
|
||||
public final fun className ()Ljava/lang/String;
|
||||
@@ -3422,11 +3424,12 @@ public abstract interface class com/facebook/react/modules/network/CustomClientB
|
||||
public abstract fun apply (Lokhttp3/OkHttpClient$Builder;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/modules/network/ForwardingCookieHandler : java/net/CookieHandler {
|
||||
public final class com/facebook/react/modules/network/ForwardingCookieHandler : java/net/CookieHandler {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
public fun addCookies (Ljava/lang/String;Ljava/util/List;)V
|
||||
public fun clearCookies (Lcom/facebook/react/bridge/Callback;)V
|
||||
public fun destroy ()V
|
||||
public final fun addCookies (Ljava/lang/String;Ljava/util/List;)V
|
||||
public final fun clearCookies (Lcom/facebook/react/bridge/Callback;)V
|
||||
public final fun destroy ()V
|
||||
public fun get (Ljava/net/URI;Ljava/util/Map;)Ljava/util/Map;
|
||||
public fun put (Ljava/net/URI;Ljava/util/Map;)V
|
||||
}
|
||||
@@ -3508,14 +3511,6 @@ public class com/facebook/react/modules/network/ProgressResponseBody : okhttp3/R
|
||||
public fun totalBytesRead ()J
|
||||
}
|
||||
|
||||
public class com/facebook/react/modules/network/ReactCookieJarContainer : com/facebook/react/modules/network/CookieJarContainer {
|
||||
public fun <init> ()V
|
||||
public fun loadForRequest (Lokhttp3/HttpUrl;)Ljava/util/List;
|
||||
public fun removeCookieJar ()V
|
||||
public fun saveFromResponse (Lokhttp3/HttpUrl;Ljava/util/List;)V
|
||||
public fun setCookieJar (Lokhttp3/CookieJar;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/modules/network/ResponseUtil {
|
||||
public fun <init> ()V
|
||||
public static fun onDataReceived (Lcom/facebook/react/bridge/ReactApplicationContext;ILcom/facebook/react/bridge/WritableMap;)V
|
||||
@@ -7770,6 +7765,7 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
|
||||
protected fun dispatchSetPressed (Z)V
|
||||
public fun draw (Landroid/graphics/Canvas;)V
|
||||
protected fun drawChild (Landroid/graphics/Canvas;Landroid/view/View;J)Z
|
||||
public fun endViewTransition (Landroid/view/View;)V
|
||||
protected fun getChildDrawingOrder (II)I
|
||||
public fun getClippingRect (Landroid/graphics/Rect;)V
|
||||
public fun getHitSlopRect ()Landroid/graphics/Rect;
|
||||
|
||||
+10
-1
@@ -878,6 +878,16 @@ public class FabricUIManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method initiates preloading of an image specified by ImageSource. It can later be consumed
|
||||
* by an ImageView.
|
||||
*/
|
||||
public void experimental_prefetchResource(
|
||||
String componentName, int surfaceId, int reactTag, ReadableMapBuffer params) {
|
||||
mMountingManager.experimental_prefetchResource(
|
||||
mReactApplicationContext, componentName, surfaceId, reactTag, params);
|
||||
}
|
||||
|
||||
public void setBinding(FabricUIManagerBinding binding) {
|
||||
mBinding = binding;
|
||||
}
|
||||
@@ -960,7 +970,6 @@ public class FabricUIManager
|
||||
* @param reactTag
|
||||
* @param eventName
|
||||
* @param canCoalesceEvent
|
||||
* @param customCoalesceKey
|
||||
* @param params
|
||||
* @param eventCategory
|
||||
*/
|
||||
|
||||
+7
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<6d8d8f4b81d7be882b315d0960499dcb>>
|
||||
* @generated SignedSource<<4a219bb47b1b9d988a164bca19eb4fa9>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -202,6 +202,12 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun excludeYogaFromRawProps(): Boolean = accessor.excludeYogaFromRawProps()
|
||||
|
||||
/**
|
||||
* Fixes a bug in Differentiator where parent views may be referenced before they're created
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean = accessor.fixDifferentiatorEmittingUpdatesWithWrongParentTag()
|
||||
|
||||
/**
|
||||
* Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React.
|
||||
*/
|
||||
|
||||
+11
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<031fce8e8b4c20a3e3d6dbecf94d138a>>
|
||||
* @generated SignedSource<<d75efd6beee8dd9d38b5d648fbecbcda>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -49,6 +49,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
|
||||
private var enableUIConsistencyCache: Boolean? = null
|
||||
private var enableViewRecyclingCache: Boolean? = null
|
||||
private var excludeYogaFromRawPropsCache: Boolean? = null
|
||||
private var fixDifferentiatorEmittingUpdatesWithWrongParentTagCache: Boolean? = null
|
||||
private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null
|
||||
private var fixMountingCoordinatorReportedPendingTransactionsOnAndroidCache: Boolean? = null
|
||||
private var fuseboxEnabledDebugCache: Boolean? = null
|
||||
@@ -328,6 +329,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean {
|
||||
var cached = fixDifferentiatorEmittingUpdatesWithWrongParentTagCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.fixDifferentiatorEmittingUpdatesWithWrongParentTag()
|
||||
fixDifferentiatorEmittingUpdatesWithWrongParentTagCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean {
|
||||
var cached = fixMappingOfEventPrioritiesBetweenFabricAndReactCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<35811667ac2543e1f64e27bbdb483ec1>>
|
||||
* @generated SignedSource<<7454ab19a01cfbb0a54f14bd83fc3a90>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -86,6 +86,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun excludeYogaFromRawProps(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun fixMountingCoordinatorReportedPendingTransactionsOnAndroid(): Boolean
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<9d829c58e49164a0b2b6b66bc0ce088a>>
|
||||
* @generated SignedSource<<70d951b2956759280afae4af8f9a2869>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -81,6 +81,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun excludeYogaFromRawProps(): Boolean = false
|
||||
|
||||
override fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean = true
|
||||
|
||||
override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean = false
|
||||
|
||||
override fun fixMountingCoordinatorReportedPendingTransactionsOnAndroid(): Boolean = false
|
||||
|
||||
+12
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<0121e113410a5b0e14eaf74a3076df2f>>
|
||||
* @generated SignedSource<<f60000cb58a9632c3aa193854be3de4e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -53,6 +53,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var enableUIConsistencyCache: Boolean? = null
|
||||
private var enableViewRecyclingCache: Boolean? = null
|
||||
private var excludeYogaFromRawPropsCache: Boolean? = null
|
||||
private var fixDifferentiatorEmittingUpdatesWithWrongParentTagCache: Boolean? = null
|
||||
private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null
|
||||
private var fixMountingCoordinatorReportedPendingTransactionsOnAndroidCache: Boolean? = null
|
||||
private var fuseboxEnabledDebugCache: Boolean? = null
|
||||
@@ -361,6 +362,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean {
|
||||
var cached = fixDifferentiatorEmittingUpdatesWithWrongParentTagCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.fixDifferentiatorEmittingUpdatesWithWrongParentTag()
|
||||
accessedFeatureFlags.add("fixDifferentiatorEmittingUpdatesWithWrongParentTag")
|
||||
fixDifferentiatorEmittingUpdatesWithWrongParentTagCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean {
|
||||
var cached = fixMappingOfEventPrioritiesBetweenFabricAndReactCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<2787d9027695dd14ec6b917a32a1a6de>>
|
||||
* @generated SignedSource<<d62af893c5d18a2152f098ff305ae41e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -81,6 +81,8 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun excludeYogaFromRawProps(): Boolean
|
||||
|
||||
@DoNotStrip public fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean
|
||||
|
||||
@DoNotStrip public fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean
|
||||
|
||||
@DoNotStrip public fun fixMountingCoordinatorReportedPendingTransactionsOnAndroid(): Boolean
|
||||
|
||||
+18
@@ -21,6 +21,24 @@ public class ReactModuleInfo(
|
||||
public val isCxxModule: Boolean,
|
||||
public val isTurboModule: Boolean
|
||||
) {
|
||||
|
||||
@Deprecated(
|
||||
"This constructor is deprecated and will be removed in the future. Use ReactModuleInfo(String, String, boolean, boolean, boolean, boolean)]",
|
||||
replaceWith =
|
||||
ReplaceWith(
|
||||
expression =
|
||||
"ReactModuleInfo(name, className, canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule)"),
|
||||
level = DeprecationLevel.WARNING)
|
||||
public constructor(
|
||||
name: String,
|
||||
className: String,
|
||||
canOverrideExistingModule: Boolean,
|
||||
needsEagerInit: Boolean,
|
||||
@Suppress("UNUSED_PARAMETER") hasConstants: Boolean,
|
||||
isCxxModule: Boolean,
|
||||
isTurboModule: Boolean
|
||||
) : this(name, className, canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule)
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* Checks if the passed class is a TurboModule. Useful to populate the parameter [isTurboModule]
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ constructor(
|
||||
// make sure to forward cookies for any requests via the okHttpClient
|
||||
// so that image requests to endpoints that use cookies still work
|
||||
val container = OkHttpCompat.getCookieJarContainer(client)
|
||||
val handler = ForwardingCookieHandler(context)
|
||||
val handler = ForwardingCookieHandler()
|
||||
container.setCookieJar(JavaNetCookieJar(handler))
|
||||
return newBuilder(context.applicationContext, client)
|
||||
.setNetworkFetcher(ReactOkHttpNetworkFetcher(client))
|
||||
|
||||
-17
@@ -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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import okhttp3.CookieJar;
|
||||
|
||||
public interface CookieJarContainer extends CookieJar {
|
||||
|
||||
void setCookieJar(CookieJar cookieJar);
|
||||
|
||||
void removeCookieJar();
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import okhttp3.CookieJar
|
||||
|
||||
public interface CookieJarContainer : CookieJar {
|
||||
public fun setCookieJar(cookieJar: CookieJar)
|
||||
|
||||
public fun removeCookieJar()
|
||||
}
|
||||
-124
@@ -1,124 +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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.CookieManager;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import java.io.IOException;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Cookie handler that forwards all cookies to the WebView CookieManager.
|
||||
*
|
||||
* <p>This class relies on CookieManager to persist cookies to disk so cookies may be lost if the
|
||||
* application is terminated before it syncs.
|
||||
*/
|
||||
public class ForwardingCookieHandler extends CookieHandler {
|
||||
private static final String VERSION_ZERO_HEADER = "Set-cookie";
|
||||
private static final String VERSION_ONE_HEADER = "Set-cookie2";
|
||||
private static final String COOKIE_HEADER = "Cookie";
|
||||
|
||||
private final ReactContext mContext;
|
||||
private @Nullable CookieManager mCookieManager;
|
||||
|
||||
public ForwardingCookieHandler(ReactContext context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> get(URI uri, Map<String, List<String>> headers)
|
||||
throws IOException {
|
||||
CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager == null) return Collections.emptyMap();
|
||||
|
||||
String cookies = cookieManager.getCookie(uri.toString());
|
||||
if (TextUtils.isEmpty(cookies)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
return Collections.singletonMap(COOKIE_HEADER, Collections.singletonList(cookies));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(URI uri, Map<String, List<String>> headers) throws IOException {
|
||||
String url = uri.toString();
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key != null && isCookieHeader(key)) {
|
||||
addCookies(url, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCookies(final Callback callback) {
|
||||
CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager != null) {
|
||||
cookieManager.removeAllCookies(value -> callback.invoke(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {}
|
||||
|
||||
public void addCookies(final String url, final List<String> cookies) {
|
||||
final CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager == null) return;
|
||||
|
||||
for (String cookie : cookies) {
|
||||
addCookieAsync(url, cookie);
|
||||
}
|
||||
cookieManager.flush();
|
||||
}
|
||||
|
||||
private void addCookieAsync(String url, String cookie) {
|
||||
CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager != null) {
|
||||
cookieManager.setCookie(url, cookie, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCookieHeader(String name) {
|
||||
return name.equalsIgnoreCase(VERSION_ZERO_HEADER) || name.equalsIgnoreCase(VERSION_ONE_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiating CookieManager will load the Chromium task taking a 100ish ms so we do it lazily
|
||||
* to make sure it's done on a background thread as needed.
|
||||
*/
|
||||
private @Nullable CookieManager getCookieManager() {
|
||||
if (mCookieManager == null) {
|
||||
try {
|
||||
mCookieManager = CookieManager.getInstance();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=559720
|
||||
return null;
|
||||
} catch (Exception exception) {
|
||||
// Ideally we would like to catch a `MissingWebViewPackageException` here.
|
||||
// That API is private so we can't access it.
|
||||
// Historically we used string matching on the error message to understand
|
||||
// if the exception was a Missing Webview One.
|
||||
// OEMs have been customizing that message making really hard to catch it.
|
||||
// Therefore we result to returning null as a default instead of rethrowing
|
||||
// the exception as it will result in a app crash at runtime.
|
||||
// a) We will return null for all the other unhandled conditions when a webview provider is
|
||||
// not found.
|
||||
// b) We already have null checks in place for `getCookieManager()` calls.
|
||||
// c) We have annotated the method as @Nullable to notify future devs about our return type.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return mCookieManager;
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import android.webkit.CookieManager
|
||||
import com.facebook.react.bridge.Callback
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import java.io.IOException
|
||||
import java.net.CookieHandler
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Cookie handler that forwards all cookies to the WebView CookieManager.
|
||||
*
|
||||
* This class relies on CookieManager to persist cookies to disk so cookies may be lost if the
|
||||
* application is terminated before it syncs.
|
||||
*/
|
||||
public class ForwardingCookieHandler() : CookieHandler() {
|
||||
|
||||
@Deprecated("Use the default constructor", ReplaceWith("ForwardingCookieHandler()"))
|
||||
public constructor(@Suppress("UNUSED_PARAMETER") reactContext: ReactContext) : this() {}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun get(uri: URI, headers: Map<String, List<String>>): Map<String, List<String>> {
|
||||
val cookies = cookieManager?.getCookie(uri.toString())
|
||||
if (cookies.isNullOrEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
return mapOf(COOKIE_HEADER to listOf(cookies))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun put(uri: URI, headers: Map<String, List<String>>) {
|
||||
val url = uri.toString()
|
||||
for ((key, value) in headers) {
|
||||
if (isCookieHeader(key)) {
|
||||
addCookies(url, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun clearCookies(callback: Callback): Unit {
|
||||
cookieManager?.removeAllCookies { value -> callback.invoke(value) }
|
||||
}
|
||||
|
||||
public fun destroy(): Unit = Unit
|
||||
|
||||
public fun addCookies(url: String, cookies: List<String>): Unit {
|
||||
for (cookie in cookies) {
|
||||
addCookieAsync(url, cookie)
|
||||
}
|
||||
cookieManager?.flush()
|
||||
}
|
||||
|
||||
private fun addCookieAsync(url: String, cookie: String) {
|
||||
cookieManager?.setCookie(url, cookie, null)
|
||||
}
|
||||
|
||||
private var cookieManager: CookieManager? = null
|
||||
/**
|
||||
* Instantiating CookieManager will load the Chromium task taking a 100ish ms so we do it lazily
|
||||
* to make sure it's done on a background thread as needed.
|
||||
*/
|
||||
get() {
|
||||
if (field == null) {
|
||||
try {
|
||||
field = CookieManager.getInstance()
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=559720
|
||||
return null
|
||||
} catch (exception: Exception) {
|
||||
// Ideally we would like to catch a `MissingWebViewPackageException` here.
|
||||
// That API is private so we can't access it.
|
||||
// Historically we used string matching on the error message to understand
|
||||
// if the exception was a Missing Webview One.
|
||||
// OEMs have been customizing that message making really hard to catch it.
|
||||
// Therefore we result to returning null as a default instead of rethrowing
|
||||
// the exception as it will result in a app crash at runtime.
|
||||
// a) We will return null for all the other unhandled conditions when a webview provider
|
||||
// is
|
||||
// not found.
|
||||
// b) We already have null checks in place for `getCookieManager()` calls.
|
||||
// c) We have annotated the method as @Nullable to notify future devs about our return
|
||||
// type.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val VERSION_ZERO_HEADER = "Set-cookie"
|
||||
private const val VERSION_ONE_HEADER = "Set-cookie2"
|
||||
private const val COOKIE_HEADER = "Cookie"
|
||||
|
||||
private fun isCookieHeader(name: String): Boolean =
|
||||
name.equals(VERSION_ZERO_HEADER, ignoreCase = true) ||
|
||||
name.equals(VERSION_ONE_HEADER, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -97,14 +97,14 @@ public final class NetworkingModule extends NativeNetworkingAndroidSpec {
|
||||
customClientBuilder = null;
|
||||
|
||||
private final OkHttpClient mClient;
|
||||
private final ForwardingCookieHandler mCookieHandler;
|
||||
private final ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler();
|
||||
private final @Nullable String mDefaultUserAgent;
|
||||
private final CookieJarContainer mCookieJarContainer;
|
||||
private final Set<Integer> mRequestIds;
|
||||
private final Set<Integer> mRequestIds = new HashSet<>();
|
||||
private final List<RequestBodyHandler> mRequestBodyHandlers = new ArrayList<>();
|
||||
private final List<UriHandler> mUriHandlers = new ArrayList<>();
|
||||
private final List<ResponseHandler> mResponseHandlers = new ArrayList<>();
|
||||
private boolean mShuttingDown;
|
||||
private boolean mShuttingDown = false;
|
||||
|
||||
public NetworkingModule(
|
||||
ReactApplicationContext reactContext,
|
||||
@@ -121,11 +121,8 @@ public final class NetworkingModule extends NativeNetworkingAndroidSpec {
|
||||
client = clientBuilder.build();
|
||||
}
|
||||
mClient = client;
|
||||
mCookieHandler = new ForwardingCookieHandler(reactContext);
|
||||
mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
|
||||
mShuttingDown = false;
|
||||
mCookieJarContainer = (CookieJarContainer) client.cookieJar();
|
||||
mDefaultUserAgent = defaultUserAgent;
|
||||
mRequestIds = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-58
@@ -1,58 +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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.CookieJar;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
/** Basic okhttp3 CookieJar container */
|
||||
public class ReactCookieJarContainer implements CookieJarContainer {
|
||||
|
||||
@Nullable private CookieJar cookieJar = null;
|
||||
|
||||
@Override
|
||||
public void setCookieJar(CookieJar cookieJar) {
|
||||
this.cookieJar = cookieJar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCookieJar() {
|
||||
cookieJar = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
|
||||
if (cookieJar != null) {
|
||||
cookieJar.saveFromResponse(url, cookies);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cookie> loadForRequest(HttpUrl url) {
|
||||
if (cookieJar != null) {
|
||||
List<Cookie> cookies = cookieJar.loadForRequest(url);
|
||||
ArrayList<Cookie> validatedCookies = new ArrayList<>();
|
||||
for (Cookie cookie : cookies) {
|
||||
try {
|
||||
Headers.Builder cookieChecker = new Headers.Builder();
|
||||
cookieChecker.add(cookie.name(), cookie.value());
|
||||
validatedCookies.add(cookie);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
return validatedCookies;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION_ERROR") // Conflicting okhttp versions
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import java.util.ArrayList
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
/** Basic okhttp3 CookieJar container */
|
||||
internal class ReactCookieJarContainer : CookieJarContainer {
|
||||
|
||||
private var cookieJar: CookieJar? = null
|
||||
|
||||
override fun setCookieJar(cookieJar: CookieJar) {
|
||||
this.cookieJar = cookieJar
|
||||
}
|
||||
|
||||
override fun removeCookieJar() {
|
||||
cookieJar = null
|
||||
}
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
cookieJar?.saveFromResponse(url, cookies)
|
||||
}
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||
val cookieJar = cookieJar ?: return emptyList()
|
||||
val cookies = cookieJar.loadForRequest(url)
|
||||
val validatedCookies = ArrayList<Cookie>()
|
||||
for (cookie in cookies) {
|
||||
try {
|
||||
val cookieChecker = Headers.Builder()
|
||||
cookieChecker.add(cookie.name(), cookie.value())
|
||||
validatedCookies.add(cookie)
|
||||
} catch (ignored: IllegalArgumentException) {}
|
||||
}
|
||||
return validatedCookies
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -46,14 +46,12 @@ public final class WebSocketModule extends NativeWebSocketModuleSpec {
|
||||
|
||||
private final Map<Integer, WebSocket> mWebSocketConnections = new ConcurrentHashMap<>();
|
||||
private final Map<Integer, ContentHandler> mContentHandlers = new ConcurrentHashMap<>();
|
||||
|
||||
private ForwardingCookieHandler mCookieHandler;
|
||||
private final ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler();
|
||||
|
||||
private static @Nullable CustomClientBuilder customClientBuilder = null;
|
||||
|
||||
public WebSocketModule(ReactApplicationContext context) {
|
||||
super(context);
|
||||
mCookieHandler = new ForwardingCookieHandler(context);
|
||||
}
|
||||
|
||||
public static void setCustomClientBuilder(CustomClientBuilder ccb) {
|
||||
|
||||
+11
-12
@@ -5,10 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
package com.facebook.react.uimanager
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
||||
/** Interface for the root native view of a React native application. */
|
||||
public interface RootView {
|
||||
@@ -17,21 +17,20 @@ public interface RootView {
|
||||
* Called when a child starts a native gesture (e.g. a scroll in a ScrollView). Should be called
|
||||
* from the child's onTouchIntercepted implementation.
|
||||
*/
|
||||
void onChildStartedNativeGesture(View childView, MotionEvent ev);
|
||||
public fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent)
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
default void onChildStartedNativeGesture(MotionEvent ev) {
|
||||
onChildStartedNativeGesture(null, ev);
|
||||
@Deprecated(
|
||||
message = "Use onChildStartedNativeGesture with a childView parameter.",
|
||||
replaceWith = ReplaceWith("onChildStartedNativeGesture"))
|
||||
public fun onChildStartedNativeGesture(ev: MotionEvent) {
|
||||
onChildStartedNativeGesture(null, ev)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child ends a native gesture. Should be called from the child's onTouchIntercepted
|
||||
* implementation.
|
||||
*/
|
||||
void onChildEndedNativeGesture(View childView, MotionEvent ev);
|
||||
public fun onChildEndedNativeGesture(childView: View, ev: MotionEvent)
|
||||
|
||||
void handleException(Throwable t);
|
||||
public fun handleException(t: Throwable)
|
||||
}
|
||||
+1
-1
@@ -495,7 +495,7 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
return super.onHoverEvent(event)
|
||||
}
|
||||
|
||||
override fun onChildStartedNativeGesture(childView: View, ev: MotionEvent) {
|
||||
override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
|
||||
eventDispatcher?.let { eventDispatcher ->
|
||||
jSTouchDispatcher.onChildStartedNativeGesture(ev, eventDispatcher)
|
||||
jSPointerDispatcher?.onChildStartedNativeGesture(childView, ev, eventDispatcher)
|
||||
|
||||
-3
@@ -62,9 +62,6 @@ public abstract class ReactClippingViewManager<T : ReactViewGroup> : ViewGroupMa
|
||||
if (removeClippedSubviews) {
|
||||
val child = getChildAt(parent, index)
|
||||
if (child != null) {
|
||||
if (child.parent != null) {
|
||||
parent.removeView(child)
|
||||
}
|
||||
parent.removeViewWithSubviewClippingEnabled(child)
|
||||
}
|
||||
} else {
|
||||
|
||||
+35
-3
@@ -138,6 +138,7 @@ public class ReactViewGroup extends ViewGroup
|
||||
private @Nullable ViewGroupDrawingOrderHelper mDrawingOrderHelper;
|
||||
private float mBackfaceOpacity;
|
||||
private String mBackfaceVisibility;
|
||||
private @Nullable Set<Integer> mChildrenRemovedWhileTransitioning;
|
||||
|
||||
/**
|
||||
* Creates a new `ReactViewGroup` instance.
|
||||
@@ -172,6 +173,7 @@ public class ReactViewGroup extends ViewGroup
|
||||
mDrawingOrderHelper = null;
|
||||
mBackfaceOpacity = 1.f;
|
||||
mBackfaceVisibility = "visible";
|
||||
mChildrenRemovedWhileTransitioning = null;
|
||||
}
|
||||
|
||||
/* package */ void recycleView() {
|
||||
@@ -354,6 +356,7 @@ public class ReactViewGroup extends ViewGroup
|
||||
return;
|
||||
}
|
||||
mRemoveClippedSubviews = removeClippedSubviews;
|
||||
mChildrenRemovedWhileTransitioning = null;
|
||||
if (removeClippedSubviews) {
|
||||
mClippingRect = new Rect();
|
||||
ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
|
||||
@@ -408,6 +411,26 @@ public class ReactViewGroup extends ViewGroup
|
||||
updateClippingToRect(mClippingRect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endViewTransition(View view) {
|
||||
super.endViewTransition(view);
|
||||
if (mChildrenRemovedWhileTransitioning != null) {
|
||||
mChildrenRemovedWhileTransitioning.remove(view.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void trackChildViewTransition(int childId) {
|
||||
if (mChildrenRemovedWhileTransitioning == null) {
|
||||
mChildrenRemovedWhileTransitioning = new HashSet<>();
|
||||
}
|
||||
mChildrenRemovedWhileTransitioning.add(childId);
|
||||
}
|
||||
|
||||
private boolean isChildRemovedWhileTransitioning(View child) {
|
||||
return mChildrenRemovedWhileTransitioning != null
|
||||
&& mChildrenRemovedWhileTransitioning.contains(child.getId());
|
||||
}
|
||||
|
||||
private void updateClippingToRect(Rect clippingRect) {
|
||||
Assertions.assertNotNull(mAllChildren);
|
||||
mInSubviewClippingLoop = true;
|
||||
@@ -573,6 +596,12 @@ public class ReactViewGroup extends ViewGroup
|
||||
} else {
|
||||
setChildrenDrawingOrderEnabled(false);
|
||||
}
|
||||
|
||||
// The parent might not be null in case the child is transitioning.
|
||||
if (child.getParent() != null) {
|
||||
trackChildViewTransition(child.getId());
|
||||
}
|
||||
|
||||
super.onViewRemoved(child);
|
||||
}
|
||||
|
||||
@@ -745,6 +774,7 @@ public class ReactViewGroup extends ViewGroup
|
||||
return (boolean) tag;
|
||||
}
|
||||
ViewParent parent = view.getParent();
|
||||
boolean transitioning = isChildRemovedWhileTransitioning(view);
|
||||
if (index != null) {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
"ReactViewGroup.isViewClipped",
|
||||
@@ -754,10 +784,12 @@ public class ReactViewGroup extends ViewGroup
|
||||
+ " parentNull="
|
||||
+ (parent == null)
|
||||
+ " parentThis="
|
||||
+ (parent == this)));
|
||||
+ (parent == this)
|
||||
+ " transitioning="
|
||||
+ transitioning));
|
||||
}
|
||||
// fallback - parent *should* be null if the view was removed
|
||||
if (parent == null) {
|
||||
// fallback - should be transitioning or have no parent if the view was removed
|
||||
if (parent == null || transitioning) {
|
||||
return true;
|
||||
} else {
|
||||
Assertions.assertCondition(parent == this);
|
||||
|
||||
+8
-12
@@ -320,18 +320,14 @@ inline void writeInsertMountItem(
|
||||
InstructionBuffer& buffer,
|
||||
const CppMountItem& mountItem) {
|
||||
buffer.writeIntArray(std::array<int, 3>{
|
||||
mountItem.newChildShadowView.tag,
|
||||
mountItem.parentShadowView.tag,
|
||||
mountItem.index});
|
||||
mountItem.newChildShadowView.tag, mountItem.parentTag, mountItem.index});
|
||||
}
|
||||
|
||||
inline void writeRemoveMountItem(
|
||||
InstructionBuffer& buffer,
|
||||
const CppMountItem& mountItem) {
|
||||
buffer.writeIntArray(std::array<int, 3>{
|
||||
mountItem.oldChildShadowView.tag,
|
||||
mountItem.parentShadowView.tag,
|
||||
mountItem.index});
|
||||
mountItem.oldChildShadowView.tag, mountItem.parentTag, mountItem.index});
|
||||
}
|
||||
|
||||
inline void writeUpdatePropsMountItem(
|
||||
@@ -377,7 +373,7 @@ inline void writeUpdateLayoutMountItem(
|
||||
|
||||
buffer.writeIntArray(std::array<int, 8>{
|
||||
mountItem.newChildShadowView.tag,
|
||||
mountItem.parentShadowView.tag,
|
||||
mountItem.parentTag,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
@@ -487,7 +483,7 @@ void FabricMountingManager::executeMount(
|
||||
}
|
||||
|
||||
for (const auto& mutation : mutations) {
|
||||
const auto& parentShadowView = mutation.parentShadowView;
|
||||
auto parentTag = mutation.parentTag;
|
||||
const auto& oldChildShadowView = mutation.oldChildShadowView;
|
||||
const auto& newChildShadowView = mutation.newChildShadowView;
|
||||
auto& mutationType = mutation.type;
|
||||
@@ -509,7 +505,7 @@ void FabricMountingManager::executeMount(
|
||||
case ShadowViewMutation::Remove: {
|
||||
if (!isVirtual) {
|
||||
cppCommonMountItems.push_back(CppMountItem::RemoveMountItem(
|
||||
parentShadowView, oldChildShadowView, index));
|
||||
parentTag, oldChildShadowView, index));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -559,7 +555,7 @@ void FabricMountingManager::executeMount(
|
||||
(maintainMutationOrder ? cppCommonMountItems
|
||||
: cppUpdateLayoutMountItems)
|
||||
.push_back(CppMountItem::UpdateLayoutMountItem(
|
||||
mutation.newChildShadowView, parentShadowView));
|
||||
mutation.newChildShadowView, parentTag));
|
||||
}
|
||||
|
||||
// OverflowInset: This is the values indicating boundaries including
|
||||
@@ -588,7 +584,7 @@ void FabricMountingManager::executeMount(
|
||||
if (!isVirtual) {
|
||||
// Insert item
|
||||
cppCommonMountItems.push_back(CppMountItem::InsertMountItem(
|
||||
parentShadowView, newChildShadowView, index));
|
||||
parentTag, newChildShadowView, index));
|
||||
|
||||
bool shouldCreateView =
|
||||
!allocatedViewTags.contains(newChildShadowView.tag);
|
||||
@@ -625,7 +621,7 @@ void FabricMountingManager::executeMount(
|
||||
(maintainMutationOrder ? cppCommonMountItems
|
||||
: cppUpdateLayoutMountItems)
|
||||
.push_back(CppMountItem::UpdateLayoutMountItem(
|
||||
newChildShadowView, parentShadowView));
|
||||
newChildShadowView, parentTag));
|
||||
|
||||
// OverflowInset: This is the values indicating boundaries including
|
||||
// children of the current view. The layout of current view may not
|
||||
|
||||
@@ -16,16 +16,16 @@ CppMountItem CppMountItem::DeleteMountItem(const ShadowView& shadowView) {
|
||||
return {CppMountItem::Type::Delete, {}, shadowView, {}, -1};
|
||||
}
|
||||
CppMountItem CppMountItem::InsertMountItem(
|
||||
const ShadowView& parentView,
|
||||
Tag parentTag,
|
||||
const ShadowView& shadowView,
|
||||
int index) {
|
||||
return {CppMountItem::Type::Insert, parentView, {}, shadowView, index};
|
||||
return {CppMountItem::Type::Insert, parentTag, {}, shadowView, index};
|
||||
}
|
||||
CppMountItem CppMountItem::RemoveMountItem(
|
||||
const ShadowView& parentView,
|
||||
Tag parentTag,
|
||||
const ShadowView& shadowView,
|
||||
int index) {
|
||||
return {CppMountItem::Type::Remove, parentView, shadowView, {}, index};
|
||||
return {CppMountItem::Type::Remove, parentTag, shadowView, {}, index};
|
||||
}
|
||||
CppMountItem CppMountItem::UpdatePropsMountItem(
|
||||
const ShadowView& oldShadowView,
|
||||
@@ -38,20 +38,20 @@ CppMountItem CppMountItem::UpdateStateMountItem(const ShadowView& shadowView) {
|
||||
}
|
||||
CppMountItem CppMountItem::UpdateLayoutMountItem(
|
||||
const ShadowView& shadowView,
|
||||
const ShadowView& parentView) {
|
||||
return {CppMountItem::Type::UpdateLayout, parentView, {}, shadowView, -1};
|
||||
Tag parentTag) {
|
||||
return {CppMountItem::Type::UpdateLayout, parentTag, {}, shadowView, -1};
|
||||
}
|
||||
CppMountItem CppMountItem::UpdateEventEmitterMountItem(
|
||||
const ShadowView& shadowView) {
|
||||
return {CppMountItem::Type::UpdateEventEmitter, {}, {}, shadowView, -1};
|
||||
return {CppMountItem::Type::UpdateEventEmitter, -1, {}, shadowView, -1};
|
||||
}
|
||||
CppMountItem CppMountItem::UpdatePaddingMountItem(
|
||||
const ShadowView& shadowView) {
|
||||
return {CppMountItem::Type::UpdatePadding, {}, {}, shadowView, -1};
|
||||
return {CppMountItem::Type::UpdatePadding, -1, {}, shadowView, -1};
|
||||
}
|
||||
CppMountItem CppMountItem::UpdateOverflowInsetMountItem(
|
||||
const ShadowView& shadowView) {
|
||||
return {CppMountItem::Type::UpdateOverflowInset, {}, {}, shadowView, -1};
|
||||
return {CppMountItem::Type::UpdateOverflowInset, -1, {}, shadowView, -1};
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -24,15 +24,11 @@ struct CppMountItem final {
|
||||
|
||||
static CppMountItem DeleteMountItem(const ShadowView& shadowView);
|
||||
|
||||
static CppMountItem InsertMountItem(
|
||||
const ShadowView& parentView,
|
||||
const ShadowView& shadowView,
|
||||
int index);
|
||||
static CppMountItem
|
||||
InsertMountItem(Tag parentTag, const ShadowView& shadowView, int index);
|
||||
|
||||
static CppMountItem RemoveMountItem(
|
||||
const ShadowView& parentView,
|
||||
const ShadowView& shadowView,
|
||||
int index);
|
||||
static CppMountItem
|
||||
RemoveMountItem(Tag parentTag, const ShadowView& shadowView, int index);
|
||||
|
||||
static CppMountItem UpdatePropsMountItem(
|
||||
const ShadowView& oldShadowView,
|
||||
@@ -42,7 +38,7 @@ struct CppMountItem final {
|
||||
|
||||
static CppMountItem UpdateLayoutMountItem(
|
||||
const ShadowView& shadowView,
|
||||
const ShadowView& parentView);
|
||||
Tag parentTag);
|
||||
|
||||
static CppMountItem UpdateEventEmitterMountItem(const ShadowView& shadowView);
|
||||
|
||||
@@ -71,7 +67,7 @@ struct CppMountItem final {
|
||||
#pragma mark - Fields
|
||||
|
||||
Type type = {Create};
|
||||
ShadowView parentShadowView = {};
|
||||
Tag parentTag = -1;
|
||||
ShadowView oldChildShadowView = {};
|
||||
ShadowView newChildShadowView = {};
|
||||
int index = {};
|
||||
|
||||
+15
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<902b269e45fcb4970c6f8a86818e1940>>
|
||||
* @generated SignedSource<<640630d7a40b53f7d507569aa6409f69>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -213,6 +213,12 @@ class ReactNativeFeatureFlagsProviderHolder
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool fixDifferentiatorEmittingUpdatesWithWrongParentTag() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("fixDifferentiatorEmittingUpdatesWithWrongParentTag");
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
@@ -464,6 +470,11 @@ bool JReactNativeFeatureFlagsCxxInterop::excludeYogaFromRawProps(
|
||||
return ReactNativeFeatureFlags::excludeYogaFromRawProps();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::fixDifferentiatorEmittingUpdatesWithWrongParentTag(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::fixDifferentiatorEmittingUpdatesWithWrongParentTag();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::fixMappingOfEventPrioritiesBetweenFabricAndReact(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
@@ -667,6 +678,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
|
||||
makeNativeMethod(
|
||||
"excludeYogaFromRawProps",
|
||||
JReactNativeFeatureFlagsCxxInterop::excludeYogaFromRawProps),
|
||||
makeNativeMethod(
|
||||
"fixDifferentiatorEmittingUpdatesWithWrongParentTag",
|
||||
JReactNativeFeatureFlagsCxxInterop::fixDifferentiatorEmittingUpdatesWithWrongParentTag),
|
||||
makeNativeMethod(
|
||||
"fixMappingOfEventPrioritiesBetweenFabricAndReact",
|
||||
JReactNativeFeatureFlagsCxxInterop::fixMappingOfEventPrioritiesBetweenFabricAndReact),
|
||||
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<17da0d7937c5c0c533293b86c8cdc9be>>
|
||||
* @generated SignedSource<<4218168e779a2241d0752771c1f51b12>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -117,6 +117,9 @@ class JReactNativeFeatureFlagsCxxInterop
|
||||
static bool excludeYogaFromRawProps(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool fixDifferentiatorEmittingUpdatesWithWrongParentTag(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool fixMappingOfEventPrioritiesBetweenFabricAndReact(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ void WritableNativeArray::pushInt(jint value) {
|
||||
array_.push_back(value);
|
||||
}
|
||||
|
||||
void WritableNativeArray::pushLong(jlong value) {
|
||||
throwIfConsumed();
|
||||
array_.push_back(value);
|
||||
}
|
||||
|
||||
void WritableNativeArray::pushString(jstring value) {
|
||||
if (value == NULL) {
|
||||
pushNull();
|
||||
@@ -81,6 +86,7 @@ void WritableNativeArray::registerNatives() {
|
||||
makeNativeMethod("pushBoolean", WritableNativeArray::pushBoolean),
|
||||
makeNativeMethod("pushDouble", WritableNativeArray::pushDouble),
|
||||
makeNativeMethod("pushInt", WritableNativeArray::pushInt),
|
||||
makeNativeMethod("pushLong", WritableNativeArray::pushLong),
|
||||
makeNativeMethod("pushString", WritableNativeArray::pushString),
|
||||
makeNativeMethod("pushNativeArray", WritableNativeArray::pushNativeArray),
|
||||
makeNativeMethod("pushNativeMap", WritableNativeArray::pushNativeMap),
|
||||
|
||||
@@ -36,6 +36,7 @@ struct WritableNativeArray
|
||||
void pushBoolean(jboolean value);
|
||||
void pushDouble(jdouble value);
|
||||
void pushInt(jint value);
|
||||
void pushLong(jlong value);
|
||||
void pushString(jstring value);
|
||||
void pushNativeArray(ReadableNativeArray* otherArray);
|
||||
void pushNativeMap(ReadableNativeMap* map);
|
||||
|
||||
@@ -44,6 +44,11 @@ void WritableNativeMap::putInt(std::string key, int val) {
|
||||
map_.insert(std::move(key), val);
|
||||
}
|
||||
|
||||
void WritableNativeMap::putLong(std::string key, jlong val) {
|
||||
throwIfConsumed();
|
||||
map_.insert(std::move(key), val);
|
||||
}
|
||||
|
||||
void WritableNativeMap::putString(std::string key, alias_ref<jstring> val) {
|
||||
if (!val) {
|
||||
putNull(std::move(key));
|
||||
@@ -90,6 +95,7 @@ void WritableNativeMap::registerNatives() {
|
||||
makeNativeMethod("putBoolean", WritableNativeMap::putBoolean),
|
||||
makeNativeMethod("putDouble", WritableNativeMap::putDouble),
|
||||
makeNativeMethod("putInt", WritableNativeMap::putInt),
|
||||
makeNativeMethod("putLong", WritableNativeMap::putLong),
|
||||
makeNativeMethod("putString", WritableNativeMap::putString),
|
||||
makeNativeMethod("putNativeArray", WritableNativeMap::putNativeArray),
|
||||
makeNativeMethod("putNativeMap", WritableNativeMap::putNativeMap),
|
||||
|
||||
@@ -35,6 +35,7 @@ struct WritableNativeMap
|
||||
void putBoolean(std::string key, bool val);
|
||||
void putDouble(std::string key, double val);
|
||||
void putInt(std::string key, int val);
|
||||
void putLong(std::string key, jlong val);
|
||||
void putString(std::string key, jni::alias_ref<jstring> val);
|
||||
void putNativeArray(std::string key, ReadableNativeArray* val);
|
||||
void putNativeMap(std::string key, ReadableNativeMap* val);
|
||||
|
||||
+7
-1
@@ -18,6 +18,12 @@ import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
/**
|
||||
* Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
|
||||
* returned.
|
||||
*/
|
||||
private fun <T> nonNullAny(type: Class<T>): T = any(type)
|
||||
|
||||
/** Tests for {@link NetworkingModule}. */
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ReactCookieJarContainerTest {
|
||||
@@ -33,7 +39,7 @@ class ReactCookieJarContainerTest {
|
||||
fun testEmptyCookies() {
|
||||
val jarContainer: ReactCookieJarContainer = mock(ReactCookieJarContainer::class.java)
|
||||
val cookies: List<Cookie> = emptyList()
|
||||
whenever(jarContainer.loadForRequest(any(HttpUrl::class.java))).thenReturn(cookies)
|
||||
whenever(jarContainer.loadForRequest(nonNullAny(HttpUrl::class.java))).thenReturn(cookies)
|
||||
assertThat(jarContainer.loadForRequest(httpUrl).size).isEqualTo(0)
|
||||
}
|
||||
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.JavaOnlyArray
|
||||
import com.facebook.react.bridge.JavaOnlyMap
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.WritableArray
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import java.net.SocketTimeoutException
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.MockedStatic
|
||||
import org.mockito.Mockito.*
|
||||
import org.mockito.Mockito.mockStatic
|
||||
|
||||
class ResponseUtilTest {
|
||||
private lateinit var reactContext: ReactApplicationContext
|
||||
private lateinit var arguments: MockedStatic<Arguments>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
reactContext = mock(ReactApplicationContext::class.java)
|
||||
|
||||
arguments = mockStatic(Arguments::class.java)
|
||||
arguments.`when`<WritableArray>(Arguments::createArray).thenAnswer { JavaOnlyArray() }
|
||||
arguments.`when`<WritableMap>(Arguments::createMap).thenAnswer { JavaOnlyMap() }
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
arguments.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDataSend() {
|
||||
val requestId = 1
|
||||
val progress = 100L
|
||||
val total = 1000L
|
||||
|
||||
ResponseUtil.onDataSend(reactContext, requestId, progress, total)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didSendNetworkData")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(3)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getInt(1)).isEqualTo(progress.toInt())
|
||||
assertThat(args.getInt(2)).isEqualTo(total.toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnIncrementalDataReceived() {
|
||||
val requestId = 1
|
||||
val data = "some data"
|
||||
val progress = 100L
|
||||
val total = 1000L
|
||||
|
||||
ResponseUtil.onIncrementalDataReceived(reactContext, requestId, data, progress, total)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didReceiveNetworkIncrementalData")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(4)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getString(1)).isEqualTo(data)
|
||||
assertThat(args.getInt(2)).isEqualTo(progress.toInt())
|
||||
assertThat(args.getInt(3)).isEqualTo(total.toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDataReceivedProgress() {
|
||||
val requestId = 1
|
||||
val progress = 500L
|
||||
val total = 1000L
|
||||
|
||||
ResponseUtil.onDataReceivedProgress(reactContext, requestId, progress, total)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didReceiveNetworkDataProgress")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(3)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getInt(1)).isEqualTo(progress.toInt())
|
||||
assertThat(args.getInt(2)).isEqualTo(total.toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDataReceived() {
|
||||
val requestId = 1
|
||||
val data = "response data"
|
||||
|
||||
ResponseUtil.onDataReceived(reactContext, requestId, data)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didReceiveNetworkData")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(2)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getString(1)).isEqualTo(data)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDataReceivedMap() {
|
||||
val requestId = 1
|
||||
val data: WritableMap = Arguments.createMap().apply { putString("key", "value") }
|
||||
|
||||
ResponseUtil.onDataReceived(reactContext, requestId, data)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didReceiveNetworkData")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(2)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getMap(1)).isEqualTo(data)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnRequestError() {
|
||||
val requestId = 1
|
||||
val error = "An error occurred"
|
||||
val e: Throwable? = null
|
||||
|
||||
ResponseUtil.onRequestError(reactContext, requestId, error, e)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didCompleteNetworkResponse")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(2)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getString(1)).isEqualTo(error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnRequestErrorWithException() {
|
||||
val requestId = 1
|
||||
val error = "Timeout error"
|
||||
val e: Throwable = SocketTimeoutException()
|
||||
|
||||
ResponseUtil.onRequestError(reactContext, requestId, error, e)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didCompleteNetworkResponse")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(3)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getString(1)).isEqualTo(error)
|
||||
assertThat(args.getBoolean(2)).isTrue
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnRequestSuccess() {
|
||||
val requestId = 1
|
||||
|
||||
ResponseUtil.onRequestSuccess(reactContext, requestId)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didCompleteNetworkResponse")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(2)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.isNull(1)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnResponseReceived() {
|
||||
val requestId = 1
|
||||
val statusCode = 200
|
||||
val headers: WritableMap =
|
||||
Arguments.createMap().apply { putString("Content-Type", "application/json") }
|
||||
val url = "http://example.com"
|
||||
|
||||
ResponseUtil.onResponseReceived(reactContext, requestId, statusCode, headers, url)
|
||||
|
||||
val eventNameCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
val eventArgumentsCaptor = ArgumentCaptor.forClass(WritableArray::class.java)
|
||||
|
||||
verify(reactContext).emitDeviceEvent(eventNameCaptor.capture(), eventArgumentsCaptor.capture())
|
||||
|
||||
assertThat(eventNameCaptor.value).isEqualTo("didReceiveNetworkResponse")
|
||||
|
||||
val args = eventArgumentsCaptor.value
|
||||
assertThat(args.size()).isEqualTo(4)
|
||||
assertThat(args.getInt(0)).isEqualTo(requestId)
|
||||
assertThat(args.getInt(1)).isEqualTo(statusCode)
|
||||
assertThat(args.getMap(2)).isEqualTo(headers)
|
||||
assertThat(args.getString(3)).isEqualTo(url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNullReactContext() {
|
||||
ResponseUtil.onDataSend(null, 1, 100, 1000)
|
||||
ResponseUtil.onIncrementalDataReceived(null, 1, "data", 100, 1000)
|
||||
ResponseUtil.onDataReceivedProgress(null, 1, 100, 1000)
|
||||
ResponseUtil.onDataReceived(null, 1, "data")
|
||||
ResponseUtil.onDataReceived(null, 1, Arguments.createMap())
|
||||
ResponseUtil.onRequestError(null, 1, "error", null)
|
||||
ResponseUtil.onRequestSuccess(null, 1)
|
||||
ResponseUtil.onResponseReceived(null, 1, 200, Arguments.createMap(), "http://example.com")
|
||||
|
||||
verify(reactContext, never()).emitDeviceEvent(anyString(), any())
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,12 @@ bool isLooselyNull(const jsi::Value& value) {
|
||||
return value.isNull() || value.isUndefined();
|
||||
}
|
||||
|
||||
bool isEmptyString(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
bool isEqualTo(
|
||||
jsi::Runtime& runtime,
|
||||
const jsi::Value& value,
|
||||
const std::string& str) {
|
||||
return jsi::Value::strictEquals(
|
||||
runtime, value, jsi::String::createFromUtf8(runtime, ""));
|
||||
runtime, value, jsi::String::createFromUtf8(runtime, str));
|
||||
}
|
||||
|
||||
std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
@@ -265,7 +268,7 @@ void JsErrorHandler::handleErrorWithCppPipeline(
|
||||
}
|
||||
|
||||
auto nameValue = errorObj.getProperty(runtime, "name");
|
||||
auto name = (isLooselyNull(nameValue) || isEmptyString(runtime, nameValue))
|
||||
auto name = (isLooselyNull(nameValue) || isEqualTo(runtime, nameValue, ""))
|
||||
? std::nullopt
|
||||
: std::optional(stringifyToCpp(runtime, nameValue));
|
||||
|
||||
@@ -383,14 +386,19 @@ void JsErrorHandler::handleErrorWithCppPipeline(
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFatal) {
|
||||
if (_hasHandledFatalError) {
|
||||
return;
|
||||
}
|
||||
_hasHandledFatalError = true;
|
||||
}
|
||||
auto errorType = errorObj.getProperty(runtime, "type");
|
||||
auto isWarn = isEqualTo(runtime, errorType, "warn");
|
||||
|
||||
_onJsError(runtime, parsedError);
|
||||
if (isFatal || !isWarn) {
|
||||
if (isFatal) {
|
||||
if (_hasHandledFatalError) {
|
||||
return;
|
||||
}
|
||||
_hasHandledFatalError = true;
|
||||
}
|
||||
|
||||
_onJsError(runtime, parsedError);
|
||||
}
|
||||
}
|
||||
|
||||
void JsErrorHandler::registerErrorListener(
|
||||
|
||||
@@ -232,6 +232,22 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
|
||||
return plain_.utf16(sym);
|
||||
}
|
||||
|
||||
void getStringData(
|
||||
const jsi::String& str,
|
||||
void* ctx,
|
||||
void (
|
||||
*cb)(void* ctx, bool ascii, const void* data, size_t num)) override {
|
||||
plain_.getStringData(str, ctx, cb);
|
||||
}
|
||||
|
||||
void getPropNameIdData(
|
||||
const jsi::PropNameID& sym,
|
||||
void* ctx,
|
||||
void (
|
||||
*cb)(void* ctx, bool ascii, const void* data, size_t num)) override {
|
||||
plain_.getPropNameIdData(sym, ctx, cb);
|
||||
}
|
||||
|
||||
Object createObject() override {
|
||||
return plain_.createObject();
|
||||
};
|
||||
@@ -690,6 +706,24 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
|
||||
return RD::utf16(sym);
|
||||
}
|
||||
|
||||
void getStringData(
|
||||
const jsi::String& str,
|
||||
void* ctx,
|
||||
void (
|
||||
*cb)(void* ctx, bool ascii, const void* data, size_t num)) override {
|
||||
Around around{with_};
|
||||
RD::getStringData(str, ctx, cb);
|
||||
}
|
||||
|
||||
void getPropNameIdData(
|
||||
const jsi::PropNameID& sym,
|
||||
void* ctx,
|
||||
void (
|
||||
*cb)(void* ctx, bool ascii, const void* data, size_t num)) override {
|
||||
Around around{with_};
|
||||
RD::getPropNameIdData(sym, ctx, cb);
|
||||
}
|
||||
|
||||
Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override {
|
||||
Around around{with_};
|
||||
return RD::createValueFromJsonUtf8(json, length);
|
||||
|
||||
@@ -258,6 +258,22 @@ std::u16string Runtime::utf16(const String& str) {
|
||||
return convertUTF8ToUTF16(utf8Str);
|
||||
}
|
||||
|
||||
void Runtime::getStringData(
|
||||
const jsi::String& str,
|
||||
void* ctx,
|
||||
void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) {
|
||||
auto utf16Str = utf16(str);
|
||||
cb(ctx, false, utf16Str.data(), utf16Str.size());
|
||||
}
|
||||
|
||||
void Runtime::getPropNameIdData(
|
||||
const jsi::PropNameID& sym,
|
||||
void* ctx,
|
||||
void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) {
|
||||
auto utf16Str = utf16(sym);
|
||||
cb(ctx, false, utf16Str.data(), utf16Str.size());
|
||||
}
|
||||
|
||||
Pointer& Pointer::operator=(Pointer&& other) noexcept {
|
||||
if (ptr_) {
|
||||
ptr_->invalidate();
|
||||
|
||||
@@ -402,6 +402,34 @@ class JSI_EXPORT Runtime {
|
||||
virtual std::u16string utf16(const String& str);
|
||||
virtual std::u16string utf16(const PropNameID& sym);
|
||||
|
||||
/// Invokes the provided callback \p cb with the String content in \p str.
|
||||
/// The callback must take in three arguments: bool ascii, const void* data,
|
||||
/// and size_t num, respectively. \p ascii indicates whether the \p data
|
||||
/// passed to the callback should be interpreted as a pointer to a sequence of
|
||||
/// \p num ASCII characters or UTF16 characters. Depending on the internal
|
||||
/// representation of the string, the function may invoke the callback
|
||||
/// multiple times, with a different format on each invocation. The callback
|
||||
/// must not access runtime functionality, as any operation on the runtime may
|
||||
/// invalidate the data pointers.
|
||||
virtual void getStringData(
|
||||
const jsi::String& str,
|
||||
void* ctx,
|
||||
void (*cb)(void* ctx, bool ascii, const void* data, size_t num));
|
||||
|
||||
/// Invokes the provided callback \p cb with the PropNameID content in \p sym.
|
||||
/// The callback must take in three arguments: bool ascii, const void* data,
|
||||
/// and size_t num, respectively. \p ascii indicates whether the \p data
|
||||
/// passed to the callback should be interpreted as a pointer to a sequence of
|
||||
/// \p num ASCII characters or UTF16 characters. Depending on the internal
|
||||
/// representation of the string, the function may invoke the callback
|
||||
/// multiple times, with a different format on each invocation. The callback
|
||||
/// must not access runtime functionality, as any operation on the runtime may
|
||||
/// invalidate the data pointers.
|
||||
virtual void getPropNameIdData(
|
||||
const jsi::PropNameID& sym,
|
||||
void* ctx,
|
||||
void (*cb)(void* ctx, bool ascii, const void* data, size_t num));
|
||||
|
||||
// These exist so derived classes can access the private parts of
|
||||
// Value, Symbol, String, and Object, which are all friends of Runtime.
|
||||
template <typename T>
|
||||
@@ -509,6 +537,22 @@ class JSI_EXPORT PropNameID : public Pointer {
|
||||
return runtime.utf16(*this);
|
||||
}
|
||||
|
||||
/// Invokes the user provided callback to process the content in PropNameId.
|
||||
/// The callback must take in three arguments: bool ascii, const void* data,
|
||||
/// and size_t num, respectively. \p ascii indicates whether the \p data
|
||||
/// passed to the callback should be interpreted as a pointer to a sequence of
|
||||
/// \p num ASCII characters or UTF16 characters. The function may invoke the
|
||||
/// callback multiple times, with a different format on each invocation. The
|
||||
/// callback must not access runtime functionality, as any operation on the
|
||||
/// runtime may invalidate the data pointers.
|
||||
template <typename CB>
|
||||
void getPropNameIdData(Runtime& runtime, CB& cb) const {
|
||||
runtime.getPropNameIdData(
|
||||
*this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) {
|
||||
(*((CB*)ctx))(ascii, data, num);
|
||||
});
|
||||
}
|
||||
|
||||
static bool compare(
|
||||
Runtime& runtime,
|
||||
const jsi::PropNameID& a,
|
||||
@@ -664,6 +708,22 @@ class JSI_EXPORT String : public Pointer {
|
||||
return runtime.utf16(*this);
|
||||
}
|
||||
|
||||
/// Invokes the user provided callback to process content in String. The
|
||||
/// callback must take in three arguments: bool ascii, const void* data, and
|
||||
/// size_t num, respectively. \p ascii indicates whether the \p data passed to
|
||||
/// the callback should be interpreted as a pointer to a sequence of \p num
|
||||
/// ASCII characters or UTF16 characters. The function may invoke the callback
|
||||
/// multiple times, with a different format on each invocation. The callback
|
||||
/// must not access runtime functionality, as any operation on the runtime may
|
||||
/// invalidate the data pointers.
|
||||
template <typename CB>
|
||||
void getStringData(Runtime& runtime, CB& cb) const {
|
||||
runtime.getStringData(
|
||||
*this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) {
|
||||
(*((CB*)ctx))(ascii, data, num);
|
||||
});
|
||||
}
|
||||
|
||||
friend class Runtime;
|
||||
friend class Value;
|
||||
};
|
||||
|
||||
@@ -1635,6 +1635,35 @@ TEST_P(JSITest, UTF16Test) {
|
||||
EXPECT_EQ(str.utf16(rd), u"\uFFFD\u007A");
|
||||
}
|
||||
|
||||
TEST_P(JSITest, GetStringDataTest) {
|
||||
// This Runtime Decorator is used to test the default getStringData
|
||||
// implementation for VMs that do not provide their own implementation
|
||||
class RD : public RuntimeDecorator<Runtime, Runtime> {
|
||||
public:
|
||||
RD(Runtime& rt) : RuntimeDecorator(rt) {}
|
||||
|
||||
void getStringData(
|
||||
const String& str,
|
||||
void* ctx,
|
||||
void (*cb)(void* ctx, bool ascii, const void* data, size_t num))
|
||||
override {
|
||||
Runtime::getStringData(str, ctx, cb);
|
||||
}
|
||||
};
|
||||
|
||||
RD rd = RD(rt);
|
||||
String str = String::createFromUtf8(rd, "hello👋");
|
||||
|
||||
std::u16string buf;
|
||||
auto cb = [&buf](bool ascii, const void* data, size_t num) {
|
||||
assert(!ascii && "Default implementation is always utf16");
|
||||
buf.append((const char16_t*)data, num);
|
||||
};
|
||||
|
||||
str.getStringData(rd, cb);
|
||||
EXPECT_EQ(buf, str.utf16(rd));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Runtimes,
|
||||
JSITest,
|
||||
|
||||
@@ -20,31 +20,39 @@ folly_config = get_folly_config()
|
||||
folly_compiler_flags = folly_config[:compiler_flags]
|
||||
folly_version = folly_config[:version]
|
||||
|
||||
use_frameworks = ENV['USE_FRAMEWORKS'] != nil
|
||||
header_search_paths = [
|
||||
"\"$(PODS_TARGET_SRCROOT)/..\"",
|
||||
"\"$(PODS_ROOT)/boost\"",
|
||||
"\"$(PODS_ROOT)/DoubleConversion\"",
|
||||
"\"$(PODS_ROOT)/fast_float/include\"",
|
||||
"\"$(PODS_ROOT)/fmt/include\"",
|
||||
"\"$(PODS_ROOT)/RCT-Folly\"",
|
||||
]
|
||||
|
||||
header_dir = 'jsinspector-modern'
|
||||
module_name = "jsinspector_modern"
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-jsinspector"
|
||||
s.version = version
|
||||
s.summary = "-" # TODO
|
||||
s.summary = "React Native subsystem for modern debugging over the Chrome DevTools Protocol (CDP)"
|
||||
s.homepage = "https://reactnative.dev/"
|
||||
s.license = package["license"]
|
||||
s.author = "Meta Platforms, Inc. and its affiliates"
|
||||
s.platforms = min_supported_versions
|
||||
s.source = source
|
||||
s.source_files = "*.{cpp,h,def}"
|
||||
s.header_dir = 'jsinspector-modern'
|
||||
s.header_dir = header_dir
|
||||
s.compiler_flags = folly_compiler_flags
|
||||
s.pod_target_xcconfig = {
|
||||
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/..\" \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fast_float/include\" \"$(PODS_ROOT)/fmt/include\"",
|
||||
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
|
||||
"DEFINES_MODULE" => "YES"
|
||||
}.merge!(use_frameworks ? {
|
||||
}.merge!(ENV['USE_FRAMEWORKS'] ? {
|
||||
"PUBLIC_HEADERS_FOLDER_PATH" => "#{module_name}.framework/Headers/#{header_dir}"
|
||||
} : {})
|
||||
|
||||
if use_frameworks
|
||||
if ENV['USE_FRAMEWORKS']
|
||||
s.module_name = module_name
|
||||
end
|
||||
|
||||
|
||||
@@ -18,3 +18,6 @@ file(GLOB react_featureflags_SRC CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(react_featureflags OBJECT ${react_featureflags_SRC})
|
||||
|
||||
target_include_directories(react_featureflags PUBLIC ${REACT_COMMON_DIR})
|
||||
|
||||
target_link_libraries(react_featureflags
|
||||
folly_runtime)
|
||||
|
||||
@@ -22,6 +22,10 @@ if ENV['USE_FRAMEWORKS']
|
||||
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../..\"" # this is needed to allow the feature flags access its own files
|
||||
end
|
||||
|
||||
folly_config = get_folly_config()
|
||||
folly_compiler_flags = folly_config[:compiler_flags]
|
||||
folly_version = folly_config[:version]
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-featureflags"
|
||||
s.version = version
|
||||
@@ -32,11 +36,14 @@ Pod::Spec.new do |s|
|
||||
s.platforms = min_supported_versions
|
||||
s.source = source
|
||||
s.source_files = "*.{cpp,h}"
|
||||
s.compiler_flags = folly_compiler_flags
|
||||
s.header_dir = "react/featureflags"
|
||||
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
|
||||
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
|
||||
"DEFINES_MODULE" => "YES" }
|
||||
|
||||
s.dependency "RCT-Folly", folly_version
|
||||
|
||||
if ENV['USE_FRAMEWORKS']
|
||||
s.module_name = "React_featureflags"
|
||||
s.header_mappings_dir = "../.."
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<ef215623465d45c563030d724287b1c9>>
|
||||
* @generated SignedSource<<2409869111055ff0b32c1f40c10042d7>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -142,6 +142,10 @@ bool ReactNativeFeatureFlags::excludeYogaFromRawProps() {
|
||||
return getAccessor().excludeYogaFromRawProps();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::fixDifferentiatorEmittingUpdatesWithWrongParentTag() {
|
||||
return getAccessor().fixDifferentiatorEmittingUpdatesWithWrongParentTag();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact() {
|
||||
return getAccessor().fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<f741660e4cf2528defe0ab1f61858aab>>
|
||||
* @generated SignedSource<<e628af8109a1d8bb6425515d824852a3>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -184,6 +184,11 @@ class ReactNativeFeatureFlags {
|
||||
*/
|
||||
RN_EXPORT static bool excludeYogaFromRawProps();
|
||||
|
||||
/**
|
||||
* Fixes a bug in Differentiator where parent views may be referenced before they're created
|
||||
*/
|
||||
RN_EXPORT static bool fixDifferentiatorEmittingUpdatesWithWrongParentTag();
|
||||
|
||||
/**
|
||||
* Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React.
|
||||
*/
|
||||
|
||||
+36
-18
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<4c3956150bbf826c2abf4f8daf569b88>>
|
||||
* @generated SignedSource<<1e5b87b564e880cfb1423a85692092ba>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -551,6 +551,24 @@ bool ReactNativeFeatureFlagsAccessor::excludeYogaFromRawProps() {
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::fixDifferentiatorEmittingUpdatesWithWrongParentTag() {
|
||||
auto flagValue = fixDifferentiatorEmittingUpdatesWithWrongParentTag_.load();
|
||||
|
||||
if (!flagValue.has_value()) {
|
||||
// This block is not exclusive but it is not necessary.
|
||||
// If multiple threads try to initialize the feature flag, we would only
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(29, "fixDifferentiatorEmittingUpdatesWithWrongParentTag");
|
||||
|
||||
flagValue = currentProvider_->fixDifferentiatorEmittingUpdatesWithWrongParentTag();
|
||||
fixDifferentiatorEmittingUpdatesWithWrongParentTag_ = flagValue;
|
||||
}
|
||||
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAndReact() {
|
||||
auto flagValue = fixMappingOfEventPrioritiesBetweenFabricAndReact_.load();
|
||||
|
||||
@@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(29, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
markFlagAsAccessed(30, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
|
||||
flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue;
|
||||
@@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMountingCoordinatorReportedPendingTrans
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(30, "fixMountingCoordinatorReportedPendingTransactionsOnAndroid");
|
||||
markFlagAsAccessed(31, "fixMountingCoordinatorReportedPendingTransactionsOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->fixMountingCoordinatorReportedPendingTransactionsOnAndroid();
|
||||
fixMountingCoordinatorReportedPendingTransactionsOnAndroid_ = flagValue;
|
||||
@@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledDebug() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(31, "fuseboxEnabledDebug");
|
||||
markFlagAsAccessed(32, "fuseboxEnabledDebug");
|
||||
|
||||
flagValue = currentProvider_->fuseboxEnabledDebug();
|
||||
fuseboxEnabledDebug_ = flagValue;
|
||||
@@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(32, "fuseboxEnabledRelease");
|
||||
markFlagAsAccessed(33, "fuseboxEnabledRelease");
|
||||
|
||||
flagValue = currentProvider_->fuseboxEnabledRelease();
|
||||
fuseboxEnabledRelease_ = flagValue;
|
||||
@@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::initEagerTurboModulesOnNativeModulesQueueA
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(33, "initEagerTurboModulesOnNativeModulesQueueAndroid");
|
||||
markFlagAsAccessed(34, "initEagerTurboModulesOnNativeModulesQueueAndroid");
|
||||
|
||||
flagValue = currentProvider_->initEagerTurboModulesOnNativeModulesQueueAndroid();
|
||||
initEagerTurboModulesOnNativeModulesQueueAndroid_ = flagValue;
|
||||
@@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::lazyAnimationCallbacks() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(34, "lazyAnimationCallbacks");
|
||||
markFlagAsAccessed(35, "lazyAnimationCallbacks");
|
||||
|
||||
flagValue = currentProvider_->lazyAnimationCallbacks();
|
||||
lazyAnimationCallbacks_ = flagValue;
|
||||
@@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::loadVectorDrawablesOnImages() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(35, "loadVectorDrawablesOnImages");
|
||||
markFlagAsAccessed(36, "loadVectorDrawablesOnImages");
|
||||
|
||||
flagValue = currentProvider_->loadVectorDrawablesOnImages();
|
||||
loadVectorDrawablesOnImages_ = flagValue;
|
||||
@@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(36, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
markFlagAsAccessed(37, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid();
|
||||
traceTurboModulePromiseRejectionsOnAndroid_ = flagValue;
|
||||
@@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(37, "useAlwaysAvailableJSErrorHandling");
|
||||
markFlagAsAccessed(38, "useAlwaysAvailableJSErrorHandling");
|
||||
|
||||
flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling();
|
||||
useAlwaysAvailableJSErrorHandling_ = flagValue;
|
||||
@@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(38, "useFabricInterop");
|
||||
markFlagAsAccessed(39, "useFabricInterop");
|
||||
|
||||
flagValue = currentProvider_->useFabricInterop();
|
||||
useFabricInterop_ = flagValue;
|
||||
@@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::useImmediateExecutorInAndroidBridgeless()
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(39, "useImmediateExecutorInAndroidBridgeless");
|
||||
markFlagAsAccessed(40, "useImmediateExecutorInAndroidBridgeless");
|
||||
|
||||
flagValue = currentProvider_->useImmediateExecutorInAndroidBridgeless();
|
||||
useImmediateExecutorInAndroidBridgeless_ = flagValue;
|
||||
@@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(40, "useNativeViewConfigsInBridgelessMode");
|
||||
markFlagAsAccessed(41, "useNativeViewConfigsInBridgelessMode");
|
||||
|
||||
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
|
||||
useNativeViewConfigsInBridgelessMode_ = flagValue;
|
||||
@@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimisedViewPreallocationOnAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(41, "useOptimisedViewPreallocationOnAndroid");
|
||||
markFlagAsAccessed(42, "useOptimisedViewPreallocationOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->useOptimisedViewPreallocationOnAndroid();
|
||||
useOptimisedViewPreallocationOnAndroid_ = flagValue;
|
||||
@@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(42, "useOptimizedEventBatchingOnAndroid");
|
||||
markFlagAsAccessed(43, "useOptimizedEventBatchingOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid();
|
||||
useOptimizedEventBatchingOnAndroid_ = flagValue;
|
||||
@@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::useRuntimeShadowNodeReferenceUpdate() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(43, "useRuntimeShadowNodeReferenceUpdate");
|
||||
markFlagAsAccessed(44, "useRuntimeShadowNodeReferenceUpdate");
|
||||
|
||||
flagValue = currentProvider_->useRuntimeShadowNodeReferenceUpdate();
|
||||
useRuntimeShadowNodeReferenceUpdate_ = flagValue;
|
||||
@@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(44, "useTurboModuleInterop");
|
||||
markFlagAsAccessed(45, "useTurboModuleInterop");
|
||||
|
||||
flagValue = currentProvider_->useTurboModuleInterop();
|
||||
useTurboModuleInterop_ = flagValue;
|
||||
@@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(45, "useTurboModules");
|
||||
markFlagAsAccessed(46, "useTurboModules");
|
||||
|
||||
flagValue = currentProvider_->useTurboModules();
|
||||
useTurboModules_ = flagValue;
|
||||
|
||||
+4
-2
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<3d98085a73dfc51541342dbb42ed89ab>>
|
||||
* @generated SignedSource<<eb44aabe7e352481267aa9a6bf035ff1>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -61,6 +61,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
bool enableUIConsistency();
|
||||
bool enableViewRecycling();
|
||||
bool excludeYogaFromRawProps();
|
||||
bool fixDifferentiatorEmittingUpdatesWithWrongParentTag();
|
||||
bool fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
bool fixMountingCoordinatorReportedPendingTransactionsOnAndroid();
|
||||
bool fuseboxEnabledDebug();
|
||||
@@ -89,7 +90,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
|
||||
bool wasOverridden_;
|
||||
|
||||
std::array<std::atomic<const char*>, 46> accessedFeatureFlags_;
|
||||
std::array<std::atomic<const char*>, 47> accessedFeatureFlags_;
|
||||
|
||||
std::atomic<std::optional<bool>> commonTestFlag_;
|
||||
std::atomic<std::optional<bool>> completeReactInstanceCreationOnBgThreadOnAndroid_;
|
||||
@@ -120,6 +121,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::atomic<std::optional<bool>> enableUIConsistency_;
|
||||
std::atomic<std::optional<bool>> enableViewRecycling_;
|
||||
std::atomic<std::optional<bool>> excludeYogaFromRawProps_;
|
||||
std::atomic<std::optional<bool>> fixDifferentiatorEmittingUpdatesWithWrongParentTag_;
|
||||
std::atomic<std::optional<bool>> fixMappingOfEventPrioritiesBetweenFabricAndReact_;
|
||||
std::atomic<std::optional<bool>> fixMountingCoordinatorReportedPendingTransactionsOnAndroid_;
|
||||
std::atomic<std::optional<bool>> fuseboxEnabledDebug_;
|
||||
|
||||
+5
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<aff3c46b8d2db3bde519e1392569d53d>>
|
||||
* @generated SignedSource<<ee899be30798eb6d386b44bc6bc027ea>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -143,6 +143,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fixDifferentiatorEmittingUpdatesWithWrongParentTag() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
* 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 SignedSource<<a3ad884be5b18b3cfad650941abfa751>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags --update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/**
|
||||
* This class is a ReactNativeFeatureFlags provider that takes the values for
|
||||
* feature flags from a folly::dynamic object (e.g. from a JSON object), if
|
||||
* they are defined. For the flags not defined in the object, it falls back to
|
||||
* the default values defined in ReactNativeFeatureFlagsDefaults.
|
||||
*
|
||||
* The API is strict about typing. It ignores null values from the
|
||||
* folly::dynamic object, but if the key is defined, the value must have the
|
||||
* correct type or otherwise throws an exception.
|
||||
*/
|
||||
class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDefaults {
|
||||
private:
|
||||
folly::dynamic values_;
|
||||
|
||||
public:
|
||||
ReactNativeFeatureFlagsDynamicProvider(folly::dynamic values): values_(std::move(values)) {
|
||||
if (!values_.isObject()) {
|
||||
throw std::invalid_argument("ReactNativeFeatureFlagsDynamicProvider: values must be an object");
|
||||
}
|
||||
}
|
||||
|
||||
bool commonTestFlag() override {
|
||||
auto value = values_["commonTestFlag"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::commonTestFlag();
|
||||
}
|
||||
|
||||
bool completeReactInstanceCreationOnBgThreadOnAndroid() override {
|
||||
auto value = values_["completeReactInstanceCreationOnBgThreadOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::completeReactInstanceCreationOnBgThreadOnAndroid();
|
||||
}
|
||||
|
||||
bool disableEventLoopOnBridgeless() override {
|
||||
auto value = values_["disableEventLoopOnBridgeless"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::disableEventLoopOnBridgeless();
|
||||
}
|
||||
|
||||
bool disableMountItemReorderingAndroid() override {
|
||||
auto value = values_["disableMountItemReorderingAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::disableMountItemReorderingAndroid();
|
||||
}
|
||||
|
||||
bool enableAlignItemsBaselineOnFabricIOS() override {
|
||||
auto value = values_["enableAlignItemsBaselineOnFabricIOS"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableAlignItemsBaselineOnFabricIOS();
|
||||
}
|
||||
|
||||
bool enableAndroidLineHeightCentering() override {
|
||||
auto value = values_["enableAndroidLineHeightCentering"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableAndroidLineHeightCentering();
|
||||
}
|
||||
|
||||
bool enableBridgelessArchitecture() override {
|
||||
auto value = values_["enableBridgelessArchitecture"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableBridgelessArchitecture();
|
||||
}
|
||||
|
||||
bool enableCppPropsIteratorSetter() override {
|
||||
auto value = values_["enableCppPropsIteratorSetter"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableCppPropsIteratorSetter();
|
||||
}
|
||||
|
||||
bool enableDeletionOfUnmountedViews() override {
|
||||
auto value = values_["enableDeletionOfUnmountedViews"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableDeletionOfUnmountedViews();
|
||||
}
|
||||
|
||||
bool enableEagerRootViewAttachment() override {
|
||||
auto value = values_["enableEagerRootViewAttachment"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableEagerRootViewAttachment();
|
||||
}
|
||||
|
||||
bool enableEventEmitterRetentionDuringGesturesOnAndroid() override {
|
||||
auto value = values_["enableEventEmitterRetentionDuringGesturesOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableEventEmitterRetentionDuringGesturesOnAndroid();
|
||||
}
|
||||
|
||||
bool enableFabricLogs() override {
|
||||
auto value = values_["enableFabricLogs"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableFabricLogs();
|
||||
}
|
||||
|
||||
bool enableFabricRenderer() override {
|
||||
auto value = values_["enableFabricRenderer"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableFabricRenderer();
|
||||
}
|
||||
|
||||
bool enableFabricRendererExclusively() override {
|
||||
auto value = values_["enableFabricRendererExclusively"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableFabricRendererExclusively();
|
||||
}
|
||||
|
||||
bool enableFixForViewCommandRace() override {
|
||||
auto value = values_["enableFixForViewCommandRace"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableFixForViewCommandRace();
|
||||
}
|
||||
|
||||
bool enableGranularShadowTreeStateReconciliation() override {
|
||||
auto value = values_["enableGranularShadowTreeStateReconciliation"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableGranularShadowTreeStateReconciliation();
|
||||
}
|
||||
|
||||
bool enableIOSViewClipToPaddingBox() override {
|
||||
auto value = values_["enableIOSViewClipToPaddingBox"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool enableImagePrefetchingAndroid() override {
|
||||
auto value = values_["enableImagePrefetchingAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableImagePrefetchingAndroid();
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid() override {
|
||||
auto value = values_["enableLayoutAnimationsOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableLayoutAnimationsOnAndroid();
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnIOS() override {
|
||||
auto value = values_["enableLayoutAnimationsOnIOS"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableLayoutAnimationsOnIOS();
|
||||
}
|
||||
|
||||
bool enableLongTaskAPI() override {
|
||||
auto value = values_["enableLongTaskAPI"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableLongTaskAPI();
|
||||
}
|
||||
|
||||
bool enableNewBackgroundAndBorderDrawables() override {
|
||||
auto value = values_["enableNewBackgroundAndBorderDrawables"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableNewBackgroundAndBorderDrawables();
|
||||
}
|
||||
|
||||
bool enablePreciseSchedulingForPremountItemsOnAndroid() override {
|
||||
auto value = values_["enablePreciseSchedulingForPremountItemsOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enablePreciseSchedulingForPremountItemsOnAndroid();
|
||||
}
|
||||
|
||||
bool enablePropsUpdateReconciliationAndroid() override {
|
||||
auto value = values_["enablePropsUpdateReconciliationAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enablePropsUpdateReconciliationAndroid();
|
||||
}
|
||||
|
||||
bool enableReportEventPaintTime() override {
|
||||
auto value = values_["enableReportEventPaintTime"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableReportEventPaintTime();
|
||||
}
|
||||
|
||||
bool enableSynchronousStateUpdates() override {
|
||||
auto value = values_["enableSynchronousStateUpdates"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableSynchronousStateUpdates();
|
||||
}
|
||||
|
||||
bool enableUIConsistency() override {
|
||||
auto value = values_["enableUIConsistency"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableUIConsistency();
|
||||
}
|
||||
|
||||
bool enableViewRecycling() override {
|
||||
auto value = values_["enableViewRecycling"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableViewRecycling();
|
||||
}
|
||||
|
||||
bool excludeYogaFromRawProps() override {
|
||||
auto value = values_["excludeYogaFromRawProps"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::excludeYogaFromRawProps();
|
||||
}
|
||||
|
||||
bool fixDifferentiatorEmittingUpdatesWithWrongParentTag() override {
|
||||
auto value = values_["fixDifferentiatorEmittingUpdatesWithWrongParentTag"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::fixDifferentiatorEmittingUpdatesWithWrongParentTag();
|
||||
}
|
||||
|
||||
bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override {
|
||||
auto value = values_["fixMappingOfEventPrioritiesBetweenFabricAndReact"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
}
|
||||
|
||||
bool fixMountingCoordinatorReportedPendingTransactionsOnAndroid() override {
|
||||
auto value = values_["fixMountingCoordinatorReportedPendingTransactionsOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::fixMountingCoordinatorReportedPendingTransactionsOnAndroid();
|
||||
}
|
||||
|
||||
bool fuseboxEnabledDebug() override {
|
||||
auto value = values_["fuseboxEnabledDebug"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::fuseboxEnabledDebug();
|
||||
}
|
||||
|
||||
bool fuseboxEnabledRelease() override {
|
||||
auto value = values_["fuseboxEnabledRelease"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::fuseboxEnabledRelease();
|
||||
}
|
||||
|
||||
bool initEagerTurboModulesOnNativeModulesQueueAndroid() override {
|
||||
auto value = values_["initEagerTurboModulesOnNativeModulesQueueAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::initEagerTurboModulesOnNativeModulesQueueAndroid();
|
||||
}
|
||||
|
||||
bool lazyAnimationCallbacks() override {
|
||||
auto value = values_["lazyAnimationCallbacks"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::lazyAnimationCallbacks();
|
||||
}
|
||||
|
||||
bool loadVectorDrawablesOnImages() override {
|
||||
auto value = values_["loadVectorDrawablesOnImages"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::loadVectorDrawablesOnImages();
|
||||
}
|
||||
|
||||
bool traceTurboModulePromiseRejectionsOnAndroid() override {
|
||||
auto value = values_["traceTurboModulePromiseRejectionsOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::traceTurboModulePromiseRejectionsOnAndroid();
|
||||
}
|
||||
|
||||
bool useAlwaysAvailableJSErrorHandling() override {
|
||||
auto value = values_["useAlwaysAvailableJSErrorHandling"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useAlwaysAvailableJSErrorHandling();
|
||||
}
|
||||
|
||||
bool useFabricInterop() override {
|
||||
auto value = values_["useFabricInterop"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useFabricInterop();
|
||||
}
|
||||
|
||||
bool useImmediateExecutorInAndroidBridgeless() override {
|
||||
auto value = values_["useImmediateExecutorInAndroidBridgeless"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useImmediateExecutorInAndroidBridgeless();
|
||||
}
|
||||
|
||||
bool useNativeViewConfigsInBridgelessMode() override {
|
||||
auto value = values_["useNativeViewConfigsInBridgelessMode"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useNativeViewConfigsInBridgelessMode();
|
||||
}
|
||||
|
||||
bool useOptimisedViewPreallocationOnAndroid() override {
|
||||
auto value = values_["useOptimisedViewPreallocationOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useOptimisedViewPreallocationOnAndroid();
|
||||
}
|
||||
|
||||
bool useOptimizedEventBatchingOnAndroid() override {
|
||||
auto value = values_["useOptimizedEventBatchingOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useOptimizedEventBatchingOnAndroid();
|
||||
}
|
||||
|
||||
bool useRuntimeShadowNodeReferenceUpdate() override {
|
||||
auto value = values_["useRuntimeShadowNodeReferenceUpdate"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useRuntimeShadowNodeReferenceUpdate();
|
||||
}
|
||||
|
||||
bool useTurboModuleInterop() override {
|
||||
auto value = values_["useTurboModuleInterop"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useTurboModuleInterop();
|
||||
}
|
||||
|
||||
bool useTurboModules() override {
|
||||
auto value = values_["useTurboModules"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::useTurboModules();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<8f8c7a372cdf9a3c06bc0d71f1ed85ad>>
|
||||
* @generated SignedSource<<e5d1c60102f7444332bd34627c02eddd>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -54,6 +54,7 @@ class ReactNativeFeatureFlagsProvider {
|
||||
virtual bool enableUIConsistency() = 0;
|
||||
virtual bool enableViewRecycling() = 0;
|
||||
virtual bool excludeYogaFromRawProps() = 0;
|
||||
virtual bool fixDifferentiatorEmittingUpdatesWithWrongParentTag() = 0;
|
||||
virtual bool fixMappingOfEventPrioritiesBetweenFabricAndReact() = 0;
|
||||
virtual bool fixMountingCoordinatorReportedPendingTransactionsOnAndroid() = 0;
|
||||
virtual bool fuseboxEnabledDebug() = 0;
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ReactNativeFeatureFlagsDynamicProviderTest : public testing::Test {
|
||||
protected:
|
||||
void TearDown() override {
|
||||
ReactNativeFeatureFlags::dangerouslyReset();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsDynamicProviderTest, providesDefaults) {
|
||||
auto values = folly::dynamic::object();
|
||||
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(
|
||||
std::move(values)));
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
|
||||
}
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsDynamicProviderTest, providesDynamicOverrides) {
|
||||
folly::dynamic values = folly::dynamic::object();
|
||||
|
||||
values["commonTestFlag"] = true;
|
||||
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(values));
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ReactNativeFeatureFlagsDynamicProviderTest,
|
||||
throwsWithIncorrectFlagTypes) {
|
||||
folly::dynamic values = folly::dynamic::object();
|
||||
|
||||
values["commonTestFlag"] = 12;
|
||||
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(values));
|
||||
|
||||
try {
|
||||
ReactNativeFeatureFlags::commonTestFlag();
|
||||
FAIL()
|
||||
<< "Expected ReactNativeFeatureFlags::commonTestFlag() to throw an exception";
|
||||
} catch (const std::runtime_error& e) {
|
||||
EXPECT_STREQ(
|
||||
"TypeError: expected dynamic type 'boolean', but had type 'int64'",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsDynamicProviderTest, throwsWithNonObjectValues) {
|
||||
folly::dynamic values = folly::dynamic("string");
|
||||
|
||||
try {
|
||||
auto provider =
|
||||
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(values);
|
||||
FAIL()
|
||||
<< "Expected ReactNativeFeatureFlagsDynamicProvider constructor to throw an exception";
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EXPECT_STREQ(
|
||||
"ReactNativeFeatureFlagsDynamicProvider: values must be an object",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+6
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<4808c1455f8e17c42036055dd6a81d7d>>
|
||||
* @generated SignedSource<<4055a9b5e34ff6740a99d4e08853fe7d>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -189,6 +189,11 @@ bool NativeReactNativeFeatureFlags::excludeYogaFromRawProps(
|
||||
return ReactNativeFeatureFlags::excludeYogaFromRawProps();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::fixDifferentiatorEmittingUpdatesWithWrongParentTag(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::fixDifferentiatorEmittingUpdatesWithWrongParentTag();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<6b4909879b76908792d89e3f24c1453a>>
|
||||
* @generated SignedSource<<9541abe6da92d991557ca6d2f1e36a9a>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -95,6 +95,8 @@ class NativeReactNativeFeatureFlags
|
||||
|
||||
bool excludeYogaFromRawProps(jsi::Runtime& runtime);
|
||||
|
||||
bool fixDifferentiatorEmittingUpdatesWithWrongParentTag(jsi::Runtime& runtime);
|
||||
|
||||
bool fixMappingOfEventPrioritiesBetweenFabricAndReact(jsi::Runtime& runtime);
|
||||
|
||||
bool fixMountingCoordinatorReportedPendingTransactionsOnAndroid(jsi::Runtime& runtime);
|
||||
|
||||
+5
-8
@@ -93,14 +93,11 @@ NativeIntersectionObserver::convertToNativeModuleEntry(
|
||||
entry.rootRect.origin.y,
|
||||
entry.rootRect.size.width,
|
||||
entry.rootRect.size.height};
|
||||
std::optional<RectAsTuple> intersectionRect;
|
||||
if (entry.intersectionRect) {
|
||||
intersectionRect = {
|
||||
entry.intersectionRect.value().origin.x,
|
||||
entry.intersectionRect.value().origin.y,
|
||||
entry.intersectionRect.value().size.width,
|
||||
entry.intersectionRect.value().size.height};
|
||||
}
|
||||
RectAsTuple intersectionRect = {
|
||||
entry.intersectionRect.origin.x,
|
||||
entry.intersectionRect.origin.y,
|
||||
entry.intersectionRect.size.width,
|
||||
entry.intersectionRect.size.height};
|
||||
|
||||
NativeIntersectionObserverEntry nativeModuleEntry = {
|
||||
entry.intersectionObserverId,
|
||||
|
||||
-29
@@ -144,35 +144,6 @@ std::tuple<double, double> NativePerformance::measureWithResult(
|
||||
return std::tuple{entry.startTime, entry.duration};
|
||||
}
|
||||
|
||||
void NativePerformance::mark(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
double startTime) {
|
||||
auto [trackName, eventName] = parseTrackName(name);
|
||||
ReactPerfLogger::mark(eventName, startTime, trackName);
|
||||
|
||||
PerformanceEntryReporter::getInstance()->reportMark(name, startTime);
|
||||
}
|
||||
|
||||
void NativePerformance::measure(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
double startTime,
|
||||
double endTime,
|
||||
std::optional<double> duration,
|
||||
std::optional<std::string> startMark,
|
||||
std::optional<std::string> endMark) {
|
||||
auto [trackName, eventName] = parseTrackName(name);
|
||||
|
||||
// TODO T190600850 support startMark/endMark
|
||||
if (!startMark && !endMark) {
|
||||
ReactPerfLogger::measure(eventName, startTime, endTime, trackName);
|
||||
}
|
||||
|
||||
PerformanceEntryReporter::getInstance()->reportMeasure(
|
||||
eventName, startTime, endTime, duration, startMark, endMark);
|
||||
}
|
||||
|
||||
void NativePerformance::clearMarks(
|
||||
jsi::Runtime& /*rt*/,
|
||||
std::optional<std::string> entryName) {
|
||||
|
||||
-15
@@ -69,21 +69,6 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
|
||||
|
||||
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
|
||||
|
||||
// https://w3c.github.io/user-timing/#mark-method
|
||||
// TODO delete when `markWithResult` is fully rolled out
|
||||
void mark(jsi::Runtime& rt, std::string name, double startTime);
|
||||
|
||||
// https://w3c.github.io/user-timing/#measure-method
|
||||
// TODO delete when `measureWithResult` is fully rolled out
|
||||
void measure(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
double startTime,
|
||||
double endTime,
|
||||
std::optional<double> duration,
|
||||
std::optional<std::string> startMark,
|
||||
std::optional<std::string> endMark);
|
||||
|
||||
// https://w3c.github.io/user-timing/#mark-method
|
||||
double markWithResult(
|
||||
jsi::Runtime& rt,
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ void LayoutAnimationDriver::animationMutationsForFrame(
|
||||
|
||||
// Create the mutation instruction
|
||||
mutationsList.emplace_back(ShadowViewMutation::UpdateMutation(
|
||||
keyframe.viewPrev, mutatedShadowView, keyframe.parentView));
|
||||
keyframe.viewPrev, mutatedShadowView, keyframe.parentTag));
|
||||
|
||||
PrintMutationInstruction("Animation Progress:", mutationsList.back());
|
||||
|
||||
|
||||
+25
-19
@@ -13,7 +13,7 @@
|
||||
|
||||
#include <react/debug/flags.h>
|
||||
#include <react/debug/react_native_assert.h>
|
||||
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <react/renderer/animations/conversions.h>
|
||||
#include <react/renderer/animations/utils.h>
|
||||
#include <react/renderer/components/image/ImageProps.h>
|
||||
@@ -392,6 +392,14 @@ LayoutAnimationKeyFrameManager::pullTransaction(
|
||||
if (keyframe.type == AnimationConfigurationType::Update &&
|
||||
mutation.newChildShadowView.tag > 0) {
|
||||
keyframe.viewPrev = mutation.newChildShadowView;
|
||||
if (ReactNativeFeatureFlags::
|
||||
fixDifferentiatorEmittingUpdatesWithWrongParentTag()) {
|
||||
keyframe.parentTag = mutation.parentTag;
|
||||
react_native_assert(
|
||||
keyframe.finalMutationsForKeyFrame.size() == 1);
|
||||
keyframe.finalMutationsForKeyFrame[0].parentTag =
|
||||
mutation.parentTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,7 +426,7 @@ LayoutAnimationKeyFrameManager::pullTransaction(
|
||||
mutation.oldChildShadowView.tag > 0) {
|
||||
executeMutationImmediately =
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
mutation.parentShadowView,
|
||||
mutation.parentTag,
|
||||
keyframe.viewPrev,
|
||||
mutation.index);
|
||||
}
|
||||
@@ -441,9 +449,9 @@ LayoutAnimationKeyFrameManager::pullTransaction(
|
||||
? mutation.newChildShadowView
|
||||
: viewStart);
|
||||
react_native_assert(viewFinal.tag > 0);
|
||||
ShadowView parent = mutation.parentShadowView;
|
||||
Tag parentTag = mutation.parentTag;
|
||||
react_native_assert(
|
||||
parent.tag > 0 ||
|
||||
parentTag > 0 ||
|
||||
mutation.type == ShadowViewMutation::Type::Update ||
|
||||
mutation.type == ShadowViewMutation::Type::Delete);
|
||||
Tag tag = viewStart.tag;
|
||||
@@ -499,7 +507,7 @@ LayoutAnimationKeyFrameManager::pullTransaction(
|
||||
/* .finalMutationsForKeyFrame = */ {},
|
||||
/* .type = */ AnimationConfigurationType::Create,
|
||||
/* .tag = */ tag,
|
||||
/* .parentView = */ parent,
|
||||
/* .parentTag = */ parentTag,
|
||||
/* .viewStart = */ viewStart,
|
||||
/* .viewEnd = */ viewFinal,
|
||||
/* .viewPrev = */ baselineShadowView,
|
||||
@@ -537,7 +545,7 @@ LayoutAnimationKeyFrameManager::pullTransaction(
|
||||
/* .finalMutationsForKeyFrame = */ {mutation},
|
||||
/* .type = */ AnimationConfigurationType::Update,
|
||||
/* .tag = */ tag,
|
||||
/* .parentView = */ parent,
|
||||
/* .parentTag = */ parentTag,
|
||||
/* .viewStart = */ viewStart,
|
||||
/* .viewEnd = */ viewFinal,
|
||||
/* .viewPrev = */ baselineShadowView,
|
||||
@@ -623,7 +631,7 @@ LayoutAnimationKeyFrameManager::pullTransaction(
|
||||
/* .finalMutationsForKeyFrame */ {mutation, deleteMutation},
|
||||
/* .type */ AnimationConfigurationType::Delete,
|
||||
/* .tag */ tag,
|
||||
/* .parentView */ parent,
|
||||
/* .parentTag */ parentTag,
|
||||
/* .viewStart */ viewStart,
|
||||
/* .viewEnd */ viewFinal,
|
||||
/* .viewPrev */ baselineShadowView,
|
||||
@@ -1176,19 +1184,17 @@ void LayoutAnimationKeyFrameManager::queueFinalMutationsForCompletedKeyFrame(
|
||||
break;
|
||||
case ShadowViewMutation::Type::Insert:
|
||||
mutationsList.push_back(ShadowViewMutation::InsertMutation(
|
||||
finalMutation.parentShadowView,
|
||||
finalMutation.parentTag,
|
||||
finalMutation.newChildShadowView,
|
||||
finalMutation.index));
|
||||
break;
|
||||
case ShadowViewMutation::Type::Remove:
|
||||
mutationsList.push_back(ShadowViewMutation::RemoveMutation(
|
||||
finalMutation.parentShadowView, prev, finalMutation.index));
|
||||
finalMutation.parentTag, prev, finalMutation.index));
|
||||
break;
|
||||
case ShadowViewMutation::Type::Update:
|
||||
mutationsList.push_back(ShadowViewMutation::UpdateMutation(
|
||||
prev,
|
||||
finalMutation.newChildShadowView,
|
||||
finalMutation.parentShadowView));
|
||||
prev, finalMutation.newChildShadowView, finalMutation.parentTag));
|
||||
break;
|
||||
}
|
||||
if (finalMutation.newChildShadowView.tag > 0) {
|
||||
@@ -1213,7 +1219,7 @@ void LayoutAnimationKeyFrameManager::queueFinalMutationsForCompletedKeyFrame(
|
||||
auto mutatedShadowView =
|
||||
createInterpolatedShadowView(1, keyframe.viewStart, keyframe.viewEnd);
|
||||
auto generatedPenultimateMutation = ShadowViewMutation::UpdateMutation(
|
||||
keyframe.viewPrev, mutatedShadowView, keyframe.parentView);
|
||||
keyframe.viewPrev, mutatedShadowView, keyframe.parentTag);
|
||||
react_native_assert(
|
||||
generatedPenultimateMutation.oldChildShadowView.tag > 0);
|
||||
react_native_assert(
|
||||
@@ -1224,7 +1230,7 @@ void LayoutAnimationKeyFrameManager::queueFinalMutationsForCompletedKeyFrame(
|
||||
mutationsList.push_back(generatedPenultimateMutation);
|
||||
|
||||
auto generatedMutation = ShadowViewMutation::UpdateMutation(
|
||||
mutatedShadowView, keyframe.viewEnd, keyframe.parentView);
|
||||
mutatedShadowView, keyframe.viewEnd, keyframe.parentTag);
|
||||
react_native_assert(generatedMutation.oldChildShadowView.tag > 0);
|
||||
react_native_assert(generatedMutation.newChildShadowView.tag > 0);
|
||||
PrintMutationInstruction(
|
||||
@@ -1233,7 +1239,7 @@ void LayoutAnimationKeyFrameManager::queueFinalMutationsForCompletedKeyFrame(
|
||||
mutationsList.push_back(generatedMutation);
|
||||
} else {
|
||||
auto mutation = ShadowViewMutation::UpdateMutation(
|
||||
keyframe.viewPrev, keyframe.viewEnd, keyframe.parentView);
|
||||
keyframe.viewPrev, keyframe.viewEnd, keyframe.parentTag);
|
||||
PrintMutationInstruction(
|
||||
logPrefix +
|
||||
"Animation Complete: Queuing up Final Synthetic Mutation:",
|
||||
@@ -1292,7 +1298,7 @@ void LayoutAnimationKeyFrameManager::
|
||||
|
||||
// Detect if they're in the same view hierarchy, but not equivalent
|
||||
// We've already detected direct conflicts and removed them.
|
||||
if (animatedKeyFrame.parentView.tag != mutation.parentShadowView.tag) {
|
||||
if (animatedKeyFrame.parentTag != mutation.parentTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1397,7 +1403,7 @@ void LayoutAnimationKeyFrameManager::adjustDelayedMutationIndicesForMutation(
|
||||
|
||||
// Detect if they're in the same view hierarchy, but not equivalent
|
||||
// (We've already detected direct conflicts and handled them above)
|
||||
if (animatedKeyFrame.parentView.tag != mutation.parentShadowView.tag) {
|
||||
if (animatedKeyFrame.parentTag != mutation.parentTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1511,8 +1517,8 @@ void LayoutAnimationKeyFrameManager::getAndEraseConflictingAnimations(
|
||||
// we need to force deletion/removal to happen immediately.
|
||||
bool conflicting = animatedKeyFrame.tag == baselineTag ||
|
||||
(mutationIsCreateOrDelete &&
|
||||
animatedKeyFrame.parentView.tag == baselineTag &&
|
||||
animatedKeyFrame.parentView.tag != 0);
|
||||
animatedKeyFrame.parentTag == baselineTag &&
|
||||
animatedKeyFrame.parentTag != 0);
|
||||
|
||||
// Conflicting animation detected: if we're mutating a tag under
|
||||
// animation, or deleting the parent of a tag under animation, or
|
||||
|
||||
@@ -70,7 +70,7 @@ struct AnimationKeyFrame {
|
||||
// Tag representing the node being animated.
|
||||
Tag tag;
|
||||
|
||||
ShadowView parentView;
|
||||
Tag parentTag;
|
||||
|
||||
// ShadowView representing the start and end points of this animation.
|
||||
ShadowView viewStart;
|
||||
|
||||
@@ -20,8 +20,7 @@ static inline bool shouldFirstComeBeforeSecondRemovesOnly(
|
||||
// come first.
|
||||
return (lhs.type == ShadowViewMutation::Type::Remove &&
|
||||
lhs.type == rhs.type) &&
|
||||
(lhs.parentShadowView.tag == rhs.parentShadowView.tag) &&
|
||||
(lhs.index > rhs.index);
|
||||
(lhs.parentTag == rhs.parentTag) && (lhs.index > rhs.index);
|
||||
}
|
||||
|
||||
static inline void handleShouldFirstComeBeforeSecondRemovesOnly(
|
||||
@@ -31,7 +30,7 @@ static inline void handleShouldFirstComeBeforeSecondRemovesOnly(
|
||||
ShadowViewMutation::List finalList;
|
||||
for (auto& mutation : list) {
|
||||
if (mutation.type == ShadowViewMutation::Type::Remove) {
|
||||
auto key = std::to_string(mutation.parentShadowView.tag);
|
||||
auto key = std::to_string(mutation.parentTag);
|
||||
removeMutationsByTag[key].push_back(mutation);
|
||||
} else {
|
||||
finalList.push_back(mutation);
|
||||
@@ -104,7 +103,7 @@ static inline bool shouldFirstComeBeforeSecondMutation(
|
||||
// Make sure that removes on the same level are sorted - highest indices
|
||||
// must come first.
|
||||
if (lhs.type == ShadowViewMutation::Type::Remove &&
|
||||
lhs.parentShadowView.tag == rhs.parentShadowView.tag) {
|
||||
lhs.parentTag == rhs.parentTag) {
|
||||
if (lhs.index > rhs.index) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
+20
-1
@@ -8,12 +8,31 @@
|
||||
#include "ImageComponentDescriptor.h"
|
||||
#include <react/renderer/imagemanager/ImageManager.h>
|
||||
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<facebook::react::ImageManager> getImageManager(
|
||||
std::shared_ptr<const facebook::react::ContextContainer>&
|
||||
contextContainer) {
|
||||
if (auto imageManager =
|
||||
contextContainer
|
||||
->find<std::shared_ptr<facebook::react::ImageManager>>(
|
||||
facebook::react::ImageManagerKey);
|
||||
imageManager.has_value()) {
|
||||
return imageManager.value();
|
||||
}
|
||||
return std::make_shared<facebook::react::ImageManager>(contextContainer);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
extern const char ImageManagerKey[] = "ImageManager";
|
||||
|
||||
ImageComponentDescriptor::ImageComponentDescriptor(
|
||||
const ComponentDescriptorParameters& parameters)
|
||||
: ConcreteComponentDescriptor(parameters),
|
||||
imageManager_(std::make_shared<ImageManager>(contextContainer_)){};
|
||||
imageManager_(getImageManager(contextContainer_)){};
|
||||
|
||||
void ImageComponentDescriptor::adopt(ShadowNode& shadowNode) const {
|
||||
ConcreteComponentDescriptor::adopt(shadowNode);
|
||||
|
||||
+2
@@ -14,6 +14,8 @@ namespace facebook::react {
|
||||
|
||||
class ImageManager;
|
||||
|
||||
extern const char ImageManagerKey[];
|
||||
|
||||
/*
|
||||
* Descriptor for <Image> component.
|
||||
*/
|
||||
|
||||
@@ -32,9 +32,11 @@ target_include_directories(react_render_imagemanager
|
||||
|
||||
target_link_libraries(react_render_imagemanager
|
||||
folly_runtime
|
||||
mapbufferjni
|
||||
react_debug
|
||||
react_render_core
|
||||
react_render_debug
|
||||
react_render_graphics
|
||||
react_render_mounting
|
||||
reactnativejni
|
||||
yoga)
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "ImageFetcher.h"
|
||||
#include <react/renderer/imagemanager/conversions.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
ImageRequest ImageFetcher::requestImage(
|
||||
const ImageSource& imageSource,
|
||||
const ImageRequestParams& imageRequestParams,
|
||||
SurfaceId surfaceId,
|
||||
Tag tag) const {
|
||||
auto fabricUIManager_ =
|
||||
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
|
||||
static auto requestImage =
|
||||
fabricUIManager_->getClass()
|
||||
->getMethod<void(
|
||||
std::string, SurfaceId, Tag, JReadableMapBuffer::javaobject)>(
|
||||
"experimental_prefetchResource");
|
||||
|
||||
auto serializedImageRequest =
|
||||
serializeImageRequest(imageSource, imageRequestParams);
|
||||
|
||||
auto readableMapBuffer =
|
||||
JReadableMapBuffer::createWithContents(std::move(serializedImageRequest));
|
||||
|
||||
requestImage(
|
||||
fabricUIManager_,
|
||||
"RCTImageView",
|
||||
surfaceId,
|
||||
tag,
|
||||
readableMapBuffer.get());
|
||||
|
||||
auto telemetry = std::make_shared<ImageTelemetry>(surfaceId);
|
||||
|
||||
return {imageSource, telemetry};
|
||||
}
|
||||
} // namespace facebook::react
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
|
||||
#include <react/common/mapbuffer/JReadableMapBuffer.h>
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
#include <react/renderer/imagemanager/ImageRequest.h>
|
||||
#include <react/renderer/imagemanager/ImageRequestParams.h>
|
||||
#include <react/utils/ContextContainer.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ImageFetcher {
|
||||
public:
|
||||
ImageFetcher(ContextContainer::Shared contextContainer)
|
||||
: contextContainer_(std::move(contextContainer)) {}
|
||||
|
||||
ImageRequest requestImage(
|
||||
const ImageSource& imageSource,
|
||||
const ImageRequestParams& imageRequestParams,
|
||||
SurfaceId surfaceId,
|
||||
Tag tag) const;
|
||||
|
||||
private:
|
||||
ContextContainer::Shared contextContainer_;
|
||||
};
|
||||
} // namespace facebook::react
|
||||
+19
-11
@@ -7,16 +7,18 @@
|
||||
|
||||
#include "ImageManager.h"
|
||||
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include "ImageFetcher.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
ImageManager::ImageManager(
|
||||
const ContextContainer::Shared& /*contextContainer*/) {
|
||||
// Silence unused-private-field warning.
|
||||
(void)self_;
|
||||
// Not implemented.
|
||||
}
|
||||
ImageManager::ImageManager(const ContextContainer::Shared& contextContainer)
|
||||
: self_(new ImageFetcher(contextContainer)) {}
|
||||
|
||||
ImageManager::~ImageManager() = default;
|
||||
ImageManager::~ImageManager() {
|
||||
// @lint-ignore CLANGTIDY cppcoreguidelines-no-malloc
|
||||
free(self_);
|
||||
}
|
||||
|
||||
ImageRequest ImageManager::requestImage(
|
||||
const ImageSource& imageSource,
|
||||
@@ -26,10 +28,16 @@ ImageRequest ImageManager::requestImage(
|
||||
|
||||
ImageRequest ImageManager::requestImage(
|
||||
const ImageSource& imageSource,
|
||||
SurfaceId /*surfaceId*/,
|
||||
const ImageRequestParams& /*imageRequestParams*/,
|
||||
Tag /* tag */) const {
|
||||
return {imageSource, nullptr, {}};
|
||||
SurfaceId surfaceId,
|
||||
const ImageRequestParams& imageRequestParams,
|
||||
Tag tag) const {
|
||||
if (ReactNativeFeatureFlags::enableImagePrefetchingAndroid()) {
|
||||
// @lint-ignore CLANGTIDY cppcoreguidelines-pro-type-cstyle-cast
|
||||
return ((ImageFetcher*)self_)
|
||||
->requestImage(imageSource, imageRequestParams, surfaceId, tag);
|
||||
} else {
|
||||
return {imageSource, nullptr, {}};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -50,6 +50,33 @@ bool ShadowViewNodePair::operator!=(const ShadowViewNodePair& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_LOGS_DIFFER
|
||||
static std::ostream& operator<<(
|
||||
std::ostream& out,
|
||||
const ShadowViewNodePair& pair) {
|
||||
out << pair.shadowView.tag;
|
||||
if (!pair.isConcreteView) {
|
||||
out << '\'';
|
||||
}
|
||||
if (pair.flattened) {
|
||||
out << '*';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(
|
||||
std::ostream& out,
|
||||
std::vector<ShadowViewNodePair*> vec) {
|
||||
for (int i = 0; i < vec.size(); i++) {
|
||||
if (i > 0) {
|
||||
out << ", ";
|
||||
}
|
||||
out << *vec[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Extremely simple and naive implementation of a map.
|
||||
* The map is simple but it's optimized for particular constraints that we have
|
||||
@@ -182,6 +209,21 @@ class TinyMap final {
|
||||
size_t erasedAtFront_{0};
|
||||
};
|
||||
|
||||
#ifdef DEBUG_LOGS_DIFFER
|
||||
template <typename KeyT, typename ValueT>
|
||||
static std::ostream& operator<<(std::ostream& out, TinyMap<KeyT, ValueT>& map) {
|
||||
auto it = map.begin();
|
||||
if (it != map.end()) {
|
||||
out << *it->second;
|
||||
++it;
|
||||
}
|
||||
for (; it != map.end(); ++it) {
|
||||
out << ", " << *it->second;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Sorting comparator for `reorderInPlaceIfNeeded`.
|
||||
*/
|
||||
@@ -365,7 +407,7 @@ static_assert(
|
||||
static void calculateShadowViewMutations(
|
||||
ViewNodePairScope& scope,
|
||||
ShadowViewMutation::List& mutations,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
std::vector<ShadowViewNodePair*>&& oldChildPairs,
|
||||
std::vector<ShadowViewNodePair*>&& newChildPairs);
|
||||
|
||||
@@ -384,7 +426,7 @@ static void updateMatchedPairSubtrees(
|
||||
OrderedMutationInstructionContainer& mutationContainer,
|
||||
TinyMap<Tag, ShadowViewNodePair*>& newRemainingPairs,
|
||||
std::vector<ShadowViewNodePair*>& oldChildPairs,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
const ShadowViewNodePair& oldPair,
|
||||
const ShadowViewNodePair& newPair);
|
||||
|
||||
@@ -392,7 +434,7 @@ static void updateMatchedPair(
|
||||
OrderedMutationInstructionContainer& mutationContainer,
|
||||
bool oldNodeFoundInOrder,
|
||||
bool newNodeFoundInOrder,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
const ShadowViewNodePair& oldPair,
|
||||
const ShadowViewNodePair& newPair);
|
||||
|
||||
@@ -400,9 +442,10 @@ static void calculateShadowViewMutationsFlattener(
|
||||
ViewNodePairScope& scope,
|
||||
ReparentMode reparentMode,
|
||||
OrderedMutationInstructionContainer& mutationContainer,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
TinyMap<Tag, ShadowViewNodePair*>& unvisitedOtherNodes,
|
||||
const ShadowViewNodePair& node,
|
||||
Tag parentTagForUpdate,
|
||||
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherNewNodes = nullptr,
|
||||
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherOldNodes = nullptr);
|
||||
|
||||
@@ -419,7 +462,7 @@ static void updateMatchedPairSubtrees(
|
||||
OrderedMutationInstructionContainer& mutationContainer,
|
||||
TinyMap<Tag, ShadowViewNodePair*>& newRemainingPairs,
|
||||
std::vector<ShadowViewNodePair*>& oldChildPairs,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
const ShadowViewNodePair& oldPair,
|
||||
const ShadowViewNodePair& newPair) {
|
||||
// Are we flattening or unflattening either one? If node was
|
||||
@@ -431,11 +474,10 @@ static void updateMatchedPairSubtrees(
|
||||
// We are either flattening or unflattening this node.
|
||||
if (oldPair.flattened != newPair.flattened) {
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR)
|
||||
<< "Differ: flattening or unflattening in updateMatchedPairSubtrees: ["
|
||||
<< oldPair.shadowView.tag << "] [" << newPair.shadowView.tag << "] "
|
||||
<< oldPair.flattened << " " << newPair.flattened << " with parent: ["
|
||||
<< parentShadowView.tag << "]";
|
||||
LOG(ERROR) << "Differ: "
|
||||
<< (newPair.flattened ? "flattening" : "unflattening")
|
||||
<< " in updateMatchedPairSubtrees: " << oldPair << " and "
|
||||
<< newPair << " with parent [" << parentTag << "]";
|
||||
});
|
||||
|
||||
// Flattening
|
||||
@@ -448,9 +490,10 @@ static void updateMatchedPairSubtrees(
|
||||
scope,
|
||||
ReparentMode::Flatten,
|
||||
mutationContainer,
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
newRemainingPairs,
|
||||
oldPair);
|
||||
oldPair,
|
||||
oldPair.shadowView.tag);
|
||||
}
|
||||
// Unflattening
|
||||
else {
|
||||
@@ -478,9 +521,10 @@ static void updateMatchedPairSubtrees(
|
||||
scope,
|
||||
ReparentMode::Unflatten,
|
||||
mutationContainer,
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
unvisitedOldChildPairs,
|
||||
newPair);
|
||||
newPair,
|
||||
parentTag);
|
||||
|
||||
// If old nodes were not visited, we know that we can delete
|
||||
// them now. They will be removed from the hierarchy by the
|
||||
@@ -519,7 +563,7 @@ static void updateMatchedPairSubtrees(
|
||||
*(newGrandChildPairsSize != 0u
|
||||
? &mutationContainer.downwardMutations
|
||||
: &mutationContainer.destructiveDownwardMutations),
|
||||
oldPair.shadowView,
|
||||
oldPair.shadowView.tag,
|
||||
std::move(oldGrandChildPairs),
|
||||
std::move(newGrandChildPairs));
|
||||
}
|
||||
@@ -537,7 +581,7 @@ static void updateMatchedPair(
|
||||
OrderedMutationInstructionContainer& mutationContainer,
|
||||
bool oldNodeFoundInOrder,
|
||||
bool newNodeFoundInOrder,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
const ShadowViewNodePair& oldPair,
|
||||
const ShadowViewNodePair& newPair) {
|
||||
oldPair.otherTreePair = &newPair;
|
||||
@@ -550,7 +594,7 @@ static void updateMatchedPair(
|
||||
if (newNodeFoundInOrder) {
|
||||
mutationContainer.insertMutations.push_back(
|
||||
ShadowViewMutation::InsertMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
newPair.shadowView,
|
||||
static_cast<int>(newPair.mountIndex)));
|
||||
}
|
||||
@@ -560,7 +604,7 @@ static void updateMatchedPair(
|
||||
if (oldNodeFoundInOrder) {
|
||||
mutationContainer.removeMutations.push_back(
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
oldPair.shadowView,
|
||||
static_cast<int>(oldPair.mountIndex)));
|
||||
}
|
||||
@@ -573,7 +617,7 @@ static void updateMatchedPair(
|
||||
if (oldNodeFoundInOrder && !newNodeFoundInOrder) {
|
||||
mutationContainer.removeMutations.push_back(
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
newPair.shadowView,
|
||||
static_cast<int>(oldPair.mountIndex)));
|
||||
}
|
||||
@@ -584,7 +628,7 @@ static void updateMatchedPair(
|
||||
if (oldPair.shadowView != newPair.shadowView) {
|
||||
mutationContainer.updateMutations.push_back(
|
||||
ShadowViewMutation::UpdateMutation(
|
||||
oldPair.shadowView, newPair.shadowView, parentShadowView));
|
||||
oldPair.shadowView, newPair.shadowView, parentTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -621,52 +665,32 @@ static void updateMatchedPair(
|
||||
* performed in the subtree. If it *is* in the map, it means the node is not
|
||||
* in the Tree, and should be Deleted/Created **after this function is
|
||||
* called**, by the caller.
|
||||
*
|
||||
* @param parentTag parent under which nodes should be mounted/unmounted
|
||||
* @param parentTagForUpdate current parent in which node is mounted,
|
||||
* used for update mutations
|
||||
*/
|
||||
static void calculateShadowViewMutationsFlattener(
|
||||
ViewNodePairScope& scope,
|
||||
ReparentMode reparentMode,
|
||||
OrderedMutationInstructionContainer& mutationContainer,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
TinyMap<Tag, ShadowViewNodePair*>& unvisitedOtherNodes,
|
||||
const ShadowViewNodePair& node,
|
||||
Tag parentTagForUpdate,
|
||||
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherNewNodes,
|
||||
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherOldNodes) {
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Flattener 1: "
|
||||
<< (reparentMode == ReparentMode::Unflatten ? "Unflattening"
|
||||
: "Flattening")
|
||||
<< " [" << node.shadowView.tag << "]";
|
||||
});
|
||||
|
||||
// Step 1: iterate through entire tree
|
||||
std::vector<ShadowViewNodePair*> treeChildren =
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(node, scope);
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Flattener 1.4: "
|
||||
LOG(ERROR) << "Differ Flattener: "
|
||||
<< (reparentMode == ReparentMode::Unflatten ? "Unflattening"
|
||||
: "Flattening")
|
||||
<< " [" << node.shadowView.tag << "]";
|
||||
LOG(ERROR) << "Differ Flattener Entry: Child Pairs: ";
|
||||
std::string strTreeChildPairs;
|
||||
for (size_t k = 0; k < treeChildren.size(); k++) {
|
||||
strTreeChildPairs.append(std::to_string(treeChildren[k]->shadowView.tag));
|
||||
strTreeChildPairs.append(treeChildren[k]->isConcreteView ? "" : "'");
|
||||
strTreeChildPairs.append(treeChildren[k]->flattened ? "*" : "");
|
||||
strTreeChildPairs.append(", ");
|
||||
}
|
||||
std::string strListChildPairs;
|
||||
for (auto& unvisitedNode : unvisitedOtherNodes) {
|
||||
strListChildPairs.append(
|
||||
std::to_string(unvisitedNode.second->shadowView.tag));
|
||||
strListChildPairs.append(unvisitedNode.second->isConcreteView ? "" : "'");
|
||||
strListChildPairs.append(unvisitedNode.second->flattened ? "*" : "");
|
||||
strListChildPairs.append(", ");
|
||||
}
|
||||
LOG(ERROR) << "Differ Flattener Entry: Tree Child Pairs: "
|
||||
<< strTreeChildPairs;
|
||||
LOG(ERROR) << "Differ Flattener Entry: List Child Pairs: "
|
||||
<< strListChildPairs;
|
||||
LOG(ERROR) << "> Tree Child Pairs: " << treeChildren;
|
||||
LOG(ERROR) << "> List Child Pairs: " << unvisitedOtherNodes;
|
||||
});
|
||||
|
||||
// Views in other tree that are visited by sub-flattening or
|
||||
@@ -777,13 +801,13 @@ static void calculateShadowViewMutationsFlattener(
|
||||
treeChildPair.otherTreePair->isConcreteView) {
|
||||
mutationContainer.removeMutations.push_back(
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
node.shadowView,
|
||||
node.shadowView.tag,
|
||||
treeChildPair.otherTreePair->shadowView,
|
||||
static_cast<int>(treeChildPair.mountIndex)));
|
||||
} else {
|
||||
mutationContainer.removeMutations.push_back(
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
node.shadowView,
|
||||
node.shadowView.tag,
|
||||
treeChildPair.shadowView,
|
||||
static_cast<int>(treeChildPair.mountIndex)));
|
||||
}
|
||||
@@ -792,7 +816,7 @@ static void calculateShadowViewMutationsFlattener(
|
||||
// we can safely insert it without checking in the other tree
|
||||
mutationContainer.insertMutations.push_back(
|
||||
ShadowViewMutation::InsertMutation(
|
||||
node.shadowView,
|
||||
node.shadowView.tag,
|
||||
treeChildPair.shadowView,
|
||||
static_cast<int>(treeChildPair.mountIndex)));
|
||||
}
|
||||
@@ -838,11 +862,18 @@ static void calculateShadowViewMutationsFlattener(
|
||||
// ShadowNode.
|
||||
if (newTreeNodePair.shadowView != oldTreeNodePair.shadowView &&
|
||||
newTreeNodePair.isConcreteView && oldTreeNodePair.isConcreteView) {
|
||||
// We execute updates before creates, so pass the current parent in when
|
||||
// unflattening.
|
||||
// TODO: whenever we insert, we already update the relevant properties,
|
||||
// so this update is redundant. We should remove this.
|
||||
mutationContainer.updateMutations.push_back(
|
||||
ShadowViewMutation::UpdateMutation(
|
||||
oldTreeNodePair.shadowView,
|
||||
newTreeNodePair.shadowView,
|
||||
node.shadowView));
|
||||
ReactNativeFeatureFlags::
|
||||
fixDifferentiatorEmittingUpdatesWithWrongParentTag()
|
||||
? parentTagForUpdate
|
||||
: node.shadowView.tag));
|
||||
}
|
||||
|
||||
// Update children if appropriate.
|
||||
@@ -852,7 +883,7 @@ static void calculateShadowViewMutationsFlattener(
|
||||
calculateShadowViewMutations(
|
||||
innerScope,
|
||||
mutationContainer.downwardMutations,
|
||||
newTreeNodePair.shadowView,
|
||||
newTreeNodePair.shadowView.tag,
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
oldTreeNodePair, innerScope),
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
@@ -873,10 +904,13 @@ static void calculateShadowViewMutationsFlattener(
|
||||
childReparentMode,
|
||||
mutationContainer,
|
||||
(reparentMode == ReparentMode::Flatten
|
||||
? parentShadowView
|
||||
: newTreeNodePair.shadowView),
|
||||
? parentTag
|
||||
: newTreeNodePair.shadowView.tag),
|
||||
unvisitedOtherNodes,
|
||||
treeChildPair,
|
||||
(reparentMode == ReparentMode::Flatten
|
||||
? oldTreeNodePair.shadowView.tag
|
||||
: parentTag),
|
||||
subVisitedNewMap,
|
||||
subVisitedOldMap);
|
||||
} else {
|
||||
@@ -914,10 +948,13 @@ static void calculateShadowViewMutationsFlattener(
|
||||
ReparentMode::Flatten,
|
||||
mutationContainer,
|
||||
(reparentMode == ReparentMode::Flatten
|
||||
? parentShadowView
|
||||
: newTreeNodePair.shadowView),
|
||||
? parentTag
|
||||
: newTreeNodePair.shadowView.tag),
|
||||
unvisitedRecursiveChildPairs,
|
||||
oldTreeNodePair,
|
||||
(reparentMode == ReparentMode::Flatten
|
||||
? oldTreeNodePair.shadowView.tag
|
||||
: parentTag),
|
||||
subVisitedNewMap,
|
||||
subVisitedOldMap);
|
||||
}
|
||||
@@ -929,10 +966,13 @@ static void calculateShadowViewMutationsFlattener(
|
||||
ReparentMode::Unflatten,
|
||||
mutationContainer,
|
||||
(reparentMode == ReparentMode::Flatten
|
||||
? parentShadowView
|
||||
: newTreeNodePair.shadowView),
|
||||
? parentTag
|
||||
: newTreeNodePair.shadowView.tag),
|
||||
unvisitedRecursiveChildPairs,
|
||||
newTreeNodePair,
|
||||
(reparentMode == ReparentMode::Flatten
|
||||
? oldTreeNodePair.shadowView.tag
|
||||
: parentTag),
|
||||
subVisitedNewMap,
|
||||
subVisitedOldMap);
|
||||
|
||||
@@ -1028,7 +1068,7 @@ static void calculateShadowViewMutationsFlattener(
|
||||
calculateShadowViewMutations(
|
||||
innerScope,
|
||||
mutationContainer.destructiveDownwardMutations,
|
||||
treeChildPair.shadowView,
|
||||
treeChildPair.shadowView.tag,
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
treeChildPair, innerScope),
|
||||
{});
|
||||
@@ -1042,7 +1082,7 @@ static void calculateShadowViewMutationsFlattener(
|
||||
calculateShadowViewMutations(
|
||||
innerScope,
|
||||
mutationContainer.downwardMutations,
|
||||
treeChildPair.shadowView,
|
||||
treeChildPair.shadowView.tag,
|
||||
{},
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
treeChildPair, innerScope));
|
||||
@@ -1054,7 +1094,7 @@ static void calculateShadowViewMutationsFlattener(
|
||||
static void calculateShadowViewMutations(
|
||||
ViewNodePairScope& scope,
|
||||
ShadowViewMutation::List& mutations,
|
||||
const ShadowView& parentShadowView,
|
||||
Tag parentTag,
|
||||
std::vector<ShadowViewNodePair*>&& oldChildPairs,
|
||||
std::vector<ShadowViewNodePair*>&& newChildPairs) {
|
||||
if (oldChildPairs.empty() && newChildPairs.empty()) {
|
||||
@@ -1067,28 +1107,9 @@ static void calculateShadowViewMutations(
|
||||
auto mutationContainer = OrderedMutationInstructionContainer{};
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Entry: Child Pairs of node: [" << parentShadowView.tag
|
||||
<< "]";
|
||||
std::string strOldChildPairs;
|
||||
for (size_t oldIndex = 0; oldIndex < oldChildPairs.size(); oldIndex++) {
|
||||
strOldChildPairs.append(
|
||||
std::to_string(oldChildPairs[oldIndex]->shadowView.tag));
|
||||
strOldChildPairs.append(
|
||||
oldChildPairs[oldIndex]->isConcreteView ? "" : "'");
|
||||
strOldChildPairs.append(oldChildPairs[oldIndex]->flattened ? "*" : "");
|
||||
strOldChildPairs.append(", ");
|
||||
}
|
||||
std::string strNewChildPairs;
|
||||
for (size_t newIndex = 0; newIndex < newChildPairs.size(); newIndex++) {
|
||||
strNewChildPairs.append(
|
||||
std::to_string(newChildPairs[newIndex]->shadowView.tag));
|
||||
strNewChildPairs.append(
|
||||
newChildPairs[newIndex]->isConcreteView ? "" : "'");
|
||||
strNewChildPairs.append(newChildPairs[newIndex]->flattened ? "*" : "");
|
||||
strNewChildPairs.append(", ");
|
||||
}
|
||||
LOG(ERROR) << "Differ Entry: Old Child Pairs: " << strOldChildPairs;
|
||||
LOG(ERROR) << "Differ Entry: New Child Pairs: " << strNewChildPairs;
|
||||
LOG(ERROR) << "Differ Entry: Child Pairs of node: [" << parentTag << "]";
|
||||
LOG(ERROR) << "> Old Child Pairs: " << oldChildPairs;
|
||||
LOG(ERROR) << "> New Child Pairs: " << newChildPairs;
|
||||
});
|
||||
|
||||
// Stage 1: Collecting `Update` mutations
|
||||
@@ -1102,7 +1123,7 @@ static void calculateShadowViewMutations(
|
||||
LOG(ERROR) << "Differ Branch 1.1: Tags Different: ["
|
||||
<< oldChildPair.shadowView.tag << "] ["
|
||||
<< newChildPair.shadowView.tag << "]" << " with parent: ["
|
||||
<< parentShadowView.tag << "]";
|
||||
<< parentTag << "]";
|
||||
});
|
||||
|
||||
// Totally different nodes, updating is impossible.
|
||||
@@ -1117,23 +1138,16 @@ static void calculateShadowViewMutations(
|
||||
}
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Branch 1.2: Same tags, update and recurse: ["
|
||||
<< oldChildPair.shadowView.tag << "]"
|
||||
<< (oldChildPair.flattened ? " (flattened)" : "")
|
||||
<< (oldChildPair.isConcreteView ? " (concrete)" : "") << "["
|
||||
<< newChildPair.shadowView.tag << "]"
|
||||
<< (newChildPair.flattened ? " (flattened)" : "")
|
||||
<< (newChildPair.isConcreteView ? " (concrete)" : "")
|
||||
<< " with parent: [" << parentShadowView.tag << "]";
|
||||
LOG(ERROR) << "Differ Branch 1.2: Same tags, update and recurse: "
|
||||
<< oldChildPair << " and " << newChildPair << " with parent: ["
|
||||
<< parentTag << "]";
|
||||
});
|
||||
|
||||
if (newChildPair.isConcreteView &&
|
||||
oldChildPair.shadowView != newChildPair.shadowView) {
|
||||
mutationContainer.updateMutations.push_back(
|
||||
ShadowViewMutation::UpdateMutation(
|
||||
oldChildPair.shadowView,
|
||||
newChildPair.shadowView,
|
||||
parentShadowView));
|
||||
oldChildPair.shadowView, newChildPair.shadowView, parentTag));
|
||||
}
|
||||
|
||||
// Recursively update tree if ShadowNode pointers are not equal
|
||||
@@ -1150,7 +1164,7 @@ static void calculateShadowViewMutations(
|
||||
*(newGrandChildPairsSize != 0u
|
||||
? &mutationContainer.downwardMutations
|
||||
: &mutationContainer.destructiveDownwardMutations),
|
||||
oldChildPair.shadowView,
|
||||
oldChildPair.shadowView.tag,
|
||||
std::move(oldGrandChildPairs),
|
||||
std::move(newGrandChildPairs));
|
||||
}
|
||||
@@ -1165,9 +1179,8 @@ static void calculateShadowViewMutations(
|
||||
const auto& oldChildPair = *oldChildPairs[index];
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Branch 2: Deleting Tag/Tree: ["
|
||||
<< oldChildPair.shadowView.tag << "]" << " with parent: ["
|
||||
<< parentShadowView.tag << "]";
|
||||
LOG(ERROR) << "Differ Branch 2: Deleting Tag/Tree: " << oldChildPair
|
||||
<< " with parent: [" << parentTag << "]";
|
||||
});
|
||||
|
||||
if (!oldChildPair.isConcreteView) {
|
||||
@@ -1178,7 +1191,7 @@ static void calculateShadowViewMutations(
|
||||
ShadowViewMutation::DeleteMutation(oldChildPair.shadowView));
|
||||
mutationContainer.removeMutations.push_back(
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
oldChildPair.shadowView,
|
||||
static_cast<int>(oldChildPair.mountIndex)));
|
||||
|
||||
@@ -1188,7 +1201,7 @@ static void calculateShadowViewMutations(
|
||||
calculateShadowViewMutations(
|
||||
innerScope,
|
||||
mutationContainer.destructiveDownwardMutations,
|
||||
oldChildPair.shadowView,
|
||||
oldChildPair.shadowView.tag,
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
oldChildPair, innerScope),
|
||||
{});
|
||||
@@ -1200,9 +1213,8 @@ static void calculateShadowViewMutations(
|
||||
const auto& newChildPair = *newChildPairs[index];
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Branch 3: Creating Tag/Tree: ["
|
||||
<< newChildPair.shadowView.tag << "]" << " with parent: ["
|
||||
<< parentShadowView.tag << "]";
|
||||
LOG(ERROR) << "Differ Branch 3: Creating Tag/Tree: " << newChildPair
|
||||
<< " with parent: [" << parentTag << "]";
|
||||
});
|
||||
|
||||
if (!newChildPair.isConcreteView) {
|
||||
@@ -1211,7 +1223,7 @@ static void calculateShadowViewMutations(
|
||||
|
||||
mutationContainer.insertMutations.push_back(
|
||||
ShadowViewMutation::InsertMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
newChildPair.shadowView,
|
||||
static_cast<int>(newChildPair.mountIndex)));
|
||||
mutationContainer.createMutations.push_back(
|
||||
@@ -1221,7 +1233,7 @@ static void calculateShadowViewMutations(
|
||||
calculateShadowViewMutations(
|
||||
innerScope,
|
||||
mutationContainer.downwardMutations,
|
||||
newChildPair.shadowView,
|
||||
newChildPair.shadowView.tag,
|
||||
{},
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
newChildPair, innerScope));
|
||||
@@ -1257,22 +1269,17 @@ static void calculateShadowViewMutations(
|
||||
|
||||
if (newTag == oldTag) {
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Branch 5: Matched Tags at indices: "
|
||||
<< oldIndex << " " << newIndex << ": ["
|
||||
<< oldChildPair.shadowView.tag << "]"
|
||||
<< (oldChildPair.flattened ? "(flattened)" : "")
|
||||
<< (oldChildPair.isConcreteView ? "(concrete)" : "")
|
||||
<< " [" << newChildPair.shadowView.tag << "]"
|
||||
<< (newChildPair.flattened ? "(flattened)" : "")
|
||||
<< (newChildPair.isConcreteView ? "(concrete)" : "")
|
||||
<< " with parent: [" << parentShadowView.tag << "]";
|
||||
LOG(ERROR) << "Differ Branch 4: Matched Tags at indices: "
|
||||
<< oldIndex << " and " << newIndex << ": "
|
||||
<< oldChildPair << " and " << newChildPair
|
||||
<< " with parent: [" << parentTag << "]";
|
||||
});
|
||||
|
||||
updateMatchedPair(
|
||||
mutationContainer,
|
||||
true,
|
||||
true,
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
oldChildPair,
|
||||
newChildPair);
|
||||
|
||||
@@ -1281,7 +1288,7 @@ static void calculateShadowViewMutations(
|
||||
mutationContainer,
|
||||
newRemainingPairs,
|
||||
oldChildPairs,
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
oldChildPair,
|
||||
newChildPair);
|
||||
|
||||
@@ -1306,11 +1313,18 @@ static void calculateShadowViewMutations(
|
||||
if (insertedIt != newInsertedPairs.end()) {
|
||||
const auto& newChildPair = *insertedIt->second;
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Branch 5: Founded reordered tags at indices: "
|
||||
<< oldIndex << ": " << oldChildPair << " and "
|
||||
<< newChildPair << " with parent: ["
|
||||
<< parentShadowView.tag << "]";
|
||||
});
|
||||
|
||||
updateMatchedPair(
|
||||
mutationContainer,
|
||||
true,
|
||||
false,
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
oldChildPair,
|
||||
newChildPair);
|
||||
|
||||
@@ -1319,7 +1333,7 @@ static void calculateShadowViewMutations(
|
||||
mutationContainer,
|
||||
newRemainingPairs,
|
||||
oldChildPairs,
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
oldChildPair,
|
||||
newChildPair);
|
||||
|
||||
@@ -1345,13 +1359,10 @@ static void calculateShadowViewMutations(
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR)
|
||||
<< "Differ Branch 9: Removing tag that was not reinserted: "
|
||||
<< oldIndex << ": [" << oldChildPair.shadowView.tag << "]"
|
||||
<< (oldChildPair.flattened ? " (flattened)" : "")
|
||||
<< (oldChildPair.isConcreteView ? " (concrete)" : "")
|
||||
<< " with parent: [" << parentShadowView.tag << "] "
|
||||
<< "node is in other tree? "
|
||||
<< (oldChildPair.inOtherTree() ? "yes" : "no");
|
||||
<< "Differ Branch 6: Removing tag that was not re-inserted: "
|
||||
<< oldChildPair << " with parent: [" << parentTag
|
||||
<< "], which is " << (oldChildPair.inOtherTree() ? "" : "not ")
|
||||
<< "in other tree";
|
||||
});
|
||||
|
||||
// Edge case: node is not found in `newRemainingPairs`, due to
|
||||
@@ -1373,7 +1384,7 @@ static void calculateShadowViewMutations(
|
||||
// hierarchy.
|
||||
mutationContainer.removeMutations.push_back(
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
otherTreeView,
|
||||
static_cast<int>(oldChildPair.mountIndex)));
|
||||
continue;
|
||||
@@ -1381,7 +1392,7 @@ static void calculateShadowViewMutations(
|
||||
|
||||
mutationContainer.removeMutations.push_back(
|
||||
ShadowViewMutation::RemoveMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
oldChildPair.shadowView,
|
||||
static_cast<int>(oldChildPair.mountIndex)));
|
||||
|
||||
@@ -1398,17 +1409,14 @@ static void calculateShadowViewMutations(
|
||||
auto& newChildPair = *newChildPairs[newIndex];
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR)
|
||||
<< "Differ Branch 10: Inserting tag/tree that was not (yet?) removed from hierarchy: "
|
||||
<< newIndex << "/" << newSize << ": ["
|
||||
<< newChildPair.shadowView.tag << "]"
|
||||
<< (newChildPair.flattened ? " (flattened)" : "")
|
||||
<< (newChildPair.isConcreteView ? " (concrete)" : "")
|
||||
<< " with parent: [" << parentShadowView.tag << "]";
|
||||
<< "Differ Branch 7: Inserting tag/tree that was not (yet?) removed from hierarchy: "
|
||||
<< newChildPair << " @ " << newIndex << "/" << newSize
|
||||
<< " with parent: [" << parentTag << "]";
|
||||
});
|
||||
if (newChildPair.isConcreteView) {
|
||||
mutationContainer.insertMutations.push_back(
|
||||
ShadowViewMutation::InsertMutation(
|
||||
parentShadowView,
|
||||
parentTag,
|
||||
newChildPair.shadowView,
|
||||
static_cast<int>(newChildPair.mountIndex)));
|
||||
}
|
||||
@@ -1438,12 +1446,10 @@ static void calculateShadowViewMutations(
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR)
|
||||
<< "Differ Branch 11: Deleting tag/tree that was not in new hierarchy: "
|
||||
<< "[" << oldChildPair.shadowView.tag << "]"
|
||||
<< (oldChildPair.flattened ? "(flattened)" : "")
|
||||
<< (oldChildPair.isConcreteView ? "(concrete)" : "")
|
||||
<< "Differ Branch 8: Deleting tag/tree that was not in new hierarchy: "
|
||||
<< oldChildPair
|
||||
<< (oldChildPair.inOtherTree() ? "(in other tree)" : "")
|
||||
<< " with parent: [" << parentShadowView.tag << "] ##"
|
||||
<< " with parent: [" << parentTag << "] ##"
|
||||
<< std::hash<ShadowView>{}(oldChildPair.shadowView);
|
||||
});
|
||||
|
||||
@@ -1458,7 +1464,7 @@ static void calculateShadowViewMutations(
|
||||
calculateShadowViewMutations(
|
||||
innerScope,
|
||||
mutationContainer.destructiveDownwardMutations,
|
||||
oldChildPair.shadowView,
|
||||
oldChildPair.shadowView.tag,
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
oldChildPair, innerScope),
|
||||
{});
|
||||
@@ -1480,12 +1486,10 @@ static void calculateShadowViewMutations(
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR)
|
||||
<< "Differ Branch 12: Inserting tag/tree that was not in old hierarchy: "
|
||||
<< "[" << newChildPair.shadowView.tag << "]"
|
||||
<< (newChildPair.flattened ? "(flattened)" : "")
|
||||
<< (newChildPair.isConcreteView ? "(concrete)" : "")
|
||||
<< "Differ Branch 9: Inserting tag/tree that was not in old hierarchy: "
|
||||
<< newChildPair
|
||||
<< (newChildPair.inOtherTree() ? "(in other tree)" : "")
|
||||
<< " with parent: [" << parentShadowView.tag << "]";
|
||||
<< " with parent: [" << parentTag << "]";
|
||||
});
|
||||
|
||||
if (!newChildPair.isConcreteView) {
|
||||
@@ -1502,7 +1506,7 @@ static void calculateShadowViewMutations(
|
||||
calculateShadowViewMutations(
|
||||
innerScope,
|
||||
mutationContainer.downwardMutations,
|
||||
newChildPair.shadowView,
|
||||
newChildPair.shadowView.tag,
|
||||
{},
|
||||
sliceChildShadowNodeViewPairsFromViewNodePair(
|
||||
newChildPair, innerScope));
|
||||
@@ -1567,7 +1571,7 @@ ShadowViewMutation::List calculateShadowViewMutations(
|
||||
calculateShadowViewMutations(
|
||||
innerViewNodePairScope,
|
||||
mutations,
|
||||
ShadowView(oldRootShadowNode),
|
||||
oldRootShadowNode.getTag(),
|
||||
sliceChildShadowNodeViewPairs(
|
||||
ShadowViewNodePair{.shadowNode = &oldRootShadowNode},
|
||||
viewNodePairScope),
|
||||
@@ -1575,6 +1579,38 @@ ShadowViewMutation::List calculateShadowViewMutations(
|
||||
ShadowViewNodePair{.shadowNode = &newRootShadowNode},
|
||||
viewNodePairScope));
|
||||
|
||||
DEBUG_LOGS({
|
||||
LOG(ERROR) << "Differ Completed: " << mutations.size() << " mutations";
|
||||
for (size_t i = 0; i < mutations.size(); i++) {
|
||||
auto& mutation = mutations[i];
|
||||
switch (mutation.type) {
|
||||
case ShadowViewMutation::Type::Create:
|
||||
LOG(ERROR) << "[" << i << "] CREATE "
|
||||
<< mutation.newChildShadowView.tag;
|
||||
break;
|
||||
case ShadowViewMutation::Type::Delete:
|
||||
LOG(ERROR) << "[" << i << "] DELETE "
|
||||
<< mutation.oldChildShadowView.tag;
|
||||
break;
|
||||
case ShadowViewMutation::Type::Insert:
|
||||
LOG(ERROR) << "[" << i << "] INSERT "
|
||||
<< mutation.newChildShadowView.tag << " INTO "
|
||||
<< mutation.parentTag << " @ " << mutation.index;
|
||||
break;
|
||||
case ShadowViewMutation::Type::Remove:
|
||||
LOG(ERROR) << "[" << i << "] REMOVE "
|
||||
<< mutation.oldChildShadowView.tag << " FROM "
|
||||
<< mutation.parentTag << " @ " << mutation.index;
|
||||
break;
|
||||
case ShadowViewMutation::Type::Update:
|
||||
LOG(ERROR) << "[" << i << "] UPDATE "
|
||||
<< mutation.newChildShadowView.tag << " IN "
|
||||
<< mutation.parentTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return mutations;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace facebook::react {
|
||||
ShadowViewMutation ShadowViewMutation::CreateMutation(ShadowView shadowView) {
|
||||
return {
|
||||
/* .type = */ Create,
|
||||
/* .parentShadowView = */ {},
|
||||
/* .parentTag = */ -1,
|
||||
/* .oldChildShadowView = */ {},
|
||||
/* .newChildShadowView = */ std::move(shadowView),
|
||||
/* .index = */ -1,
|
||||
@@ -24,7 +24,7 @@ ShadowViewMutation ShadowViewMutation::CreateMutation(ShadowView shadowView) {
|
||||
ShadowViewMutation ShadowViewMutation::DeleteMutation(ShadowView shadowView) {
|
||||
return {
|
||||
/* .type = */ Delete,
|
||||
/* .parentShadowView = */ {},
|
||||
/* .parentTag = */ -1,
|
||||
/* .oldChildShadowView = */ std::move(shadowView),
|
||||
/* .newChildShadowView = */ {},
|
||||
/* .index = */ -1,
|
||||
@@ -32,12 +32,12 @@ ShadowViewMutation ShadowViewMutation::DeleteMutation(ShadowView shadowView) {
|
||||
}
|
||||
|
||||
ShadowViewMutation ShadowViewMutation::InsertMutation(
|
||||
ShadowView parentShadowView,
|
||||
Tag parentTag,
|
||||
ShadowView childShadowView,
|
||||
int index) {
|
||||
return {
|
||||
/* .type = */ Insert,
|
||||
/* .parentShadowView = */ std::move(parentShadowView),
|
||||
/* .parentTag = */ parentTag,
|
||||
/* .oldChildShadowView = */ {},
|
||||
/* .newChildShadowView = */ std::move(childShadowView),
|
||||
/* .index = */ index,
|
||||
@@ -45,12 +45,12 @@ ShadowViewMutation ShadowViewMutation::InsertMutation(
|
||||
}
|
||||
|
||||
ShadowViewMutation ShadowViewMutation::RemoveMutation(
|
||||
ShadowView parentShadowView,
|
||||
Tag parentTag,
|
||||
ShadowView childShadowView,
|
||||
int index) {
|
||||
return {
|
||||
/* .type = */ Remove,
|
||||
/* .parentShadowView = */ std::move(parentShadowView),
|
||||
/* .parentTag = */ parentTag,
|
||||
/* .oldChildShadowView = */ std::move(childShadowView),
|
||||
/* .newChildShadowView = */ {},
|
||||
/* .index = */ index,
|
||||
@@ -60,10 +60,10 @@ ShadowViewMutation ShadowViewMutation::RemoveMutation(
|
||||
ShadowViewMutation ShadowViewMutation::UpdateMutation(
|
||||
ShadowView oldChildShadowView,
|
||||
ShadowView newChildShadowView,
|
||||
ShadowView parentShadowView) {
|
||||
Tag parentTag) {
|
||||
return {
|
||||
/* .type = */ Update,
|
||||
/* .parentShadowView = */ std::move(parentShadowView),
|
||||
/* .parentTag = */ parentTag,
|
||||
/* .oldChildShadowView = */ std::move(oldChildShadowView),
|
||||
/* .newChildShadowView = */ std::move(newChildShadowView),
|
||||
/* .index = */ -1,
|
||||
@@ -88,12 +88,12 @@ bool ShadowViewMutation::mutatedViewIsVirtual() const {
|
||||
|
||||
ShadowViewMutation::ShadowViewMutation(
|
||||
Type type,
|
||||
ShadowView parentShadowView,
|
||||
Tag parentTag,
|
||||
ShadowView oldChildShadowView,
|
||||
ShadowView newChildShadowView,
|
||||
int index)
|
||||
: type(type),
|
||||
parentShadowView(std::move(parentShadowView)),
|
||||
parentTag(parentTag),
|
||||
oldChildShadowView(std::move(oldChildShadowView)),
|
||||
newChildShadowView(std::move(newChildShadowView)),
|
||||
index(index) {}
|
||||
@@ -131,10 +131,10 @@ std::vector<DebugStringConvertibleObject> getDebugProps(
|
||||
mutation.newChildShadowView,
|
||||
options)}
|
||||
: DebugStringConvertibleObject{},
|
||||
mutation.parentShadowView.componentHandle != 0
|
||||
mutation.parentTag != -1
|
||||
? DebugStringConvertibleObject{"parent",
|
||||
getDebugDescription(
|
||||
mutation.parentShadowView,
|
||||
mutation.parentTag,
|
||||
options)}
|
||||
: DebugStringConvertibleObject{},
|
||||
mutation.index != -1
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/*
|
||||
/**
|
||||
* Describes a single native view tree mutation which may contain
|
||||
* pointers to an old shadow view, a new shadow view, a parent shadow view and
|
||||
* final index of inserted or updated view.
|
||||
@@ -26,43 +26,73 @@ struct ShadowViewMutation final {
|
||||
|
||||
#pragma mark - Designated Initializers
|
||||
|
||||
/*
|
||||
/**
|
||||
* Creates and returns an `Create` mutation.
|
||||
*/
|
||||
static ShadowViewMutation CreateMutation(ShadowView shadowView);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Creates and returns an `Delete` mutation.
|
||||
*/
|
||||
static ShadowViewMutation DeleteMutation(ShadowView shadowView);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Creates and returns an `Insert` mutation.
|
||||
*/
|
||||
static ShadowViewMutation InsertMutation(
|
||||
ShadowView parentShadowView,
|
||||
ShadowView childShadowView,
|
||||
int index);
|
||||
static ShadowViewMutation
|
||||
InsertMutation(Tag parentTag, ShadowView childShadowView, int index);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Creates and returns an `Insert` mutation.
|
||||
* @deprecated Pass parentTag instead of parentShadowView.
|
||||
*/
|
||||
static ShadowViewMutation InsertMutation(
|
||||
const ShadowView& parentShadowView,
|
||||
ShadowView childShadowView,
|
||||
int index) {
|
||||
return InsertMutation(parentShadowView.tag, childShadowView, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a `Remove` mutation.
|
||||
*/
|
||||
static ShadowViewMutation RemoveMutation(
|
||||
ShadowView parentShadowView,
|
||||
ShadowView childShadowView,
|
||||
int index);
|
||||
static ShadowViewMutation
|
||||
RemoveMutation(Tag parentTag, ShadowView childShadowView, int index);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Creates and returns a `Remove` mutation.
|
||||
* @deprecated Pass parentTag instead of parentShadowView.
|
||||
*/
|
||||
static ShadowViewMutation RemoveMutation(
|
||||
const ShadowView& parentShadowView,
|
||||
ShadowView childShadowView,
|
||||
int index) {
|
||||
return RemoveMutation(parentShadowView.tag, childShadowView, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an `Update` mutation.
|
||||
*/
|
||||
static ShadowViewMutation UpdateMutation(
|
||||
ShadowView oldChildShadowView,
|
||||
ShadowView newChildShadowView,
|
||||
ShadowView parentShadowView);
|
||||
Tag parentTag);
|
||||
|
||||
/**
|
||||
* Creates and returns an `Update` mutation.
|
||||
* @deprecated Pass parentTag instead of parentShadowView.
|
||||
*/
|
||||
static ShadowViewMutation UpdateMutation(
|
||||
ShadowView oldChildShadowView,
|
||||
ShadowView newChildShadowView,
|
||||
const ShadowView& parentShadowView) {
|
||||
return UpdateMutation(
|
||||
oldChildShadowView, newChildShadowView, parentShadowView.tag);
|
||||
}
|
||||
|
||||
#pragma mark - Type
|
||||
|
||||
enum Type {
|
||||
enum Type : std::uint8_t {
|
||||
Create = 1,
|
||||
Delete = 2,
|
||||
Insert = 4,
|
||||
@@ -73,7 +103,7 @@ struct ShadowViewMutation final {
|
||||
#pragma mark - Fields
|
||||
|
||||
Type type = {Create};
|
||||
ShadowView parentShadowView = {};
|
||||
Tag parentTag = -1;
|
||||
ShadowView oldChildShadowView = {};
|
||||
ShadowView newChildShadowView = {};
|
||||
int index = -1;
|
||||
@@ -88,7 +118,7 @@ struct ShadowViewMutation final {
|
||||
private:
|
||||
ShadowViewMutation(
|
||||
Type type,
|
||||
ShadowView parentShadowView,
|
||||
Tag parentTag,
|
||||
ShadowView oldChildShadowView,
|
||||
ShadowView newChildShadowView,
|
||||
int index);
|
||||
|
||||
@@ -42,7 +42,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) {
|
||||
for (const auto& mutation : mutations) {
|
||||
switch (mutation.type) {
|
||||
case ShadowViewMutation::Create: {
|
||||
react_native_assert(mutation.parentShadowView == ShadowView{});
|
||||
react_native_assert(mutation.parentTag == -1);
|
||||
react_native_assert(mutation.oldChildShadowView == ShadowView{});
|
||||
react_native_assert(mutation.newChildShadowView.props);
|
||||
auto stubView = std::make_shared<StubView>();
|
||||
@@ -69,7 +69,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) {
|
||||
<< "] ##"
|
||||
<< std::hash<ShadowView>{}(mutation.oldChildShadowView);
|
||||
});
|
||||
react_native_assert(mutation.parentShadowView == ShadowView{});
|
||||
react_native_assert(mutation.parentTag == -1);
|
||||
react_native_assert(mutation.newChildShadowView == ShadowView{});
|
||||
auto tag = mutation.oldChildShadowView.tag;
|
||||
react_native_assert(hasTag(tag));
|
||||
@@ -98,7 +98,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) {
|
||||
case ShadowViewMutation::Insert: {
|
||||
if (!mutation.mutatedViewIsVirtual()) {
|
||||
react_native_assert(mutation.oldChildShadowView == ShadowView{});
|
||||
auto parentTag = mutation.parentShadowView.tag;
|
||||
auto parentTag = mutation.parentTag;
|
||||
auto childTag = mutation.newChildShadowView.tag;
|
||||
if (!hasTag(parentTag)) {
|
||||
LOG(ERROR)
|
||||
@@ -140,7 +140,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) {
|
||||
case ShadowViewMutation::Remove: {
|
||||
if (!mutation.mutatedViewIsVirtual()) {
|
||||
react_native_assert(mutation.newChildShadowView == ShadowView{});
|
||||
auto parentTag = mutation.parentShadowView.tag;
|
||||
auto parentTag = mutation.parentTag;
|
||||
auto childTag = mutation.oldChildShadowView.tag;
|
||||
if (!hasTag(parentTag)) {
|
||||
LOG(ERROR)
|
||||
@@ -221,6 +221,10 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) {
|
||||
react_native_assert(hasTag(mutation.newChildShadowView.tag));
|
||||
auto oldStubView = registry_[mutation.newChildShadowView.tag];
|
||||
react_native_assert(oldStubView->tag != 0);
|
||||
if (mutation.parentTag != 0) {
|
||||
react_native_assert(hasTag(mutation.parentTag));
|
||||
react_native_assert(oldStubView->parentTag == mutation.parentTag);
|
||||
}
|
||||
if ((ShadowView)(*oldStubView) != mutation.oldChildShadowView) {
|
||||
LOG(ERROR)
|
||||
<< "StubView: ASSERT FAILURE: UPDATE mutation assertion failure: oldChildShadowView does not match oldStubView: ["
|
||||
|
||||
@@ -55,7 +55,7 @@ static void calculateShadowViewMutationsForNewTree(
|
||||
mutations.push_back(
|
||||
ShadowViewMutation::CreateMutation(newChildPair->shadowView));
|
||||
mutations.push_back(ShadowViewMutation::InsertMutation(
|
||||
parentShadowView,
|
||||
parentShadowView.tag,
|
||||
newChildPair->shadowView,
|
||||
static_cast<int>(newChildPair->mountIndex)));
|
||||
|
||||
|
||||
@@ -558,8 +558,7 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) {
|
||||
EXPECT_EQ(mutations1[0].oldChildShadowView.tag, childG->getTag());
|
||||
EXPECT_EQ(mutations1[1].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations1[1].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
// This is incorrect! ChildH does not exist yet at this point
|
||||
EXPECT_EQ(mutations1[1].parentShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations1[1].parentTag, childG->getTag());
|
||||
EXPECT_EQ(mutations1[2].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations1[2].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations1[3].type, ShadowViewMutation::Create);
|
||||
@@ -574,7 +573,7 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) {
|
||||
EXPECT_EQ(mutations2.size(), 5);
|
||||
EXPECT_EQ(mutations2[0].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations2[0].oldChildShadowView.tag, childG->getTag());
|
||||
EXPECT_EQ(mutations2[0].parentShadowView.tag, emptyRootNode->getTag());
|
||||
EXPECT_EQ(mutations2[0].parentTag, emptyRootNode->getTag());
|
||||
EXPECT_EQ(mutations2[1].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations2[1].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations2[2].type, ShadowViewMutation::Remove);
|
||||
@@ -594,7 +593,7 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) {
|
||||
EXPECT_EQ(mutations3.size(), 15);
|
||||
EXPECT_EQ(mutations3[0].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations3[0].oldChildShadowView.tag, childG->getTag());
|
||||
EXPECT_EQ(mutations3[0].parentShadowView.tag, emptyRootNode->getTag());
|
||||
EXPECT_EQ(mutations3[0].parentTag, emptyRootNode->getTag());
|
||||
EXPECT_EQ(mutations3[1].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations3[1].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations3[2].type, ShadowViewMutation::Create);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user