diff --git a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js index e869e9fa41d5..e9e7c73821a1 100644 --- a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -12,7 +12,9 @@ import type {AnyAttributeType} from '../../Renderer/shims/ReactNativeTypes'; import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; import processAspectRatio from '../../StyleSheet/processAspectRatio'; -import processBackgroundImage from '../../StyleSheet/processBackgroundImage'; +import processBackgroundImage, { + processBackgroundImageAssets, +} from '../../StyleSheet/processBackgroundImage'; import processBackgroundPosition from '../../StyleSheet/processBackgroundPosition'; import processBackgroundRepeat from '../../StyleSheet/processBackgroundRepeat'; import processBackgroundSize from '../../StyleSheet/processBackgroundSize'; @@ -45,7 +47,7 @@ export const boxShadowAttribute: AnyAttributeType = nativeCSSParsing : {process: processBoxShadow}; export const backgroundImageAttribute: AnyAttributeType = nativeCSSParsing - ? true + ? {process: processBackgroundImageAssets} : {process: processBackgroundImage}; export const backgroundSizeAttribute: AnyAttributeType = nativeCSSParsing diff --git a/packages/react-native/Libraries/Components/View/__tests__/View-itest.js b/packages/react-native/Libraries/Components/View/__tests__/View-itest.js index a26f29766aed..bf9148cdf2f0 100644 --- a/packages/react-native/Libraries/Components/View/__tests__/View-itest.js +++ b/packages/react-native/Libraries/Components/View/__tests__/View-itest.js @@ -353,6 +353,31 @@ describe('', () => { }); describe('background-image', () => { + it('resolves a require() asset in the object syntax', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root.getRenderedOutput({props: ['backgroundImage']}).toJSX(), + ).toEqual( + , + ); + }); + it('it parses CSS and object syntax', () => { const root = Fantom.createRoot(); diff --git a/packages/react-native/Libraries/Components/View/__tests__/View-nativeCSSParsing-itest.js b/packages/react-native/Libraries/Components/View/__tests__/View-nativeCSSParsing-itest.js index d82df171b772..1146352c65f0 100644 --- a/packages/react-native/Libraries/Components/View/__tests__/View-nativeCSSParsing-itest.js +++ b/packages/react-native/Libraries/Components/View/__tests__/View-nativeCSSParsing-itest.js @@ -11,6 +11,8 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; +import type {BackgroundImageValue} from '../../../StyleSheet/StyleSheetTypes'; + import * as Fantom from '@react-native/fantom'; import * as React from 'react'; import {View} from 'react-native'; @@ -71,5 +73,66 @@ describe(' native CSS parsing', () => { expect(backgroundImage).toContain('linear-gradient'); expect(backgroundImage).toContain('rgba(230, 100, 101, 1)'); }); + + it('keeps a url layer with an empty uri, like web', () => { + expect( + mountedProp( + {backgroundImage: [{type: 'url', uri: ''}]}, + 'backgroundImage', + ), + ).toBe('[url()]'); + const backgroundImage = mountedProp( + {backgroundImage: 'url(""), linear-gradient(red, blue)'}, + 'backgroundImage', + ); + expect(backgroundImage).toContain('url()'); + expect(backgroundImage).toContain('linear-gradient'); + }); + + it('drops the whole list when a url layer is invalid, like web', () => { + const gradient: BackgroundImageValue = { + type: 'linear-gradient', + direction: 'to bottom', + colorStops: [ + {color: 'red', positions: ['0%']}, + {color: 'blue', positions: ['100%']}, + ], + }; + expect( + mountedProp({backgroundImage: [gradient]}, 'backgroundImage'), + ).toContain('linear-gradient'); + // An asset number that the asset registry does not know stays a number, + // which is not a valid uri. + expect( + mountedProp( + {backgroundImage: [{type: 'url', uri: 999999}, gradient]}, + 'backgroundImage', + ), + ).toBeUndefined(); + expect( + mountedProp( + {backgroundImage: 'url(my photo.png), linear-gradient(red, blue)'}, + 'backgroundImage', + ), + ).toBeUndefined(); + }); + + it('resolves a require() asset in the object syntax', () => { + expect( + mountedProp( + { + backgroundImage: [ + { + type: 'url', + uri: require('../../../Image/__tests__/img/img1.png'), + }, + ], + }, + 'backgroundImage', + ), + ).toBe( + '[url(file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png)]', + ); + }); }); }); diff --git a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js index 0e926394122a..40cb689af914 100644 --- a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js +++ b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js @@ -769,7 +769,16 @@ type RadialGradientValue = { }>, }; -export type BackgroundImageValue = LinearGradientValue | RadialGradientValue; +type URLBackgroundImageValue = { + type: 'url', + uri: string | number, +}; + +export type GradientBackgroundImageValue = + LinearGradientValue | RadialGradientValue; + +export type BackgroundImageValue = + GradientBackgroundImageValue | URLBackgroundImageValue; export type BackgroundSizeValue = { x: string | number, diff --git a/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js b/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js index 5dc05646b27e..8fc19c32df1b 100644 --- a/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js +++ b/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js @@ -363,6 +363,10 @@ describe('processBackgroundImage', () => { {color: 'blue', positions: ['100%']}, ], }, + { + type: 'url', + uri: 'https://example.com', + }, ]; const result = processBackgroundImage(input); expect(result).toEqual([ @@ -374,6 +378,10 @@ describe('processBackgroundImage', () => { {color: processColor('blue'), position: '100%'}, ], }, + { + type: 'url', + uri: 'https://example.com', + }, ]); }); @@ -657,7 +665,11 @@ describe('processBackgroundImage', () => { ], }, ]); - expect(result[0].colorStops[0].color).toEqual({ + const layer = result[0]; + if (layer.type !== 'linear-gradient') { + throw new Error(`Expected a linear-gradient, got ${layer.type}`); + } + expect(layer.colorStops[0].color).toEqual({ semantic: ['systemRedColor'], }); }); @@ -674,7 +686,11 @@ describe('processBackgroundImage', () => { ], }, ]); - expect(result[0].colorStops[0].color).toEqual({ + const layer = result[0]; + if (layer.type !== 'linear-gradient') { + throw new Error(`Expected a linear-gradient, got ${layer.type}`); + } + expect(layer.colorStops[0].color).toEqual({ dynamic: {light: 0xff000000, dark: 0xffffffff}, }); }); @@ -696,7 +712,11 @@ describe('processBackgroundImage', () => { ], }, ]); - expect(result[0].colorStops[0].color).toEqual({ + const layer = result[0]; + if (layer.type !== 'linear-gradient') { + throw new Error(`Expected a linear-gradient, got ${layer.type}`); + } + expect(layer.colorStops[0].color).toEqual({ resource_paths: ['?attr/colorPrimary'], }); }); @@ -1188,4 +1208,193 @@ describe('processBackgroundImage', () => { expect(result1).toEqual([]); expect(result2).toEqual([]); }); + + it('should parse url unquoted', () => { + const result = processBackgroundImage('url(https://example.com/image.png)'); + expect(result).toEqual([ + {type: 'url', uri: 'https://example.com/image.png'}, + ]); + }); + + it('should parse url double quoted', () => { + const result = processBackgroundImage( + 'url("https://example.com/image.png")', + ); + expect(result).toEqual([ + {type: 'url', uri: 'https://example.com/image.png'}, + ]); + }); + + it('should parse url single quoted', () => { + const result = processBackgroundImage( + "url('https://example.com/image.png')", + ); + expect(result).toEqual([ + {type: 'url', uri: 'https://example.com/image.png'}, + ]); + }); + + it('should parse url case insensitive', () => { + const result = processBackgroundImage('UrL(https://example.com/image.png)'); + expect(result).toEqual([ + {type: 'url', uri: 'https://example.com/image.png'}, + ]); + }); + + it('should parse url with query params', () => { + const result = processBackgroundImage( + 'url(https://example.com/image.png?size=Large&format=webp)', + ); + expect(result).toEqual([ + { + type: 'url', + uri: 'https://example.com/image.png?size=Large&format=webp', + }, + ]); + }); + + it('should parse url with whitespace', () => { + const result = processBackgroundImage( + 'url( https://example.com/image.png )', + ); + expect(result).toEqual([ + {type: 'url', uri: 'https://example.com/image.png'}, + ]); + }); + + it('should parse multiple urls', () => { + const result = processBackgroundImage( + 'url(https://example.com/bg1.png), url(https://example.com/bg2.png)', + ); + expect(result).toEqual([ + {type: 'url', uri: 'https://example.com/bg1.png'}, + {type: 'url', uri: 'https://example.com/bg2.png'}, + ]); + }); + + it('should parse url mixed with gradients', () => { + const result = processBackgroundImage( + 'radial-gradient(circle at top left, red, blue), url(https://example.com/image.png), linear-gradient(to bottom, green, yellow)', + ); + expect(result).toEqual([ + { + type: 'radial-gradient', + shape: 'circle', + size: 'farthest-corner', + position: {top: '0%', left: '0%'}, + colorStops: [ + {color: processColor('red'), position: null}, + {color: processColor('blue'), position: null}, + ], + }, + {type: 'url', uri: 'https://example.com/image.png'}, + { + type: 'linear-gradient', + direction: {type: 'angle', value: 180}, + colorStops: [ + {color: processColor('green'), position: null}, + {color: processColor('yellow'), position: null}, + ], + }, + ]); + }); + + // An empty url is valid CSS. It draws nothing, but it stays a layer, so the + // other layers and their background sizes and positions keep their index. + it('should keep an empty url as a layer', () => { + expect(processBackgroundImage('url()')).toEqual([{type: 'url', uri: ''}]); + }); + + it('should keep an empty quoted url as a layer', () => { + expect(processBackgroundImage('url("")')).toEqual([{type: 'url', uri: ''}]); + }); + + it('should keep an empty uri in the object syntax as a layer', () => { + expect(processBackgroundImage([{type: 'url', uri: ''}])).toEqual([ + {type: 'url', uri: ''}, + ]); + }); + + it('should keep the other layers next to an empty url', () => { + const result = processBackgroundImage( + 'url(""), linear-gradient(red, blue)', + ); + expect(result).toHaveLength(2); + expect(result[0]).toEqual({type: 'url', uri: ''}); + expect(result[1].type).toBe('linear-gradient'); + }); + + it('should return empty when one url is invalid', () => { + expect( + processBackgroundImage('url(my photo.png), linear-gradient(red, blue)'), + ).toEqual([]); + }); + + it('should keep parentheses inside a quoted url', () => { + // Parentheses are valid unencoded in urls, and CSS requires quotes for them. + const result = processBackgroundImage( + 'url("https://upload.wikimedia.org/wikipedia/commons/6/69/Surface_of_Mercury_(14433689302).jpg"), linear-gradient(red, blue)', + ); + expect(result).toEqual([ + { + type: 'url', + uri: 'https://upload.wikimedia.org/wikipedia/commons/6/69/Surface_of_Mercury_(14433689302).jpg', + }, + { + type: 'linear-gradient', + direction: {type: 'angle', value: 180}, + colorStops: [ + {color: processColor('red'), position: null}, + {color: processColor('blue'), position: null}, + ], + }, + ]); + }); + + it('should keep the comma in a quoted data uri', () => { + const result = processBackgroundImage( + 'url("data:image/png;base64,iVBORw0KGgo="), url(https://example.com/a.png)', + ); + expect(result).toEqual([ + {type: 'url', uri: 'data:image/png;base64,iVBORw0KGgo='}, + {type: 'url', uri: 'https://example.com/a.png'}, + ]); + }); + + // https://www.w3.org/TR/css-syntax-3/#consume-url-token + for (const [name, backgroundImage] of [ + ['whitespace inside an unquoted url', 'url(my photo.png)'], + [ + 'parentheses inside an unquoted url', + 'url(https://upload.wikimedia.org/wikipedia/commons/6/69/Surface_of_Mercury_(14433689302).jpg)', + ], + ['a quote inside an unquoted url', 'url(a"b.png)'], + ['text after a quoted url', 'url("a.png" x)'], + ['mismatched quotes', 'url("a.png\')'], + ]) { + it(`should return empty for ${name}`, () => { + expect(processBackgroundImage(backgroundImage)).toEqual([]); + }); + } + + it('should parse a url with a missing closing parenthesis', () => { + expect(processBackgroundImage('url(a.png')).toEqual([ + {type: 'url', uri: 'a.png'}, + ]); + }); + + it('should keep a backslash in a quoted url', () => { + expect(processBackgroundImage('url("a\\20b.png")')).toEqual([ + {type: 'url', uri: 'a\\20b.png'}, + ]); + }); + + for (const [name, backgroundImage] of [ + ['an escaped parenthesis', 'url(a\\)b.png)'], + ['an escaped quote in a quoted url', 'url("a\\"b.png")'], + ]) { + it(`should return empty for ${name}`, () => { + expect(processBackgroundImage(backgroundImage)).toEqual([]); + }); + } }); diff --git a/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js b/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js index 23c0b20cc525..7463e2b60d93 100644 --- a/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js +++ b/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js @@ -13,16 +13,19 @@ import type {ProcessedColorValue} from './processColor'; import type { BackgroundImageValue, + GradientBackgroundImageValue, RadialGradientPosition, RadialGradientShape, RadialGradientSize, } from './StyleSheetTypes'; +const resolveAssetSource = require('../Image/resolveAssetSource').default; const processColor = require('./processColor').default; // Pre-compiled regex patterns for performance - avoids regex compilation on each call const NEWLINE_REGEX = /\n/g; const GRADIENT_REGEX = /^(linear|radial)-gradient\(((?:\([^)]*\)|[^()])*)\)/; +const URL_FUNCTION_REGEX = /^url\(/i; const COMMA_SPLIT_REGEX = /,(?![^(]*\))/; const WHITESPACE_SPLIT_REGEX = /\s+/; const COLOR_STOP_PARTS_REGEX = /\S+\([^)]*\)|\S+/g; @@ -70,13 +73,63 @@ type RadialGradientBackgroundImage = { }>, }; +type URLBackgroundImage = { + type: 'url', + uri: string, + // Natural size in layout units, known only for bundled assets. Native falls + // back to the decoded image's own size when these are absent. + intrinsicWidth?: number, + intrinsicHeight?: number, +}; + // null color indicate that the transition hint syntax is used. e.g. red, 20%, blue type ColorStopColor = ProcessedColorValue | null; // percentage or pixel value type ColorStopPosition = number | string | null; type ParsedBackgroundImageValue = - LinearGradientBackgroundImage | RadialGradientBackgroundImage; + | LinearGradientBackgroundImage + | RadialGradientBackgroundImage + | URLBackgroundImage; + +/** + * With native CSS parsing, native code parses `backgroundImage` but cannot + * resolve a `require()` asset, so this resolves only those. + */ +export function processBackgroundImageAssets( + backgroundImage: ?(ReadonlyArray | string), +): ?(ReadonlyArray | string) { + if (!Array.isArray(backgroundImage)) { + return backgroundImage; + } + return backgroundImage.map(bgImage => + bgImage.type === 'url' && typeof bgImage.uri === 'number' + ? (processURLBackgroundImage(bgImage.uri) ?? bgImage) + : bgImage, + ); +} + +function processURLBackgroundImage(uri: number | string): ?URLBackgroundImage { + if (typeof uri === 'string') { + return {type: 'url', uri}; + } + const source = resolveAssetSource(uri); + if (source == null || source.uri == null) { + return null; + } + // The asset registry records these already divided by the asset's scale, so + // they are layout units and need no density maths native side. Matches how + // `Image` derives its default dimensions. The registry reports both + // dimensions or neither. + return typeof source.width === 'number' && typeof source.height === 'number' + ? { + type: 'url', + uri: source.uri, + intrinsicWidth: source.width, + intrinsicHeight: source.height, + } + : {type: 'url', uri: source.uri}; +} export default function processBackgroundImage( backgroundImage: ?(ReadonlyArray | string), @@ -92,6 +145,16 @@ export default function processBackgroundImage( ); } else if (Array.isArray(backgroundImage)) { for (const bgImage of backgroundImage) { + if (bgImage.type === 'url') { + const urlBackgroundImage = processURLBackgroundImage(bgImage.uri); + if (urlBackgroundImage == null) { + // If the URI is invalid, return an empty array. Same as web. + return []; + } + result = result.concat(urlBackgroundImage); + continue; + } + const processedColorStops = processColorStops(bgImage); if (processedColorStops == null) { // If a color stop is invalid, return an empty array and do not apply any gradient. Same as web. @@ -191,7 +254,9 @@ export default function processBackgroundImage( return result; } -function processColorStops(bgImage: BackgroundImageValue): ReadonlyArray<{ +function processColorStops( + bgImage: GradientBackgroundImageValue, +): ReadonlyArray<{ color: ColorStopColor, position: ColorStopPosition, }> | null { @@ -258,10 +323,26 @@ function processColorStops(bgImage: BackgroundImageValue): ReadonlyArray<{ function parseBackgroundImageCSSString( cssString: string, ): ReadonlyArray { - const gradients = []; + const backgroundImages: Array = []; const bgImageStrings = splitGradients(cssString); for (const bgImageString of bgImageStrings) { + if (URL_FUNCTION_REGEX.test(bgImageString)) { + const uri = parseURLFunctionBody(bgImageString.slice('url('.length)); + if (uri == null) { + // An invalid url makes the whole list invalid. Same as web. + return []; + } + // A url() in a css string is not an asset, so its natural size is only + // known once the image is decoded. An empty url stays as a layer that + // draws nothing. Same as web. + backgroundImages.push({ + type: 'url', + uri, + }); + continue; + } + const bgImage = bgImageString.toLowerCase(); const match = GRADIENT_REGEX.exec(bgImage); if (match) { @@ -272,11 +353,92 @@ function parseBackgroundImageCSSString( : parseLinearGradientCSSString(gradientContent); if (gradient != null) { - gradients.push(gradient); + backgroundImages.push(gradient); + } + } + } + return backgroundImages; +} + +const isCSSWhitespace = (char: string): boolean => + char === ' ' || + char === '\t' || + char === '\n' || + char === '\r' || + char === '\f'; + +const isCSSNewline = (char: string): boolean => + char === '\n' || char === '\r' || char === '\f'; + +// https://www.w3.org/TR/css-syntax-3/#non-printable-code-point +const isCSSNonPrintable = (char: string): boolean => { + const code = char.charCodeAt(0); + return ( + code <= 0x08 || + code === 0x0b || + (code >= 0x0e && code <= 0x1f) || + code === 0x7f + ); +}; + +// Parses the text after `url(`. Returns the url, or null for an invalid url. +// The unquoted form follows https://www.w3.org/TR/css-syntax-3/#consume-url-token +// and the quoted form is a string token followed by `)`. +function parseURLFunctionBody(input: string): ?string { + let i = 0; + while (i < input.length && isCSSWhitespace(input[i])) { + i++; + } + + const quote = input[i]; + if (quote === '"' || quote === "'") { + let uri = ''; + i++; + while (true) { + if (i >= input.length || isCSSNewline(input[i])) { + return null; + } + const char = input[i]; + if (char === quote) { + i++; + break; } + uri += char; + i++; + } + while (i < input.length && isCSSWhitespace(input[i])) { + i++; + } + return input[i] === ')' && i === input.length - 1 ? uri : null; + } + + let uri = ''; + while (i < input.length) { + const char = input[i]; + if (char === ')') { + return i === input.length - 1 ? uri : null; + } + if (isCSSWhitespace(char)) { + while (i < input.length && isCSSWhitespace(input[i])) { + i++; + } + return i >= input.length || (input[i] === ')' && i === input.length - 1) + ? uri + : null; + } + if ( + char === '"' || + char === "'" || + char === '(' || + isCSSNonPrintable(char) + ) { + return null; } + uri += char; + i++; } - return gradients; + // A missing `)` at the end is a parse error, but the url is kept. + return uri; } function parseRadialGradientCSSString( diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BackgroundImagePropsConversions.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BackgroundImagePropsConversions.cpp index d72c64a2548b..a4084b91a02e 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BackgroundImagePropsConversions.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BackgroundImagePropsConversions.cpp @@ -25,6 +25,30 @@ namespace facebook::react { using RawValueMap = std::unordered_map; using RawValueList = std::vector; +// Returns nothing for a missing or non-string uri. An empty uri stays as a +// layer that draws nothing, like `url("")` on the web. +static std::optional parseURLBackgroundImage( + const RawValueMap& rawBackgroundImageMap) { + auto uriIt = rawBackgroundImageMap.find("uri"); + if (uriIt == rawBackgroundImageMap.end() || + !uriIt->second.hasType()) { + return std::nullopt; + } + URLBackgroundImage urlBackgroundImage; + urlBackgroundImage.uri = (std::string)(uriIt->second); + auto widthIt = rawBackgroundImageMap.find("intrinsicWidth"); + if (widthIt != rawBackgroundImageMap.end() && + widthIt->second.hasType()) { + urlBackgroundImage.intrinsicWidth = (Float)(widthIt->second); + } + auto heightIt = rawBackgroundImageMap.find("intrinsicHeight"); + if (heightIt != rawBackgroundImageMap.end() && + heightIt->second.hasType()) { + urlBackgroundImage.intrinsicHeight = (Float)(heightIt->second); + } + return urlBackgroundImage; +} + inline GradientKeyword parseGradientKeyword(const std::string& keyword) { if (keyword == "to top right") { return GradientKeyword::ToTopRight; @@ -208,6 +232,13 @@ void parseProcessedBackgroundImage( } backgroundImage.emplace_back(std::move(radialGradient)); + } else if (type == "url") { + auto urlBackgroundImage = parseURLBackgroundImage(rawBackgroundImageMap); + if (!urlBackgroundImage) { + result = {}; + return; + } + backgroundImage.emplace_back(std::move(*urlBackgroundImage)); } } @@ -409,6 +440,13 @@ void parseUnprocessedBackgroundImageList( } backgroundImage.emplace_back(std::move(radialGradient)); + } else if (type == "url") { + auto urlBackgroundImage = parseURLBackgroundImage(rawBackgroundImageMap); + if (!urlBackgroundImage) { + result = {}; + return; + } + backgroundImage.emplace_back(std::move(*urlBackgroundImage)); } } @@ -484,6 +522,13 @@ void fromCSSColorStop( std::optional fromCSSBackgroundImage( const CSSBackgroundImage& cssBackgroundImage) { + if (std::holds_alternative(cssBackgroundImage)) { + const auto& urlFunc = std::get(cssBackgroundImage); + URLBackgroundImage urlBackgroundImage; + urlBackgroundImage.uri = urlFunc.url; + return BackgroundImage{urlBackgroundImage}; + } + if (std::holds_alternative(cssBackgroundImage)) { const auto& gradient = std::get(cssBackgroundImage); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp index 7fe5a6dfdab3..d14e7b5b6e1c 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp @@ -103,6 +103,16 @@ TEST(ViewPropsDiffTest, gradientColorHintEmitsNullColor) { "position", nullptr)))))}}); } +// `require()` assets send their natural size with the uri. +TEST(ViewPropsDiffTest, urlBackgroundImageKeepsIntrinsicSize) { + expectPayloads({{dynamic::object( + "backgroundImage", + dynamic::array( + dynamic::object("type", "url")("uri", "https://example.com/a.png"), + dynamic::object("type", "url")("uri", "asset.png")( + "intrinsicWidth", 100)("intrinsicHeight", 50)))}}); +} + // BaseViewManager.setViewState replaces the whole state: an absent `selected` // deselects the view and an absent `disabled` re-enables it. Props 2.0 sends an // unset `checked` as "none", which Java ignores. diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSBackgroundImage.h b/packages/react-native/ReactCommon/react/renderer/css/CSSBackgroundImage.h index 4f45e0c752c0..78ab70fb9065 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSBackgroundImage.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSBackgroundImage.h @@ -823,11 +823,59 @@ struct CSSDataTypeParser { static_assert(CSSDataType); +struct CSSURLFunction { + std::string url{}; + + bool operator==(const CSSURLFunction &rhs) const = default; +}; + +template <> +struct CSSDataTypeParser { + static auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional + { + if (token.type() != CSSTokenType::Url) { + return {}; + } + + return CSSURLFunction{std::string(token.stringValue())}; + } + + /* + * A quoted url stays an ordinary function block holding a . + */ + static auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSValueParser &parser) + -> std::optional + { + if (!iequals(func.name, "url")) { + return {}; + } + + auto &syntaxParser = parser.syntaxParser(); + syntaxParser.consumeWhitespace(); + + auto url = syntaxParser.consumeComponentValue>( + [](const CSSPreservedToken &token) -> std::optional { + if (token.type() != CSSTokenType::String) { + return {}; + } + return std::string(token.stringValue()); + }); + + if (!url.has_value()) { + return {}; + } + + return CSSURLFunction{*url}; + } +}; + +static_assert(CSSDataType); + /** * Representation of * https://www.w3.org/TR/css-backgrounds-3/#background-image */ -using CSSBackgroundImage = CSSCompoundDataType; +using CSSBackgroundImage = CSSCompoundDataType; /** * Representation of diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSToken.h b/packages/react-native/ReactCommon/react/renderer/css/CSSToken.h index 9c22245b1216..3caa79e5886c 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSToken.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSToken.h @@ -34,6 +34,10 @@ enum class CSSTokenType { Percentage, WhiteSpace, Hash, + String, + BadString, + Url, + BadUrl, }; /* diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h index f8320b03573f..635d95075522 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h @@ -14,6 +14,7 @@ #include #include +#include namespace facebook::react { @@ -41,6 +42,9 @@ class CSSTokenizer { } switch (nextChar) { + case '"': + case '\'': + return consumeStringToken(nextChar); case '(': return consumeCharacter(CSSTokenType::OpenParen); case ')': @@ -187,12 +191,124 @@ class CSSTokenizer { if (peek() == '(') { advance(); consumeRunningValue(); + + // Handle e.g. url(http://a.com/1.png) + if (iequals(ident.stringValue(), "url")) { + while (isWhitespace(peek())) { + advance(); + } + consumeRunningValue(); + + // A quoted body is a inside an ordinary function block. + // Everything else is a + if (peek() != '"' && peek() != '\'') { + return consumeUrlToken(); + } + } + return {CSSTokenType::Function, ident.stringValue()}; } return ident; } + constexpr CSSToken consumeUrlToken() + { + // https://www.w3.org/TR/css-syntax-3/#consume-url-token + while (isWhitespace(peek())) { + advance(); + } + consumeRunningValue(); + + while (true) { + char next = peek(); + + if (next == ')') { + auto value = consumeRunningValue(); + advance(); + consumeRunningValue(); + return {CSSTokenType::Url, value}; + } + + if (next == '\0') { + return {CSSTokenType::Url, consumeRunningValue()}; + } + + if (isWhitespace(next)) { + auto value = consumeRunningValue(); + while (isWhitespace(peek())) { + advance(); + } + consumeRunningValue(); + if (peek() == ')') { + advance(); + consumeRunningValue(); + return {CSSTokenType::Url, value}; + } + if (peek() == '\0') { + return {CSSTokenType::Url, value}; + } + // Whitespace inside an unquoted url is not allowed. + return consumeRemnantsOfBadUrl(); + } + + if (next == '"' || next == '\'' || next == '(' || isNonPrintable(next)) { + return consumeRemnantsOfBadUrl(); + } + + advance(); + } + } + + constexpr CSSToken consumeStringToken(char endingCodePoint) + { + // https://www.w3.org/TR/css-syntax-3/#consume-string-token + advance(); + consumeRunningValue(); + + while (true) { + char next = peek(); + + if (next == endingCodePoint) { + auto value = consumeRunningValue(); + advance(); + consumeRunningValue(); + return {CSSTokenType::String, value}; + } + + if (next == '\0') { + // Parse error, but the string is still returned. + return {CSSTokenType::String, consumeRunningValue()}; + } + + if (isNewline(next)) { + // Reconsume the newline so it tokenizes normally. + consumeRunningValue(); + return CSSToken{CSSTokenType::BadString}; + } + + advance(); + } + } + + constexpr CSSToken consumeRemnantsOfBadUrl() + { + // https://www.w3.org/TR/css-syntax-3/#consume-remnants-of-bad-url + while (true) { + char next = peek(); + if (next == ')') { + advance(); + consumeRunningValue(); + return CSSToken{CSSTokenType::BadUrl}; + } + if (next == '\0') { + consumeRunningValue(); + return CSSToken{CSSTokenType::BadUrl}; + } + advance(); + } + } + constexpr CSSToken consumeIdentSequence() { // https://www.w3.org/TR/css-syntax-3/#consume-an-ident-sequence @@ -246,6 +362,18 @@ class CSSTokenizer { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } + static constexpr bool isNewline(char c) + { + // https://www.w3.org/TR/css-syntax-3/#newline + return c == '\n' || c == '\r' || c == '\f'; + } + + static constexpr bool isNonPrintable(char c) + { + // https://www.w3.org/TR/css-syntax-3/#non-printable-code-point + return (c >= '\0' && c <= '\x08') || c == '\x0B' || (c >= '\x0E' && c <= '\x1F') || c == '\x7F'; + } + std::string_view remainingCharacters_; size_t position_{0}; }; diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSBackgroundImageTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSBackgroundImageTest.cpp index 9851cc87277f..3a3db02065ce 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSBackgroundImageTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSBackgroundImageTest.cpp @@ -557,4 +557,156 @@ TEST_F(CSSBackgroundImageTest, RadialGradientNegativeRadius) { } } +TEST_F(CSSBackgroundImageTest, URLBasicUnquoted) { + auto result = parseCSSProperty( + "url(https://example.com/image.png)"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/image.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLDoubleQuoted) { + auto result = parseCSSProperty( + "url(\"https://example.com/image.png\")"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/image.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLSingleQuoted) { + auto result = parseCSSProperty( + "url('https://example.com/image.png')"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/image.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLCaseInsensitive) { + auto result = parseCSSProperty( + "UrL(https://example.com/image.png)"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/image.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLWithQueryParams) { + auto result = parseCSSProperty( + "url(https://example.com/image.png?size=Large&format=webp)"); + decltype(result) expected = CSSURLFunction{ + .url = "https://example.com/image.png?size=Large&format=webp"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLWithWhitespace) { + auto result = parseCSSProperty( + "url( https://example.com/image.png )"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/image.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, MultipleURLs) { + auto result = parseCSSProperty( + "url(https://example.com/bg1.png), url(https://example.com/bg2.png)"); + decltype(result) expected = CSSBackgroundImageList{ + {CSSURLFunction{.url = "https://example.com/bg1.png"}, + CSSURLFunction{.url = "https://example.com/bg2.png"}}}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLMixedWithGradients) { + auto result = parseCSSProperty( + "radial-gradient(circle at top left, red, blue), url(https://example.com/image.png), linear-gradient(to bottom, green, yellow)"); + decltype(result) expected = CSSBackgroundImageList{ + {CSSRadialGradientFunction{ + .shape = CSSRadialGradientShape::Circle, + .size = CSSRadialGradientSizeKeyword::FarthestCorner, + .position = + CSSRadialGradientPosition{ + .top = CSSPercentage{.value = 0.0f}, + .left = CSSPercentage{.value = 0.0f}}, + .items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}}, + CSSURLFunction{.url = "https://example.com/image.png"}, + CSSLinearGradientFunction{ + .direction = + CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}}, + .items = { + makeCSSColorStop(0, 128, 0), makeCSSColorStop(255, 255, 0)}}}}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLEmpty) { + auto result = parseCSSProperty("url()"); + decltype(result) expected = CSSURLFunction{.url = ""}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLEmptyQuoted) { + auto result = parseCSSProperty("url(\"\")"); + decltype(result) expected = CSSURLFunction{.url = ""}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLWithPort) { + auto result = parseCSSProperty( + "url(http://localhost:8081/assets/bunny.png)"); + decltype(result) expected = + CSSURLFunction{.url = "http://localhost:8081/assets/bunny.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLWithNumericQueryValues) { + auto result = parseCSSProperty( + "url(https://example.com/x.png?w=100&h=200)"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/x.png?w=100&h=200"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLWithNumericPathSegment) { + auto result = parseCSSProperty( + "url(https://example.com/img/2024/photo.png)"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/img/2024/photo.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLWithPercentEncoding) { + auto result = parseCSSProperty( + "url(https://example.com/my%20file.png)"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/my%20file.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLQuotedWithPort) { + auto result = parseCSSProperty( + "url(\"http://localhost:8081/1.png\")"); + decltype(result) expected = + CSSURLFunction{.url = "http://localhost:8081/1.png"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLQuotedWithNumericQueryValues) { + auto result = parseCSSProperty( + "url('https://example.com/x.png?w=100&h=200')"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/x.png?w=100&h=200"}; + ASSERT_EQ(result, expected); +} + +TEST_F(CSSBackgroundImageTest, URLBadUrlIsInvalid) { + // Whitespace inside an unquoted url yields a . + auto result = parseCSSProperty("url(my file.png)"); + ASSERT_TRUE(std::holds_alternative(result)); +} + +TEST_F(CSSBackgroundImageTest, URLWithFragment) { + auto result = parseCSSProperty( + "url(https://example.com/sprite.svg#icon)"); + decltype(result) expected = + CSSURLFunction{.url = "https://example.com/sprite.svg#icon"}; + ASSERT_EQ(result, expected); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp index 33b67e14b32a..463bc5a3dd93 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp @@ -239,4 +239,125 @@ TEST(CSSTokenizer, hash_values) { CSSToken{CSSTokenType::Delim, "*"}, CSSToken{CSSTokenType::EndOfFile}); } + +// https://www.w3.org/TR/css-syntax-3/#consume-string-token + +TEST(CSSTokenizer, string_values) { + EXPECT_TOKENS( + "\"hello\"", + CSSToken{CSSTokenType::String, "hello"}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "'hello'", + CSSToken{CSSTokenType::String, "hello"}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "\"it's\"", + CSSToken{CSSTokenType::String, "it's"}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "\"http://localhost:8081/1.png\"", + CSSToken{CSSTokenType::String, "http://localhost:8081/1.png"}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "\"\"", + CSSToken{CSSTokenType::String, ""}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "\"abc", + CSSToken{CSSTokenType::String, "abc"}, + CSSToken{CSSTokenType::EndOfFile}); +} + +TEST(CSSTokenizer, bad_string_values) { + EXPECT_TOKENS( + "\"abc\ndef\"", + CSSToken{CSSTokenType::BadString}, + CSSToken{CSSTokenType::WhiteSpace}, + CSSToken{CSSTokenType::Ident, "def"}, + CSSToken{CSSTokenType::String, ""}, + CSSToken{CSSTokenType::EndOfFile}); +} + +// https://www.w3.org/TR/css-syntax-3/#consume-url-token +TEST(CSSTokenizer, url_values) { + EXPECT_TOKENS( + "url(a.png)", + CSSToken{CSSTokenType::Url, "a.png"}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "url(http://localhost:8081/1.png)", + CSSToken{CSSTokenType::Url, "http://localhost:8081/1.png"}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "url( a.png )", + CSSToken{CSSTokenType::Url, "a.png"}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "url()", + CSSToken{CSSTokenType::Url, ""}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "URL(a.png)", + CSSToken{CSSTokenType::Url, "a.png"}, + CSSToken{CSSTokenType::EndOfFile}); + + // A quoted body stays a function block holding a . + EXPECT_TOKENS( + "url(\"a.png\")", + CSSToken{CSSTokenType::Function, "url"}, + CSSToken{CSSTokenType::String, "a.png"}, + CSSToken{CSSTokenType::CloseParen}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "url( 'a.png')", + CSSToken{CSSTokenType::Function, "url"}, + CSSToken{CSSTokenType::String, "a.png"}, + CSSToken{CSSTokenType::CloseParen}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "url(a.png", + CSSToken{CSSTokenType::Url, "a.png"}, + CSSToken{CSSTokenType::EndOfFile}); +} + +TEST(CSSTokenizer, bad_url_values) { + EXPECT_TOKENS( + "url(my file.png)", + CSSToken{CSSTokenType::BadUrl}, + CSSToken{CSSTokenType::EndOfFile}); + + // Nor is an unescaped paren or quote. Recovery stops at the first `)`, which + // here is the inner one, so the remainder tokenizes as ordinary values. + EXPECT_TOKENS( + "url(a(1).png)", + CSSToken{CSSTokenType::BadUrl}, + CSSToken{CSSTokenType::Delim, "."}, + CSSToken{CSSTokenType::Ident, "png"}, + CSSToken{CSSTokenType::CloseParen}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "url(a\"b.png)", + CSSToken{CSSTokenType::BadUrl}, + CSSToken{CSSTokenType::EndOfFile}); + + EXPECT_TOKENS( + "url(my file.png) auto", + CSSToken{CSSTokenType::BadUrl}, + CSSToken{CSSTokenType::WhiteSpace}, + CSSToken{CSSTokenType::Ident, "auto"}, + CSSToken{CSSTokenType::EndOfFile}); +} } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.cpp index 9ee16ac87392..dacf854e950e 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.cpp +++ b/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.cpp @@ -10,11 +10,26 @@ namespace facebook::react { #ifdef RN_SERIALIZABLE_STATE +folly::dynamic URLBackgroundImage::toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["type"] = "url"; + result["uri"] = uri; + if (intrinsicWidth > 0) { + result["intrinsicWidth"] = intrinsicWidth; + } + if (intrinsicHeight > 0) { + result["intrinsicHeight"] = intrinsicHeight; + } + return result; +} + folly::dynamic toDynamic(const BackgroundImage& backgroundImage) { if (std::holds_alternative(backgroundImage)) { return std::get(backgroundImage).toDynamic(); } else if (std::holds_alternative(backgroundImage)) { return std::get(backgroundImage).toDynamic(); + } else if (std::holds_alternative(backgroundImage)) { + return std::get(backgroundImage).toDynamic(); } return folly::dynamic(nullptr); } diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.h b/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.h index ab0c62e506ca..ddfa4e4a06a2 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/BackgroundImage.h @@ -9,12 +9,43 @@ #include +#include + #include #include namespace facebook::react { -using BackgroundImage = std::variant; +struct URLBackgroundImage { + std::string uri{}; + // Natural size in layout units, supplied by the asset registry. Zero when the + // source is a plain url, in which case the decoded image's size is used. + Float intrinsicWidth{0}; + Float intrinsicHeight{0}; + + bool operator==(const URLBackgroundImage &rhs) const + { + return uri == rhs.uri && intrinsicWidth == rhs.intrinsicWidth && intrinsicHeight == rhs.intrinsicHeight; + } + + bool operator!=(const URLBackgroundImage &rhs) const + { + return !(*this == rhs); + } + +#ifdef RN_SERIALIZABLE_STATE + folly::dynamic toDynamic() const; +#endif + +#if RN_DEBUG_STRING_CONVERTIBLE + void toString(std::stringstream &ss) const + { + ss << "url(" << uri << ")"; + } +#endif +}; + +using BackgroundImage = std::variant; #ifdef RN_SERIALIZABLE_STATE folly::dynamic toDynamic(const BackgroundImage &backgroundImage); @@ -36,6 +67,8 @@ inline std::string toString(std::vector &value) std::get(backgroundImage).toString(ss); } else if (std::holds_alternative(backgroundImage)) { std::get(backgroundImage).toString(ss); + } else if (std::holds_alternative(backgroundImage)) { + std::get(backgroundImage).toString(ss); } } ss << "]"; diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index c0f8309f0ac5..725e574fb7ab 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -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<> + * @generated SignedSource<<95d2bad1aa94a3efd4afa1667a0571d0>> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -1585,7 +1585,8 @@ declare type AttributeType = readonly process?: (arg1: V) => T } declare type AutoCapitalize = "characters" | "none" | "sentences" | "words" -declare type BackgroundImageValue = LinearGradientValue | RadialGradientValue +declare type BackgroundImageValue = + GradientBackgroundImageValue | URLBackgroundImageValue declare type BackgroundPositionValue = | { bottom: number | string @@ -2356,6 +2357,8 @@ declare function getWithFallback_DEPRECATED( name: string, viewConfigProvider: () => PartialViewConfig, ): React.ComponentType +declare type GradientBackgroundImageValue = + LinearGradientValue | RadialGradientValue declare type hairlineWidth = typeof hairlineWidth declare interface HardwareBackPressEvent { readonly timeStamp: number @@ -5436,6 +5439,10 @@ declare type URIRequestBody = { readonly string?: string readonly uri: string } +declare type URLBackgroundImageValue = { + type: "url" + uri: number | string +} declare function useAnimatedColor( inputValue?: InputValue, config?: AnimatedColorConfig | null | undefined, @@ -5782,18 +5789,18 @@ export { AccessibilityValue, // cf8bcb74 ActionSheetIOS, // b558559e ActionSheetIOSOptions, // 1756eb5a - ActivityIndicator, // d5bcb272 + ActivityIndicator, // a4cfe6fa ActivityIndicatorInstance, // 1b5e0dd5 - ActivityIndicatorProps, // 508a24c6 + ActivityIndicatorProps, // 9f8427d0 Alert, // a398a509 AlertButton, // bf1a3b60 AlertButtonStyle, // ec9fb242 AlertOptions, // 8a116d2a AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // 33fe7464 + Animated, // 179682f1 AppConfig, // 35c0ca70 - AppRegistry, // edec2a30 + AppRegistry, // e0767bbc AppState, // 12012be5 AppStateEvent, // 80f034c3 AppStateStatus, // 447e5ef2 @@ -5805,8 +5812,8 @@ export { BackPressEventName, // 4620fb76 BlurEvent, // d5e68e96 BoxShadowValue, // b679703f - Button, // f253fc94 - ButtonInstance, // bcf2ef72 + Button, // a46d0637 + ButtonInstance, // 77cb31b6 ButtonProps, // 9886bcc6 Clipboard, // 41addb89 CodegenTypes, // 9067fd06 @@ -5825,9 +5832,9 @@ export { DimensionsPayload, // 653bc26c DisplayMetrics, // 1dc35cef DisplayMetricsAndroid, // 872e62eb - DrawerLayoutAndroid, // beec8621 + DrawerLayoutAndroid, // 503e4339 DrawerLayoutAndroidInstance, // aa243cbf - DrawerLayoutAndroidProps, // 53289373 + DrawerLayoutAndroidProps, // 96c5df99 DrawerSlideEvent, // 38213685 DropShadowValue, // e9df2606 DynamicColorIOS, // d96c228c @@ -5843,9 +5850,9 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // 5ba109bd - FlatListInstance, // 1095fe06 - FlatListProps, // dd43b1d1 + FlatList, // eec7135f + FlatListInstance, // 9e634184 + FlatListProps, // 33a63ace FocusEvent, // f2e4363d FontVariant, // 7c7558bb GestureResponderEvent, // 5cd2a663 @@ -5856,17 +5863,17 @@ export { IEventEmitter, // fbef6131 IOSKeyboardEvent, // e67bfe3a IgnorePattern, // ec6f6ece - Image, // a24a5777 - ImageBackground, // 1fbbacef - ImageBackgroundInstance, // 9c900a60 - ImageBackgroundProps, // b078f0ac + Image, // 354531d1 + ImageBackground, // 5aa7743c + ImageBackgroundInstance, // 61957968 + ImageBackgroundProps, // e1d3c37b ImageErrorEvent, // b6227714 ImageInstance, // df709524 ImageLoadEvent, // 31709c38 ImageProgressEventIOS, // 9243d460 - ImageProps, // e6a7a863 + ImageProps, // a6134217 ImagePropsAndroid, // ee00e1d5 - ImagePropsBase, // f41b5a7c + ImagePropsBase, // dff40c63 ImagePropsIOS, // 1998f91a ImageRequireSource, // 681d683b ImageResizeMode, // d51106e2 @@ -5874,19 +5881,19 @@ export { ImageSize, // 1c47cf88 ImageSource, // ea31cf4a ImageSourcePropType, // f522e093 - ImageStyle, // 85dc694a + ImageStyle, // 4367a619 ImageURISource, // 443d047c - InputAccessoryView, // ab6e2a66 - InputAccessoryViewProps, // 73685b83 + InputAccessoryView, // c4b7cab1 + InputAccessoryViewProps, // 6fe8615a InputModeOptions, // 4e8581b9 Insets, // e7fe432a KeyDownEvent, // a12d46ea KeyEvent, // 20fa4267 KeyUpEvent, // 0b5d0144 Keyboard, // 49414c97 - KeyboardAvoidingView, // 1fee106e - KeyboardAvoidingViewInstance, // 26062fd4 - KeyboardAvoidingViewProps, // 635ae7fc + KeyboardAvoidingView, // f306b578 + KeyboardAvoidingViewInstance, // 2b1296e3 + KeyboardAvoidingViewProps, // acd6df64 KeyboardEvent, // c3f895d4 KeyboardEventEasing, // af4091c8 KeyboardEventName, // 59299ad6 @@ -5911,10 +5918,10 @@ export { MeasureInWindowOnSuccessCallback, // a285f598 MeasureLayoutOnSuccessCallback, // 3592502a MeasureOnSuccessCallback, // 82824e59 - Modal, // f4b88be5 + Modal, // a8467aba ModalBaseProps, // a6e4334f ModalInstance, // 0f04c1bf - ModalProps, // c07a247a + ModalProps, // 6872e3d8 ModalPropsAndroid, // 515fb173 ModalPropsIOS, // a65a8f5c ModeChangeEvent, // ccf30dc4 @@ -5950,15 +5957,15 @@ export { PointerEvent, // a506d9d7 PressabilityConfig, // 0fd655fe PressabilityEventHandlers, // be359ba3 - Pressable, // 56235a5f + Pressable, // cdcb9733 PressableAndroidRippleConfig, // ee32eaca PressableInstance, // ed26f040 - PressableProps, // 7a18f8cd + PressableProps, // b3ecb90c PressableStateCallbackType, // f83728cd ProcessedColorValue, // 33f74304 - ProgressBarAndroid, // 30b4a1eb + ProgressBarAndroid, // 474cf450 ProgressBarAndroidInstance, // f17242be - ProgressBarAndroidProps, // 98fa6a86 + ProgressBarAndroidProps, // 4b9267fe PublicRootInstance, // 8040afd7 PublicTextInstance, // f9adf69a PushNotificationEventName, // 84e7e150 @@ -5966,9 +5973,9 @@ export { PushNotificationPermissions, // c2e7ae4f Rationale, // 5df1b1c1 ReactNativeVersion, // abd76827 - RefreshControl, // e74ab0c4 - RefreshControlInstance, // 7e1aa88d - RefreshControlProps, // e728e273 + RefreshControl, // 2df6eebc + RefreshControlInstance, // 21783645 + RefreshControlProps, // 629ece48 RefreshControlPropsAndroid, // 8ac931ca RefreshControlPropsIOS, // 72a36381 Registry, // 6c39216d @@ -5977,28 +5984,28 @@ export { Role, // af7b889d RootTag, // 3cd10504 RootTagContext, // 38bfc8f6 - RootViewStyleProvider, // d5909148 + RootViewStyleProvider, // 63fa2305 Runnable, // 594dd93a Runnables, // 4367c557 SafeAreaInsetsChangeEvent, // cedb5309 - SafeAreaView, // dda99293 + SafeAreaView, // 22e242f0 SafeAreaViewInstance, // 2b5d4391 ScaledSize, // 07e417c7 ScrollEvent, // 956fdb27 - ScrollResponderType, // 52c38666 + ScrollResponderType, // 205eaaf9 ScrollToLocationParamsType, // d7ecdad1 - ScrollView, // 08fe7620 - ScrollViewImperativeMethods, // 682315af - ScrollViewInstance, // bcc836b9 - ScrollViewProps, // d845721f + ScrollView, // 623f0163 + ScrollViewImperativeMethods, // 171241c8 + ScrollViewInstance, // e9e452f5 + ScrollViewProps, // 08501dc1 ScrollViewPropsAndroid, // 02f3df2e ScrollViewPropsIOS, // c378379b ScrollViewScrollToOptions, // 3313411e SectionBase, // 9f13db00 - SectionList, // b48c3713 + SectionList, // f1026786 SectionListData, // 1a4de01a - SectionListInstance, // e2814c16 - SectionListProps, // bf7d4c45 + SectionListInstance, // ae9ef11b + SectionListProps, // 0ed5877c SectionListRenderItem, // 715b2086 SectionListRenderItemInfo, // 4a48a922 Separators, // 6a45f7e3 @@ -6015,19 +6022,19 @@ export { StatusBarProps, // c2a44d88 StatusBarStyle, // 78f53eea StyleProp, // fa0e9b4a - StyleSheet, // 8bb13f55 + StyleSheet, // 99103509 SubmitBehavior, // c4ddf490 - Switch, // 7ea8a527 + Switch, // 478941a9 SwitchChangeEvent, // 3b472666 SwitchInstance, // 73dce99f - SwitchProps, // aa00dcf4 + SwitchProps, // 29ae4b34 Systrace, // 626d178c TVViewPropsIOS, // 1c612e4e TargetedEvent, // 16e98910 TaskProvider, // 266dedf2 - Text, // 935ec305 + Text, // 95167c21 TextContentType, // 239b3ecc - TextInput, // cf6467dc + TextInput, // 4e4f8d85 TextInputAndroidProps, // 9ebbc103 TextInputBlurEvent, // b4c7a58c TextInputChangeEvent, // 92363e10 @@ -6037,46 +6044,46 @@ export { TextInputIOSProps, // fb3c9327 TextInputInstance, // 53fe7b73 TextInputKeyPressEvent, // cbbc322a - TextInputProps, // bb087c92 + TextInputProps, // 1bd63490 TextInputSelectionChangeEvent, // 4a8f10c2 TextInputSubmitEditingEvent, // e811fdbd TextInstance, // 7c7e2069 TextLayoutEvent, // 1a21e24a - TextProps, // b3707dfc - TextStyle, // 898adfd1 + TextProps, // e988b34e + TextStyle, // 39a48bdc ToastAndroid, // 88a8969a - TouchableHighlight, // a015710f + TouchableHighlight, // 8f67d8d8 TouchableHighlightInstance, // 811af7ca - TouchableHighlightProps, // faaeb188 - TouchableNativeFeedback, // 744ee0ee - TouchableNativeFeedbackInstance, // b90e3bae - TouchableNativeFeedbackProps, // 39abde16 - TouchableOpacity, // 6aec357f + TouchableHighlightProps, // a8f65339 + TouchableNativeFeedback, // 7e8ee859 + TouchableNativeFeedbackInstance, // 6e557698 + TouchableNativeFeedbackProps, // 75bcc9dc + TouchableOpacity, // cc0e39ce TouchableOpacityInstance, // 0cd8a347 - TouchableOpacityProps, // 0d78e636 - TouchableWithoutFeedback, // 92cfc35d - TouchableWithoutFeedbackProps, // 7e8a325c + TouchableOpacityProps, // 7e31b67b + TouchableWithoutFeedback, // 7426f441 + TouchableWithoutFeedbackProps, // 1cbda744 TransformsStyle, // 65e70f18 TurboModule, // dfe29706 TurboModuleRegistry, // 4ace6db2 UIManager, // 6a18fc05 UTFSequence, // ad625158 Vibration, // 31e4bbf8 - View, // 85575778 + View, // 6663cafa ViewInstance, // 0283d2fc - ViewProps, // 43cbabe5 + ViewProps, // f53e2945 ViewPropsAndroid, // 66d760c6 ViewPropsIOS, // 58ee19bf - ViewStyle, // 4e2746d6 + ViewStyle, // a2b11ecc ViewabilityConfig, // eb3f7ed8 ViewabilityConfigCallbackPair, // bdd0788c VirtualViewMode, // 6be59722 VirtualizedList, // 68c7345e VirtualizedListInstance, // 423ee7c0 - VirtualizedListProps, // 09a41c15 + VirtualizedListProps, // 5c5e38ac VirtualizedSectionList, // 9fd9cd61 VirtualizedSectionListInstance, // 12b706d5 - VirtualizedSectionListProps, // c63aa8a2 + VirtualizedSectionListProps, // d634af43 WrapperComponentProvider, // 9b4247f6 codegenNativeCommands, // 322f3f4e codegenNativeComponent, // 3da62daa diff --git a/packages/react-native/types_DEPRECATED/Libraries/StyleSheet/StyleSheetTypes.d.ts b/packages/react-native/types_DEPRECATED/Libraries/StyleSheet/StyleSheetTypes.d.ts index 478e9d092195..61c26118f91f 100644 --- a/packages/react-native/types_DEPRECATED/Libraries/StyleSheet/StyleSheetTypes.d.ts +++ b/packages/react-native/types_DEPRECATED/Libraries/StyleSheet/StyleSheetTypes.d.ts @@ -407,7 +407,13 @@ type RadialGradientValue = { }>; }; -export type BackgroundImageValue = LinearGradientValue | RadialGradientValue; +type URLBackgroundImageValue = { + type: 'url'; + uri: string | number; +}; + +export type BackgroundImageValue = + LinearGradientValue | RadialGradientValue | URLBackgroundImageValue; export type BackgroundSizeValue = { x: string | number; diff --git a/packages/rn-tester/js/examples/BackgroundImage/BackgroundImageExample.js b/packages/rn-tester/js/examples/BackgroundImage/BackgroundImageExample.js index 949369ed4137..e577fefa6939 100644 --- a/packages/rn-tester/js/examples/BackgroundImage/BackgroundImageExample.js +++ b/packages/rn-tester/js/examples/BackgroundImage/BackgroundImageExample.js @@ -441,4 +441,68 @@ exports.examples = [ ); }, }, + { + title: 'URL Image', + name: 'url-image', + render(): React.Node { + return ( + + ); + }, + }, + { + title: 'URL Image from local file', + name: 'local-image', + render(): React.Node { + return ( + + ); + }, + }, + { + title: 'URL Image from local file at its natural size', + name: 'local-image-natural-size', + description: + 'A @3x asset draws at its size in points (33x33), not at its pixel size (99x99).', + render(): React.Node { + return ( + + ); + }, + }, ] as Array; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index ca72d3985fc9..29d402cd297a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -391,7 +391,7 @@ using facebook::react::AndroidDrawerLayoutState = facebook::react::StateData; using facebook::react::AndroidSwipeRefreshLayoutComponentDescriptor = facebook::react::ConcreteComponentDescriptor; using facebook::react::AndroidSwipeRefreshLayoutShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::AndroidSwipeRefreshLayoutState = facebook::react::StateData; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -3660,6 +3660,15 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public folly::dynamic toDynamic() const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 5e5dad5f5ce5..bf9282a24743 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -482,14 +482,14 @@ using facebook::react::AndroidSwipeRefreshLayoutState = facebook::react::StateDa using facebook::react::AnimationEndCallback = facebook::react::AsyncCallback; using facebook::react::AnimationTimestamp = std::chrono::duration; using facebook::react::BackgroundExecutor = std::function&& callback)>; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; using facebook::react::BorderRadii = facebook::react::RectangleCorners; using facebook::react::BorderStyles = facebook::react::RectangleEdges; using facebook::react::BorderWidths = facebook::react::RectangleEdges; -using facebook::react::CSSBackgroundImage = facebook::react::CSSCompoundDataType; +using facebook::react::CSSBackgroundImage = facebook::react::CSSCompoundDataType; using facebook::react::CSSBackgroundImageList = facebook::react::CSSCommaSeparatedList; using facebook::react::CSSFilterFunction = facebook::react::CSSCompoundDataType; using facebook::react::CSSFilterList = facebook::react::CSSWhitespaceSeparatedList; @@ -4868,6 +4868,8 @@ enum facebook::react::CSSRadialGradientSizeKeyword : uint8_t { } enum facebook::react::CSSTokenType { + BadString, + BadUrl, CloseCurly, CloseParen, CloseSquare, @@ -4883,6 +4885,8 @@ enum facebook::react::CSSTokenType { OpenParen, OpenSquare, Percentage, + String, + Url, WhiteSpace, } @@ -5979,6 +5983,11 @@ struct facebook::react::CSSTranslateY { public std::variant value; } +struct facebook::react::CSSURLFunction { + public bool operator==(const facebook::react::CSSURLFunction& rhs) const = default; + public std::string url; +} + struct facebook::react::CSSZero { public constexpr bool operator==(const facebook::react::CSSZero& rhs) const = default; } @@ -6703,6 +6712,15 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public folly::dynamic toDynamic() const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); @@ -8269,6 +8287,11 @@ struct facebook::react::CSSDataTypeParser : publ struct facebook::react::CSSDataTypeParser : public facebook::react::detail::CSSVariantComponentTransformParser { } +struct facebook::react::CSSDataTypeParser { + public static std::optional consumeFunctionBlock(const facebook::react::CSSFunctionBlock& func, facebook::react::CSSValueParser& parser); + public static std::optional consumePreservedToken(const facebook::react::CSSPreservedToken& token); +} + struct facebook::react::CSSDataTypeParser { public static constexpr std::optional consumePreservedToken(const facebook::react::CSSPreservedToken& token); } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 1a105ab8080c..c0e47c5fa3e3 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -391,7 +391,7 @@ using facebook::react::AndroidDrawerLayoutState = facebook::react::StateData; using facebook::react::AndroidSwipeRefreshLayoutComponentDescriptor = facebook::react::ConcreteComponentDescriptor; using facebook::react::AndroidSwipeRefreshLayoutShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::AndroidSwipeRefreshLayoutState = facebook::react::StateData; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -3653,6 +3653,15 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public folly::dynamic toDynamic() const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 1a105ab8080c..c0e47c5fa3e3 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -391,7 +391,7 @@ using facebook::react::AndroidDrawerLayoutState = facebook::react::StateData; using facebook::react::AndroidSwipeRefreshLayoutComponentDescriptor = facebook::react::ConcreteComponentDescriptor; using facebook::react::AndroidSwipeRefreshLayoutShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::AndroidSwipeRefreshLayoutState = facebook::react::StateData; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -3653,6 +3653,15 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public folly::dynamic toDynamic() const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index e04f3b738889..237dc762996b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -3133,7 +3133,7 @@ using facebook::react::ModuleConstants = _RCTTypedModuleConstants; using facebook::react::ActivityIndicatorViewShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::ActivityIndicatorViewState = facebook::react::StateData; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -5629,6 +5629,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index cb8ca685d4f2..d1890fb4d425 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -3263,14 +3263,14 @@ using facebook::react::ActivityIndicatorViewState = facebook::react::StateData; using facebook::react::AnimationEndCallback = facebook::react::AsyncCallback; using facebook::react::AnimationTimestamp = std::chrono::duration; using facebook::react::BackgroundExecutor = std::function&& callback)>; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; using facebook::react::BorderRadii = facebook::react::RectangleCorners; using facebook::react::BorderStyles = facebook::react::RectangleEdges; using facebook::react::BorderWidths = facebook::react::RectangleEdges; -using facebook::react::CSSBackgroundImage = facebook::react::CSSCompoundDataType; +using facebook::react::CSSBackgroundImage = facebook::react::CSSCompoundDataType; using facebook::react::CSSBackgroundImageList = facebook::react::CSSCommaSeparatedList; using facebook::react::CSSFilterFunction = facebook::react::CSSCompoundDataType; using facebook::react::CSSFilterList = facebook::react::CSSWhitespaceSeparatedList; @@ -6997,6 +6997,8 @@ enum facebook::react::CSSRadialGradientSizeKeyword : uint8_t { } enum facebook::react::CSSTokenType { + BadString, + BadUrl, CloseCurly, CloseParen, CloseSquare, @@ -7012,6 +7014,8 @@ enum facebook::react::CSSTokenType { OpenParen, OpenSquare, Percentage, + String, + Url, WhiteSpace, } @@ -8130,6 +8134,11 @@ struct facebook::react::CSSTranslateY { public std::variant value; } +struct facebook::react::CSSURLFunction { + public bool operator==(const facebook::react::CSSURLFunction& rhs) const = default; + public std::string url; +} + struct facebook::react::CSSZero { public constexpr bool operator==(const facebook::react::CSSZero& rhs) const = default; } @@ -8693,6 +8702,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); @@ -10148,6 +10165,11 @@ struct facebook::react::CSSDataTypeParser : publ struct facebook::react::CSSDataTypeParser : public facebook::react::detail::CSSVariantComponentTransformParser { } +struct facebook::react::CSSDataTypeParser { + public static std::optional consumeFunctionBlock(const facebook::react::CSSFunctionBlock& func, facebook::react::CSSValueParser& parser); + public static std::optional consumePreservedToken(const facebook::react::CSSPreservedToken& token); +} + struct facebook::react::CSSDataTypeParser { public static constexpr std::optional consumePreservedToken(const facebook::react::CSSPreservedToken& token); } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 9010fcf23951..22bf9fb4d9a6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -3126,7 +3126,7 @@ using facebook::react::ModuleConstants = _RCTTypedModuleConstants; using facebook::react::ActivityIndicatorViewShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::ActivityIndicatorViewState = facebook::react::StateData; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -5615,6 +5615,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 6bb9397bd2e4..8d95e82eb9de 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -3133,7 +3133,7 @@ using facebook::react::ModuleConstants = _RCTTypedModuleConstants; using facebook::react::ActivityIndicatorViewShadowNode = facebook::react::ConcreteViewShadowNode; using facebook::react::ActivityIndicatorViewState = facebook::react::StateData; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -5622,6 +5622,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public facebook::react::Float intrinsicHeight; + public facebook::react::Float intrinsicWidth; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 507e71d72698..858fbe99bd8f 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -80,7 +80,7 @@ static const facebook::react::LayoutMetrics facebook::react::EmptyLayoutMetrics; static const facebook::react::TelemetryTimePoint facebook::react::kTelemetryUndefinedTimePoint; static constexpr auto facebook::react::kPropNameLengthHardCap; static constexpr facebook::react::RawPropsValueIndex facebook::react::kRawPropsValueIndexEmpty; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -2071,6 +2071,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public Float intrinsicHeight; + public Float intrinsicWidth; + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api index cb97eccc7f11..d90b5c9ff8db 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -110,14 +110,14 @@ using facebook::react::ActivePointerRegistry = std::unordered_map; using facebook::react::AnimationTimestamp = std::chrono::duration; using facebook::react::BackgroundExecutor = std::function&& callback)>; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; using facebook::react::BorderRadii = facebook::react::RectangleCorners; using facebook::react::BorderStyles = facebook::react::RectangleEdges; using facebook::react::BorderWidths = facebook::react::RectangleEdges; -using facebook::react::CSSBackgroundImage = facebook::react::CSSCompoundDataType; +using facebook::react::CSSBackgroundImage = facebook::react::CSSCompoundDataType; using facebook::react::CSSBackgroundImageList = facebook::react::CSSCommaSeparatedList; using facebook::react::CSSFilterFunction = facebook::react::CSSCompoundDataType; using facebook::react::CSSFilterList = facebook::react::CSSWhitespaceSeparatedList; @@ -3279,6 +3279,8 @@ enum facebook::react::CSSRadialGradientSizeKeyword : uint8_t { } enum facebook::react::CSSTokenType { + BadString, + BadUrl, CloseCurly, CloseParen, CloseSquare, @@ -3294,6 +3296,8 @@ enum facebook::react::CSSTokenType { OpenParen, OpenSquare, Percentage, + String, + Url, WhiteSpace, } @@ -4340,6 +4344,11 @@ struct facebook::react::CSSTranslateY { public std::variant value; } +struct facebook::react::CSSURLFunction { + public bool operator==(const facebook::react::CSSURLFunction& rhs) const = default; + public std::string url; +} + struct facebook::react::CSSZero { public constexpr bool operator==(const facebook::react::CSSZero& rhs) const = default; } @@ -4869,6 +4878,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public Float intrinsicHeight; + public Float intrinsicWidth; + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); @@ -5505,6 +5522,11 @@ struct facebook::react::CSSDataTypeParser : publ struct facebook::react::CSSDataTypeParser : public facebook::react::detail::CSSVariantComponentTransformParser { } +struct facebook::react::CSSDataTypeParser { + public static std::optional consumeFunctionBlock(const facebook::react::CSSFunctionBlock& func, facebook::react::CSSValueParser& parser); + public static std::optional consumePreservedToken(const facebook::react::CSSPreservedToken& token); +} + struct facebook::react::CSSDataTypeParser { public static constexpr std::optional consumePreservedToken(const facebook::react::CSSPreservedToken& token); } diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 004abd9081df..513d29f70e0e 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -80,7 +80,7 @@ static const facebook::react::LayoutMetrics facebook::react::EmptyLayoutMetrics; static const facebook::react::TelemetryTimePoint facebook::react::kTelemetryUndefinedTimePoint; static constexpr auto facebook::react::kPropNameLengthHardCap; static constexpr facebook::react::RawPropsValueIndex facebook::react::kRawPropsValueIndexEmpty; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -2064,6 +2064,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public Float intrinsicHeight; + public Float intrinsicWidth; + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 004abd9081df..513d29f70e0e 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -80,7 +80,7 @@ static const facebook::react::LayoutMetrics facebook::react::EmptyLayoutMetrics; static const facebook::react::TelemetryTimePoint facebook::react::kTelemetryUndefinedTimePoint; static constexpr auto facebook::react::kPropNameLengthHardCap; static constexpr facebook::react::RawPropsValueIndex facebook::react::kRawPropsValueIndexEmpty; -using facebook::react::BackgroundImage = std::variant; +using facebook::react::BackgroundImage = std::variant; using facebook::react::BackgroundSize = std::variant; using facebook::react::BorderColors = facebook::react::RectangleEdges; using facebook::react::BorderCurves = facebook::react::RectangleCorners; @@ -2064,6 +2064,14 @@ struct facebook::react::TransformOrigin { public std::array xy; } +struct facebook::react::URLBackgroundImage { + public Float intrinsicHeight; + public Float intrinsicWidth; + public bool operator!=(const facebook::react::URLBackgroundImage& rhs) const; + public bool operator==(const facebook::react::URLBackgroundImage& rhs) const; + public std::string uri; +} + struct facebook::react::ValueUnit { public constexpr ValueUnit() = default; public constexpr ValueUnit(float v, facebook::react::UnitType u);