From 4889f5fc1eacae5f7425e5190dae8948509c89fd Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 6 Mar 2026 16:07:44 -0800 Subject: [PATCH] RELEASE: Releasing 0 package(s) (0.74-stable) --- ...-a40f1783-563c-4fcd-b074-1960bd4d254e.json | 7 - ...-6ede3088-858e-4020-98bb-16bf34a5a427.json | 7 - .../@react-native-windows/cli/CHANGELOG.json | 15 + .../telemetry/CHANGELOG.json | 15 + .../lib-commonjs/beachballBump.d.ts | 29 + .../lib-commonjs/beachballBump.js | 46 + .../lib-commonjs/beachballBump.js.map | 1 + .../prepare-release/lib-commonjs/git.d.ts | 54 + .../prepare-release/lib-commonjs/git.js | 107 + .../prepare-release/lib-commonjs/git.js.map | 1 + .../prepare-release/lib-commonjs/github.d.ts | 48 + .../prepare-release/lib-commonjs/github.js | 103 + .../lib-commonjs/github.js.map | 1 + .../lib-commonjs/prepareRelease.d.ts | 14 + .../lib-commonjs/prepareRelease.js | 308 ++ .../lib-commonjs/prepareRelease.js.map | 1 + .../prepare-release/lib-commonjs/proc.d.ts | 43 + .../prepare-release/lib-commonjs/proc.js | 86 + .../prepare-release/lib-commonjs/proc.js.map | 1 + .../lib-commonjs/releaseSummary.d.ts | 35 + .../lib-commonjs/releaseSummary.js | 119 + .../lib-commonjs/releaseSummary.js.map | 1 + .../CustomAccessibilityNativeComponent.d.ts | 5 + .../CustomAccessibilityNativeComponent.js | 1 + .../CustomAccessibilityNativeComponent.js.map | 1 + .../lib-commonjs/DrawingIsland.d.ts | 8 + .../lib-commonjs/DrawingIsland.js | 39 + .../lib-commonjs/DrawingIsland.js.map | 1 + .../DrawingIslandNativeComponent.d.ts | 5 + .../DrawingIslandNativeComponent.js | 1 + .../DrawingIslandNativeComponent.js.map | 1 + ...FabricXamlCalendarViewNativeComponent.d.ts | 18 + .../FabricXamlCalendarViewNativeComponent.js | 1 + ...bricXamlCalendarViewNativeComponent.js.map | 1 + .../FabricXamlComboBoxNativeComponent.d.ts | 19 + .../FabricXamlComboBoxNativeComponent.js | 1 + .../FabricXamlComboBoxNativeComponent.js.map | 1 + .../lib-commonjs/MovingLight.d.ts | 17 + .../lib-commonjs/MovingLight.js | 46 + .../lib-commonjs/MovingLight.js.map | 1 + .../MovingLightNativeComponent.d.ts | 81 + .../MovingLightNativeComponent.js | 1 + .../MovingLightNativeComponent.js.map | 1 + .../lib-commonjs/index.d.ts | 7 + .../lib-commonjs/index.js | 17 + .../lib-commonjs/index.js.map | 1 + .../ApiLoaders/JSRuntimeApi.cpp | 79 + .../ApiLoaders/JSRuntimeApi.h | 51 + .../ApiLoaders/JSRuntimeApi.inc | 50 + .../ApiLoaders/NodeApi.cpp | 41 + .../ApiLoaders/NodeApi.h | 127 + .../ApiLoaders/NodeApi.inc | 125 + .../ApiLoaders/NodeApi_posix.cpp | 16 + .../ApiLoaders/NodeApi_win.cpp | 23 + .../Microsoft.ReactNative.Cxx/JSI/decorator.h | 1172 ++++++ .../JSI/instrumentation.h | 150 + vnext/Microsoft.ReactNative.Cxx/JSI/jsi-inl.h | 425 ++ vnext/Microsoft.ReactNative.Cxx/JSI/jsi.cpp | 990 +++++ vnext/Microsoft.ReactNative.Cxx/JSI/jsi.h | 2031 +++++++++ .../JSI/threadsafe.h | 79 + .../NodeApiJsiRuntime.cpp | 3695 +++++++++++++++++ .../NodeApiJsiRuntime.h | 38 + .../ReactCommon/CallInvoker.h | 61 + .../ReactCommon/SchedulerPriority.h | 20 + .../ReactCommon/TurboModule.cpp | 64 + .../ReactCommon/TurboModule.h | 153 + .../ReactCommon/TurboModuleUtils.cpp | 52 + .../ReactCommon/TurboModuleUtils.h | 31 + .../ReactCommon/react/bridging/AString.h | 44 + .../ReactCommon/react/bridging/Array.h | 134 + .../ReactCommon/react/bridging/Base.h | 139 + .../ReactCommon/react/bridging/Bool.h | 27 + .../ReactCommon/react/bridging/Bridging.h | 22 + .../react/bridging/CallbackWrapper.h | 68 + .../ReactCommon/react/bridging/Class.h | 75 + .../ReactCommon/react/bridging/Convert.h | 177 + .../ReactCommon/react/bridging/Error.h | 56 + .../ReactCommon/react/bridging/EventEmitter.h | 136 + .../ReactCommon/react/bridging/Function.h | 263 ++ .../react/bridging/HighResTimeStamp.h | 41 + .../react/bridging/LongLivedObject.cpp | 63 + .../react/bridging/LongLivedObject.h | 61 + .../ReactCommon/react/bridging/Number.h | 66 + .../ReactCommon/react/bridging/Object.h | 86 + .../ReactCommon/react/bridging/Promise.h | 110 + .../ReactCommon/react/bridging/Value.h | 98 + .../ReactCommon/react/timing/primitives.h | 350 ++ .../node-api/js_native_api.h | 627 +++ .../node-api/js_native_api_types.h | 211 + .../node-api/js_runtime_api.h | 214 + .../node-api/node_api.h | 270 ++ .../node-api/node_api_types.h | 52 + .../stubs/glog/logging.h | 82 + 93 files changed, 14248 insertions(+), 14 deletions(-) delete mode 100644 change/@react-native-windows-cli-a40f1783-563c-4fcd-b074-1960bd4d254e.json delete mode 100644 change/@react-native-windows-telemetry-6ede3088-858e-4020-98bb-16bf34a5a427.json create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.d.ts create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js.map create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/git.d.ts create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/git.js create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/git.js.map create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/github.d.ts create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/github.js create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/github.js.map create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.d.ts create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js.map create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/proc.d.ts create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js.map create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.d.ts create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js create mode 100644 packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js create mode 100644 packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/DrawingIsland.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/DrawingIsland.js create mode 100644 packages/sample-custom-component/lib-commonjs/DrawingIsland.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js create mode 100644 packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js create mode 100644 packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js create mode 100644 packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/MovingLight.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/MovingLight.js create mode 100644 packages/sample-custom-component/lib-commonjs/MovingLight.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js create mode 100644 packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js.map create mode 100644 packages/sample-custom-component/lib-commonjs/index.d.ts create mode 100644 packages/sample-custom-component/lib-commonjs/index.js create mode 100644 packages/sample-custom-component/lib-commonjs/index.js.map create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.inc create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.inc create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_posix.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_win.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/JSI/decorator.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/JSI/instrumentation.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/JSI/jsi-inl.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/JSI/jsi.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/JSI/jsi.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/JSI/threadsafe.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/CallInvoker.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/SchedulerPriority.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/AString.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Array.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Base.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bool.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bridging.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/CallbackWrapper.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Class.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Convert.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Error.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/EventEmitter.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Function.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/HighResTimeStamp.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.cpp create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Number.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Object.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Promise.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Value.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/timing/primitives.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api_types.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/node-api/js_runtime_api.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/node-api/node_api.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/node-api/node_api_types.h create mode 100644 vnext/Microsoft.ReactNative.Cxx/stubs/glog/logging.h diff --git a/change/@react-native-windows-cli-a40f1783-563c-4fcd-b074-1960bd4d254e.json b/change/@react-native-windows-cli-a40f1783-563c-4fcd-b074-1960bd4d254e.json deleted file mode 100644 index 3dc65cd1b7a..00000000000 --- a/change/@react-native-windows-cli-a40f1783-563c-4fcd-b074-1960bd4d254e.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "none", - "comment": "Scope various foo and bar test package to rnw-scripts.\"", - "packageName": "@react-native-windows/cli", - "email": "yicyao@microsoft.com", - "dependentChangeType": "none" -} diff --git a/change/@react-native-windows-telemetry-6ede3088-858e-4020-98bb-16bf34a5a427.json b/change/@react-native-windows-telemetry-6ede3088-858e-4020-98bb-16bf34a5a427.json deleted file mode 100644 index 4d39505c9ac..00000000000 --- a/change/@react-native-windows-telemetry-6ede3088-858e-4020-98bb-16bf34a5a427.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "none", - "comment": "Fix telemetry test.", - "packageName": "@react-native-windows/telemetry", - "email": "yicyao@microsoft.com", - "dependentChangeType": "none" -} diff --git a/packages/@react-native-windows/cli/CHANGELOG.json b/packages/@react-native-windows/cli/CHANGELOG.json index 2e5ab046e80..e8d3625c5d0 100644 --- a/packages/@react-native-windows/cli/CHANGELOG.json +++ b/packages/@react-native-windows/cli/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@react-native-windows/cli", "entries": [ + { + "date": "Sat, 07 Mar 2026 00:07:43 GMT", + "version": "0.74.12", + "tag": "@react-native-windows/cli_v0.74.12", + "comments": { + "none": [ + { + "author": "yicyao@microsoft.com", + "package": "@react-native-windows/cli", + "commit": "d3ed6d4a5e4e632c3cbbb0d912e07f6bf9fe20d2", + "comment": "Scope various foo and bar test package to rnw-scripts.\"" + } + ] + } + }, { "date": "Mon, 22 Sep 2025 15:31:50 GMT", "version": "0.74.12", diff --git a/packages/@react-native-windows/telemetry/CHANGELOG.json b/packages/@react-native-windows/telemetry/CHANGELOG.json index 1063d7bb44a..9f5930ef055 100644 --- a/packages/@react-native-windows/telemetry/CHANGELOG.json +++ b/packages/@react-native-windows/telemetry/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@react-native-windows/telemetry", "entries": [ + { + "date": "Sat, 07 Mar 2026 00:07:43 GMT", + "version": "0.74.2", + "tag": "@react-native-windows/telemetry_v0.74.2", + "comments": { + "none": [ + { + "author": "yicyao@microsoft.com", + "package": "@react-native-windows/telemetry", + "commit": "d3ed6d4a5e4e632c3cbbb0d912e07f6bf9fe20d2", + "comment": "Fix telemetry test." + } + ] + } + }, { "date": "Mon, 02 Sep 2024 15:14:36 GMT", "version": "0.74.2", diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.d.ts b/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.d.ts new file mode 100644 index 00000000000..4faa1daf97f --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.d.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Beachball change file detection and version bump invocation. + * + * @format + */ +/** + * Check whether there are pending beachball change files in the repo. + * + * Beachball stores change files as JSON in the `change/` directory at the + * repo root. If there are any .json files there, there are pending changes. + */ +export declare function hasChangeFiles(repoRoot: string): boolean; +/** + * Run beachball bump to consume change files and update versions/changelogs. + * + * Invokes: npx beachball bump --branch / --yes --verbose + * + * The --yes flag suppresses prompts. + * The --verbose flag provides detailed output. + * The --branch flag tells beachball the baseline branch to diff against. + */ +export declare function bumpVersions(opts: { + targetBranch: string; + remote: string; + cwd: string; +}): Promise; diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js b/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js new file mode 100644 index 00000000000..aa99be67fae --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js @@ -0,0 +1,46 @@ +"use strict"; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Beachball change file detection and version bump invocation. + * + * @format + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.bumpVersions = exports.hasChangeFiles = void 0; +const fs_1 = __importDefault(require("@react-native-windows/fs")); +const path_1 = __importDefault(require("path")); +const proc_1 = require("./proc"); +/** + * Check whether there are pending beachball change files in the repo. + * + * Beachball stores change files as JSON in the `change/` directory at the + * repo root. If there are any .json files there, there are pending changes. + */ +function hasChangeFiles(repoRoot) { + const changeDir = path_1.default.join(repoRoot, 'change'); + if (!fs_1.default.existsSync(changeDir)) { + return false; + } + const entries = fs_1.default.readdirSync(changeDir); + return entries.some(entry => entry.endsWith('.json')); +} +exports.hasChangeFiles = hasChangeFiles; +/** + * Run beachball bump to consume change files and update versions/changelogs. + * + * Invokes: npx beachball bump --branch / --yes --verbose + * + * The --yes flag suppresses prompts. + * The --verbose flag provides detailed output. + * The --branch flag tells beachball the baseline branch to diff against. + */ +async function bumpVersions(opts) { + await (0, proc_1.exec)(`npx beachball bump --branch ${opts.remote}/${opts.targetBranch} --yes --verbose`, { cwd: opts.cwd }); +} +exports.bumpVersions = bumpVersions; +//# sourceMappingURL=beachballBump.js.map \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js.map b/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js.map new file mode 100644 index 00000000000..a8518993028 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/beachballBump.js.map @@ -0,0 +1 @@ +{"version":3,"file":"beachballBump.js","sourceRoot":"","sources":["../src/beachballBump.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;AAEH,kEAA0C;AAC1C,gDAAwB;AAExB,iCAA4B;AAE5B;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,QAAgB;IAC7C,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE;QAC7B,OAAO,KAAK,CAAC;KACd;IAED,MAAM,OAAO,GAAG,YAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAC1C,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACxD,CAAC;AARD,wCAQC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,YAAY,CAAC,IAIlC;IACC,MAAM,IAAA,WAAI,EACR,+BAA+B,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,YAAY,kBAAkB,EACjF,EAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAC,CAChB,CAAC;AACJ,CAAC;AATD,oCASC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * Beachball change file detection and version bump invocation.\n *\n * @format\n */\n\nimport fs from '@react-native-windows/fs';\nimport path from 'path';\n\nimport {exec} from './proc';\n\n/**\n * Check whether there are pending beachball change files in the repo.\n *\n * Beachball stores change files as JSON in the `change/` directory at the\n * repo root. If there are any .json files there, there are pending changes.\n */\nexport function hasChangeFiles(repoRoot: string): boolean {\n const changeDir = path.join(repoRoot, 'change');\n if (!fs.existsSync(changeDir)) {\n return false;\n }\n\n const entries = fs.readdirSync(changeDir);\n return entries.some(entry => entry.endsWith('.json'));\n}\n\n/**\n * Run beachball bump to consume change files and update versions/changelogs.\n *\n * Invokes: npx beachball bump --branch / --yes --verbose\n *\n * The --yes flag suppresses prompts.\n * The --verbose flag provides detailed output.\n * The --branch flag tells beachball the baseline branch to diff against.\n */\nexport async function bumpVersions(opts: {\n targetBranch: string;\n remote: string;\n cwd: string;\n}): Promise {\n await exec(\n `npx beachball bump --branch ${opts.remote}/${opts.targetBranch} --yes --verbose`,\n {cwd: opts.cwd},\n );\n}\n"]} \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/git.d.ts b/packages/@rnw-scripts/prepare-release/lib-commonjs/git.d.ts new file mode 100644 index 00000000000..f13f69c0413 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/git.d.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Git operations module. Provides a typed wrapper around git CLI commands. + * Simplified from fork-sync/src/modules/git.ts. + * + * @format + */ +/** + * Typed wrapper around a git repository directory. + * All methods execute git commands with cwd set to the wrapped directory. + */ +export declare class GitRepo { + readonly dir: string; + constructor(dir: string); + /** Fetch from a remote. */ + fetch(remote: string): Promise; + /** Checkout an existing ref (branch, tag, or commit). */ + checkout(ref: string): Promise; + /** + * Create or reset a branch to a start point. + * Uses `git checkout -B` which creates the branch if it doesn't exist, + * or resets it if it does. + */ + checkoutNewBranch(name: string, startPoint?: string): Promise; + /** Stage all changes (git add --all). */ + stageAll(): Promise; + /** Create a commit with the given message. */ + commit(message: string): Promise; + /** Push a branch to a remote, optionally with --force. */ + push(remote: string, branch: string, opts?: { + force?: boolean; + }): Promise; + /** Resolve a ref to its SHA (git rev-parse). */ + revParse(ref: string): Promise; + /** Get the current branch name, or 'HEAD' if in detached state. */ + currentBranch(): Promise; + /** Check for uncommitted changes (git status --porcelain). */ + statusPorcelain(pathspec?: string): Promise; + /** List all remotes with their URLs (git remote -v). */ + remoteList(): Promise; + /** Git log with format and optional range. */ + log(opts: { + format: string; + range?: string; + }): Promise; + /** Get file names changed between two refs, optionally filtered by path. */ + diffNameOnly(ref1: string, ref2?: string, pathspec?: string): Promise; + /** Check if a branch exists on a remote. Returns the ls-remote output or empty. */ + lsRemote(remote: string, branch: string): Promise; + /** Internal helper: run git with the repo's cwd. */ + private git; +} diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/git.js b/packages/@rnw-scripts/prepare-release/lib-commonjs/git.js new file mode 100644 index 00000000000..ad25c37033c --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/git.js @@ -0,0 +1,107 @@ +"use strict"; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Git operations module. Provides a typed wrapper around git CLI commands. + * Simplified from fork-sync/src/modules/git.ts. + * + * @format + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GitRepo = void 0; +const proc_1 = require("./proc"); +/** + * Typed wrapper around a git repository directory. + * All methods execute git commands with cwd set to the wrapped directory. + */ +class GitRepo { + constructor(dir) { + this.dir = dir; + } + /** Fetch from a remote. */ + async fetch(remote) { + await this.git('fetch', remote); + } + /** Checkout an existing ref (branch, tag, or commit). */ + async checkout(ref) { + await this.git('checkout', ref); + } + /** + * Create or reset a branch to a start point. + * Uses `git checkout -B` which creates the branch if it doesn't exist, + * or resets it if it does. + */ + async checkoutNewBranch(name, startPoint) { + const args = ['checkout', '-B', name]; + if (startPoint) { + args.push(startPoint); + } + await this.git(...args); + } + /** Stage all changes (git add --all). */ + async stageAll() { + await this.git('add', '--all'); + } + /** Create a commit with the given message. */ + async commit(message) { + await this.git('commit', '-m', message); + } + /** Push a branch to a remote, optionally with --force. */ + async push(remote, branch, opts) { + const args = ['push', remote, branch]; + if (opts?.force) { + args.push('--force'); + } + await this.git(...args); + } + /** Resolve a ref to its SHA (git rev-parse). */ + async revParse(ref) { + return this.git('rev-parse', ref); + } + /** Get the current branch name, or 'HEAD' if in detached state. */ + async currentBranch() { + return this.git('rev-parse', '--abbrev-ref', 'HEAD'); + } + /** Check for uncommitted changes (git status --porcelain). */ + async statusPorcelain(pathspec) { + const args = ['status', '--porcelain']; + if (pathspec) { + args.push('--', pathspec); + } + return this.git(...args); + } + /** List all remotes with their URLs (git remote -v). */ + async remoteList() { + return this.git('remote', '-v'); + } + /** Git log with format and optional range. */ + async log(opts) { + const args = ['log', `--format=${opts.format}`]; + if (opts.range) { + args.push(opts.range); + } + return this.git(...args); + } + /** Get file names changed between two refs, optionally filtered by path. */ + async diffNameOnly(ref1, ref2, pathspec) { + const args = ['diff', '--name-only', ref1]; + if (ref2) { + args.push(ref2); + } + if (pathspec) { + args.push('--', pathspec); + } + return this.git(...args); + } + /** Check if a branch exists on a remote. Returns the ls-remote output or empty. */ + async lsRemote(remote, branch) { + return this.git('ls-remote', '--heads', remote, `refs/heads/${branch}`); + } + /** Internal helper: run git with the repo's cwd. */ + async git(...args) { + return (0, proc_1.spawn)('git', args, { cwd: this.dir }); + } +} +exports.GitRepo = GitRepo; +//# sourceMappingURL=git.js.map \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/git.js.map b/packages/@rnw-scripts/prepare-release/lib-commonjs/git.js.map new file mode 100644 index 00000000000..0a7a8555273 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/git.js.map @@ -0,0 +1 @@ +{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,iCAA6B;AAE7B;;;GAGG;AACH,MAAa,OAAO;IAGlB,YAAY,GAAW;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,2BAA2B;IAC3B,KAAK,CAAC,KAAK,CAAC,MAAc;QACxB,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,iBAAiB,CAAC,IAAY,EAAE,UAAmB;QACvD,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACtC,IAAI,UAAU,EAAE;YACd,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;SACvB;QACD,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,yCAAyC;IACzC,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,MAAM,CAAC,OAAe;QAC1B,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,0DAA0D;IAC1D,KAAK,CAAC,IAAI,CACR,MAAc,EACd,MAAc,EACd,IAAwB;QAExB,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,IAAI,IAAI,EAAE,KAAK,EAAE;YACf,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;SACtB;QACD,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,gDAAgD;IAChD,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,mEAAmE;IACnE,KAAK,CAAC,aAAa;QACjB,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,eAAe,CAAC,QAAiB;QACrC,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACvC,IAAI,QAAQ,EAAE;YACZ,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;SAC3B;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,UAAU;QACd,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,GAAG,CAAC,IAAsC;QAC9C,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,YAAY,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,KAAK,EAAE;YACd,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACvB;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,YAAY,CAChB,IAAY,EACZ,IAAa,EACb,QAAiB;QAEjB,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACjB;QACD,IAAI,QAAQ,EAAE;YACZ,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;SAC3B;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,MAAc;QAC3C,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,oDAAoD;IAC5C,KAAK,CAAC,GAAG,CAAC,GAAG,IAAc;QACjC,OAAO,IAAA,YAAK,EAAC,KAAK,EAAE,IAAI,EAAE,EAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAC,CAAC,CAAC;IAC7C,CAAC;CACF;AA/GD,0BA+GC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * Git operations module. Provides a typed wrapper around git CLI commands.\n * Simplified from fork-sync/src/modules/git.ts.\n *\n * @format\n */\n\nimport {spawn} from './proc';\n\n/**\n * Typed wrapper around a git repository directory.\n * All methods execute git commands with cwd set to the wrapped directory.\n */\nexport class GitRepo {\n readonly dir: string;\n\n constructor(dir: string) {\n this.dir = dir;\n }\n\n /** Fetch from a remote. */\n async fetch(remote: string): Promise {\n await this.git('fetch', remote);\n }\n\n /** Checkout an existing ref (branch, tag, or commit). */\n async checkout(ref: string): Promise {\n await this.git('checkout', ref);\n }\n\n /**\n * Create or reset a branch to a start point.\n * Uses `git checkout -B` which creates the branch if it doesn't exist,\n * or resets it if it does.\n */\n async checkoutNewBranch(name: string, startPoint?: string): Promise {\n const args = ['checkout', '-B', name];\n if (startPoint) {\n args.push(startPoint);\n }\n await this.git(...args);\n }\n\n /** Stage all changes (git add --all). */\n async stageAll(): Promise {\n await this.git('add', '--all');\n }\n\n /** Create a commit with the given message. */\n async commit(message: string): Promise {\n await this.git('commit', '-m', message);\n }\n\n /** Push a branch to a remote, optionally with --force. */\n async push(\n remote: string,\n branch: string,\n opts?: {force?: boolean},\n ): Promise {\n const args = ['push', remote, branch];\n if (opts?.force) {\n args.push('--force');\n }\n await this.git(...args);\n }\n\n /** Resolve a ref to its SHA (git rev-parse). */\n async revParse(ref: string): Promise {\n return this.git('rev-parse', ref);\n }\n\n /** Get the current branch name, or 'HEAD' if in detached state. */\n async currentBranch(): Promise {\n return this.git('rev-parse', '--abbrev-ref', 'HEAD');\n }\n\n /** Check for uncommitted changes (git status --porcelain). */\n async statusPorcelain(pathspec?: string): Promise {\n const args = ['status', '--porcelain'];\n if (pathspec) {\n args.push('--', pathspec);\n }\n return this.git(...args);\n }\n\n /** List all remotes with their URLs (git remote -v). */\n async remoteList(): Promise {\n return this.git('remote', '-v');\n }\n\n /** Git log with format and optional range. */\n async log(opts: {format: string; range?: string}): Promise {\n const args = ['log', `--format=${opts.format}`];\n if (opts.range) {\n args.push(opts.range);\n }\n return this.git(...args);\n }\n\n /** Get file names changed between two refs, optionally filtered by path. */\n async diffNameOnly(\n ref1: string,\n ref2?: string,\n pathspec?: string,\n ): Promise {\n const args = ['diff', '--name-only', ref1];\n if (ref2) {\n args.push(ref2);\n }\n if (pathspec) {\n args.push('--', pathspec);\n }\n return this.git(...args);\n }\n\n /** Check if a branch exists on a remote. Returns the ls-remote output or empty. */\n async lsRemote(remote: string, branch: string): Promise {\n return this.git('ls-remote', '--heads', remote, `refs/heads/${branch}`);\n }\n\n /** Internal helper: run git with the repo's cwd. */\n private async git(...args: string[]): Promise {\n return spawn('git', args, {cwd: this.dir});\n }\n}\n"]} \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/github.d.ts b/packages/@rnw-scripts/prepare-release/lib-commonjs/github.d.ts new file mode 100644 index 00000000000..10aa9eeaa11 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/github.d.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * GitHub operations via the gh CLI. + * + * @format + */ +export interface PRInfo { + number: number; + url: string; +} +/** + * Find an existing open PR by head branch name. + * Returns null if no matching PR exists. + */ +export declare function findPR(opts: { + head: string; + cwd: string; + repo?: string; +}): Promise; +/** + * Create a new pull request. Returns the PR number and URL. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +export declare function createPR(opts: { + head: string; + base: string; + title: string; + body: string; + cwd: string; + repo?: string; +}): Promise; +/** + * Update an existing pull request's body text. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +export declare function updatePR(opts: { + number: number; + title: string; + body: string; + cwd: string; + repo?: string; +}): Promise; diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/github.js b/packages/@rnw-scripts/prepare-release/lib-commonjs/github.js new file mode 100644 index 00000000000..3214723db82 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/github.js @@ -0,0 +1,103 @@ +"use strict"; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * GitHub operations via the gh CLI. + * + * @format + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.updatePR = exports.createPR = exports.findPR = void 0; +const fs_1 = __importDefault(require("@react-native-windows/fs")); +const os_1 = __importDefault(require("os")); +const path_1 = __importDefault(require("path")); +const proc_1 = require("./proc"); +/** + * Run a callback with a temporary file containing the given content. + * The file is cleaned up after the callback completes (success or failure). + */ +async function withTempFile(content, fn) { + const tmpFile = path_1.default.join(os_1.default.tmpdir(), `gh-pr-body-${process.pid}-${Date.now()}.md`); + fs_1.default.writeFileSync(tmpFile, content, 'utf8'); + try { + return await fn(tmpFile); + } + finally { + try { + fs_1.default.unlinkSync(tmpFile); + } + catch { + // Ignore cleanup errors + } + } +} +/** + * Find an existing open PR by head branch name. + * Returns null if no matching PR exists. + */ +async function findPR(opts) { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + const result = await (0, proc_1.exec)(`gh pr list --head "${opts.head}" --json number,url --limit 1${repoFlag}`, { cwd: opts.cwd, fallback: '[]' }); + let prs; + try { + prs = JSON.parse(result); + } + catch { + return null; + } + if (!Array.isArray(prs) || prs.length === 0) { + return null; + } + return { number: prs[0].number, url: prs[0].url }; +} +exports.findPR = findPR; +/** + * Create a new pull request. Returns the PR number and URL. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +async function createPR(opts) { + return withTempFile(opts.body, async (bodyFile) => { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + const result = await (0, proc_1.exec)(`gh pr create --head "${opts.head}" --base "${opts.base}"` + + ` --title "${escapeForShell(opts.title)}"` + + ` --body-file "${bodyFile}"${repoFlag}`, { cwd: opts.cwd }); + // gh pr create outputs the PR URL on success + const url = result.trim(); + const match = url.match(/\/pull\/(\d+)/); + const number = match ? parseInt(match[1], 10) : 0; + return { number, url }; + }); +} +exports.createPR = createPR; +/** + * Update an existing pull request's body text. + * + * Uses --body-file with a temp file to avoid shell escaping issues + * with multiline markdown content on Windows cmd.exe. + */ +async function updatePR(opts) { + await withTempFile(opts.body, async (bodyFile) => { + const repoFlag = opts.repo ? ` --repo "${opts.repo}"` : ''; + await (0, proc_1.exec)(`gh pr edit ${opts.number} --title "${escapeForShell(opts.title)}"` + + ` --body-file "${bodyFile}"${repoFlag}`, { cwd: opts.cwd }); + }); +} +exports.updatePR = updatePR; +/** + * Escape a string for safe inclusion in a double-quoted shell argument. + * Used for single-line values like titles; multiline content uses --body-file. + */ +function escapeForShell(str) { + return str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\$/g, '\\$') + .replace(/`/g, '\\`'); +} +//# sourceMappingURL=github.js.map \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/github.js.map b/packages/@rnw-scripts/prepare-release/lib-commonjs/github.js.map new file mode 100644 index 00000000000..717046f8945 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/github.js.map @@ -0,0 +1 @@ +{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;AAEH,kEAA0C;AAC1C,4CAAoB;AACpB,gDAAwB;AAExB,iCAA4B;AAO5B;;;GAGG;AACH,KAAK,UAAU,YAAY,CACzB,OAAe,EACf,EAAoC;IAEpC,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CACvB,YAAE,CAAC,MAAM,EAAE,EACX,cAAc,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,KAAK,CAC7C,CAAC;IACF,YAAE,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC3C,IAAI;QACF,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;KAC1B;YAAS;QACR,IAAI;YACF,YAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;SACxB;QAAC,MAAM;YACN,wBAAwB;SACzB;KACF;AACH,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,MAAM,CAAC,IAI5B;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,MAAM,MAAM,GAAG,MAAM,IAAA,WAAI,EACvB,sBAAsB,IAAI,CAAC,IAAI,gCAAgC,QAAQ,EAAE,EACzE,EAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAC,CAChC,CAAC;IAEF,IAAI,GAAyC,CAAC;IAC9C,IAAI;QACF,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;KAC1B;IAAC,MAAM;QACN,OAAO,IAAI,CAAC;KACb;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE;QAC3C,OAAO,IAAI,CAAC;KACb;IAED,OAAO,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAC,CAAC;AAClD,CAAC;AAvBD,wBAuBC;AAED;;;;;GAKG;AACI,KAAK,UAAU,QAAQ,CAAC,IAO9B;IACC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAC,QAAQ,EAAC,EAAE;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,MAAM,IAAA,WAAI,EACvB,wBAAwB,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,IAAI,GAAG;YACxD,aAAa,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG;YAC1C,iBAAiB,QAAQ,IAAI,QAAQ,EAAE,EACzC,EAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAC,CAChB,CAAC;QAEF,6CAA6C;QAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAElD,OAAO,EAAC,MAAM,EAAE,GAAG,EAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC;AAxBD,4BAwBC;AAED;;;;;GAKG;AACI,KAAK,UAAU,QAAQ,CAAC,IAM9B;IACC,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAC,QAAQ,EAAC,EAAE;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAA,WAAI,EACR,cAAc,IAAI,CAAC,MAAM,aAAa,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG;YACjE,iBAAiB,QAAQ,IAAI,QAAQ,EAAE,EACzC,EAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAC,CAChB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAfD,4BAeC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,GAAW;IACjC,OAAO,GAAG;SACP,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC1B,CAAC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * GitHub operations via the gh CLI.\n *\n * @format\n */\n\nimport fs from '@react-native-windows/fs';\nimport os from 'os';\nimport path from 'path';\n\nimport {exec} from './proc';\n\nexport interface PRInfo {\n number: number;\n url: string;\n}\n\n/**\n * Run a callback with a temporary file containing the given content.\n * The file is cleaned up after the callback completes (success or failure).\n */\nasync function withTempFile(\n content: string,\n fn: (filePath: string) => Promise,\n): Promise {\n const tmpFile = path.join(\n os.tmpdir(),\n `gh-pr-body-${process.pid}-${Date.now()}.md`,\n );\n fs.writeFileSync(tmpFile, content, 'utf8');\n try {\n return await fn(tmpFile);\n } finally {\n try {\n fs.unlinkSync(tmpFile);\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Find an existing open PR by head branch name.\n * Returns null if no matching PR exists.\n */\nexport async function findPR(opts: {\n head: string;\n cwd: string;\n repo?: string;\n}): Promise {\n const repoFlag = opts.repo ? ` --repo \"${opts.repo}\"` : '';\n const result = await exec(\n `gh pr list --head \"${opts.head}\" --json number,url --limit 1${repoFlag}`,\n {cwd: opts.cwd, fallback: '[]'},\n );\n\n let prs: Array<{number: number; url: string}>;\n try {\n prs = JSON.parse(result);\n } catch {\n return null;\n }\n\n if (!Array.isArray(prs) || prs.length === 0) {\n return null;\n }\n\n return {number: prs[0].number, url: prs[0].url};\n}\n\n/**\n * Create a new pull request. Returns the PR number and URL.\n *\n * Uses --body-file with a temp file to avoid shell escaping issues\n * with multiline markdown content on Windows cmd.exe.\n */\nexport async function createPR(opts: {\n head: string;\n base: string;\n title: string;\n body: string;\n cwd: string;\n repo?: string;\n}): Promise {\n return withTempFile(opts.body, async bodyFile => {\n const repoFlag = opts.repo ? ` --repo \"${opts.repo}\"` : '';\n const result = await exec(\n `gh pr create --head \"${opts.head}\" --base \"${opts.base}\"` +\n ` --title \"${escapeForShell(opts.title)}\"` +\n ` --body-file \"${bodyFile}\"${repoFlag}`,\n {cwd: opts.cwd},\n );\n\n // gh pr create outputs the PR URL on success\n const url = result.trim();\n const match = url.match(/\\/pull\\/(\\d+)/);\n const number = match ? parseInt(match[1], 10) : 0;\n\n return {number, url};\n });\n}\n\n/**\n * Update an existing pull request's body text.\n *\n * Uses --body-file with a temp file to avoid shell escaping issues\n * with multiline markdown content on Windows cmd.exe.\n */\nexport async function updatePR(opts: {\n number: number;\n title: string;\n body: string;\n cwd: string;\n repo?: string;\n}): Promise {\n await withTempFile(opts.body, async bodyFile => {\n const repoFlag = opts.repo ? ` --repo \"${opts.repo}\"` : '';\n await exec(\n `gh pr edit ${opts.number} --title \"${escapeForShell(opts.title)}\"` +\n ` --body-file \"${bodyFile}\"${repoFlag}`,\n {cwd: opts.cwd},\n );\n });\n}\n\n/**\n * Escape a string for safe inclusion in a double-quoted shell argument.\n * Used for single-line values like titles; multiline content uses --body-file.\n */\nfunction escapeForShell(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\$/g, '\\\\$')\n .replace(/`/g, '\\\\`');\n}\n"]} \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.d.ts b/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.d.ts new file mode 100644 index 00000000000..2651c96ba94 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.d.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * This script automates the version bump PR workflow. It checks for pending + * beachball change files, bumps versions, and creates or updates a + * "Version Packages" pull request. + * + * Usage: + * npx prepare-release --branch [--dry-run] [--no-color] [--help] + * + * @format + */ +export {}; diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js b/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js new file mode 100644 index 00000000000..d03ad90d396 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js @@ -0,0 +1,308 @@ +"use strict"; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * This script automates the version bump PR workflow. It checks for pending + * beachball change files, bumps versions, and creates or updates a + * "Version Packages" pull request. + * + * Usage: + * npx prepare-release --branch [--dry-run] [--no-color] [--help] + * + * @format + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const node_util_1 = require("node:util"); +const fs_1 = __importDefault(require("@react-native-windows/fs")); +const path_1 = __importDefault(require("path")); +const find_repo_root_1 = __importDefault(require("@react-native-windows/find-repo-root")); +const git_1 = require("./git"); +const github_1 = require("./github"); +const beachballBump_1 = require("./beachballBump"); +const releaseSummary_1 = require("./releaseSummary"); +// --------------------------------------------------------------------------- +// Color utilities (from npmPack.js pattern) +// --------------------------------------------------------------------------- +const ansi = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + gray: '\x1b[90m', +}; +let useColors = true; +function colorize(text, color) { + if (!useColors) { + return text; + } + return color + text + ansi.reset; +} +// --------------------------------------------------------------------------- +// CLI help +// --------------------------------------------------------------------------- +function showHelp() { + console.log(` +prepare-release - Automate version bump PRs using beachball + +Usage: + npx prepare-release --branch [options] + +Options: + --branch Target branch to prepare release for (required) + --dry-run Do everything except push and PR create/update + --no-color Disable colored output + --help, -h Show this help message + +Examples: + npx prepare-release --branch main + npx prepare-release --branch 0.76-stable --dry-run +`); +} +// --------------------------------------------------------------------------- +// Remote detection +// --------------------------------------------------------------------------- +/** + * Normalize a git URL to a canonical form for comparison. + * + * Handles SSH (git@github.com:org/repo.git), HTTPS, with/without .git suffix. + * Output: lowercase "github.com/org/repo" + */ +function normalizeGitUrl(url) { + let normalized = url; + // Strip trailing .git + normalized = normalized.replace(/\.git$/, ''); + // Convert SSH format: git@github.com:org/repo -> github.com/org/repo + normalized = normalized.replace(/^git@([^:]+):/, '$1/'); + // Convert HTTPS format: https://github.com/org/repo -> github.com/org/repo + normalized = normalized.replace(/^https?:\/\//, ''); + // Lowercase for case-insensitive comparison + normalized = normalized.toLowerCase(); + // Strip trailing slash + normalized = normalized.replace(/\/$/, ''); + return normalized; +} +/** + * Extract "owner/repo" from a git URL for use with `gh --repo`. + * + * E.g. "git@github.com:microsoft/react-native-windows.git" + * -> "microsoft/react-native-windows" + */ +function extractGitHubRepo(url) { + const normalized = normalizeGitUrl(url); + // normalized is like "github.com/owner/repo" + const match = normalized.match(/github\.com\/(.+)/); + if (!match) { + throw new Error(`Could not extract GitHub owner/repo from "${url}"`); + } + return match[1]; +} +/** + * Detect which git remote matches the repository URL from package.json. + * + * Parses `git remote -v` output and matches against the normalized repo URL. + * On CI this is typically "origin"; on developer machines it may differ. + */ +async function detectRemote(git, repoUrl) { + const canonical = normalizeGitUrl(repoUrl); + const remoteOutput = await git.remoteList(); + for (const line of remoteOutput.split('\n')) { + // Lines: "origin\thttps://github.com/microsoft/react-native-windows.git (fetch)" + const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)/); + if (!match) { + continue; + } + const remoteName = match[1]; + const remoteUrl = match[2]; + if (normalizeGitUrl(remoteUrl) === canonical) { + return remoteName; + } + } + throw new Error(`Could not find a git remote matching "${repoUrl}". ` + + 'Run "git remote -v" and verify the repository URL in root package.json.'); +} +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- +(async () => { + // 1. Parse CLI arguments + const { values } = (0, node_util_1.parseArgs)({ + options: { + branch: { type: 'string' }, + 'dry-run': { type: 'boolean', default: false }, + help: { type: 'boolean', short: 'h', default: false }, + 'no-color': { type: 'boolean', default: false }, + }, + }); + if (values.help) { + showHelp(); + process.exit(0); + } + useColors = !values['no-color']; + const targetBranch = values.branch; + if (!targetBranch) { + console.error(colorize('Error: --branch is required', ansi.red)); + showHelp(); + process.exit(1); + } + const dryRun = values['dry-run']; + if (dryRun) { + console.log(colorize('[DRY RUN MODE]', ansi.yellow)); + } + try { + // 2. Find repo root + const repoRoot = await (0, find_repo_root_1.default)(); + console.log(`${colorize('Repository root:', ansi.bright)} ${repoRoot}`); + // 3. Read repository URL from root package.json + const rootPkgJsonPath = path_1.default.join(repoRoot, 'package.json'); + const rootPkgJson = JSON.parse(fs_1.default.readFileSync(rootPkgJsonPath, 'utf8')); + const repoUrl = rootPkgJson.repository?.url ?? ''; + if (!repoUrl) { + throw new Error('Could not find repository.url in root package.json'); + } + console.log(`${colorize('Repository URL:', ansi.bright)} ${repoUrl}`); + // 4. Detect git remote name + const git = new git_1.GitRepo(repoRoot); + const remoteName = await detectRemote(git, repoUrl); + console.log(`${colorize('Git remote:', ansi.bright)} ${remoteName}`); + // 4b. Extract GitHub owner/repo for gh CLI --repo flag + const githubRepo = extractGitHubRepo(repoUrl); + console.log(`${colorize('GitHub repo:', ansi.bright)} ${githubRepo}`); + // 5. Fetch from remote + console.log(colorize(`Fetching from ${remoteName}...`, ansi.dim)); + await git.fetch(remoteName); + // 6. Check for pending change files + console.log(colorize('Checking for change files...', ansi.dim)); + if (!(0, beachballBump_1.hasChangeFiles)(repoRoot)) { + console.log(colorize('No pending change files found. Nothing to do.', ansi.green)); + process.exit(0); + } + console.log(colorize('Found pending change files.', ansi.green)); + // 7. Check for existing PR + const prBranch = `prepare-release/${targetBranch}`; + console.log(colorize(`Looking for existing PR from ${prBranch}...`, ansi.dim)); + const existingPR = await (0, github_1.findPR)({ + head: prBranch, + cwd: repoRoot, + repo: githubRepo, + }); + if (existingPR) { + console.log(`${colorize('Found existing PR:', ansi.bright)} #${existingPR.number} (${existingPR.url})`); + } + else { + console.log(colorize('No existing PR found. Will create one.', ansi.dim)); + } + // 8. Save original branch so we can restore it when done + const originalBranch = await git.currentBranch(); + console.log(colorize(`Saving current branch: ${originalBranch}`, ansi.dim)); + try { + // 9. Create/reset the prepare-release branch from target branch HEAD + console.log(colorize(`Creating branch ${prBranch} from ${remoteName}/${targetBranch}...`, ansi.dim)); + await git.checkoutNewBranch(prBranch, `${remoteName}/${targetBranch}`); + // 10. Run beachball bump + console.log(colorize('Running beachball bump...', ansi.bright)); + await (0, beachballBump_1.bumpVersions)({ + targetBranch, + remote: remoteName, + cwd: repoRoot, + }); + // 11. Check if beachball actually changed anything + const status = await git.statusPorcelain(); + if (!status) { + console.log(colorize('beachball bump made no changes. Nothing to commit.', ansi.yellow)); + process.exit(0); + } + // 12. Collect bumped package info for PR description + // Parse changed package.json paths from git status --porcelain output + const changedFiles = status + .split('\n') + .map(line => line.trim().split(/\s+/).pop()) + .filter(f => f.endsWith('package.json')); + const bumpedPackages = (0, releaseSummary_1.collectBumpedPackages)(changedFiles, repoRoot); + console.log((0, releaseSummary_1.generateConsoleSummary)(bumpedPackages)); + // 13. Stage all + commit + const commitMessage = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`; + console.log(colorize(`Committing: "${commitMessage}"...`, ansi.dim)); + await git.stageAll(); + await git.commit(commitMessage); + // 14. Force-push the branch + if (dryRun) { + console.log(colorize(`[DRY RUN] Would force-push ${prBranch} to ${remoteName}`, ansi.yellow)); + } + else { + console.log(colorize(`Force-pushing ${prBranch} to ${remoteName}...`, ansi.dim)); + await git.push(remoteName, prBranch, { force: true }); + // Verify the branch landed on the remote + const lsRemoteOut = await git.lsRemote(remoteName, prBranch); + if (!lsRemoteOut) { + throw new Error(`Push verification failed: branch "${prBranch}" not found on ` + + `"${remoteName}" after push. Check your push permissions.`); + } + console.log(colorize(`Push verified: ${prBranch} exists on ${remoteName}`, ansi.green)); + } + // 15. Create or update PR + const prTitle = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`; + const prBody = (0, releaseSummary_1.generatePRBody)(targetBranch, bumpedPackages); + if (existingPR) { + if (dryRun) { + console.log(colorize(`[DRY RUN] Would update PR #${existingPR.number}`, ansi.yellow)); + } + else { + console.log(colorize(`Updating PR #${existingPR.number}...`, ansi.dim)); + await (0, github_1.updatePR)({ + number: existingPR.number, + title: prTitle, + body: prBody, + cwd: repoRoot, + repo: githubRepo, + }); + console.log(`${colorize('PR updated:', ansi.green)} ${existingPR.url}`); + } + } + else { + if (dryRun) { + console.log(colorize(`[DRY RUN] Would create PR: "${prTitle}"`, ansi.yellow)); + } + else { + console.log(colorize('Creating pull request...', ansi.dim)); + const newPR = await (0, github_1.createPR)({ + head: prBranch, + base: targetBranch, + title: prTitle, + body: prBody, + cwd: repoRoot, + repo: githubRepo, + }); + console.log(`${colorize('PR created:', ansi.green)} ${newPR.url}`); + } + } + // 16. Done + console.log(''); + console.log(colorize('Done!', ansi.green + ansi.bright)); + if (dryRun) { + console.log(colorize('\nPR body that would be used:', ansi.dim)); + console.log(prBody); + } + } + finally { + // Always restore the original branch + if (originalBranch && originalBranch !== 'HEAD') { + console.log(colorize(`Restoring original branch: ${originalBranch}`, ansi.dim)); + await git.checkout(originalBranch); + } + } + } + catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`${colorize('Error:', ansi.red)} ${message}`); + process.exit(1); + } +})(); +//# sourceMappingURL=prepareRelease.js.map \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js.map b/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js.map new file mode 100644 index 00000000000..ca8fd30b665 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/prepareRelease.js.map @@ -0,0 +1 @@ +{"version":3,"file":"prepareRelease.js","sourceRoot":"","sources":["../src/prepareRelease.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;;;AAEH,yCAAoC;AACpC,kEAA0C;AAC1C,gDAAwB;AAExB,0FAAgE;AAEhE,+BAA8B;AAC9B,qCAAoD;AACpD,mDAA6D;AAC7D,qDAI0B;AAE1B,8EAA8E;AAC9E,4CAA4C;AAC5C,8EAA8E;AAE9E,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,UAAU;IACf,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU;CACjB,CAAC;AAEF,IAAI,SAAS,GAAG,IAAI,CAAC;AAErB,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAa;IAC3C,IAAI,CAAC,SAAS,EAAE;QACd,OAAO,IAAI,CAAC;KACb;IACD,OAAO,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;AACnC,CAAC;AAED,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,SAAS,QAAQ;IACf,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;CAeb,CAAC,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E;;;;;GAKG;AACH,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,UAAU,GAAG,GAAG,CAAC;IAErB,sBAAsB;IACtB,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAE9C,qEAAqE;IACrE,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IAExD,2EAA2E;IAC3E,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAEpD,4CAA4C;IAC5C,UAAU,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IAEtC,uBAAuB;IACvB,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAE3C,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,GAAW;IACpC,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACxC,6CAA6C;IAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,EAAE;QACV,MAAM,IAAI,KAAK,CAAC,6CAA6C,GAAG,GAAG,CAAC,CAAC;KACtE;IACD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,YAAY,CAAC,GAAY,EAAE,OAAe;IACvD,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,UAAU,EAAE,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;QAC3C,iFAAiF;QACjF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,EAAE;YACV,SAAS;SACV;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAE3B,IAAI,eAAe,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE;YAC5C,OAAO,UAAU,CAAC;SACnB;KACF;IAED,MAAM,IAAI,KAAK,CACb,yCAAyC,OAAO,KAAK;QACnD,yEAAyE,CAC5E,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,OAAO;AACP,8EAA8E;AAE9E,CAAC,KAAK,IAAI,EAAE;IACV,yBAAyB;IACzB,MAAM,EAAC,MAAM,EAAC,GAAG,IAAA,qBAAS,EAAC;QACzB,OAAO,EAAE;YACP,MAAM,EAAE,EAAC,IAAI,EAAE,QAAQ,EAAC;YACxB,SAAS,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAC;YAC5C,IAAI,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAC;YACnD,UAAU,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAC;SAC9C;KACF,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,EAAE;QACf,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;IAED,SAAS,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAEhC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,IAAI,CAAC,YAAY,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,6BAA6B,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACjE,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAEjC,IAAI,MAAM,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;KACtD;IAED,IAAI;QACF,oBAAoB;QACpB,MAAM,QAAQ,GAAG,MAAM,IAAA,wBAAY,GAAE,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,kBAAkB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC;QAExE,gDAAgD;QAChD,MAAM,eAAe,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;QACzE,MAAM,OAAO,GAAW,WAAW,CAAC,UAAU,EAAE,GAAG,IAAI,EAAE,CAAC;QAE1D,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;SACvE;QACD,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,iBAAiB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC;QAEtE,4BAA4B;QAC5B,MAAM,GAAG,GAAG,IAAI,aAAO,CAAC,QAAQ,CAAC,CAAC;QAClC,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC;QAErE,uDAAuD;QACvD,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC;QAEtE,uBAAuB;QACvB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,iBAAiB,UAAU,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,MAAM,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAE5B,oCAAoC;QACpC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,8BAA8B,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,IAAA,8BAAc,EAAC,QAAQ,CAAC,EAAE;YAC7B,OAAO,CAAC,GAAG,CACT,QAAQ,CAAC,+CAA+C,EAAE,IAAI,CAAC,KAAK,CAAC,CACtE,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACjB;QACD,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,6BAA6B,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAEjE,2BAA2B;QAC3B,MAAM,QAAQ,GAAG,mBAAmB,YAAY,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CACT,QAAQ,CAAC,gCAAgC,QAAQ,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAClE,CAAC;QACF,MAAM,UAAU,GAAG,MAAM,IAAA,eAAM,EAAC;YAC9B,IAAI,EAAE,QAAQ;YACd,GAAG,EAAE,QAAQ;YACb,IAAI,EAAE,UAAU;SACjB,CAAC,CAAC;QAEH,IAAI,UAAU,EAAE;YACd,OAAO,CAAC,GAAG,CACT,GAAG,QAAQ,CAAC,oBAAoB,EAAE,IAAI,CAAC,MAAM,CAAC,KAC5C,UAAU,CAAC,MACb,KAAK,UAAU,CAAC,GAAG,GAAG,CACvB,CAAC;SACH;aAAM;YACL,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,wCAAwC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;SAC3E;QAED,yDAAyD;QACzD,MAAM,cAAc,GAAG,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,0BAA0B,cAAc,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAE5E,IAAI;YACF,qEAAqE;YACrE,OAAO,CAAC,GAAG,CACT,QAAQ,CACN,mBAAmB,QAAQ,SAAS,UAAU,IAAI,YAAY,KAAK,EACnE,IAAI,CAAC,GAAG,CACT,CACF,CAAC;YACF,MAAM,GAAG,CAAC,iBAAiB,CAAC,QAAQ,EAAE,GAAG,UAAU,IAAI,YAAY,EAAE,CAAC,CAAC;YAEvE,yBAAyB;YACzB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,2BAA2B,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAChE,MAAM,IAAA,4BAAY,EAAC;gBACjB,YAAY;gBACZ,MAAM,EAAE,UAAU;gBAClB,GAAG,EAAE,QAAQ;aACd,CAAC,CAAC;YAEH,mDAAmD;YACnD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,eAAe,EAAE,CAAC;YAC3C,IAAI,CAAC,MAAM,EAAE;gBACX,OAAO,CAAC,GAAG,CACT,QAAQ,CACN,oDAAoD,EACpD,IAAI,CAAC,MAAM,CACZ,CACF,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aACjB;YAED,qDAAqD;YACrD,0EAA0E;YAC1E,MAAM,YAAY,GAAG,MAAM;iBACxB,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,EAAG,CAAC;iBAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;YAE3C,MAAM,cAAc,GAAG,IAAA,sCAAqB,EAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;YACrE,OAAO,CAAC,GAAG,CAAC,IAAA,uCAAsB,EAAC,cAAc,CAAC,CAAC,CAAC;YAEpD,yBAAyB;YACzB,MAAM,aAAa,GAAG,sBAAsB,cAAc,CAAC,MAAM,gBAAgB,YAAY,GAAG,CAAC;YACjG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,aAAa,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACrE,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YAEhC,4BAA4B;YAC5B,IAAI,MAAM,EAAE;gBACV,OAAO,CAAC,GAAG,CACT,QAAQ,CACN,8BAA8B,QAAQ,OAAO,UAAU,EAAE,EACzD,IAAI,CAAC,MAAM,CACZ,CACF,CAAC;aACH;iBAAM;gBACL,OAAO,CAAC,GAAG,CACT,QAAQ,CAAC,iBAAiB,QAAQ,OAAO,UAAU,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CACpE,CAAC;gBACF,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,EAAC,KAAK,EAAE,IAAI,EAAC,CAAC,CAAC;gBAEpD,yCAAyC;gBACzC,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;gBAC7D,IAAI,CAAC,WAAW,EAAE;oBAChB,MAAM,IAAI,KAAK,CACb,qCAAqC,QAAQ,iBAAiB;wBAC5D,IAAI,UAAU,4CAA4C,CAC7D,CAAC;iBACH;gBACD,OAAO,CAAC,GAAG,CACT,QAAQ,CACN,kBAAkB,QAAQ,cAAc,UAAU,EAAE,EACpD,IAAI,CAAC,KAAK,CACX,CACF,CAAC;aACH;YAED,0BAA0B;YAC1B,MAAM,OAAO,GAAG,sBAAsB,cAAc,CAAC,MAAM,gBAAgB,YAAY,GAAG,CAAC;YAC3F,MAAM,MAAM,GAAG,IAAA,+BAAc,EAAC,YAAY,EAAE,cAAc,CAAC,CAAC;YAE5D,IAAI,UAAU,EAAE;gBACd,IAAI,MAAM,EAAE;oBACV,OAAO,CAAC,GAAG,CACT,QAAQ,CACN,8BAA8B,UAAU,CAAC,MAAM,EAAE,EACjD,IAAI,CAAC,MAAM,CACZ,CACF,CAAC;iBACH;qBAAM;oBACL,OAAO,CAAC,GAAG,CACT,QAAQ,CAAC,gBAAgB,UAAU,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAC3D,CAAC;oBACF,MAAM,IAAA,iBAAQ,EAAC;wBACb,MAAM,EAAE,UAAU,CAAC,MAAM;wBACzB,KAAK,EAAE,OAAO;wBACd,IAAI,EAAE,MAAM;wBACZ,GAAG,EAAE,QAAQ;wBACb,IAAI,EAAE,UAAU;qBACjB,CAAC,CAAC;oBACH,OAAO,CAAC,GAAG,CACT,GAAG,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,GAAG,EAAE,CAC3D,CAAC;iBACH;aACF;iBAAM;gBACL,IAAI,MAAM,EAAE;oBACV,OAAO,CAAC,GAAG,CACT,QAAQ,CAAC,+BAA+B,OAAO,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CACjE,CAAC;iBACH;qBAAM;oBACL,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,0BAA0B,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;oBAC5D,MAAM,KAAK,GAAG,MAAM,IAAA,iBAAQ,EAAC;wBAC3B,IAAI,EAAE,QAAQ;wBACd,IAAI,EAAE,YAAY;wBAClB,KAAK,EAAE,OAAO;wBACd,IAAI,EAAE,MAAM;wBACZ,GAAG,EAAE,QAAQ;wBACb,IAAI,EAAE,UAAU;qBACjB,CAAC,CAAC;oBACH,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;iBACpE;aACF;YAED,WAAW;YACX,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAEzD,IAAI,MAAM,EAAE;gBACV,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,+BAA+B,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBACjE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;aACrB;SACF;gBAAS;YACR,qCAAqC;YACrC,IAAI,cAAc,IAAI,cAAc,KAAK,MAAM,EAAE;gBAC/C,OAAO,CAAC,GAAG,CACT,QAAQ,CAAC,8BAA8B,cAAc,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CACnE,CAAC;gBACF,MAAM,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;aACpC;SACF;KACF;IAAC,OAAO,GAAG,EAAE;QACZ,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC;QAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC,CAAC,EAAE,CAAC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * This script automates the version bump PR workflow. It checks for pending\n * beachball change files, bumps versions, and creates or updates a\n * \"Version Packages\" pull request.\n *\n * Usage:\n * npx prepare-release --branch [--dry-run] [--no-color] [--help]\n *\n * @format\n */\n\nimport {parseArgs} from 'node:util';\nimport fs from '@react-native-windows/fs';\nimport path from 'path';\n\nimport findRepoRoot from '@react-native-windows/find-repo-root';\n\nimport {GitRepo} from './git';\nimport {findPR, createPR, updatePR} from './github';\nimport {hasChangeFiles, bumpVersions} from './beachballBump';\nimport {\n collectBumpedPackages,\n generatePRBody,\n generateConsoleSummary,\n} from './releaseSummary';\n\n// ---------------------------------------------------------------------------\n// Color utilities (from npmPack.js pattern)\n// ---------------------------------------------------------------------------\n\nconst ansi = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n};\n\nlet useColors = true;\n\nfunction colorize(text: string, color: string): string {\n if (!useColors) {\n return text;\n }\n return color + text + ansi.reset;\n}\n\n// ---------------------------------------------------------------------------\n// CLI help\n// ---------------------------------------------------------------------------\n\nfunction showHelp(): void {\n console.log(`\nprepare-release - Automate version bump PRs using beachball\n\nUsage:\n npx prepare-release --branch [options]\n\nOptions:\n --branch Target branch to prepare release for (required)\n --dry-run Do everything except push and PR create/update\n --no-color Disable colored output\n --help, -h Show this help message\n\nExamples:\n npx prepare-release --branch main\n npx prepare-release --branch 0.76-stable --dry-run\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Remote detection\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize a git URL to a canonical form for comparison.\n *\n * Handles SSH (git@github.com:org/repo.git), HTTPS, with/without .git suffix.\n * Output: lowercase \"github.com/org/repo\"\n */\nfunction normalizeGitUrl(url: string): string {\n let normalized = url;\n\n // Strip trailing .git\n normalized = normalized.replace(/\\.git$/, '');\n\n // Convert SSH format: git@github.com:org/repo -> github.com/org/repo\n normalized = normalized.replace(/^git@([^:]+):/, '$1/');\n\n // Convert HTTPS format: https://github.com/org/repo -> github.com/org/repo\n normalized = normalized.replace(/^https?:\\/\\//, '');\n\n // Lowercase for case-insensitive comparison\n normalized = normalized.toLowerCase();\n\n // Strip trailing slash\n normalized = normalized.replace(/\\/$/, '');\n\n return normalized;\n}\n\n/**\n * Extract \"owner/repo\" from a git URL for use with `gh --repo`.\n *\n * E.g. \"git@github.com:microsoft/react-native-windows.git\"\n * -> \"microsoft/react-native-windows\"\n */\nfunction extractGitHubRepo(url: string): string {\n const normalized = normalizeGitUrl(url);\n // normalized is like \"github.com/owner/repo\"\n const match = normalized.match(/github\\.com\\/(.+)/);\n if (!match) {\n throw new Error(`Could not extract GitHub owner/repo from \"${url}\"`);\n }\n return match[1];\n}\n\n/**\n * Detect which git remote matches the repository URL from package.json.\n *\n * Parses `git remote -v` output and matches against the normalized repo URL.\n * On CI this is typically \"origin\"; on developer machines it may differ.\n */\nasync function detectRemote(git: GitRepo, repoUrl: string): Promise {\n const canonical = normalizeGitUrl(repoUrl);\n const remoteOutput = await git.remoteList();\n\n for (const line of remoteOutput.split('\\n')) {\n // Lines: \"origin\\thttps://github.com/microsoft/react-native-windows.git (fetch)\"\n const match = line.match(/^(\\S+)\\s+(\\S+)\\s+\\(fetch\\)/);\n if (!match) {\n continue;\n }\n const remoteName = match[1];\n const remoteUrl = match[2];\n\n if (normalizeGitUrl(remoteUrl) === canonical) {\n return remoteName;\n }\n }\n\n throw new Error(\n `Could not find a git remote matching \"${repoUrl}\". ` +\n 'Run \"git remote -v\" and verify the repository URL in root package.json.',\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\n(async () => {\n // 1. Parse CLI arguments\n const {values} = parseArgs({\n options: {\n branch: {type: 'string'},\n 'dry-run': {type: 'boolean', default: false},\n help: {type: 'boolean', short: 'h', default: false},\n 'no-color': {type: 'boolean', default: false},\n },\n });\n\n if (values.help) {\n showHelp();\n process.exit(0);\n }\n\n useColors = !values['no-color'];\n\n const targetBranch = values.branch;\n if (!targetBranch) {\n console.error(colorize('Error: --branch is required', ansi.red));\n showHelp();\n process.exit(1);\n }\n\n const dryRun = values['dry-run'];\n\n if (dryRun) {\n console.log(colorize('[DRY RUN MODE]', ansi.yellow));\n }\n\n try {\n // 2. Find repo root\n const repoRoot = await findRepoRoot();\n console.log(`${colorize('Repository root:', ansi.bright)} ${repoRoot}`);\n\n // 3. Read repository URL from root package.json\n const rootPkgJsonPath = path.join(repoRoot, 'package.json');\n const rootPkgJson = JSON.parse(fs.readFileSync(rootPkgJsonPath, 'utf8'));\n const repoUrl: string = rootPkgJson.repository?.url ?? '';\n\n if (!repoUrl) {\n throw new Error('Could not find repository.url in root package.json');\n }\n console.log(`${colorize('Repository URL:', ansi.bright)} ${repoUrl}`);\n\n // 4. Detect git remote name\n const git = new GitRepo(repoRoot);\n const remoteName = await detectRemote(git, repoUrl);\n console.log(`${colorize('Git remote:', ansi.bright)} ${remoteName}`);\n\n // 4b. Extract GitHub owner/repo for gh CLI --repo flag\n const githubRepo = extractGitHubRepo(repoUrl);\n console.log(`${colorize('GitHub repo:', ansi.bright)} ${githubRepo}`);\n\n // 5. Fetch from remote\n console.log(colorize(`Fetching from ${remoteName}...`, ansi.dim));\n await git.fetch(remoteName);\n\n // 6. Check for pending change files\n console.log(colorize('Checking for change files...', ansi.dim));\n if (!hasChangeFiles(repoRoot)) {\n console.log(\n colorize('No pending change files found. Nothing to do.', ansi.green),\n );\n process.exit(0);\n }\n console.log(colorize('Found pending change files.', ansi.green));\n\n // 7. Check for existing PR\n const prBranch = `prepare-release/${targetBranch}`;\n console.log(\n colorize(`Looking for existing PR from ${prBranch}...`, ansi.dim),\n );\n const existingPR = await findPR({\n head: prBranch,\n cwd: repoRoot,\n repo: githubRepo,\n });\n\n if (existingPR) {\n console.log(\n `${colorize('Found existing PR:', ansi.bright)} #${\n existingPR.number\n } (${existingPR.url})`,\n );\n } else {\n console.log(colorize('No existing PR found. Will create one.', ansi.dim));\n }\n\n // 8. Save original branch so we can restore it when done\n const originalBranch = await git.currentBranch();\n console.log(colorize(`Saving current branch: ${originalBranch}`, ansi.dim));\n\n try {\n // 9. Create/reset the prepare-release branch from target branch HEAD\n console.log(\n colorize(\n `Creating branch ${prBranch} from ${remoteName}/${targetBranch}...`,\n ansi.dim,\n ),\n );\n await git.checkoutNewBranch(prBranch, `${remoteName}/${targetBranch}`);\n\n // 10. Run beachball bump\n console.log(colorize('Running beachball bump...', ansi.bright));\n await bumpVersions({\n targetBranch,\n remote: remoteName,\n cwd: repoRoot,\n });\n\n // 11. Check if beachball actually changed anything\n const status = await git.statusPorcelain();\n if (!status) {\n console.log(\n colorize(\n 'beachball bump made no changes. Nothing to commit.',\n ansi.yellow,\n ),\n );\n process.exit(0);\n }\n\n // 12. Collect bumped package info for PR description\n // Parse changed package.json paths from git status --porcelain output\n const changedFiles = status\n .split('\\n')\n .map(line => line.trim().split(/\\s+/).pop()!)\n .filter(f => f.endsWith('package.json'));\n\n const bumpedPackages = collectBumpedPackages(changedFiles, repoRoot);\n console.log(generateConsoleSummary(bumpedPackages));\n\n // 13. Stage all + commit\n const commitMessage = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`;\n console.log(colorize(`Committing: \"${commitMessage}\"...`, ansi.dim));\n await git.stageAll();\n await git.commit(commitMessage);\n\n // 14. Force-push the branch\n if (dryRun) {\n console.log(\n colorize(\n `[DRY RUN] Would force-push ${prBranch} to ${remoteName}`,\n ansi.yellow,\n ),\n );\n } else {\n console.log(\n colorize(`Force-pushing ${prBranch} to ${remoteName}...`, ansi.dim),\n );\n await git.push(remoteName, prBranch, {force: true});\n\n // Verify the branch landed on the remote\n const lsRemoteOut = await git.lsRemote(remoteName, prBranch);\n if (!lsRemoteOut) {\n throw new Error(\n `Push verification failed: branch \"${prBranch}\" not found on ` +\n `\"${remoteName}\" after push. Check your push permissions.`,\n );\n }\n console.log(\n colorize(\n `Push verified: ${prBranch} exists on ${remoteName}`,\n ansi.green,\n ),\n );\n }\n\n // 15. Create or update PR\n const prTitle = `RELEASE: Releasing ${bumpedPackages.length} package(s) (${targetBranch})`;\n const prBody = generatePRBody(targetBranch, bumpedPackages);\n\n if (existingPR) {\n if (dryRun) {\n console.log(\n colorize(\n `[DRY RUN] Would update PR #${existingPR.number}`,\n ansi.yellow,\n ),\n );\n } else {\n console.log(\n colorize(`Updating PR #${existingPR.number}...`, ansi.dim),\n );\n await updatePR({\n number: existingPR.number,\n title: prTitle,\n body: prBody,\n cwd: repoRoot,\n repo: githubRepo,\n });\n console.log(\n `${colorize('PR updated:', ansi.green)} ${existingPR.url}`,\n );\n }\n } else {\n if (dryRun) {\n console.log(\n colorize(`[DRY RUN] Would create PR: \"${prTitle}\"`, ansi.yellow),\n );\n } else {\n console.log(colorize('Creating pull request...', ansi.dim));\n const newPR = await createPR({\n head: prBranch,\n base: targetBranch,\n title: prTitle,\n body: prBody,\n cwd: repoRoot,\n repo: githubRepo,\n });\n console.log(`${colorize('PR created:', ansi.green)} ${newPR.url}`);\n }\n }\n\n // 16. Done\n console.log('');\n console.log(colorize('Done!', ansi.green + ansi.bright));\n\n if (dryRun) {\n console.log(colorize('\\nPR body that would be used:', ansi.dim));\n console.log(prBody);\n }\n } finally {\n // Always restore the original branch\n if (originalBranch && originalBranch !== 'HEAD') {\n console.log(\n colorize(`Restoring original branch: ${originalBranch}`, ansi.dim),\n );\n await git.checkout(originalBranch);\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`${colorize('Error:', ansi.red)} ${message}`);\n process.exit(1);\n }\n})();\n"]} \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.d.ts b/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.d.ts new file mode 100644 index 00000000000..8334cac621d --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.d.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Async wrapper around Node.js child_process.spawn. + * Simplified from fork-sync/src/modules/proc.ts. + * + * @format + */ +/** + * Error thrown when a spawned process exits with a non-zero code. + */ +export declare class ExecError extends Error { + readonly command: string; + readonly args: readonly string[]; + readonly cwd: string | undefined; + readonly exitCode: number | null; + readonly stderr: string; + constructor(opts: { + command: string; + args: readonly string[]; + cwd?: string; + exitCode: number | null; + stderr: string; + }); +} +export interface SpawnOpts { + cwd?: string; + /** If set, return this value instead of throwing on non-zero exit */ + fallback?: string; + /** Extra environment variables (merged with process.env) */ + env?: Record; +} +/** + * Spawn a command (no shell) and return its trimmed stdout. + * Throws ExecError on non-zero exit unless `fallback` is provided. + */ +export declare function spawn(command: string, args: readonly string[], opts?: SpawnOpts): Promise; +/** + * Execute a command string in a shell and return its trimmed stdout. + * Uses shell mode, needed for .cmd shims on Windows (npx, gh). + */ +export declare function exec(command: string, opts?: SpawnOpts): Promise; diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js b/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js new file mode 100644 index 00000000000..eb2d6ed9d6e --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js @@ -0,0 +1,86 @@ +"use strict"; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Async wrapper around Node.js child_process.spawn. + * Simplified from fork-sync/src/modules/proc.ts. + * + * @format + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.exec = exports.spawn = exports.ExecError = void 0; +const child_process_1 = require("child_process"); +/** + * Error thrown when a spawned process exits with a non-zero code. + */ +class ExecError extends Error { + constructor(opts) { + const cmdStr = [opts.command, ...opts.args].join(' '); + super(opts.stderr || + `Command failed with exit code ${opts.exitCode}: ${cmdStr}`); + this.name = 'ExecError'; + this.command = opts.command; + this.args = opts.args; + this.cwd = opts.cwd; + this.exitCode = opts.exitCode; + this.stderr = opts.stderr; + } +} +exports.ExecError = ExecError; +/** + * Spawn a command (no shell) and return its trimmed stdout. + * Throws ExecError on non-zero exit unless `fallback` is provided. + */ +function spawn(command, args, opts) { + return spawnImpl(command, [...args], opts, false); +} +exports.spawn = spawn; +/** + * Execute a command string in a shell and return its trimmed stdout. + * Uses shell mode, needed for .cmd shims on Windows (npx, gh). + */ +function exec(command, opts) { + return spawnImpl(command, [], opts, true); +} +exports.exec = exec; +function spawnImpl(command, args, opts, shell) { + return new Promise((resolve, reject) => { + const child = (0, child_process_1.spawn)(command, args, { + cwd: opts?.cwd, + env: opts?.env ? { ...process.env, ...opts.env } : undefined, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + shell, + }); + const stdoutChunks = []; + const stderrChunks = []; + child.stdout.on('data', (chunk) => stdoutChunks.push(chunk)); + child.stderr.on('data', (chunk) => stderrChunks.push(chunk)); + child.on('error', err => { + reject(err); + }); + child.on('close', exitCode => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trimEnd(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trimEnd(); + if (exitCode !== 0) { + if (opts?.fallback !== undefined) { + resolve(opts.fallback); + } + else { + reject(new ExecError({ + command, + args, + cwd: opts?.cwd, + exitCode, + stderr, + })); + } + } + else { + resolve(stdout); + } + }); + }); +} +//# sourceMappingURL=proc.js.map \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js.map b/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js.map new file mode 100644 index 00000000000..b3aa7f95fe1 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/proc.js.map @@ -0,0 +1 @@ +{"version":3,"file":"proc.js","sourceRoot":"","sources":["../src/proc.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,iDAAiD;AAEjD;;GAEG;AACH,MAAa,SAAU,SAAQ,KAAK;IAOlC,YAAY,IAMX;QACC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtD,KAAK,CACH,IAAI,CAAC,MAAM;YACT,iCAAiC,IAAI,CAAC,QAAQ,KAAK,MAAM,EAAE,CAC9D,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC;CACF;AA1BD,8BA0BC;AAUD;;;GAGG;AACH,SAAgB,KAAK,CACnB,OAAe,EACf,IAAuB,EACvB,IAAgB;IAEhB,OAAO,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AACpD,CAAC;AAND,sBAMC;AAED;;;GAGG;AACH,SAAgB,IAAI,CAAC,OAAe,EAAE,IAAgB;IACpD,OAAO,SAAS,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAFD,oBAEC;AAED,SAAS,SAAS,CAChB,OAAe,EACf,IAAc,EACd,IAA2B,EAC3B,KAAc;IAEd,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,KAAK,GAAG,IAAA,qBAAS,EAAC,OAAO,EAAE,IAAI,EAAE;YACrC,GAAG,EAAE,IAAI,EAAE,GAAG;YACd,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAC,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAC,CAAC,CAAC,CAAC,SAAS;YAC1D,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;YACjC,WAAW,EAAE,IAAI;YACjB,KAAK;SACN,CAAC,CAAC;QAEH,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,MAAM,YAAY,GAAa,EAAE,CAAC;QAElC,KAAK,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,KAAK,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAEtE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;YACtB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE;YAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;YACtE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;YAEtE,IAAI,QAAQ,KAAK,CAAC,EAAE;gBAClB,IAAI,IAAI,EAAE,QAAQ,KAAK,SAAS,EAAE;oBAChC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;iBACxB;qBAAM;oBACL,MAAM,CACJ,IAAI,SAAS,CAAC;wBACZ,OAAO;wBACP,IAAI;wBACJ,GAAG,EAAE,IAAI,EAAE,GAAG;wBACd,QAAQ;wBACR,MAAM;qBACP,CAAC,CACH,CAAC;iBACH;aACF;iBAAM;gBACL,OAAO,CAAC,MAAM,CAAC,CAAC;aACjB;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * Async wrapper around Node.js child_process.spawn.\n * Simplified from fork-sync/src/modules/proc.ts.\n *\n * @format\n */\n\nimport {spawn as nodeSpawn} from 'child_process';\n\n/**\n * Error thrown when a spawned process exits with a non-zero code.\n */\nexport class ExecError extends Error {\n readonly command: string;\n readonly args: readonly string[];\n readonly cwd: string | undefined;\n readonly exitCode: number | null;\n readonly stderr: string;\n\n constructor(opts: {\n command: string;\n args: readonly string[];\n cwd?: string;\n exitCode: number | null;\n stderr: string;\n }) {\n const cmdStr = [opts.command, ...opts.args].join(' ');\n super(\n opts.stderr ||\n `Command failed with exit code ${opts.exitCode}: ${cmdStr}`,\n );\n this.name = 'ExecError';\n this.command = opts.command;\n this.args = opts.args;\n this.cwd = opts.cwd;\n this.exitCode = opts.exitCode;\n this.stderr = opts.stderr;\n }\n}\n\nexport interface SpawnOpts {\n cwd?: string;\n /** If set, return this value instead of throwing on non-zero exit */\n fallback?: string;\n /** Extra environment variables (merged with process.env) */\n env?: Record;\n}\n\n/**\n * Spawn a command (no shell) and return its trimmed stdout.\n * Throws ExecError on non-zero exit unless `fallback` is provided.\n */\nexport function spawn(\n command: string,\n args: readonly string[],\n opts?: SpawnOpts,\n): Promise {\n return spawnImpl(command, [...args], opts, false);\n}\n\n/**\n * Execute a command string in a shell and return its trimmed stdout.\n * Uses shell mode, needed for .cmd shims on Windows (npx, gh).\n */\nexport function exec(command: string, opts?: SpawnOpts): Promise {\n return spawnImpl(command, [], opts, true);\n}\n\nfunction spawnImpl(\n command: string,\n args: string[],\n opts: SpawnOpts | undefined,\n shell: boolean,\n): Promise {\n return new Promise((resolve, reject) => {\n const child = nodeSpawn(command, args, {\n cwd: opts?.cwd,\n env: opts?.env ? {...process.env, ...opts.env} : undefined,\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n shell,\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n\n child.stdout!.on('data', (chunk: Buffer) => stdoutChunks.push(chunk));\n child.stderr!.on('data', (chunk: Buffer) => stderrChunks.push(chunk));\n\n child.on('error', err => {\n reject(err);\n });\n\n child.on('close', exitCode => {\n const stdout = Buffer.concat(stdoutChunks).toString('utf8').trimEnd();\n const stderr = Buffer.concat(stderrChunks).toString('utf8').trimEnd();\n\n if (exitCode !== 0) {\n if (opts?.fallback !== undefined) {\n resolve(opts.fallback);\n } else {\n reject(\n new ExecError({\n command,\n args,\n cwd: opts?.cwd,\n exitCode,\n stderr,\n }),\n );\n }\n } else {\n resolve(stdout);\n }\n });\n });\n}\n"]} \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.d.ts b/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.d.ts new file mode 100644 index 00000000000..3f3e2109c45 --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.d.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Parse bumped packages and generate PR description markdown. + * + * @format + */ +export interface BumpedPackage { + name: string; + version: string; + comments: Array<{ + comment: string; + author: string; + }>; +} +/** + * Collect information about packages that were bumped by beachball. + * + * For each changed package.json, reads the new version and parses + * CHANGELOG.json for the latest changelog entry. + * + * @param changedPackageJsonPaths Relative paths to package.json files + * that were modified by beachball bump (from git status --porcelain) + * @param repoRoot The repository root directory + */ +export declare function collectBumpedPackages(changedPackageJsonPaths: string[], repoRoot: string): BumpedPackage[]; +/** + * Generate the pull request body markdown. + */ +export declare function generatePRBody(targetBranch: string, packages: BumpedPackage[]): string; +/** + * Generate a console-friendly summary of bumped packages. + */ +export declare function generateConsoleSummary(packages: BumpedPackage[]): string; diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js b/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js new file mode 100644 index 00000000000..5912998b71a --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js @@ -0,0 +1,119 @@ +"use strict"; +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * Parse bumped packages and generate PR description markdown. + * + * @format + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generateConsoleSummary = exports.generatePRBody = exports.collectBumpedPackages = void 0; +const fs_1 = __importDefault(require("@react-native-windows/fs")); +const path_1 = __importDefault(require("path")); +/** + * Collect information about packages that were bumped by beachball. + * + * For each changed package.json, reads the new version and parses + * CHANGELOG.json for the latest changelog entry. + * + * @param changedPackageJsonPaths Relative paths to package.json files + * that were modified by beachball bump (from git status --porcelain) + * @param repoRoot The repository root directory + */ +function collectBumpedPackages(changedPackageJsonPaths, repoRoot) { + const bumped = []; + for (const relPath of changedPackageJsonPaths) { + const fullPath = path_1.default.join(repoRoot, relPath); + if (!fs_1.default.existsSync(fullPath)) { + continue; + } + let pkgJson; + try { + pkgJson = JSON.parse(fs_1.default.readFileSync(fullPath, 'utf8')); + } + catch { + continue; + } + const name = pkgJson.name; + const version = pkgJson.version; + if (!name || !version) { + continue; + } + // Try to read CHANGELOG.json from the same directory + const pkgDir = path_1.default.dirname(fullPath); + const changelogPath = path_1.default.join(pkgDir, 'CHANGELOG.json'); + const comments = []; + if (fs_1.default.existsSync(changelogPath)) { + try { + const changelog = JSON.parse(fs_1.default.readFileSync(changelogPath, 'utf8')); + // CHANGELOG.json structure: + // { entries: [{ version, comments: { : [{ comment, author }] } }] } + const latest = changelog.entries?.[0]; + if (latest && latest.version === version) { + for (const typeComments of Object.values(latest.comments || {})) { + for (const c of typeComments) { + comments.push({ + comment: c.comment || '', + author: c.author || '', + }); + } + } + } + } + catch { + // If CHANGELOG.json is malformed, skip comments + } + } + bumped.push({ name, version, comments }); + } + // Sort by package name for consistent output + bumped.sort((a, b) => a.name.localeCompare(b.name)); + return bumped; +} +exports.collectBumpedPackages = collectBumpedPackages; +/** + * Generate the pull request body markdown. + */ +function generatePRBody(targetBranch, packages) { + const lines = []; + lines.push('This PR was auto-generated by `prepare-release`. ' + + `When ready to release, merge this PR into \`${targetBranch}\`. ` + + 'If not ready yet, this PR will be updated as more changes merge.'); + lines.push(''); + lines.push(`## Packages to Release (${packages.length})`); + lines.push(''); + for (const pkg of packages) { + lines.push(`### ${pkg.name}@${pkg.version}`); + if (pkg.comments.length > 0) { + for (const c of pkg.comments) { + lines.push(`- ${c.comment}`); + } + } + else { + lines.push('- *(dependency update)*'); + } + lines.push(''); + } + return lines.join('\n'); +} +exports.generatePRBody = generatePRBody; +/** + * Generate a console-friendly summary of bumped packages. + */ +function generateConsoleSummary(packages) { + if (packages.length === 0) { + return 'No packages were bumped.'; + } + const lines = []; + lines.push(`Bumped ${packages.length} package(s):`); + for (const pkg of packages) { + lines.push(` ${pkg.name} => ${pkg.version}`); + } + return lines.join('\n'); +} +exports.generateConsoleSummary = generateConsoleSummary; +//# sourceMappingURL=releaseSummary.js.map \ No newline at end of file diff --git a/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js.map b/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js.map new file mode 100644 index 00000000000..a399fac8d6d --- /dev/null +++ b/packages/@rnw-scripts/prepare-release/lib-commonjs/releaseSummary.js.map @@ -0,0 +1 @@ +{"version":3,"file":"releaseSummary.js","sourceRoot":"","sources":["../src/releaseSummary.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;AAEH,kEAA0C;AAC1C,gDAAwB;AAQxB;;;;;;;;;GASG;AACH,SAAgB,qBAAqB,CACnC,uBAAiC,EACjC,QAAgB;IAEhB,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,OAAO,IAAI,uBAAuB,EAAE;QAC7C,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;YAC5B,SAAS;SACV;QAED,IAAI,OAA0C,CAAC;QAC/C,IAAI;YACF,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;SACzD;QAAC,MAAM;YACN,SAAS;SACV;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE;YACrB,SAAS;SACV;QAED,qDAAqD;QACrD,MAAM,MAAM,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAA6C,EAAE,CAAC;QAE9D,IAAI,YAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE;YAChC,IAAI;gBACF,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;gBAErE,4BAA4B;gBAC5B,gFAAgF;gBAChF,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;gBACtC,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO,EAAE;oBACxC,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE;wBAC/D,KAAK,MAAM,CAAC,IAAI,YAGd,EAAE;4BACF,QAAQ,CAAC,IAAI,CAAC;gCACZ,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,EAAE;gCACxB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE;6BACvB,CAAC,CAAC;yBACJ;qBACF;iBACF;aACF;YAAC,MAAM;gBACN,gDAAgD;aACjD;SACF;QAED,MAAM,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAC,CAAC,CAAC;KACxC;IAED,6CAA6C;IAC7C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC;AAChB,CAAC;AA7DD,sDA6DC;AAED;;GAEG;AACH,SAAgB,cAAc,CAC5B,YAAoB,EACpB,QAAyB;IAEzB,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CACR,mDAAmD;QACjD,+CAA+C,YAAY,MAAM;QACjE,kEAAkE,CACrE,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,2BAA2B,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE;QAC1B,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7C,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YAC3B,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE;gBAC5B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;aAC9B;SACF;aAAM;YACL,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;SACvC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;KAChB;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AA5BD,wCA4BC;AAED;;GAEG;AACH,SAAgB,sBAAsB,CAAC,QAAyB;IAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;QACzB,OAAO,0BAA0B,CAAC;KACnC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,UAAU,QAAQ,CAAC,MAAM,cAAc,CAAC,CAAC;IACpD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE;QAC1B,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;KAC/C;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAXD,wDAWC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * Parse bumped packages and generate PR description markdown.\n *\n * @format\n */\n\nimport fs from '@react-native-windows/fs';\nimport path from 'path';\n\nexport interface BumpedPackage {\n name: string;\n version: string;\n comments: Array<{comment: string; author: string}>;\n}\n\n/**\n * Collect information about packages that were bumped by beachball.\n *\n * For each changed package.json, reads the new version and parses\n * CHANGELOG.json for the latest changelog entry.\n *\n * @param changedPackageJsonPaths Relative paths to package.json files\n * that were modified by beachball bump (from git status --porcelain)\n * @param repoRoot The repository root directory\n */\nexport function collectBumpedPackages(\n changedPackageJsonPaths: string[],\n repoRoot: string,\n): BumpedPackage[] {\n const bumped: BumpedPackage[] = [];\n\n for (const relPath of changedPackageJsonPaths) {\n const fullPath = path.join(repoRoot, relPath);\n if (!fs.existsSync(fullPath)) {\n continue;\n }\n\n let pkgJson: {name?: string; version?: string};\n try {\n pkgJson = JSON.parse(fs.readFileSync(fullPath, 'utf8'));\n } catch {\n continue;\n }\n\n const name = pkgJson.name;\n const version = pkgJson.version;\n if (!name || !version) {\n continue;\n }\n\n // Try to read CHANGELOG.json from the same directory\n const pkgDir = path.dirname(fullPath);\n const changelogPath = path.join(pkgDir, 'CHANGELOG.json');\n const comments: Array<{comment: string; author: string}> = [];\n\n if (fs.existsSync(changelogPath)) {\n try {\n const changelog = JSON.parse(fs.readFileSync(changelogPath, 'utf8'));\n\n // CHANGELOG.json structure:\n // { entries: [{ version, comments: { : [{ comment, author }] } }] }\n const latest = changelog.entries?.[0];\n if (latest && latest.version === version) {\n for (const typeComments of Object.values(latest.comments || {})) {\n for (const c of typeComments as Array<{\n comment: string;\n author: string;\n }>) {\n comments.push({\n comment: c.comment || '',\n author: c.author || '',\n });\n }\n }\n }\n } catch {\n // If CHANGELOG.json is malformed, skip comments\n }\n }\n\n bumped.push({name, version, comments});\n }\n\n // Sort by package name for consistent output\n bumped.sort((a, b) => a.name.localeCompare(b.name));\n return bumped;\n}\n\n/**\n * Generate the pull request body markdown.\n */\nexport function generatePRBody(\n targetBranch: string,\n packages: BumpedPackage[],\n): string {\n const lines: string[] = [];\n\n lines.push(\n 'This PR was auto-generated by `prepare-release`. ' +\n `When ready to release, merge this PR into \\`${targetBranch}\\`. ` +\n 'If not ready yet, this PR will be updated as more changes merge.',\n );\n lines.push('');\n lines.push(`## Packages to Release (${packages.length})`);\n lines.push('');\n\n for (const pkg of packages) {\n lines.push(`### ${pkg.name}@${pkg.version}`);\n if (pkg.comments.length > 0) {\n for (const c of pkg.comments) {\n lines.push(`- ${c.comment}`);\n }\n } else {\n lines.push('- *(dependency update)*');\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generate a console-friendly summary of bumped packages.\n */\nexport function generateConsoleSummary(packages: BumpedPackage[]): string {\n if (packages.length === 0) {\n return 'No packages were bumped.';\n }\n\n const lines: string[] = [];\n lines.push(`Bumped ${packages.length} package(s):`);\n for (const pkg of packages) {\n lines.push(` ${pkg.name} => ${pkg.version}`);\n }\n return lines.join('\\n');\n}\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.d.ts b/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.d.ts new file mode 100644 index 00000000000..f2036e7ad0d --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.d.ts @@ -0,0 +1,5 @@ +import type { ViewProps } from 'react-native'; +export interface CustomAccessibilityProps extends ViewProps { +} +declare const _default: import("react-native").HostComponent; +export default _default; diff --git a/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js b/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js new file mode 100644 index 00000000000..865ca3d0d0c --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js @@ -0,0 +1 @@ +Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.__INTERNAL_VIEW_CONFIG=void 0;var _reactNative=require("react-native");var NativeComponentRegistry=require('react-native/Libraries/NativeComponent/NativeComponentRegistry');var nativeComponentName='CustomAccessibility';var __INTERNAL_VIEW_CONFIG=exports.__INTERNAL_VIEW_CONFIG={uiViewClassName:"CustomAccessibility",validAttributes:{}};var _default=exports.default=NativeComponentRegistry.get(nativeComponentName,function(){return __INTERNAL_VIEW_CONFIG;}); \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js.map b/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js.map new file mode 100644 index 00000000000..bcda94e2041 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/CustomAccessibilityNativeComponent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CustomAccessibilityNativeComponent.js","sourceRoot":"","sources":["../src/CustomAccessibilityNativeComponent.ts"],"names":[],"mappings":";;AAAA,+CAAsD;AAMtD,kBAAe,IAAA,qCAAsB,EAA2B,qBAAqB,CAAC,CAAC","sourcesContent":["import { codegenNativeComponent } from 'react-native';\nimport type { ViewProps } from 'react-native';\n\nexport interface CustomAccessibilityProps extends ViewProps {\n}\n\nexport default codegenNativeComponent('CustomAccessibility');\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/DrawingIsland.d.ts b/packages/sample-custom-component/lib-commonjs/DrawingIsland.d.ts new file mode 100644 index 00000000000..8fb0395dbb3 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/DrawingIsland.d.ts @@ -0,0 +1,8 @@ +import type { ViewProps } from 'react-native'; +import * as React from 'react'; +export interface DrawingIslandHandle { +} +interface DrawingIslandProps extends ViewProps { +} +declare const DrawingIslandWithForwardedRef: React.ForwardRefExoticComponent>; +export default DrawingIslandWithForwardedRef; diff --git a/packages/sample-custom-component/lib-commonjs/DrawingIsland.js b/packages/sample-custom-component/lib-commonjs/DrawingIsland.js new file mode 100644 index 00000000000..a540effea53 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/DrawingIsland.js @@ -0,0 +1,39 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const DrawingIslandNativeComponent_1 = __importDefault(require("./DrawingIslandNativeComponent")); +const React = __importStar(require("react")); +; +function DrawingIsland(props, ref) { + React.useImperativeHandle(ref, () => ({}), []); + const nativeComponentRef = React.useRef(null); + return (React.createElement(DrawingIslandNativeComponent_1.default, { ref: nativeComponentRef, ...props })); +} +const DrawingIslandWithForwardedRef = React.forwardRef(DrawingIsland); +exports.default = DrawingIslandWithForwardedRef; +//# sourceMappingURL=DrawingIsland.js.map \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/DrawingIsland.js.map b/packages/sample-custom-component/lib-commonjs/DrawingIsland.js.map new file mode 100644 index 00000000000..bcffdfb44c7 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/DrawingIsland.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DrawingIsland.js","sourceRoot":"","sources":["../src/DrawingIsland.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,kGAA0E;AAC1E,6CAA+B;AAG9B,CAAC;AAKF,SAAS,aAAa,CACpB,KAAyB,EACzB,GAAmC;IAGnC,KAAK,CAAC,mBAAmB,CACvB,GAAG,EACH,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EACV,EAAE,CACH,CAAC;IAEF,MAAM,kBAAkB,GAAG,KAAK,CAAC,MAAM,CAE7B,IAAI,CAAC,CAAC;IAEhB,OAAO,CACD,oBAAC,sCAA4B,IAC3B,GAAG,EAAE,kBAAkB,KACnB,KAAK,GACT,CACP,CAAC;AACJ,CAAC;AAED,MAAM,6BAA6B,GAAG,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAEtE,kBAAe,6BAA6B,CAAC","sourcesContent":["import type { ViewProps } from 'react-native';\nimport DrawingIslandNativeComponent from './DrawingIslandNativeComponent';\nimport * as React from 'react';\n\nexport interface DrawingIslandHandle {\n};\n\ninterface DrawingIslandProps extends ViewProps {\n}\n \nfunction DrawingIsland(\n props: DrawingIslandProps,\n ref: React.Ref,\n) {\n \n React.useImperativeHandle(\n ref,\n () => ({}),\n [],\n );\n\n const nativeComponentRef = React.useRef | null>(null);\n\n return (\n \n );\n}\n \nconst DrawingIslandWithForwardedRef = React.forwardRef(DrawingIsland);\n\nexport default DrawingIslandWithForwardedRef;\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.d.ts b/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.d.ts new file mode 100644 index 00000000000..33df6bae583 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.d.ts @@ -0,0 +1,5 @@ +import type { ViewProps } from 'react-native'; +export interface DrawingIslandProps extends ViewProps { +} +declare const _default: import("react-native").HostComponent; +export default _default; diff --git a/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js b/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js new file mode 100644 index 00000000000..fe0c5510fd1 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js @@ -0,0 +1 @@ +Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.__INTERNAL_VIEW_CONFIG=void 0;var _reactNative=require("react-native");var NativeComponentRegistry=require('react-native/Libraries/NativeComponent/NativeComponentRegistry');var nativeComponentName='DrawingIsland';var __INTERNAL_VIEW_CONFIG=exports.__INTERNAL_VIEW_CONFIG={uiViewClassName:"DrawingIsland",validAttributes:{}};var _default=exports.default=NativeComponentRegistry.get(nativeComponentName,function(){return __INTERNAL_VIEW_CONFIG;}); \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js.map b/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js.map new file mode 100644 index 00000000000..ef03ae95237 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/DrawingIslandNativeComponent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DrawingIslandNativeComponent.js","sourceRoot":"","sources":["../src/DrawingIslandNativeComponent.ts"],"names":[],"mappings":";;AAAA,+CAAsD;AAMtD,kBAAe,IAAA,qCAAsB,EAAqB,eAAe,CAAC,CAAC","sourcesContent":["import { codegenNativeComponent } from 'react-native';\nimport type { ViewProps } from 'react-native';\n\nexport interface DrawingIslandProps extends ViewProps {\n}\n\nexport default codegenNativeComponent('DrawingIsland');\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.d.ts b/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.d.ts new file mode 100644 index 00000000000..464d29526f9 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.d.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + * @flow + */ +import type { ViewProps } from 'react-native'; +import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes'; +type SelectedDatesChangedEvent = Readonly<{ + value: boolean; + startDate: string; +}>; +export interface CalendarViewProps extends ViewProps { + label: string; + onSelectedDatesChanged?: DirectEventHandler; +} +declare const _default: import("react-native").HostComponent; +export default _default; diff --git a/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js b/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js new file mode 100644 index 00000000000..c68578f241b --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js @@ -0,0 +1 @@ +'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.__INTERNAL_VIEW_CONFIG=void 0;var _reactNative=require("react-native");var NativeComponentRegistry=require('react-native/Libraries/NativeComponent/NativeComponentRegistry');var _require=require('react-native/Libraries/NativeComponent/ViewConfigIgnore'),ConditionallyIgnoredEventHandlers=_require.ConditionallyIgnoredEventHandlers;var nativeComponentName='CalendarView';var __INTERNAL_VIEW_CONFIG=exports.__INTERNAL_VIEW_CONFIG={uiViewClassName:"CalendarView",directEventTypes:{topSelectedDatesChanged:{registrationName:"onSelectedDatesChanged"}},validAttributes:Object.assign({label:true},ConditionallyIgnoredEventHandlers({onSelectedDatesChanged:true}))};var _default=exports.default=NativeComponentRegistry.get(nativeComponentName,function(){return __INTERNAL_VIEW_CONFIG;}); \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js.map b/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js.map new file mode 100644 index 00000000000..3f0a29eaf8c --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/FabricXamlCalendarViewNativeComponent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"FabricXamlCalendarViewNativeComponent.js","sourceRoot":"","sources":["../src/FabricXamlCalendarViewNativeComponent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,CAAC;;AAEb,wDAAwD;AACxD,+DAA+D;AAE/D,+CAAoD;AAcpD,kBAAe,IAAA,qCAAsB,EAAoB,cAAc,CAAC,CAAC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n * @format\n * @flow\n */\n\n'use strict';\n\n// Temporary test example for UseExperimentalWinUI3=true\n// Remove when we get react-native-xaml working well for Fabric\n\nimport {codegenNativeComponent} from 'react-native';\nimport type {ViewProps} from 'react-native';\nimport type {DirectEventHandler} from 'react-native/Libraries/Types/CodegenTypes';\n\ntype SelectedDatesChangedEvent = Readonly<{\n value: boolean;\n startDate: string;\n}>;\n\nexport interface CalendarViewProps extends ViewProps {\n label: string;\n onSelectedDatesChanged?: DirectEventHandler;\n}\n\nexport default codegenNativeComponent('CalendarView');\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.d.ts b/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.d.ts new file mode 100644 index 00000000000..f73a62c1f40 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.d.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + * @flow + */ +import type { ViewProps } from 'react-native'; +import type { DirectEventHandler, Int32 } from 'react-native/Libraries/Types/CodegenTypes'; +type SelectionChangedEvent = Readonly<{ + selectedIndex: Int32; + selectedValue: string; +}>; +export interface ComboBoxProps extends ViewProps { + selectedIndex?: Int32; + placeholder?: string; + onSelectionChanged?: DirectEventHandler; +} +declare const _default: import("react-native").HostComponent; +export default _default; diff --git a/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js b/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js new file mode 100644 index 00000000000..8de6089028b --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js @@ -0,0 +1 @@ +'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.__INTERNAL_VIEW_CONFIG=void 0;var _reactNative=require("react-native");var NativeComponentRegistry=require('react-native/Libraries/NativeComponent/NativeComponentRegistry');var _require=require('react-native/Libraries/NativeComponent/ViewConfigIgnore'),ConditionallyIgnoredEventHandlers=_require.ConditionallyIgnoredEventHandlers;var nativeComponentName='ComboBox';var __INTERNAL_VIEW_CONFIG=exports.__INTERNAL_VIEW_CONFIG={uiViewClassName:"ComboBox",directEventTypes:{topSelectionChanged:{registrationName:"onSelectionChanged"}},validAttributes:Object.assign({selectedIndex:true,placeholder:true},ConditionallyIgnoredEventHandlers({onSelectionChanged:true}))};var _default=exports.default=NativeComponentRegistry.get(nativeComponentName,function(){return __INTERNAL_VIEW_CONFIG;}); \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js.map b/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js.map new file mode 100644 index 00000000000..a7d54a02fad --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/FabricXamlComboBoxNativeComponent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"FabricXamlComboBoxNativeComponent.js","sourceRoot":"","sources":["../src/FabricXamlComboBoxNativeComponent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,CAAC;;AAEb,mEAAmE;AACnE,oFAAoF;AAEpF,+CAAoD;AAkBpD,kBAAe,IAAA,qCAAsB,EAAgB,UAAU,CAAC,CAAC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n * @format\n * @flow\n */\n\n'use strict';\n\n// ComboBox component for testing XAML popup positioning bug #15557\n// The ComboBox dropdown popup should appear at the correct position after scrolling\n\nimport {codegenNativeComponent} from 'react-native';\nimport type {ViewProps} from 'react-native';\nimport type {\n DirectEventHandler,\n Int32,\n} from 'react-native/Libraries/Types/CodegenTypes';\n\ntype SelectionChangedEvent = Readonly<{\n selectedIndex: Int32;\n selectedValue: string;\n}>;\n\nexport interface ComboBoxProps extends ViewProps {\n selectedIndex?: Int32;\n placeholder?: string;\n onSelectionChanged?: DirectEventHandler;\n}\n\nexport default codegenNativeComponent('ComboBox');\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/MovingLight.d.ts b/packages/sample-custom-component/lib-commonjs/MovingLight.d.ts new file mode 100644 index 00000000000..abe2cce7f66 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/MovingLight.d.ts @@ -0,0 +1,17 @@ +import type { ColorValue, ViewProps } from 'react-native'; +import * as React from 'react'; +export interface MovingLightHandle { + setLightOn(value: boolean): void; +} +interface MovingLightProps extends ViewProps { + size?: number; + color?: ColorValue; + eventParam?: string; + objectProp?: { + number: number; + string: string; + }; + onSomething?: (value: string) => Promise | void; +} +declare const MovingLightWithForwardedRef: React.ForwardRefExoticComponent>; +export default MovingLightWithForwardedRef; diff --git a/packages/sample-custom-component/lib-commonjs/MovingLight.js b/packages/sample-custom-component/lib-commonjs/MovingLight.js new file mode 100644 index 00000000000..e82d1d38f31 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/MovingLight.js @@ -0,0 +1,46 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const MovingLightNativeComponent_1 = __importStar(require("./MovingLightNativeComponent")); +const React = __importStar(require("react")); +; +function MovingLight(props, ref) { + const { onSomething, ...restProps } = props; + const handleSomething = (event) => { + void onSomething?.(event.nativeEvent.value); + }; + React.useImperativeHandle(ref, () => ({ + setLightOn(value) { + if (nativeComponentRef.current != null) { + MovingLightNativeComponent_1.Commands.setLightOn(nativeComponentRef.current, value); + } + } + }), []); + const nativeComponentRef = React.useRef(null); + return (React.createElement(MovingLightNativeComponent_1.default, { ref: nativeComponentRef, onSomething: handleSomething, ...restProps })); +} +const MovingLightWithForwardedRef = React.forwardRef(MovingLight); +exports.default = MovingLightWithForwardedRef; +//# sourceMappingURL=MovingLight.js.map \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/MovingLight.js.map b/packages/sample-custom-component/lib-commonjs/MovingLight.js.map new file mode 100644 index 00000000000..a3398113d87 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/MovingLight.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MovingLight.js","sourceRoot":"","sources":["../src/MovingLight.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AACA,2FAAkG;AAClG,6CAA+B;AAI9B,CAAC;AAaF,SAAS,WAAW,CAClB,KAAuB,EACvB,GAAiC;IAEjC,MAAM,EAAC,WAAW,EAAE,GAAG,SAAS,EAAC,GAAG,KAAK,CAAC;IAE1C,MAAM,eAAe,GAAG,CAAC,KAA2C,EAAE,EAAE;QACtE,KAAK,WAAW,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC;IAEF,KAAK,CAAC,mBAAmB,CACvB,GAAG,EACH,GAAG,EAAE,CAAC,CAAC;QACL,UAAU,CAAC,KAAc;YACrB,IAAI,kBAAkB,CAAC,OAAO,IAAI,IAAI,EAAE;gBACpC,qCAAQ,CAAC,UAAU,CACjB,kBAAkB,CAAC,OAAO,EAC1B,KAAK,CACN,CAAC;aACH;QACP,CAAC;KACF,CAAC,EACF,EAAE,CACH,CAAC;IAEF,MAAM,kBAAkB,GAAG,KAAK,CAAC,MAAM,CAE7B,IAAI,CAAC,CAAC;IAEhB,OAAO,CACD,oBAAC,oCAA0B,IACzB,GAAG,EAAE,kBAAkB,EACvB,WAAW,EAAE,eAAe,KACxB,SAAS,GACb,CACP,CAAC;AACJ,CAAC;AAED,MAAM,2BAA2B,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAElE,kBAAe,2BAA2B,CAAC","sourcesContent":["import type { ColorValue, NativeSyntheticEvent, ViewProps } from 'react-native';\nimport MovingLightNativeComponent, {Commands, SomethingEvent} from './MovingLightNativeComponent';\nimport * as React from 'react';\n\nexport interface MovingLightHandle {\n setLightOn(value: boolean): void,\n};\n\ninterface MovingLightProps extends ViewProps {\n // Props\n size?: number;\n color?: ColorValue\n eventParam?: string;\n objectProp?: { number: number, string: string};\n\n // Events\n onSomething?: (value: string) => Promise | void,\n}\n \nfunction MovingLight(\n props: MovingLightProps,\n ref: React.Ref,\n) {\n const {onSomething, ...restProps} = props;\n\n const handleSomething = (event: NativeSyntheticEvent) => {\n void onSomething?.(event.nativeEvent.value);\n };\n\n React.useImperativeHandle(\n ref,\n () => ({\n setLightOn(value: boolean) {\n if (nativeComponentRef.current != null) {\n Commands.setLightOn(\n nativeComponentRef.current,\n value,\n );\n }\n }\n }),\n [],\n );\n\n const nativeComponentRef = React.useRef | null>(null);\n\n return (\n \n );\n}\n \nconst MovingLightWithForwardedRef = React.forwardRef(MovingLight);\n\nexport default MovingLightWithForwardedRef;\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.d.ts b/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.d.ts new file mode 100644 index 00000000000..708cea23e83 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.d.ts @@ -0,0 +1,81 @@ +/// +import type { ColorValue, HostComponent, ViewProps } from 'react-native'; +import type { DirectEventHandler, Float, Double, Int32, WithDefault, UnsafeMixed } from 'react-native/Libraries/Types/CodegenTypes'; +export type SomethingEvent = { + value: string; + target: Int32; +}; +export interface MovingLightProps extends ViewProps { + size?: WithDefault; + color?: ColorValue; + testMixed?: UnsafeMixed; + eventParam?: string; + objectProp?: { + number: Double; + string: string; + }; + onSomething?: DirectEventHandler; + onTestObjectEvent?: DirectEventHandler<{ + target: Int32; + testObject: UnsafeMixed; + }>; + onEventWithInlineTypes?: DirectEventHandler<{ + target: Int32; + contentInset: { + top: Double; + bottom: Double; + left: Double; + right: Double; + }; + contentOffset: { + x: Double; + y: Double; + }; + contentSize: { + width: Double; + height: Double; + }; + layoutMeasurement: { + width: Double; + height: Double; + }; + velocity: { + x: Double; + y: Double; + }; + isUserTriggered: boolean; + }>; + onEventWithMultipleAliasTypes?: DirectEventHandler<{ + target: Int32; + contentInset: { + top: Double; + bottom: Double; + left: Double; + right: Double; + }; + contentOffset: { + x: Double; + y: Double; + }; + contentSize: { + width: Double; + height: Double; + }; + layoutMeasurement: { + width: Double; + height: Double; + }; + velocity: { + x: Double; + y: Double; + }; + isUserTriggered: boolean; + }>; +} +type ComponentType = HostComponent; +interface NativeCommands { + setLightOn: (viewRef: React.ElementRef, value: boolean) => void; +} +export declare const Commands: NativeCommands; +declare const _default: HostComponent; +export default _default; diff --git a/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js b/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js new file mode 100644 index 00000000000..fd171d5dc08 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js @@ -0,0 +1 @@ +Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.__INTERNAL_VIEW_CONFIG=exports.Commands=void 0;var _reactNative=require("react-native");var NativeComponentRegistry=require('react-native/Libraries/NativeComponent/NativeComponentRegistry');var _require=require('react-native/Libraries/NativeComponent/ViewConfigIgnore'),ConditionallyIgnoredEventHandlers=_require.ConditionallyIgnoredEventHandlers;var _require2=require("react-native/Libraries/ReactNative/RendererProxy"),dispatchCommand=_require2.dispatchCommand;var nativeComponentName='MovingLight';var __INTERNAL_VIEW_CONFIG=exports.__INTERNAL_VIEW_CONFIG={uiViewClassName:"MovingLight",directEventTypes:{topSomething:{registrationName:"onSomething"},topTestObjectEvent:{registrationName:"onTestObjectEvent"},topEventWithInlineTypes:{registrationName:"onEventWithInlineTypes"},topEventWithMultipleAliasTypes:{registrationName:"onEventWithMultipleAliasTypes"}},validAttributes:Object.assign({size:true,color:{process:require('react-native/Libraries/StyleSheet/processColor').default},testMixed:true,eventParam:true,objectProp:true},ConditionallyIgnoredEventHandlers({onSomething:true,onTestObjectEvent:true,onEventWithInlineTypes:true,onEventWithMultipleAliasTypes:true}))};var _default=exports.default=NativeComponentRegistry.get(nativeComponentName,function(){return __INTERNAL_VIEW_CONFIG;});var Commands=exports.Commands={setLightOn:function setLightOn(ref,value){dispatchCommand(ref,"setLightOn",[value]);}}; \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js.map b/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js.map new file mode 100644 index 00000000000..48dc19d1a37 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/MovingLightNativeComponent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MovingLightNativeComponent.js","sourceRoot":"","sources":["../src/MovingLightNativeComponent.ts"],"names":[],"mappings":";;;AAAA,+CAA6E;AAuChE,QAAA,QAAQ,GAAmB,IAAA,oCAAqB,EAAiB;IAC5E,iBAAiB,EAAE,CAAC,YAAY,CAAC;CAClC,CAAC,CAAC;AAEH,kBAAe,IAAA,qCAAsB,EAAmB,aAAa,CAAC,CAAC","sourcesContent":["import { codegenNativeComponent, codegenNativeCommands } from 'react-native';\nimport type { ColorValue, HostComponent, ViewProps } from 'react-native';\n\nimport type {\n DirectEventHandler,\n Float,\n Double,\n Int32,\n WithDefault,\n UnsafeMixed,\n} from 'react-native/Libraries/Types/CodegenTypes';\n\nexport type SomethingEvent = {\n value: string,\n target: Int32,\n};\n\nexport interface MovingLightProps extends ViewProps {\n // Props\n size?: WithDefault;\n color?: ColorValue;\n testMixed?: UnsafeMixed;\n eventParam?: string;\n objectProp?: { number: Double, string: string };\n\n // Events\n onSomething?: DirectEventHandler,\n onTestObjectEvent?: DirectEventHandler<{ target: Int32; testObject: UnsafeMixed }>;\n onEventWithInlineTypes?: DirectEventHandler<{ target: Int32; contentInset: { top: Double; bottom: Double; left: Double; right: Double }; contentOffset: { x: Double; y: Double }; contentSize: { width: Double; height: Double }; layoutMeasurement: { width: Double; height: Double }; velocity: { x: Double; y: Double }; isUserTriggered: boolean }>;\n onEventWithMultipleAliasTypes?: DirectEventHandler<{ target: Int32; contentInset: { top: Double; bottom: Double; left: Double; right: Double }; contentOffset: { x: Double; y: Double }; contentSize: { width: Double; height: Double }; layoutMeasurement: { width: Double; height: Double }; velocity: { x: Double; y: Double }; isUserTriggered: boolean }>;\n}\n\n\ntype ComponentType = HostComponent;\n\ninterface NativeCommands {\n setLightOn: (viewRef: React.ElementRef, value: boolean) => void;\n}\n\nexport const Commands: NativeCommands = codegenNativeCommands({\n supportedCommands: ['setLightOn'],\n});\n\nexport default codegenNativeComponent('MovingLight');\n"]} \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/index.d.ts b/packages/sample-custom-component/lib-commonjs/index.d.ts new file mode 100644 index 00000000000..1f2e8e80f9d --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/index.d.ts @@ -0,0 +1,7 @@ +import MovingLight from './MovingLight'; +import type { MovingLightHandle } from './MovingLight'; +import DrawingIsland from './DrawingIsland'; +import CalendarView from './FabricXamlCalendarViewNativeComponent'; +import ComboBox from './FabricXamlComboBoxNativeComponent'; +import CustomAccessibility from './CustomAccessibilityNativeComponent'; +export { CustomAccessibility, DrawingIsland, MovingLight, MovingLightHandle, CalendarView, ComboBox, }; diff --git a/packages/sample-custom-component/lib-commonjs/index.js b/packages/sample-custom-component/lib-commonjs/index.js new file mode 100644 index 00000000000..37f44b24f54 --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/index.js @@ -0,0 +1,17 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ComboBox = exports.CalendarView = exports.MovingLight = exports.DrawingIsland = exports.CustomAccessibility = void 0; +const MovingLight_1 = __importDefault(require("./MovingLight")); +exports.MovingLight = MovingLight_1.default; +const DrawingIsland_1 = __importDefault(require("./DrawingIsland")); +exports.DrawingIsland = DrawingIsland_1.default; +const FabricXamlCalendarViewNativeComponent_1 = __importDefault(require("./FabricXamlCalendarViewNativeComponent")); +exports.CalendarView = FabricXamlCalendarViewNativeComponent_1.default; +const FabricXamlComboBoxNativeComponent_1 = __importDefault(require("./FabricXamlComboBoxNativeComponent")); +exports.ComboBox = FabricXamlComboBoxNativeComponent_1.default; +const CustomAccessibilityNativeComponent_1 = __importDefault(require("./CustomAccessibilityNativeComponent")); +exports.CustomAccessibility = CustomAccessibilityNativeComponent_1.default; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/sample-custom-component/lib-commonjs/index.js.map b/packages/sample-custom-component/lib-commonjs/index.js.map new file mode 100644 index 00000000000..5dc30517a3f --- /dev/null +++ b/packages/sample-custom-component/lib-commonjs/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,gEAAwC;AAcpC,sBAdG,qBAAW,CAcH;AAXf,oEAA4C;AAUxC,wBAVG,uBAAa,CAUH;AARjB,oHAAkE;AAW9D,uBAXG,+CAAY,CAWH;AAThB,4GAA0D;AAUtD,mBAVG,2CAAQ,CAUH;AARZ,8GAAuE;AAGnE,8BAHG,4CAAmB,CAGH","sourcesContent":["import MovingLight from './MovingLight';\nimport type { MovingLightHandle } from './MovingLight';\n\nimport DrawingIsland from './DrawingIsland';\n\nimport CalendarView from './FabricXamlCalendarViewNativeComponent'\n\nimport ComboBox from './FabricXamlComboBoxNativeComponent'\n\nimport CustomAccessibility from './CustomAccessibilityNativeComponent';\n\nexport {\n CustomAccessibility,\n DrawingIsland,\n MovingLight,\n MovingLightHandle,\n CalendarView,\n ComboBox,\n};\n"]} \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.cpp b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.cpp new file mode 100644 index 00000000000..de5cb071107 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.cpp @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "JSRuntimeApi.h" + +EXTERN_C_START + +// Default JSR function implementations if they are not found in the engine DLL. +extern napi_status NAPI_CDECL default_jsr_open_napi_env_scope(napi_env env, jsr_napi_env_scope *scope); +extern napi_status NAPI_CDECL default_jsr_close_napi_env_scope(napi_env env, jsr_napi_env_scope scope); +extern napi_status NAPI_CDECL default_jsr_get_description(napi_env env, const char **result); +extern napi_status NAPI_CDECL default_jsr_queue_microtask(napi_env env, napi_value callback); +extern napi_status NAPI_CDECL default_jsr_drain_microtasks(napi_env env, int32_t max_count_hint, bool *result); +extern napi_status NAPI_CDECL default_jsr_is_inspectable(napi_env env, bool *result); + +extern napi_status NAPI_CDECL default_jsr_create_prepared_script( + napi_env env, + const uint8_t *script_utf8, + size_t script_length, + jsr_data_delete_cb script_delete_cb, + void *deleter_data, + const char *source_url, + jsr_prepared_script *result); +extern napi_status NAPI_CDECL default_jsr_delete_prepared_script(napi_env env, jsr_prepared_script prepared_script); +extern napi_status NAPI_CDECL +default_jsr_prepared_script_run(napi_env env, jsr_prepared_script prepared_script, napi_value *result); + +EXTERN_C_END + +namespace Microsoft::NodeApiJsi { + +namespace { + +struct JSRuntimeApiNames { +#define JSR_FUNC(func) static constexpr const char func[] = #func; +#define JSR_JSI_FUNC JSR_FUNC +#define JSR_PREPARED_SCRIPT JSR_FUNC +#include "JSRuntimeApi.inc" +}; + +// Prepared script functions either should be all loaded or we use all default functions. +void loadPreparedScriptFuncs() { + JSRuntimeApi *current = JSRuntimeApi::current(); + bool useDefault = false; +#define JSR_PREPARED_SCRIPT(func) \ + decltype(::func) *loaded_##func = \ + reinterpret_cast(current->getFuncPtr(JSRuntimeApiNames::func)); \ + useDefault = useDefault || loaded_##func == nullptr; +#include "JSRuntimeApi.inc" +#define JSR_PREPARED_SCRIPT(func) \ + size_t offset_##func = offsetof(JSRuntimeApi, func); \ + *reinterpret_cast(reinterpret_cast(current) + offset_##func) = \ + useDefault ? &default_##func : loaded_##func; +#include "JSRuntimeApi.inc" +} + +} // namespace + +thread_local JSRuntimeApi *JSRuntimeApi::current_{}; + +JSRuntimeApi::JSRuntimeApi(IFuncResolver *funcResolver) + : NodeApi(funcResolver) +#define JSR_FUNC(func) \ + , \ + func(&ApiFuncResolver:: \ + stub) +#define JSR_JSI_FUNC(func) \ + , \ + func(&ApiFuncResolver:: \ + optionalStub<&default_##func>) +#define JSR_PREPARED_SCRIPT(func) \ + , \ + func(&ApiFuncResolver:: \ + preloadStub<&loadPreparedScriptFuncs>) +#include "JSRuntimeApi.inc" +{ +} + +} // namespace Microsoft::NodeApiJsi diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.h b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.h new file mode 100644 index 00000000000..394a7d5a214 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.h @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef APILOADERS_JSRUNTIMEAPI_H_ +#define APILOADERS_JSRUNTIMEAPI_H_ + +#include +#include "NodeApi.h" + +namespace Microsoft::NodeApiJsi { + +class JSRuntimeApi : public NodeApi { + public: + JSRuntimeApi(IFuncResolver *funcResolver); + + static JSRuntimeApi *current() { + return current_; + } + + static void setCurrent(JSRuntimeApi *current) { + NodeApi::setCurrent(current); + current_ = current; + } + + class Scope : public NodeApi::Scope { + public: + Scope(JSRuntimeApi *api) : NodeApi::Scope(api), prevJSRuntimeApi_(JSRuntimeApi::current_) { + JSRuntimeApi::current_ = api; + } + + ~Scope() { + JSRuntimeApi::current_ = prevJSRuntimeApi_; + } + + private: + JSRuntimeApi *prevJSRuntimeApi_; + }; + + public: +#define JSR_FUNC(func) decltype(::func) *const func; +#define JSR_JSI_FUNC JSR_FUNC +#define JSR_PREPARED_SCRIPT JSR_FUNC +#include "JSRuntimeApi.inc" + + private: + static thread_local JSRuntimeApi *current_; +}; + +} // namespace Microsoft::NodeApiJsi + +#endif // !APILOADERS_JSRUNTIMEAPI_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.inc b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.inc new file mode 100644 index 00000000000..857fb5459ce --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/JSRuntimeApi.inc @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef JSR_FUNC +#define JSR_FUNC(func) +#endif + +#ifndef JSR_JSI_FUNC +#define JSR_JSI_FUNC(func) +#endif + +#ifndef JSR_PREPARED_SCRIPT +#define JSR_PREPARED_SCRIPT(func) +#endif + +// The JS runtime functions sorted alphabetically. +JSR_FUNC(jsr_collect_garbage) +JSR_FUNC(jsr_config_enable_gc_api) +JSR_FUNC(jsr_config_enable_inspector) +JSR_FUNC(jsr_config_set_explicit_microtasks) +JSR_FUNC(jsr_config_set_inspector_break_on_start) +JSR_FUNC(jsr_config_set_inspector_port) +JSR_FUNC(jsr_config_set_inspector_runtime_name) +JSR_FUNC(jsr_config_set_script_cache) +JSR_FUNC(jsr_config_set_task_runner) +JSR_FUNC(jsr_create_config) +JSR_FUNC(jsr_create_runtime) +JSR_FUNC(jsr_delete_config) +JSR_FUNC(jsr_delete_runtime) +JSR_FUNC(jsr_get_and_clear_last_unhandled_promise_rejection) +JSR_FUNC(jsr_has_unhandled_promise_rejection) +JSR_FUNC(jsr_run_script) +JSR_FUNC(jsr_runtime_get_node_api_env) + +// The JS runtime functions needed for JSI. +JSR_JSI_FUNC(jsr_close_napi_env_scope) +JSR_JSI_FUNC(jsr_queue_microtask) +JSR_JSI_FUNC(jsr_drain_microtasks) +JSR_JSI_FUNC(jsr_get_description) +JSR_JSI_FUNC(jsr_is_inspectable) +JSR_JSI_FUNC(jsr_open_napi_env_scope) + +// The JS runtime functions needed for prepared script. +JSR_PREPARED_SCRIPT(jsr_create_prepared_script) +JSR_PREPARED_SCRIPT(jsr_delete_prepared_script) +JSR_PREPARED_SCRIPT(jsr_prepared_script_run) + +#undef JSR_FUNC +#undef JSR_JSI_FUNC +#undef JSR_PREPARED_SCRIPT \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.cpp b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.cpp new file mode 100644 index 00000000000..c581149f3ef --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.cpp @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "NodeApi.h" + +namespace Microsoft::NodeApiJsi { + +namespace { + +struct NodeApiNames { +#define NODE_API_FUNC(func) static constexpr const char func[] = #func; +#include "NodeApi.inc" +}; + +} // namespace + +LibFuncResolver::LibFuncResolver(const char *libName) : libHandle_(LibLoader::loadLib(libName)) {} + +FuncPtr LibFuncResolver::getFuncPtr(const char *funcName) { + return LibLoader::getFuncPtr(libHandle_, funcName); +} + +DelayLoadedApi::DelayLoadedApi(IFuncResolver *funcResolver) : funcResolver_(funcResolver) {} + +DelayLoadedApi::~DelayLoadedApi() = default; + +FuncPtr DelayLoadedApi::getFuncPtr(const char *funcName) { + return funcResolver_->getFuncPtr(funcName); +} + +thread_local NodeApi *NodeApi::current_{}; + +NodeApi::NodeApi(IFuncResolver *funcResolver) + : DelayLoadedApi(funcResolver) +#define NODE_API_FUNC(func) \ + , func(&ApiFuncResolver::stub) +#include "NodeApi.inc" +{ +} + +} // namespace Microsoft::NodeApiJsi diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.h b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.h new file mode 100644 index 00000000000..05fa460fd9a --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.h @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#ifndef APILOADERS_NODEAPI_H_ +#define APILOADERS_NODEAPI_H_ + +#include + +namespace Microsoft::NodeApiJsi { + +using LibHandle = struct LibHandle_t *; +using FuncPtr = struct FuncPtr_t *; + +class LibLoader { + public: + static LibHandle loadLib(const char *libName); + static FuncPtr getFuncPtr(LibHandle libHandle, const char *funcName); +}; + +template +struct ApiFuncResolver; + +template +struct ApiFuncResolver { + // Stub is a special function that loads the targeting function on the first call, + // replaces function pointer field with the loaded function, and executes the function. + // After this all future calls to the loaded function are going to be directly from + // the field and not use this Stub. + static TResult NAPI_CDECL stub(TArgs... args) { + using TFunc = TResult(NAPI_CDECL *)(TArgs...); + TApi *current = TApi::current(); + TFunc func = reinterpret_cast(current->getFuncPtr(funcName)); + *reinterpret_cast(reinterpret_cast(current) + offset) = func; + return (*func)(args...); + } + + // Optional stub tries to load target function on the first call and set it + // as the function pointer. If loading fails, then it uses provided default + // function implementation. The default function can either do the required + // work or return a failure. + template + static TResult NAPI_CDECL optionalStub(TArgs... args) { + using TFunc = TResult(NAPI_CDECL *)(TArgs...); + TApi *current = TApi::current(); + TFunc func = reinterpret_cast(current->getFuncPtr(funcName)); + if (func == nullptr) { + func = defaultFunc; + } + *reinterpret_cast(reinterpret_cast(current) + offset) = func; + return (*func)(args...); + } + + template + static TResult NAPI_CDECL preloadStub(TArgs... args) { + using TFunc = TResult(NAPI_CDECL *)(TArgs...); + preloadFunc(); + TApi *current = TApi::current(); + TFunc func = *reinterpret_cast(reinterpret_cast(current) + offset); + return (*func)(args...); + } +}; + +struct IFuncResolver { + virtual FuncPtr getFuncPtr(const char *funcName) = 0; +}; + +class LibFuncResolver : public IFuncResolver { + public: + LibFuncResolver(const char *libName); + FuncPtr getFuncPtr(const char *funcName) override; + + private: + LibHandle libHandle_; +}; + +class DelayLoadedApi { + public: + DelayLoadedApi(IFuncResolver *funcResolver); + ~DelayLoadedApi(); + + FuncPtr getFuncPtr(const char *funcName); + + DelayLoadedApi(const DelayLoadedApi &) = delete; + DelayLoadedApi &operator=(const DelayLoadedApi &) = delete; + + private: + IFuncResolver *funcResolver_; +}; + +class NodeApi : public DelayLoadedApi { + public: + NodeApi(IFuncResolver *funcResolver); + + static NodeApi *current() { + return current_; + } + + static void setCurrent(NodeApi *current) { + current_ = current; + } + + class Scope { + public: + Scope(NodeApi *nodeApi) : prevNodeApi_(NodeApi::current_) { + NodeApi::current_ = nodeApi; + } + + ~Scope() { + NodeApi::current_ = prevNodeApi_; + } + + private: + NodeApi *prevNodeApi_; + }; + + public: +#define NODE_API_FUNC(func) decltype(::func) *const func; +#include "NodeApi.inc" + + private: + static thread_local NodeApi *current_; +}; + +} // namespace Microsoft::NodeApiJsi + +#endif // !APILOADERS_NODEAPI_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.inc b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.inc new file mode 100644 index 00000000000..a4fd08dbf38 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi.inc @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef NODE_API_FUNC +#define NODE_API_FUNC(func) +#endif + +// The Node-API functions sorted alphabetically. +NODE_API_FUNC(napi_add_finalizer) +NODE_API_FUNC(napi_adjust_external_memory) +NODE_API_FUNC(napi_call_function) +NODE_API_FUNC(napi_check_object_type_tag) +NODE_API_FUNC(napi_close_escapable_handle_scope) +NODE_API_FUNC(napi_close_handle_scope) +NODE_API_FUNC(napi_coerce_to_bool) +NODE_API_FUNC(napi_coerce_to_number) +NODE_API_FUNC(napi_coerce_to_object) +NODE_API_FUNC(napi_coerce_to_string) +NODE_API_FUNC(napi_create_array_with_length) +NODE_API_FUNC(napi_create_array) +NODE_API_FUNC(napi_create_arraybuffer) +NODE_API_FUNC(napi_create_bigint_int64) +NODE_API_FUNC(napi_create_bigint_uint64) +NODE_API_FUNC(napi_create_bigint_words) +NODE_API_FUNC(napi_create_dataview) +NODE_API_FUNC(napi_create_date) +NODE_API_FUNC(napi_create_double) +NODE_API_FUNC(napi_create_error) +NODE_API_FUNC(napi_create_external_arraybuffer) +NODE_API_FUNC(napi_create_external) +NODE_API_FUNC(napi_create_function) +NODE_API_FUNC(napi_create_int32) +NODE_API_FUNC(napi_create_int64) +NODE_API_FUNC(napi_create_object) +NODE_API_FUNC(napi_create_promise) +NODE_API_FUNC(napi_create_range_error) +NODE_API_FUNC(napi_create_reference) +NODE_API_FUNC(napi_create_string_latin1) +NODE_API_FUNC(napi_create_string_utf16) +NODE_API_FUNC(napi_create_string_utf8) +NODE_API_FUNC(napi_create_symbol) +NODE_API_FUNC(napi_create_type_error) +NODE_API_FUNC(napi_create_typedarray) +NODE_API_FUNC(napi_create_uint32) +NODE_API_FUNC(napi_define_class) +NODE_API_FUNC(napi_define_properties) +NODE_API_FUNC(napi_delete_element) +NODE_API_FUNC(napi_delete_property) +NODE_API_FUNC(napi_delete_reference) +NODE_API_FUNC(napi_detach_arraybuffer) +NODE_API_FUNC(napi_escape_handle) +NODE_API_FUNC(napi_get_all_property_names) +NODE_API_FUNC(napi_get_and_clear_last_exception) +NODE_API_FUNC(napi_get_array_length) +NODE_API_FUNC(napi_get_arraybuffer_info) +NODE_API_FUNC(napi_get_boolean) +NODE_API_FUNC(napi_get_cb_info) +NODE_API_FUNC(napi_get_dataview_info) +NODE_API_FUNC(napi_get_date_value) +NODE_API_FUNC(napi_get_element) +NODE_API_FUNC(napi_get_global) +NODE_API_FUNC(napi_get_instance_data) +NODE_API_FUNC(napi_get_last_error_info) +NODE_API_FUNC(napi_get_named_property) +NODE_API_FUNC(napi_get_new_target) +NODE_API_FUNC(napi_get_null) +NODE_API_FUNC(napi_get_property_names) +NODE_API_FUNC(napi_get_property) +NODE_API_FUNC(napi_get_prototype) +NODE_API_FUNC(napi_get_reference_value) +NODE_API_FUNC(napi_get_typedarray_info) +NODE_API_FUNC(napi_get_undefined) +NODE_API_FUNC(napi_get_value_bigint_int64) +NODE_API_FUNC(napi_get_value_bigint_uint64) +NODE_API_FUNC(napi_get_value_bigint_words) +NODE_API_FUNC(napi_get_value_bool) +NODE_API_FUNC(napi_get_value_double) +NODE_API_FUNC(napi_get_value_external) +NODE_API_FUNC(napi_get_value_int32) +NODE_API_FUNC(napi_get_value_int64) +NODE_API_FUNC(napi_get_value_string_latin1) +NODE_API_FUNC(napi_get_value_string_utf16) +NODE_API_FUNC(napi_get_value_string_utf8) +NODE_API_FUNC(napi_get_value_uint32) +NODE_API_FUNC(napi_get_version) +NODE_API_FUNC(napi_has_element) +NODE_API_FUNC(napi_has_named_property) +NODE_API_FUNC(napi_has_own_property) +NODE_API_FUNC(napi_has_property) +NODE_API_FUNC(napi_instanceof) +NODE_API_FUNC(napi_is_array) +NODE_API_FUNC(napi_is_arraybuffer) +NODE_API_FUNC(napi_is_dataview) +NODE_API_FUNC(napi_is_date) +NODE_API_FUNC(napi_is_detached_arraybuffer) +NODE_API_FUNC(napi_is_error) +NODE_API_FUNC(napi_is_exception_pending) +NODE_API_FUNC(napi_is_promise) +NODE_API_FUNC(napi_is_typedarray) +NODE_API_FUNC(napi_new_instance) +NODE_API_FUNC(napi_object_freeze) +NODE_API_FUNC(napi_object_seal) +NODE_API_FUNC(napi_open_escapable_handle_scope) +NODE_API_FUNC(napi_open_handle_scope) +NODE_API_FUNC(napi_reference_ref) +NODE_API_FUNC(napi_reference_unref) +NODE_API_FUNC(napi_reject_deferred) +NODE_API_FUNC(napi_remove_wrap) +NODE_API_FUNC(napi_resolve_deferred) +NODE_API_FUNC(napi_run_script) +NODE_API_FUNC(napi_set_element) +NODE_API_FUNC(napi_set_instance_data) +NODE_API_FUNC(napi_set_named_property) +NODE_API_FUNC(napi_set_property) +NODE_API_FUNC(napi_strict_equals) +NODE_API_FUNC(napi_throw_error) +NODE_API_FUNC(napi_throw_range_error) +NODE_API_FUNC(napi_throw_type_error) +NODE_API_FUNC(napi_throw) +NODE_API_FUNC(napi_type_tag_object) +NODE_API_FUNC(napi_typeof) +NODE_API_FUNC(napi_unwrap) +NODE_API_FUNC(napi_wrap) + +#undef NODE_API_FUNC diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_posix.cpp b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_posix.cpp new file mode 100644 index 00000000000..f22976d0172 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_posix.cpp @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "NodeApi.h" + +namespace Microsoft::NodeApiJsi { + +LibHandle LibLoader::loadLib(const char *libName) { + // TODO: implement +} + +FuncPtr LibLoader::getFuncPtr(LibHandle libHandle, const char *funcName) { + // TODO: implement +} + +} // namespace Microsoft::NodeApiJsi diff --git a/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_win.cpp b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_win.cpp new file mode 100644 index 00000000000..ced83f835ef --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ApiLoaders/NodeApi_win.cpp @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "NodeApi.h" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +namespace Microsoft::NodeApiJsi { + +LibHandle LibLoader::loadLib(const char *libName) { + return reinterpret_cast(LoadLibraryA(libName)); +} + +FuncPtr LibLoader::getFuncPtr(LibHandle libHandle, const char *funcName) { + return reinterpret_cast(GetProcAddress(reinterpret_cast(libHandle), funcName)); +} + +} // namespace Microsoft::NodeApiJsi diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/decorator.h b/vnext/Microsoft.ReactNative.Cxx/JSI/decorator.h new file mode 100644 index 00000000000..2844a2b3159 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/decorator.h @@ -0,0 +1,1172 @@ +/* + * 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 + +#include +#include + +// This file contains objects to help API users create their own +// runtime adapters, i.e. if you want to compose runtimes to add your +// own behavior. + +namespace facebook { +namespace jsi { + +// Use this to wrap host functions. It will pass the member runtime as +// the first arg to the callback. The first argument to the ctor +// should be the decorated runtime, not the plain one. +class DecoratedHostFunction { + public: + DecoratedHostFunction(Runtime& drt, HostFunctionType plainHF) + : drt_(drt), plainHF_(std::move(plainHF)) {} + + Runtime& decoratedRuntime() { + return drt_; + } + + Value + operator()(Runtime&, const Value& thisVal, const Value* args, size_t count) { + return plainHF_(decoratedRuntime(), thisVal, args, count); + } + + private: + template + friend class RuntimeDecorator; + + Runtime& drt_; + HostFunctionType plainHF_; +}; + +// From the perspective of the caller, a plain HostObject is passed to +// the decorated Runtime, and the HostObject methods expect to get +// passed that Runtime. But the plain Runtime will pass itself to its +// callback, so we need a helper here which curries the decorated +// Runtime, and calls the plain HostObject with it. +// +// If the concrete RuntimeDecorator derives DecoratedHostObject, it +// should call the base class get() and set() to invoke the plain +// HostObject functionality. The Runtime& it passes does not matter, +// as it is not used. +class DecoratedHostObject : public HostObject { + public: + DecoratedHostObject(Runtime& drt, std::shared_ptr plainHO) + : drt_(drt), plainHO_(plainHO) {} + + // The derived class methods can call this to get a reference to the + // decorated runtime, since the rt passed to the callback will be + // the plain runtime. + Runtime& decoratedRuntime() { + return drt_; + } + + Value get(Runtime&, const PropNameID& name) override { + return plainHO_->get(decoratedRuntime(), name); + } + + void set(Runtime&, const PropNameID& name, const Value& value) override { + plainHO_->set(decoratedRuntime(), name, value); + } + + std::vector getPropertyNames(Runtime&) override { + return plainHO_->getPropertyNames(decoratedRuntime()); + } + + private: + template + friend class RuntimeDecorator; + + Runtime& drt_; + std::shared_ptr plainHO_; +}; + +/// C++ variant on a standard Decorator pattern, using template +/// parameters. The \c Plain template parameter type is the +/// undecorated Runtime type. You can usually use \c Runtime here, +/// but if you know the concrete type ahead of time and it's final, +/// the compiler can devirtualize calls to the decorated +/// implementation. The \c Base template parameter type will be used +/// as the base class of the decorated type. Here, too, you can +/// usually use \c Runtime, but if you want the decorated type to +/// implement a derived class of Runtime, you can specify that here. +/// For an example, see threadsafe.h. +template +class RuntimeDecorator : public Base, private jsi::Instrumentation { + public: + Plain& plain() { + static_assert( + std::is_base_of::value, + "RuntimeDecorator's Plain type must derive from jsi::Runtime"); + static_assert( + std::is_base_of::value, + "RuntimeDecorator's Base type must derive from jsi::Runtime"); + return plain_; + } + const Plain& plain() const { + return plain_; + } + +#if JSI_VERSION >= 20 + ICast* castInterface(const UUID& interfaceUUID) override { + return plain().castInterface(interfaceUUID); + } +#endif + + Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) override { + return plain().evaluateJavaScript(buffer, sourceURL); + } + std::shared_ptr prepareJavaScript( + const std::shared_ptr& buffer, + std::string sourceURL) override { + return plain().prepareJavaScript(buffer, std::move(sourceURL)); + } + Value evaluatePreparedJavaScript( + const std::shared_ptr& js) override { + return plain().evaluatePreparedJavaScript(js); + } +#if JSI_VERSION >= 12 + void queueMicrotask(const jsi::Function& callback) override { + return plain().queueMicrotask(callback); + } +#endif +#if JSI_VERSION >= 4 + bool drainMicrotasks(int maxMicrotasksHint) override { + return plain().drainMicrotasks(maxMicrotasksHint); + } +#endif + Object global() override { + return plain().global(); + } + std::string description() override { + return plain().description(); + } + bool isInspectable() override { + return plain().isInspectable(); + } + Instrumentation& instrumentation() override { + return *this; + } + + protected: + // plain is generally going to be a reference to an object managed + // by a derived class. We cache it here so this class can be + // concrete, and avoid making virtual calls to find the plain + // Runtime. Note that the ctor and dtor do not access through the + // reference, so passing a reference to an object before its + // lifetime has started is ok. + RuntimeDecorator(Plain& plain) : plain_(plain) {} + + Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { + return plain_.cloneSymbol(pv); + }; +#if JSI_VERSION >= 6 + Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { + return plain_.cloneBigInt(pv); + }; +#endif + Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { + return plain_.cloneString(pv); + } + Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { + return plain_.cloneObject(pv); + } + Runtime::PointerValue* clonePropNameID( + const Runtime::PointerValue* pv) override { + return plain_.clonePropNameID(pv); + } + + PropNameID createPropNameIDFromAscii(const char* str, size_t length) + override { + return plain_.createPropNameIDFromAscii(str, length); + } + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) + override { + return plain_.createPropNameIDFromUtf8(utf8, length); + } + PropNameID createPropNameIDFromString(const String& str) override { + return plain_.createPropNameIDFromString(str); + } +#if JSI_VERSION >= 19 + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + return plain_.createPropNameIDFromUtf16(utf16, length); + } +#endif +#if JSI_VERSION >= 5 + PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { + return plain_.createPropNameIDFromSymbol(sym); + } +#endif + std::string utf8(const PropNameID& id) override { + return plain_.utf8(id); + } + bool compare(const PropNameID& a, const PropNameID& b) override { + return plain_.compare(a, b); + } + + std::string symbolToString(const Symbol& sym) override { + return plain_.symbolToString(sym); + } + +#if JSI_VERSION >= 8 + BigInt createBigIntFromInt64(int64_t value) override { + return plain_.createBigIntFromInt64(value); + } + BigInt createBigIntFromUint64(uint64_t value) override { + return plain_.createBigIntFromUint64(value); + } + bool bigintIsInt64(const BigInt& b) override { + return plain_.bigintIsInt64(b); + } + bool bigintIsUint64(const BigInt& b) override { + return plain_.bigintIsUint64(b); + } + uint64_t truncate(const BigInt& b) override { + return plain_.truncate(b); + } + String bigintToString(const BigInt& bigint, int radix) override { + return plain_.bigintToString(bigint, radix); + } +#endif + + String createStringFromAscii(const char* str, size_t length) override { + return plain_.createStringFromAscii(str, length); + } + String createStringFromUtf8(const uint8_t* utf8, size_t length) override { + return plain_.createStringFromUtf8(utf8, length); + } +#if JSI_VERSION >= 19 + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + return plain_.createStringFromUtf16(utf16, length); + } +#endif + std::string utf8(const String& s) override { + return plain_.utf8(s); + } + +#if JSI_VERSION >= 14 + std::u16string utf16(const String& str) override { + return plain_.utf16(str); + } + std::u16string utf16(const PropNameID& sym) override { + return plain_.utf16(sym); + } +#endif + +#if JSI_VERSION >= 16 + 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); + } +#endif + +#if JSI_VERSION >= 18 + Object createObjectWithPrototype(const Value& prototype) override { + return plain_.createObjectWithPrototype(prototype); + } +#endif + + Object createObject() override { + return plain_.createObject(); + } + + Object createObject(std::shared_ptr ho) override { + return plain_.createObject( + std::make_shared(*this, std::move(ho))); + } + std::shared_ptr getHostObject(const jsi::Object& o) override { + std::shared_ptr dho = plain_.getHostObject(o); + return static_cast(*dho).plainHO_; + } + HostFunctionType& getHostFunction(const jsi::Function& f) override { + HostFunctionType& dhf = plain_.getHostFunction(f); + // This will fail if a cpp file including this header is not compiled + // with RTTI. + return dhf.target()->plainHF_; + } + +#if JSI_VERSION >= 7 + bool hasNativeState(const Object& o) override { + return plain_.hasNativeState(o); + } + std::shared_ptr getNativeState(const Object& o) override { + return plain_.getNativeState(o); + } + void setNativeState(const Object& o, std::shared_ptr state) + override { + plain_.setNativeState(o, state); + } +#endif + +#if JSI_VERSION >= 11 + void setExternalMemoryPressure(const Object& obj, size_t amt) override { + plain_.setExternalMemoryPressure(obj, amt); + } +#endif + +#if JSI_VERSION >= 17 + void setPrototypeOf(const Object& object, const Value& prototype) override { + plain_.setPrototypeOf(object, prototype); + } + + Value getPrototypeOf(const Object& object) override { + return plain_.getPrototypeOf(object); + } +#endif + + Value getProperty(const Object& o, const PropNameID& name) override { + return plain_.getProperty(o, name); + } + Value getProperty(const Object& o, const String& name) override { + return plain_.getProperty(o, name); + } + +#if JSI_VERSION >= 21 + Value getProperty(const Object& o, const Value& name) override { + return plain_.getProperty(o, name); + } +#endif + + bool hasProperty(const Object& o, const PropNameID& name) override { + return plain_.hasProperty(o, name); + } + bool hasProperty(const Object& o, const String& name) override { + return plain_.hasProperty(o, name); + } + +#if JSI_VERSION >= 21 + bool hasProperty(const Object& o, const Value& name) override { + return plain_.hasProperty(o, name); + } +#endif + + void setPropertyValue( + JSI_CONST_10 Object& o, + const PropNameID& name, + const Value& value) override { + plain_.setPropertyValue(o, name, value); + } + void setPropertyValue( + JSI_CONST_10 Object& o, + const String& name, + const Value& value) override { + plain_.setPropertyValue(o, name, value); + } + +#if JSI_VERSION >= 21 + void setPropertyValue(const Object& o, const Value& name, const Value& value) + override { + plain_.setPropertyValue(o, name, value); + } + + void deleteProperty(const Object& object, const PropNameID& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + plain_.deleteProperty(object, name); + } +#endif + + bool isArray(const Object& o) const override { + return plain_.isArray(o); + } + bool isArrayBuffer(const Object& o) const override { + return plain_.isArrayBuffer(o); + } + bool isFunction(const Object& o) const override { + return plain_.isFunction(o); + } + bool isHostObject(const jsi::Object& o) const override { + return plain_.isHostObject(o); + } + bool isHostFunction(const jsi::Function& f) const override { + return plain_.isHostFunction(f); + } + Array getPropertyNames(const Object& o) override { + return plain_.getPropertyNames(o); + } + + WeakObject createWeakObject(const Object& o) override { + return plain_.createWeakObject(o); + } + Value lockWeakObject(JSI_NO_CONST_3 JSI_CONST_10 WeakObject& wo) override { + return plain_.lockWeakObject(wo); + } + + Array createArray(size_t length) override { + return plain_.createArray(length); + } +#if JSI_VERSION >= 9 + ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) override { + return plain_.createArrayBuffer(std::move(buffer)); + } +#endif + size_t size(const Array& a) override { + return plain_.size(a); + } + size_t size(const ArrayBuffer& ab) override { + return plain_.size(ab); + } + uint8_t* data(const ArrayBuffer& ab) override { + return plain_.data(ab); + } + Value getValueAtIndex(const Array& a, size_t i) override { + return plain_.getValueAtIndex(a, i); + } + void setValueAtIndexImpl(JSI_CONST_10 Array& a, size_t i, const Value& value) + override { + plain_.setValueAtIndexImpl(a, i, value); + } + + Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { + return plain_.createFunctionFromHostFunction( + name, paramCount, DecoratedHostFunction(*this, std::move(func))); + } + Value call( + const Function& f, + const Value& jsThis, + const Value* args, + size_t count) override { + return plain_.call(f, jsThis, args, count); + } + Value callAsConstructor(const Function& f, const Value* args, size_t count) + override { + return plain_.callAsConstructor(f, args, count); + } + +#if JSI_VERSION >= 20 + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override { + return plain_.setRuntimeDataImpl(uuid, data, deleter); + } + + const void* getRuntimeDataImpl(const UUID& uuid) override { + return plain_.getRuntimeDataImpl(uuid); + } +#endif + + // Private data for managing scopes. + Runtime::ScopeState* pushScope() override { + return plain_.pushScope(); + } + void popScope(Runtime::ScopeState* ss) override { + plain_.popScope(ss); + } + + bool strictEquals(const Symbol& a, const Symbol& b) const override { + return plain_.strictEquals(a, b); + } +#if JSI_VERSION >= 6 + bool strictEquals(const BigInt& a, const BigInt& b) const override { + return plain_.strictEquals(a, b); + } +#endif + bool strictEquals(const String& a, const String& b) const override { + return plain_.strictEquals(a, b); + } + bool strictEquals(const Object& a, const Object& b) const override { + return plain_.strictEquals(a, b); + } + + bool instanceOf(const Object& o, const Function& f) override { + return plain_.instanceOf(o, f); + } + + // jsi::Instrumentation methods + + std::string getRecordedGCStats() override { + return plain().instrumentation().getRecordedGCStats(); + } + + std::unordered_map getHeapInfo( + bool includeExpensive) override { + return plain().instrumentation().getHeapInfo(includeExpensive); + } + + void collectGarbage(std::string cause) override { + plain().instrumentation().collectGarbage(std::move(cause)); + } + + void startTrackingHeapObjectStackTraces( + std::function)> callback) override { + plain().instrumentation().startTrackingHeapObjectStackTraces( + std::move(callback)); + } + + void stopTrackingHeapObjectStackTraces() override { + plain().instrumentation().stopTrackingHeapObjectStackTraces(); + } + + void startHeapSampling(size_t samplingInterval) override { + plain().instrumentation().startHeapSampling(samplingInterval); + } + + void stopHeapSampling(std::ostream& os) override { + plain().instrumentation().stopHeapSampling(os); + } + +#if JSI_VERSION >= 13 + void createSnapshotToFile( + const std::string& path, + const HeapSnapshotOptions& options) override { + plain().instrumentation().createSnapshotToFile(path, options); + } +#else + void createSnapshotToFile(const std::string& path) override { + plain().instrumentation().createSnapshotToFile(path); + } +#endif + +#if JSI_VERSION >= 13 + void createSnapshotToStream( + std::ostream& os, + const HeapSnapshotOptions& options) override { + plain().instrumentation().createSnapshotToStream(os, options); + } +#else + void createSnapshotToStream(std::ostream& os) override { + plain().instrumentation().createSnapshotToStream(os); + } +#endif + + std::string flushAndDisableBridgeTrafficTrace() override { + return const_cast(plain()) + .instrumentation() + .flushAndDisableBridgeTrafficTrace(); + } + + void writeBasicBlockProfileTraceToFile( + const std::string& fileName) const override { + const_cast(plain()) + .instrumentation() + .writeBasicBlockProfileTraceToFile(fileName); + } + +#if JSI_VERSION >= 21 + void dumpOpcodeStats(std::ostream& os) const override { + const_cast(plain()).instrumentation().dumpOpcodeStats(os); + } +#endif + + /// Dump external profiler symbols to the given file name. + void dumpProfilerSymbolsToFile(const std::string& fileName) const override { + const_cast(plain()).instrumentation().dumpProfilerSymbolsToFile( + fileName); + } + + private: + Plain& plain_; +}; + +namespace detail { + +// This metaprogramming allows the With type's methods to be +// optional. + +template +struct BeforeCaller { + static void before(T&) {} +}; + +template +struct AfterCaller { + static void after(T&) {} +}; + +// decltype((void)&...) is either SFINAE, or void. +// So, if SFINAE does not happen for T, then this specialization exists +// for BeforeCaller, and always applies. If not, only the +// default above exists, and that is used instead. +template +struct BeforeCaller { + static void before(T& t) { + t.before(); + } +}; + +template +struct AfterCaller { + static void after(T& t) { + t.after(); + } +}; + +// It's possible to use multiple decorators by nesting +// WithRuntimeDecorator<...>, but this specialization allows use of +// std::tuple of decorator classes instead. See testlib.cpp for an +// example. +template +struct BeforeCaller> { + static void before(std::tuple& tuple) { + all_before<0, T...>(tuple); + } + + private: + template + static void all_before(std::tuple& tuple) { + detail::BeforeCaller::before(std::get(tuple)); + all_before(tuple); + } + + template + static void all_before(std::tuple&) {} +}; + +template +struct AfterCaller> { + static void after(std::tuple& tuple) { + all_after<0, T...>(tuple); + } + + private: + template + static void all_after(std::tuple& tuple) { + all_after(tuple); + detail::AfterCaller::after(std::get(tuple)); + } + + template + static void all_after(std::tuple&) {} +}; + +} // namespace detail + +// A decorator which implements an around idiom. A With instance is +// RAII constructed before each call to the undecorated class; the +// ctor is passed a single argument of type WithArg&. Plain and Base +// are used as in the base class. +template +class WithRuntimeDecorator : public RuntimeDecorator { + public: + using RD = RuntimeDecorator; + + // The reference arguments to the ctor are stored, but not used by + // the ctor, and there is no ctor, so they can be passed members of + // the derived class. + WithRuntimeDecorator(Plain& plain, With& with) : RD(plain), with_(with) {} + +#if JSI_VERSION >= 20 + ICast* castInterface(const UUID& interfaceUUID) override { + Around around{with_}; + return RD::castInterface(interfaceUUID); + } +#endif + + Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) override { + Around around{with_}; + return RD::evaluateJavaScript(buffer, sourceURL); + } + std::shared_ptr prepareJavaScript( + const std::shared_ptr& buffer, + std::string sourceURL) override { + Around around{with_}; + return RD::prepareJavaScript(buffer, std::move(sourceURL)); + } + Value evaluatePreparedJavaScript( + const std::shared_ptr& js) override { + Around around{with_}; + return RD::evaluatePreparedJavaScript(js); + } +#if JSI_VERSION >= 12 + void queueMicrotask(const Function& callback) override { + Around around{with_}; + RD::queueMicrotask(callback); + } +#endif +#if JSI_VERSION >= 4 + bool drainMicrotasks(int maxMicrotasksHint) override { + Around around{with_}; + return RD::drainMicrotasks(maxMicrotasksHint); + } +#endif + Object global() override { + Around around{with_}; + return RD::global(); + } + std::string description() override { + Around around{with_}; + return RD::description(); + } + bool isInspectable() override { + Around around{with_}; + return RD::isInspectable(); + } + + // The jsi:: prefix is necessary because MSVC compiler complains C2247: + // Instrumentation is not accessible because RuntimeDecorator uses private + // to inherit from Instrumentation. + // TODO(T40821815) Consider removing this workaround when updating MSVC + jsi::Instrumentation& instrumentation() override { + Around around{with_}; + return RD::instrumentation(); + } + + protected: + Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneSymbol(pv); + } +#if JSI_VERSION >= 6 + Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneBigInt(pv); + } +#endif + Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneString(pv); + } + Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneObject(pv); + } + Runtime::PointerValue* clonePropNameID( + const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::clonePropNameID(pv); + } + + PropNameID createPropNameIDFromAscii(const char* str, size_t length) + override { + Around around{with_}; + return RD::createPropNameIDFromAscii(str, length); + } + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) + override { + Around around{with_}; + return RD::createPropNameIDFromUtf8(utf8, length); + } +#if JSI_VERSION >= 19 + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + Around around{with_}; + return RD::createPropNameIDFromUtf16(utf16, length); + } +#endif + PropNameID createPropNameIDFromString(const String& str) override { + Around around{with_}; + return RD::createPropNameIDFromString(str); + } +#if JSI_VERSION >= 5 + PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { + Around around{with_}; + return RD::createPropNameIDFromSymbol(sym); + } +#endif + std::string utf8(const PropNameID& id) override { + Around around{with_}; + return RD::utf8(id); + } + bool compare(const PropNameID& a, const PropNameID& b) override { + Around around{with_}; + return RD::compare(a, b); + } + + std::string symbolToString(const Symbol& sym) override { + Around around{with_}; + return RD::symbolToString(sym); + } + +#if JSI_VERSION >= 8 + BigInt createBigIntFromInt64(int64_t i) override { + Around around{with_}; + return RD::createBigIntFromInt64(i); + } + BigInt createBigIntFromUint64(uint64_t i) override { + Around around{with_}; + return RD::createBigIntFromUint64(i); + } + bool bigintIsInt64(const BigInt& bi) override { + Around around{with_}; + return RD::bigintIsInt64(bi); + } + bool bigintIsUint64(const BigInt& bi) override { + Around around{with_}; + return RD::bigintIsUint64(bi); + } + uint64_t truncate(const BigInt& bi) override { + Around around{with_}; + return RD::truncate(bi); + } + String bigintToString(const BigInt& bi, int i) override { + Around around{with_}; + return RD::bigintToString(bi, i); + } +#endif + + String createStringFromAscii(const char* str, size_t length) override { + Around around{with_}; + return RD::createStringFromAscii(str, length); + } + String createStringFromUtf8(const uint8_t* utf8, size_t length) override { + Around around{with_}; + return RD::createStringFromUtf8(utf8, length); + } +#if JSI_VERSION >= 19 + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + Around around{with_}; + return RD::createStringFromUtf16(utf16, length); + } +#endif + std::string utf8(const String& s) override { + Around around{with_}; + return RD::utf8(s); + } + +#if JSI_VERSION >= 14 + std::u16string utf16(const String& str) override { + Around around{with_}; + return RD::utf16(str); + } + std::u16string utf16(const PropNameID& sym) override { + Around around{with_}; + return RD::utf16(sym); + } +#endif + +#if JSI_VERSION >= 16 + 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); + } +#endif + + Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override { + Around around{with_}; + return RD::createValueFromJsonUtf8(json, length); + } + +#if JSI_VERSION >= 18 + Object createObjectWithPrototype(const Value& prototype) override { + Around around{with_}; + return RD::createObjectWithPrototype(prototype); + } +#endif + + Object createObject() override { + Around around{with_}; + return RD::createObject(); + } + Object createObject(std::shared_ptr ho) override { + Around around{with_}; + return RD::createObject(std::move(ho)); + } + std::shared_ptr getHostObject(const jsi::Object& o) override { + Around around{with_}; + return RD::getHostObject(o); + } + HostFunctionType& getHostFunction(const jsi::Function& f) override { + Around around{with_}; + return RD::getHostFunction(f); + } + +#if JSI_VERSION >= 7 + bool hasNativeState(const Object& o) override { + Around around{with_}; + return RD::hasNativeState(o); + } + std::shared_ptr getNativeState(const Object& o) override { + Around around{with_}; + return RD::getNativeState(o); + } + void setNativeState(const Object& o, std::shared_ptr state) + override { + Around around{with_}; + RD::setNativeState(o, state); + } +#endif + +#if JSI_VERSION >= 17 + void setPrototypeOf(const Object& object, const Value& prototype) override { + Around around{with_}; + RD::setPrototypeOf(object, prototype); + } + + Value getPrototypeOf(const Object& object) override { + Around around{with_}; + return RD::getPrototypeOf(object); + } +#endif + + Value getProperty(const Object& o, const PropNameID& name) override { + Around around{with_}; + return RD::getProperty(o, name); + } + Value getProperty(const Object& o, const String& name) override { + Around around{with_}; + return RD::getProperty(o, name); + } + +#if JSI_VERSION >= 21 + Value getProperty(const Object& o, const Value& name) override { + Around around{with_}; + return RD::getProperty(o, name); + } +#endif + + bool hasProperty(const Object& o, const PropNameID& name) override { + Around around{with_}; + return RD::hasProperty(o, name); + } + bool hasProperty(const Object& o, const String& name) override { + Around around{with_}; + return RD::hasProperty(o, name); + } + +#if JSI_VERSION >= 21 + bool hasProperty(const Object& o, const Value& name) override { + Around around{with_}; + return RD::hasProperty(o, name); + } +#endif + + void setPropertyValue( + JSI_CONST_10 Object& o, + const PropNameID& name, + const Value& value) override { + Around around{with_}; + RD::setPropertyValue(o, name, value); + } + void setPropertyValue( + JSI_CONST_10 Object& o, + const String& name, + const Value& value) override { + Around around{with_}; + RD::setPropertyValue(o, name, value); + } +#if JSI_VERSION >= 21 + void setPropertyValue(const Object& o, const Value& name, const Value& value) + override { + Around around{with_}; + RD::setPropertyValue(o, name, value); + } + + void deleteProperty(const Object& object, const PropNameID& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } +#endif + + bool isArray(const Object& o) const override { + Around around{with_}; + return RD::isArray(o); + } + bool isArrayBuffer(const Object& o) const override { + Around around{with_}; + return RD::isArrayBuffer(o); + } + bool isFunction(const Object& o) const override { + Around around{with_}; + return RD::isFunction(o); + } + bool isHostObject(const jsi::Object& o) const override { + Around around{with_}; + return RD::isHostObject(o); + } + bool isHostFunction(const jsi::Function& f) const override { + Around around{with_}; + return RD::isHostFunction(f); + } + Array getPropertyNames(const Object& o) override { + Around around{with_}; + return RD::getPropertyNames(o); + } + + WeakObject createWeakObject(const Object& o) override { + Around around{with_}; + return RD::createWeakObject(o); + } + Value lockWeakObject(JSI_NO_CONST_3 JSI_CONST_10 WeakObject& wo) override { + Around around{with_}; + return RD::lockWeakObject(wo); + } + + Array createArray(size_t length) override { + Around around{with_}; + return RD::createArray(length); + } +#if JSI_VERSION >= 9 + ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) override { + return RD::createArrayBuffer(std::move(buffer)); + } +#endif + size_t size(const Array& a) override { + Around around{with_}; + return RD::size(a); + } + size_t size(const ArrayBuffer& ab) override { + Around around{with_}; + return RD::size(ab); + } + uint8_t* data(const ArrayBuffer& ab) override { + Around around{with_}; + return RD::data(ab); + } + Value getValueAtIndex(const Array& a, size_t i) override { + Around around{with_}; + return RD::getValueAtIndex(a, i); + } + void setValueAtIndexImpl(JSI_CONST_10 Array& a, size_t i, const Value& value) + override { + Around around{with_}; + RD::setValueAtIndexImpl(a, i, value); + } + + Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { + Around around{with_}; + return RD::createFunctionFromHostFunction( + name, paramCount, std::move(func)); + } + Value call( + const Function& f, + const Value& jsThis, + const Value* args, + size_t count) override { + Around around{with_}; + return RD::call(f, jsThis, args, count); + } + Value callAsConstructor(const Function& f, const Value* args, size_t count) + override { + Around around{with_}; + return RD::callAsConstructor(f, args, count); + } + + // Private data for managing scopes. + Runtime::ScopeState* pushScope() override { + Around around{with_}; + return RD::pushScope(); + } + void popScope(Runtime::ScopeState* ss) override { + Around around{with_}; + RD::popScope(ss); + } + + bool strictEquals(const Symbol& a, const Symbol& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + } + +#if JSI_VERSION >= 6 + bool strictEquals(const BigInt& a, const BigInt& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + } +#endif + + bool strictEquals(const String& a, const String& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + } + bool strictEquals(const Object& a, const Object& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + } + + bool instanceOf(const Object& o, const Function& f) override { + Around around{with_}; + return RD::instanceOf(o, f); + } + +#if JSI_VERSION >= 11 + void setExternalMemoryPressure(const jsi::Object& obj, size_t amount) + override { + Around around{with_}; + RD::setExternalMemoryPressure(obj, amount); + } +#endif + +#if JSI_VERSION >= 20 + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override { + Around around{with_}; + RD::setRuntimeDataImpl(uuid, data, deleter); + } + + const void* getRuntimeDataImpl(const UUID& uuid) override { + Around around{with_}; + return RD::getRuntimeDataImpl(uuid); + } +#endif + + private: + // Wrap an RAII type around With& to guarantee after always happens. + struct Around { + Around(With& with) : with_(with) { + detail::BeforeCaller::before(with_); + } + ~Around() { + detail::AfterCaller::after(with_); + } + + With& with_; + }; + + With& with_; +}; + +} // namespace jsi +} // namespace facebook diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/instrumentation.h b/vnext/Microsoft.ReactNative.Cxx/JSI/instrumentation.h new file mode 100644 index 00000000000..73a6b0a26fd --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/instrumentation.h @@ -0,0 +1,150 @@ +/* + * 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 +#include +#include +#include +#include + +#include + +namespace facebook { +namespace jsi { + +/// Methods for starting and collecting instrumentation, an \c Instrumentation +/// instance is associated with a particular \c Runtime instance, which it +/// controls the instrumentation of. +/// None of these functions should return newly created jsi values, nor should +/// it modify the values of any jsi values in the heap (although GCs are fine). +class JSI_EXPORT Instrumentation { + public: +#if JSI_VERSION >= 13 + /// Additional options controlling what to include when capturing a heap + /// snapshot. + struct HeapSnapshotOptions { + bool captureNumericValue{false}; + }; +#endif + + virtual ~Instrumentation() = default; + + /// Returns GC statistics as a JSON-encoded string, with an object containing + /// "type" and "version" fields outermost. "type" is a string, unique to a + /// particular implementation of \c jsi::Instrumentation, and "version" is a + /// number to indicate any revision to that implementation and its output + /// format. + /// + /// \pre This call can only be made on the instrumentation instance of a + /// runtime initialised to collect GC statistics. + /// + /// \post All cumulative measurements mentioned in the output are accumulated + /// across the entire lifetime of the Runtime. + /// + /// \return the GC statistics collected so far, as a JSON-encoded string. + virtual std::string getRecordedGCStats() = 0; + + /// Request statistics about the current state of the runtime's heap. This + /// function can be called at any time, and should produce information that is + /// correct at the instant it is called (i.e, not stale). + /// + /// \return a map from a string key to a number associated with that + /// statistic. + virtual std::unordered_map getHeapInfo( + bool includeExpensive) = 0; + + /// Perform a full garbage collection. + /// \param cause The cause of this collection, as it should be reported in + /// logs. + virtual void collectGarbage(std::string cause) = 0; + + /// A HeapStatsUpdate is a tuple of the fragment index, the number of objects + /// in that fragment, and the number of bytes used by those objects. + /// A "fragment" is a view of all objects allocated within a time slice. + using HeapStatsUpdate = std::tuple; + + /// Start capturing JS stack-traces for all JS heap allocated objects. These + /// can be accessed via \c ::createSnapshotToFile(). + /// \param fragmentCallback If present, invoke this callback every so often + /// with the most recently seen object ID, and a list of fragments that have + /// been updated. This callback will be invoked on the same thread that the + /// runtime is using. + virtual void startTrackingHeapObjectStackTraces( + std::function stats)> fragmentCallback) = 0; + + /// Stop capture JS stack-traces for JS heap allocated objects. + virtual void stopTrackingHeapObjectStackTraces() = 0; + + /// Start a heap sampling profiler that will sample heap allocations, and the + /// stack trace they were allocated at. Reports a summary of which functions + /// allocated the most. + /// \param samplingInterval The number of bytes allocated to wait between + /// samples. This will be used as the expected value of a poisson + /// distribution. + virtual void startHeapSampling(size_t samplingInterval) = 0; + + /// Turns off the heap sampling profiler previously enabled via + /// \c startHeapSampling. Writes the output of the sampling heap profiler to + /// \p os. The output is a JSON formatted string. + virtual void stopHeapSampling(std::ostream& os) = 0; + +#if JSI_VERSION >= 13 + /// Captures the heap to a file + /// + /// \param path to save the heap capture. + /// \param options additional options for what to capture. + virtual void createSnapshotToFile( + const std::string& path, + const HeapSnapshotOptions& options = {false}) = 0; +#else + /// Captures the heap to a file + /// + /// \param path to save the heap capture + virtual void createSnapshotToFile(const std::string& path) = 0; +#endif + +#if JSI_VERSION >= 13 + /// Captures the heap to an output stream + /// + /// \param os output stream to write to. + /// \param options additional options for what to capture. + virtual void createSnapshotToStream( + std::ostream& os, + const HeapSnapshotOptions& options = {false}) = 0; +#else + /// Captures the heap to an output stream + /// + /// \param os output stream to write to. + virtual void createSnapshotToStream(std::ostream& os) = 0; +#endif + + /// If the runtime has been created to trace to a temp file, flush + /// any unwritten parts of the trace of bridge traffic to the file, + /// and return the name of the file. Otherwise, return the empty string. + /// Tracing is disabled after this call. + virtual std::string flushAndDisableBridgeTrafficTrace() = 0; + + /// Write basic block profile trace to the given file name. + virtual void writeBasicBlockProfileTraceToFile( + const std::string& fileName) const = 0; + +#if JSI_VERSION >= 21 + /// Write the opcode stats to the given stream. + virtual void dumpOpcodeStats(std::ostream& os) const = 0; +#endif + + /// Dump external profiler symbols to the given file name. + virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0; +}; + +} // namespace jsi +} // namespace facebook diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/jsi-inl.h b/vnext/Microsoft.ReactNative.Cxx/JSI/jsi-inl.h new file mode 100644 index 00000000000..67b52ce5ae3 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/jsi-inl.h @@ -0,0 +1,425 @@ +/* + * 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 + +namespace facebook { +namespace jsi { +namespace detail { + +inline Value toValue(Runtime&, std::nullptr_t) { + return Value::null(); +} +inline Value toValue(Runtime&, bool b) { + return Value(b); +} +inline Value toValue(Runtime&, double d) { + return Value(d); +} +inline Value toValue(Runtime&, float f) { + return Value(static_cast(f)); +} +inline Value toValue(Runtime&, int i) { + return Value(i); +} +inline Value toValue(Runtime& runtime, const char* str) { + return String::createFromAscii(runtime, str); +} +inline Value toValue(Runtime& runtime, const std::string& str) { + return String::createFromUtf8(runtime, str); +} +template +inline Value toValue(Runtime& runtime, const T& other) { + static_assert( + std::is_base_of::value, + "This type cannot be converted to Value"); + return Value(runtime, other); +} +inline Value toValue(Runtime& runtime, const Value& value) { + return Value(runtime, value); +} +inline Value&& toValue(Runtime&, Value&& value) { + return std::move(value); +} + +inline PropNameID toPropNameID(Runtime& runtime, const char* name) { + return PropNameID::forAscii(runtime, name); +} +inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) { + return PropNameID::forUtf8(runtime, name); +} +inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) { + return std::move(name); +} + +/// Helper to throw while still compiling with exceptions turned off. +template +[[noreturn]] inline void throwOrDie(Args&&... args) { + std::rethrow_exception( + std::make_exception_ptr(E{std::forward(args)...})); +} + +} // namespace detail + +template +inline T Runtime::make(Runtime::PointerValue* pv) { + return T(pv); +} + +#if JSI_VERSION >= 3 +inline Runtime::PointerValue* Runtime::getPointerValue(jsi::Pointer& pointer) { + return pointer.ptr_; +} +#endif + +inline const Runtime::PointerValue* Runtime::getPointerValue( + const jsi::Pointer& pointer) { + return pointer.ptr_; +} + +inline const Runtime::PointerValue* Runtime::getPointerValue( + const jsi::Value& value) { + return value.data_.pointer.ptr_; +} + +#if JSI_VERSION >= 20 +inline void Runtime::setRuntimeData( + const UUID& uuid, + const std::shared_ptr& data) { + auto* dataPtr = new std::shared_ptr(data); + setRuntimeDataImpl(uuid, dataPtr, [](const void* data) { + delete (const std::shared_ptr*)data; + }); +} + +inline std::shared_ptr Runtime::getRuntimeData(const UUID& uuid) { + auto* data = (const std::shared_ptr*)getRuntimeDataImpl(uuid); + return data ? *data : nullptr; +} +#endif + +#if JSI_VERSION >= 17 +Value Object::getPrototype(Runtime& runtime) const { + return runtime.getPrototypeOf(*this); +} +#endif + +inline Value Object::getProperty(Runtime& runtime, const char* name) const { + return getProperty(runtime, String::createFromAscii(runtime, name)); +} + +inline Value Object::getProperty(Runtime& runtime, const String& name) const { + return runtime.getProperty(*this, name); +} + +inline Value Object::getProperty(Runtime& runtime, const PropNameID& name) + const { + return runtime.getProperty(*this, name); +} + +#if JSI_VERSION >= 21 +inline Value Object::getProperty(Runtime& runtime, const Value& name) const { + return runtime.getProperty(*this, name); +} +#endif + +inline bool Object::hasProperty(Runtime& runtime, const char* name) const { + return hasProperty(runtime, String::createFromAscii(runtime, name)); +} + +inline bool Object::hasProperty(Runtime& runtime, const String& name) const { + return runtime.hasProperty(*this, name); +} + +inline bool Object::hasProperty(Runtime& runtime, const PropNameID& name) + const { + return runtime.hasProperty(*this, name); +} + +#if JSI_VERSION >= 21 +inline bool Object::hasProperty(Runtime& runtime, const Value& name) const { + return runtime.hasProperty(*this, name); +} +#endif + +template +void Object::setProperty(Runtime& runtime, const char* name, T&& value) + JSI_CONST_10 { + setProperty( + runtime, String::createFromAscii(runtime, name), std::forward(value)); +} + +template +void Object::setProperty(Runtime& runtime, const String& name, T&& value) + JSI_CONST_10 { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +template +void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value) + JSI_CONST_10 { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +#if JSI_VERSION >= 21 +template +void Object::setProperty(Runtime& runtime, const Value& name, T&& value) const { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +inline void Object::deleteProperty(Runtime& runtime, const char* name) const { + deleteProperty(runtime, String::createFromAscii(runtime, name)); +} + +inline void Object::deleteProperty(Runtime& runtime, const String& name) const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(Runtime& runtime, const PropNameID& name) + const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(Runtime& runtime, const Value& name) const { + runtime.deleteProperty(*this, name); +} +#endif + +inline Array Object::getArray(Runtime& runtime) const& { + assert(runtime.isArray(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + return Array(runtime.cloneObject(ptr_)); +} + +inline Array Object::getArray(Runtime& runtime) && { + assert(runtime.isArray(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + Runtime::PointerValue* value = ptr_; + ptr_ = nullptr; + return Array(value); +} + +inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& { + assert(runtime.isArrayBuffer(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + return ArrayBuffer(runtime.cloneObject(ptr_)); +} + +inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && { + assert(runtime.isArrayBuffer(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + Runtime::PointerValue* value = ptr_; + ptr_ = nullptr; + return ArrayBuffer(value); +} + +inline Function Object::getFunction(Runtime& runtime) const& { + assert(runtime.isFunction(*this)); + return Function(runtime.cloneObject(ptr_)); +} + +inline Function Object::getFunction(Runtime& runtime) && { + assert(runtime.isFunction(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + Runtime::PointerValue* value = ptr_; + ptr_ = nullptr; + return Function(value); +} + +template +inline bool Object::isHostObject(Runtime& runtime) const { + return runtime.isHostObject(*this) && + std::dynamic_pointer_cast(runtime.getHostObject(*this)); +} + +template <> +inline bool Object::isHostObject(Runtime& runtime) const { + return runtime.isHostObject(*this); +} + +template +inline std::shared_ptr Object::getHostObject(Runtime& runtime) const { + assert(isHostObject(runtime)); + return std::static_pointer_cast(runtime.getHostObject(*this)); +} + +template +inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { + if (!isHostObject(runtime)) { + detail::throwOrDie( + "Object is not a HostObject of desired type"); + } + return std::static_pointer_cast(runtime.getHostObject(*this)); +} + +template <> +inline std::shared_ptr Object::getHostObject( + Runtime& runtime) const { + assert(runtime.isHostObject(*this)); + return runtime.getHostObject(*this); +} + +#if JSI_VERSION >= 7 +template +inline bool Object::hasNativeState(Runtime& runtime) const { + return runtime.hasNativeState(*this) && + std::dynamic_pointer_cast(runtime.getNativeState(*this)); +} + +template <> +inline bool Object::hasNativeState(Runtime& runtime) const { + return runtime.hasNativeState(*this); +} + +template +inline std::shared_ptr Object::getNativeState(Runtime& runtime) const { + assert(hasNativeState(runtime)); + return std::static_pointer_cast(runtime.getNativeState(*this)); +} + +inline void Object::setNativeState( + Runtime& runtime, + std::shared_ptr state) const { + runtime.setNativeState(*this, state); +} +#endif + +#if JSI_VERSION >= 11 +inline void Object::setExternalMemoryPressure(Runtime& runtime, size_t amt) + const { + runtime.setExternalMemoryPressure(*this, amt); +} +#endif + +inline Array Object::getPropertyNames(Runtime& runtime) const { + return runtime.getPropertyNames(*this); +} + +inline Value WeakObject::lock(Runtime& runtime) JSI_CONST_10 { + return runtime.lockWeakObject(*this); +} + +template +void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) + JSI_CONST_10 { + setValueAtIndexImpl( + runtime, i, detail::toValue(runtime, std::forward(value))); +} + +inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const { + return runtime.getValueAtIndex(*this, i); +} + +inline Function Function::createFromHostFunction( + Runtime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func) { + return runtime.createFunctionFromHostFunction( + name, paramCount, std::move(func)); +} + +inline Value Function::call(Runtime& runtime, const Value* args, size_t count) + const { + return runtime.call(*this, Value::undefined(), args, count); +} + +inline Value Function::call(Runtime& runtime, std::initializer_list args) + const { + return call(runtime, args.begin(), args.size()); +} + +template +inline Value Function::call(Runtime& runtime, Args&&... args) const { + // A more awesome version of this would be able to create raw values + // which can be used directly without wrapping and unwrapping, but + // this will do for now. + return call(runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +inline Value Function::callWithThis( + Runtime& runtime, + const Object& jsThis, + const Value* args, + size_t count) const { + return runtime.call(*this, Value(runtime, jsThis), args, count); +} + +inline Value Function::callWithThis( + Runtime& runtime, + const Object& jsThis, + std::initializer_list args) const { + return callWithThis(runtime, jsThis, args.begin(), args.size()); +} + +template +inline Value Function::callWithThis( + Runtime& runtime, + const Object& jsThis, + Args&&... args) const { + // A more awesome version of this would be able to create raw values + // which can be used directly without wrapping and unwrapping, but + // this will do for now. + return callWithThis( + runtime, jsThis, {detail::toValue(runtime, std::forward(args))...}); +} + +template +inline Array Array::createWithElements(Runtime& runtime, Args&&... args) { + return createWithElements( + runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +template +inline std::vector PropNameID::names( + Runtime& runtime, + Args&&... args) { + return names({detail::toPropNameID(runtime, std::forward(args))...}); +} + +template +inline std::vector PropNameID::names( + PropNameID (&&propertyNames)[N]) { + std::vector result; + result.reserve(N); + for (auto& name : propertyNames) { + result.push_back(std::move(name)); + } + return result; +} + +inline Value Function::callAsConstructor( + Runtime& runtime, + const Value* args, + size_t count) const { + return runtime.callAsConstructor(*this, args, count); +} + +inline Value Function::callAsConstructor( + Runtime& runtime, + std::initializer_list args) const { + return callAsConstructor(runtime, args.begin(), args.size()); +} + +template +inline Value Function::callAsConstructor(Runtime& runtime, Args&&... args) + const { + return callAsConstructor( + runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +#if JSI_VERSION >= 8 +String BigInt::toString(Runtime& runtime, int radix) const { + return runtime.bigintToString(*this, radix); +} +#endif + +} // namespace jsi +} // namespace facebook diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/jsi.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/jsi.cpp new file mode 100644 index 00000000000..10e42d7b954 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/jsi.cpp @@ -0,0 +1,990 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include + +namespace facebook { +namespace jsi { + +namespace { + +#if JSI_VERSION >= 20 +/// A global map used to store custom runtime data for VMs that do not provide +/// their own default implementation of setRuntimeData and getRuntimeData. +struct RuntimeDataGlobal { + /// Mutex protecting the Runtime data map + std::mutex mutex_{}; + /// Maps a runtime pointer to a map of its custom data. At destruction of the + /// runtime, its entry will be removed from the global map. + std::unordered_map< + Runtime*, + std::unordered_map< + UUID, + std::pair, + UUID::Hash>> + dataMap_; +}; + +RuntimeDataGlobal& getRuntimeDataGlobal() { + static RuntimeDataGlobal runtimeData{}; + return runtimeData; +} + +/// A host object that, when destructed, will remove the runtime's custom data +/// entry from the global map of custom data. +class RemoveRuntimeDataHostObject : public jsi::HostObject { + public: + explicit RemoveRuntimeDataHostObject(Runtime* runtime) : runtime_(runtime) {} + + RemoveRuntimeDataHostObject(const RemoveRuntimeDataHostObject&) = default; + RemoveRuntimeDataHostObject(RemoveRuntimeDataHostObject&&) = default; + RemoveRuntimeDataHostObject& operator=(const RemoveRuntimeDataHostObject&) = + default; + RemoveRuntimeDataHostObject& operator=(RemoveRuntimeDataHostObject&&) = + default; + + ~RemoveRuntimeDataHostObject() override { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(runtime_); + // We install the RemoveRuntimeDataHostObject only when the first custom + // data for the runtime is added, and only this object is responsible for + // clearing runtime data. Thus, we should always be able to find the data + // entry. + assert( + runtimeMapIt != runtimeDataGlobal.dataMap_.end() && + "Custom runtime data not found for this runtime"); + + for (auto [_, entry] : runtimeMapIt->second) { + auto* deleter = entry.second; + deleter(entry.first); + } + runtimeDataGlobal.dataMap_.erase(runtime_); + } + + private: + Runtime* runtime_; +}; +#endif + +// This is used for generating short exception strings. +std::string kindToString(const Value& v, Runtime* rt = nullptr) { + if (v.isUndefined()) { + return "undefined"; + } else if (v.isNull()) { + return "null"; + } else if (v.isBool()) { + return v.getBool() ? "true" : "false"; + } else if (v.isNumber()) { + return "a number"; + } else if (v.isString()) { + return "a string"; + } else if (v.isSymbol()) { + return "a symbol"; +#if JSI_VERSION >= 6 + } else if (v.isBigInt()) { + return "a bigint"; +#endif + } else { + assert(v.isObject() && "Expecting object."); + return rt != nullptr && v.getObject(*rt).isFunction(*rt) ? "a function" + : "an object"; + } +} + +// getPropertyAsFunction() will try to create a JSError. If the +// failure is in building a JSError, this will lead to infinite +// recursion. This function is used in place of getPropertyAsFunction +// when building JSError, to avoid that infinite recursion. +Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) { + Value v = runtime.global().getProperty(runtime, name); + if (!v.isObject()) { + throw JSINativeException( + std::string("callGlobalFunction: JS global property '") + name + + "' is " + kindToString(v, &runtime) + ", expected a Function"); + } + Object o = v.getObject(runtime); + if (!o.isFunction(runtime)) { + throw JSINativeException( + std::string("callGlobalFunction: JS global property '") + name + + "' is a non-callable Object, expected a Function"); + } + Function f = std::move(o).getFunction(runtime); + return f.call(runtime, arg); +} + +#if JSI_VERSION >= 14 +// Given a sequence of UTF8 encoded bytes, advance the input to past where a +// 32-bit unicode codepoint as been decoded and return the codepoint. If the +// UTF8 encoding is invalid, then return the value with the unicode replacement +// character (U+FFFD). This decoder also relies on zero termination at end of +// the input for bound checks. +// \param input char pointer pointing to the current character +// \return Unicode codepoint +uint32_t decodeUTF8(const char*& input) { + uint32_t ch = (unsigned char)input[0]; + if (ch <= 0x7f) { + input += 1; + return ch; + } + uint32_t ret; + constexpr uint32_t replacementCharacter = 0xFFFD; + if ((ch & 0xE0) == 0xC0) { + uint32_t ch1 = (unsigned char)input[1]; + if ((ch1 & 0xC0) != 0x80) { + input += 1; + return replacementCharacter; + } + ret = ((ch & 0x1F) << 6) | (ch1 & 0x3F); + input += 2; + if (ret <= 0x7F) { + return replacementCharacter; + } + } else if ((ch & 0xF0) == 0xE0) { + uint32_t ch1 = (unsigned char)input[1]; + if ((ch1 & 0x40) != 0 || (ch1 & 0x80) == 0) { + input += 1; + return replacementCharacter; + } + uint32_t ch2 = (unsigned char)input[2]; + if ((ch2 & 0x40) != 0 || (ch2 & 0x80) == 0) { + input += 2; + return replacementCharacter; + } + ret = ((ch & 0x0F) << 12) | ((ch1 & 0x3F) << 6) | (ch2 & 0x3F); + input += 3; + if (ret <= 0x7FF) { + return replacementCharacter; + } + } else if ((ch & 0xF8) == 0xF0) { + uint32_t ch1 = (unsigned char)input[1]; + if ((ch1 & 0x40) != 0 || (ch1 & 0x80) == 0) { + input += 1; + return replacementCharacter; + } + uint32_t ch2 = (unsigned char)input[2]; + if ((ch2 & 0x40) != 0 || (ch2 & 0x80) == 0) { + input += 2; + return replacementCharacter; + } + uint32_t ch3 = (unsigned char)input[3]; + if ((ch3 & 0x40) != 0 || (ch3 & 0x80) == 0) { + input += 3; + return replacementCharacter; + } + ret = ((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | + (ch3 & 0x3F); + input += 4; + if (ret <= 0xFFFF) { + return replacementCharacter; + } + if (ret > 0x10FFFF) { + return replacementCharacter; + } + } else { + input += 1; + return replacementCharacter; + } + return ret; +} + +// Given a valid 32-bit unicode codepoint, encode it as UTF-16 into the output. +void encodeUTF16(std::u16string& out, uint32_t cp) { + if (cp < 0x10000) { + out.push_back((uint16_t)cp); + return; + } + cp -= 0x10000; + uint16_t highSurrogate = 0xD800 + ((cp >> 10) & 0x3FF); + out.push_back(highSurrogate); + uint16_t lowSurrogate = 0xDC00 + (cp & 0x3FF); + out.push_back(lowSurrogate); +} + +// Convert the UTF8 encoded string into a UTF16 encoded string. If the +// input is not valid UTF8, the replacement character (U+FFFD) is used to +// represent the invalid sequence. +std::u16string convertUTF8ToUTF16(const std::string& utf8) { + std::u16string ret; + const char* curr = utf8.data(); + const char* end = curr + utf8.length(); + while (curr < end) { + auto cp = decodeUTF8(curr); + encodeUTF16(ret, cp); + } + return ret; +} +#endif + +#if JSI_VERSION >= 19 +// Given a unsigned number, which is less than 16, return the hex character. +inline char hexDigit(unsigned x) { + return static_cast(x < 10 ? '0' + x : 'A' + (x - 10)); +} + +// Given a sequence of UTF 16 code units, return true if all code units are +// ASCII characters +bool isAllASCII(const char16_t* utf16, size_t length) { + for (const char16_t* e = utf16 + length; utf16 != e; ++utf16) { + if (*utf16 > 0x7F) + return false; + } + return true; +} + +// Given a sequences of UTF 16 code units, return a string that explicitly +// expresses the code units +std::string getUtf16CodeUnitString(const char16_t* utf16, size_t length) { + // Every character will need 4 hex digits + the character escape "\u". + // Plus 2 character for the opening and closing single quote. + std::string s = std::string(6 * length + 2, 0); + s.front() = '\''; + + for (size_t i = 0; i != length; ++i) { + char16_t ch = utf16[i]; + size_t start = (6 * i) + 1; + + s[start] = '\\'; + s[start + 1] = 'u'; + + s[start + 2] = hexDigit((ch >> 12) & 0x000f); + s[start + 3] = hexDigit((ch >> 8) & 0x000f); + s[start + 4] = hexDigit((ch >> 4) & 0x000f); + s[start + 5] = hexDigit(ch & 0x000f); + } + s.back() = '\''; + return s; +} +#endif + +} // namespace + +Buffer::~Buffer() = default; + +#if JSI_VERSION >= 9 +MutableBuffer::~MutableBuffer() = default; +#endif + +PreparedJavaScript::~PreparedJavaScript() = default; + +Value HostObject::get(Runtime&, const PropNameID&) { + return Value(); +} + +void HostObject::set(Runtime& rt, const PropNameID& name, const Value&) { + std::string msg("TypeError: Cannot assign to property '"); + msg += name.utf8(rt); + msg += "' on HostObject with default setter"; + throw JSError(rt, msg); +} + +HostObject::~HostObject() {} + +#if JSI_VERSION >= 7 +NativeState::~NativeState() {} +#endif + +Runtime::~Runtime() {} + +#if JSI_VERSION >= 20 +ICast* Runtime::castInterface(const UUID& /*interfaceUUID*/) { + return nullptr; +} +#endif + +Instrumentation& Runtime::instrumentation() { + class NoInstrumentation : public Instrumentation { + std::string getRecordedGCStats() override { + return ""; + } + + std::unordered_map getHeapInfo(bool) override { + return std::unordered_map{}; + } + + void collectGarbage(std::string) override {} + + void startTrackingHeapObjectStackTraces( + std::function)>) override {} + void stopTrackingHeapObjectStackTraces() override {} + + void startHeapSampling(size_t) override {} + void stopHeapSampling(std::ostream&) override {} + +#if JSI_VERSION >= 13 + void createSnapshotToFile( + const std::string& /*path*/, + const HeapSnapshotOptions& /*options*/) override +#else + void createSnapshotToFile(const std::string&) override +#endif + { + throw JSINativeException( + "Default instrumentation cannot create a heap snapshot"); + } + +#if JSI_VERSION >= 13 + void createSnapshotToStream( + std::ostream& /*os*/, + const HeapSnapshotOptions& /*options*/) override +#else + void createSnapshotToStream(std::ostream&) override +#endif + { + throw JSINativeException( + "Default instrumentation cannot create a heap snapshot"); + } + + std::string flushAndDisableBridgeTrafficTrace() override { + std::abort(); + } + + void writeBasicBlockProfileTraceToFile(const std::string&) const override { + std::abort(); + } + +#if JSI_VERSION >= 21 + void dumpOpcodeStats(std::ostream&) const override { + std::abort(); + } +#endif + + void dumpProfilerSymbolsToFile(const std::string&) const override { + std::abort(); + } + }; + + static NoInstrumentation sharedInstance; + return sharedInstance; +} + +#if JSI_VERSION >= 2 +Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) { + Function parseJson = global() + .getPropertyAsObject(*this, "JSON") + .getPropertyAsFunction(*this, "parse"); + return parseJson.call(*this, String::createFromUtf8(*this, json, length)); +} +#else +Value Value::createFromJsonUtf8( + Runtime& runtime, + const uint8_t* json, + size_t length) { + Function parseJson = runtime.global() + .getPropertyAsObject(runtime, "JSON") + .getPropertyAsFunction(runtime, "parse"); + return parseJson.call(runtime, String::createFromUtf8(runtime, json, length)); +} +#endif + +#if JSI_VERSION >= 19 +String Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) { + if (isAllASCII(utf16, length)) { + std::string buffer(length, '\0'); + for (size_t i = 0; i < length; ++i) { + buffer[i] = static_cast(utf16[i]); + } + return createStringFromAscii(buffer.data(), length); + } + auto s = getUtf16CodeUnitString(utf16, length); + return global() + .getPropertyAsFunction(*this, "eval") + .call(*this, s) + .getString(*this); +} + +PropNameID Runtime::createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length) { + auto jsString = createStringFromUtf16(utf16, length); + return createPropNameIDFromString(jsString); +} +#endif + +#if JSI_VERSION >= 14 +std::u16string Runtime::utf16(const PropNameID& sym) { + auto utf8Str = utf8(sym); + return convertUTF8ToUTF16(utf8Str); +} + +std::u16string Runtime::utf16(const String& str) { + auto utf8Str = utf8(str); + return convertUTF8ToUTF16(utf8Str); +} +#endif + +#if JSI_VERSION >= 16 +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()); +} +#endif + +#if JSI_VERSION >= 17 +void Runtime::setPrototypeOf(const Object& object, const Value& prototype) { + auto setPrototypeOfFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "setPrototypeOf"); + setPrototypeOfFn.call(*this, object, prototype).asObject(*this); +} + +Value Runtime::getPrototypeOf(const Object& object) { + auto setPrototypeOfFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "getPrototypeOf"); + return setPrototypeOfFn.call(*this, object); +} +#endif + +#if JSI_VERSION >= 18 +Object Runtime::createObjectWithPrototype(const Value& prototype) { + auto createFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "create"); + return createFn.call(*this, prototype).asObject(*this); +} +#endif + +#if JSI_VERSION >= 21 +void Runtime::deleteProperty(const Object& object, const PropNameID& name) { + auto nameStr = String::createFromUtf16(*this, name.utf16(*this)); + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, nameStr).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const String& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const Value& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} +#endif + +#if JSI_VERSION >= 20 +void Runtime::setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + if (auto it = runtimeDataGlobal.dataMap_.find(this); + it != runtimeDataGlobal.dataMap_.end()) { + auto& map = it->second; + if (auto entryIt = map.find(uuid); entryIt != map.end()) { + // Free the old data + auto oldData = entryIt->second.first; + auto oldDataDeleter = entryIt->second.second; + oldDataDeleter(oldData); + } + map[uuid] = {data, deleter}; + return; + } + // No custom data entry exist for this runtime in the global map, so create + // one. + runtimeDataGlobal.dataMap_[this][uuid] = {data, deleter}; + + // The first time data is added for this runtime is added to the map, install + // a host object on the global object of the runtime. This host object is used + // to release the runtime's entry from the global custom data map when the + // runtime is destroyed. + // Also, try to protect the host object by making it non-configurable, + // non-enumerable, and non-writable. These JSI operations are purposely + // performed after runtime-specific data map is added and the host object is + // created to prevent data leaks if any operations fail. + Object ho = Object::createFromHostObject( + *this, std::make_shared(this)); + global().setProperty(*this, "_jsiRuntimeDataCleanUp", ho); + auto definePropertyFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "defineProperty"); + auto desc = Object(*this); + desc.setProperty(*this, "configurable", Value(false)); + desc.setProperty(*this, "enumerable", Value(false)); + desc.setProperty(*this, "writable", Value(false)); + definePropertyFn.call(*this, global(), "_jsiRuntimeDataCleanUp", desc); +} + +const void* Runtime::getRuntimeDataImpl(const UUID& uuid) { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + if (auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(this); + runtimeMapIt != runtimeDataGlobal.dataMap_.end()) { + if (auto customDataIt = runtimeMapIt->second.find(uuid); + customDataIt != runtimeMapIt->second.end()) { + return customDataIt->second.first; + } + } + return nullptr; +} +#endif + +#if JSI_VERSION >= 21 +Value Runtime::getProperty(const Object& object, const Value& name) { + auto getFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "get"); + return getFn.call(*this, object, name); +} + +bool Runtime::hasProperty(const Object& object, const Value& name) { + auto hasFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "has"); + return hasFn.call(*this, object, name).getBool(); +} + +void Runtime::setPropertyValue( + const Object& object, + const Value& name, + const Value& value) { + auto setFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "set"); + auto setResult = setFn.call(*this, object, name, value).getBool(); + if (!setResult) { + throw JSError(*this, "Failed to set the property"); + } +} +#endif + +Pointer& Pointer::operator=(Pointer&& other) JSI_NOEXCEPT_15 { + if (ptr_) { + ptr_->invalidate(); + } + ptr_ = other.ptr_; + other.ptr_ = nullptr; + return *this; +} + +Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const { + Value v = getProperty(runtime, name); + + if (!v.isObject()) { + throw JSError( + runtime, + std::string("getPropertyAsObject: property '") + name + "' is " + + kindToString(v, &runtime) + ", expected an Object"); + } + + return v.getObject(runtime); +} + +Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) + const { + Object obj = getPropertyAsObject(runtime, name); + if (!obj.isFunction(runtime)) { + throw JSError( + runtime, + std::string("getPropertyAsFunction: property '") + name + "' is " + + kindToString(std::move(obj), &runtime) + ", expected a Function"); + }; + + return std::move(obj).getFunction(runtime); +} + +Array Object::asArray(Runtime& runtime) const& { + if (!isArray(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); + } + return getArray(runtime); +} + +Array Object::asArray(Runtime& runtime) && { + if (!isArray(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); + } + return std::move(*this).getArray(runtime); +} + +Function Object::asFunction(Runtime& runtime) const& { + if (!isFunction(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); + } + return getFunction(runtime); +} + +Function Object::asFunction(Runtime& runtime) && { + if (!isFunction(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); + } + return std::move(*this).getFunction(runtime); +} + +Value::Value(Value&& other) JSI_NOEXCEPT_15 : Value(other.kind_) { + if (kind_ == BooleanKind) { + data_.boolean = other.data_.boolean; + } else if (kind_ == NumberKind) { + data_.number = other.data_.number; + } else if (kind_ >= PointerKind) { + new (&data_.pointer) Pointer(std::move(other.data_.pointer)); + } + // when the other's dtor runs, nothing will happen. + other.kind_ = UndefinedKind; +} + +Value::Value(Runtime& runtime, const Value& other) : Value(other.kind_) { + // data_ is uninitialized, so use placement new to create non-POD + // types in it. Any other kind of initialization will call a dtor + // first, which is incorrect. + if (kind_ == BooleanKind) { + data_.boolean = other.data_.boolean; + } else if (kind_ == NumberKind) { + data_.number = other.data_.number; + } else if (kind_ == SymbolKind) { + new (&data_.pointer) Pointer(runtime.cloneSymbol(other.data_.pointer.ptr_)); +#if JSI_VERSION >= 6 + } else if (kind_ == BigIntKind) { + new (&data_.pointer) Pointer(runtime.cloneBigInt(other.data_.pointer.ptr_)); +#endif + } else if (kind_ == StringKind) { + new (&data_.pointer) Pointer(runtime.cloneString(other.data_.pointer.ptr_)); + } else if (kind_ >= ObjectKind) { + new (&data_.pointer) Pointer(runtime.cloneObject(other.data_.pointer.ptr_)); + } +} + +Value::~Value() { + if (kind_ >= PointerKind) { + data_.pointer.~Pointer(); + } +} + +bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) { + if (a.kind_ != b.kind_) { + return false; + } + switch (a.kind_) { + case UndefinedKind: + case NullKind: + return true; + case BooleanKind: + return a.data_.boolean == b.data_.boolean; + case NumberKind: + return a.data_.number == b.data_.number; + case SymbolKind: + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); +#if JSI_VERSION >= 6 + case BigIntKind: + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); +#endif + case StringKind: + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); + case ObjectKind: + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); + } + return false; +} + +bool Value::asBool() const { + if (!isBool()) { + throw JSINativeException( + "Value is " + kindToString(*this) + ", expected a boolean"); + } + + return getBool(); +} + +double Value::asNumber() const { + if (!isNumber()) { + throw JSINativeException( + "Value is " + kindToString(*this) + ", expected a number"); + } + + return getNumber(); +} + +Object Value::asObject(Runtime& rt) const& { + if (!isObject()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); + } + + return getObject(rt); +} + +Object Value::asObject(Runtime& rt) && { + if (!isObject()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); + } + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); +} + +Symbol Value::asSymbol(Runtime& rt) const& { + if (!isSymbol()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); + } + + return getSymbol(rt); +} + +Symbol Value::asSymbol(Runtime& rt) && { + if (!isSymbol()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); + } + + return std::move(*this).getSymbol(rt); +} + +#if JSI_VERSION >= 6 +BigInt Value::asBigInt(Runtime& rt) const& { + if (!isBigInt()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); + } + + return getBigInt(rt); +} + +BigInt Value::asBigInt(Runtime& rt) && { + if (!isBigInt()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); + } + + return std::move(*this).getBigInt(rt); +} +#endif + +String Value::asString(Runtime& rt) const& { + if (!isString()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); + } + + return getString(rt); +} + +String Value::asString(Runtime& rt) && { + if (!isString()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); + } + + return std::move(*this).getString(rt); +} + +String Value::toString(Runtime& runtime) const { + Function toString = runtime.global().getPropertyAsFunction(runtime, "String"); + return toString.call(runtime, *this).getString(runtime); +} + +#if JSI_VERSION >= 8 +uint64_t BigInt::asUint64(Runtime& runtime) const { + if (!isUint64(runtime)) { + throw JSError(runtime, "Lossy truncation in BigInt64::asUint64"); + } + return getUint64(runtime); +} + +int64_t BigInt::asInt64(Runtime& runtime) const { + if (!isInt64(runtime)) { + throw JSError(runtime, "Lossy truncation in BigInt64::asInt64"); + } + return getInt64(runtime); +} +#endif + +Array Array::createWithElements( + Runtime& rt, + std::initializer_list elements) { + Array result(rt, elements.size()); + size_t index = 0; + for (const auto& element : elements) { + result.setValueAtIndex(rt, index++, element); + } + return result; +} + +std::vector HostObject::getPropertyNames(Runtime&) { + return {}; +} + +Runtime::ScopeState* Runtime::pushScope() { + return nullptr; +} + +void Runtime::popScope(ScopeState*) {} + +JSError::JSError(Runtime& rt, Value&& value) { + setValue(rt, std::move(value)); +} + +JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) { + try { + setValue( + rt, + callGlobalFunction(rt, "Error", String::createFromUtf8(rt, message_))); + } catch (const JSIException& ex) { + message_ = std::string(ex.what()) + " (while raising " + message_ + ")"; + setValue(rt, String::createFromUtf8(rt, message_)); + } +} + +JSError::JSError(Runtime& rt, std::string msg, std::string stack) + : message_(std::move(msg)), stack_(std::move(stack)) { + try { + Object e(rt); + e.setProperty(rt, "message", String::createFromUtf8(rt, message_)); + e.setProperty(rt, "stack", String::createFromUtf8(rt, stack_)); + setValue(rt, std::move(e)); + } catch (const JSIException& ex) { + setValue(rt, String::createFromUtf8(rt, ex.what())); + } +} + +JSError::JSError(std::string what, Runtime& rt, Value&& value) + : JSIException(std::move(what)) { + setValue(rt, std::move(value)); +} + +JSError::JSError(Value&& value, std::string message, std::string stack) + : JSIException(message + "\n\n" + stack), + value_(std::make_shared(std::move(value))), + message_(std::move(message)), + stack_(std::move(stack)) {} + +void JSError::setValue(Runtime& rt, Value&& value) { + value_ = std::make_shared(std::move(value)); + + if ((message_.empty() || stack_.empty()) && value_->isObject()) { + auto obj = value_->getObject(rt); + + if (message_.empty()) { + try { + Value message = obj.getProperty(rt, "message"); + if (!message.isUndefined() && !message.isString()) { + message = callGlobalFunction(rt, "String", message); + } + if (message.isString()) { + message_ = message.getString(rt).utf8(rt); + } else if (!message.isUndefined()) { + message_ = "String(e.message) is a " + kindToString(message, &rt); + } + } catch (const JSIException& ex) { + message_ = std::string("[Exception while creating message string: ") + + ex.what() + "]"; + } + } + + if (stack_.empty()) { + try { + Value stack = obj.getProperty(rt, "stack"); + if (!stack.isUndefined() && !stack.isString()) { + stack = callGlobalFunction(rt, "String", stack); + } + if (stack.isString()) { + stack_ = stack.getString(rt).utf8(rt); + } else if (!stack.isUndefined()) { + stack_ = "String(e.stack) is a " + kindToString(stack, &rt); + } + } catch (const JSIException& ex) { + message_ = std::string("[Exception while creating stack string: ") + + ex.what() + "]"; + } + } + } + + if (message_.empty()) { + try { + if (value_->isString()) { + message_ = value_->getString(rt).utf8(rt); + } else { + Value message = callGlobalFunction(rt, "String", *value_); + if (message.isString()) { + message_ = message.getString(rt).utf8(rt); + } else { + message_ = "String(e) is a " + kindToString(message, &rt); + } + } + } catch (const JSIException& ex) { + message_ = std::string("[Exception while creating message string: ") + + ex.what() + "]"; + } + } + + if (stack_.empty()) { + stack_ = "no stack"; + } + + if (what_.empty()) { + what_ = message_ + "\n\n" + stack_; + } +} + +JSIException::~JSIException() {} + +JSINativeException::~JSINativeException() {} + +JSError::~JSError() {} + +} // namespace jsi +} // namespace facebook diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/jsi.h b/vnext/Microsoft.ReactNative.Cxx/JSI/jsi.h new file mode 100644 index 00000000000..30db9bb09a4 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/jsi.h @@ -0,0 +1,2031 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +// JSI version defines set of features available in the API. +// Each significant API change must be under a new version. +// The JSI_VERSION can be provided as a parameter to compiler +// or in the optional "jsi_version.h" file. + +#ifndef JSI_VERSION +#if defined(__has_include) && __has_include() +#include +#endif +#endif + +#ifndef JSI_VERSION +// Use the latest version by default +#define JSI_VERSION 21 +#endif + +#if JSI_VERSION >= 3 +#define JSI_NO_CONST_3 +#else +#define JSI_NO_CONST_3 const +#endif + +#if JSI_VERSION >= 10 +#define JSI_CONST_10 const +#else +#define JSI_CONST_10 +#endif + +#if JSI_VERSION >= 15 +#define JSI_NOEXCEPT_15 noexcept +#else +#define JSI_NOEXCEPT_15 +#endif + +#ifndef JSI_EXPORT +#ifdef _MSC_VER +#ifdef CREATE_SHARED_LIBRARY +#define JSI_EXPORT __declspec(dllexport) +#else +#define JSI_EXPORT +#endif // CREATE_SHARED_LIBRARY +#else // _MSC_VER +#define JSI_EXPORT __attribute__((visibility("default"))) +#endif // _MSC_VER +#endif // !defined(JSI_EXPORT) + +class FBJSRuntime; +namespace facebook { +namespace jsi { + +#if JSI_VERSION >= 20 +/// UUID version 1 implementation. This should be constructed with constant +/// arguments to identify fixed UUIDs. +class JSI_EXPORT UUID { + public: + // Construct from raw parts + constexpr UUID( + uint32_t timeLow, + uint16_t timeMid, + uint16_t timeHighAndVersion, + uint16_t variantAndClockSeq, + uint64_t node) + : high( + ((uint64_t)(timeLow) << 32) | ((uint64_t)(timeMid) << 16) | + ((uint64_t)(timeHighAndVersion))), + low(((uint64_t)(variantAndClockSeq) << 48) | node) {} + + // Default constructor (zero UUID) + constexpr UUID() : high(0), low(0) {} + + constexpr UUID(const UUID&) = default; + constexpr UUID& operator=(const UUID&) = default; + + constexpr bool operator==(const UUID& other) const { + return high == other.high && low == other.low; + } + constexpr bool operator!=(const UUID& other) const { + return !(*this == other); + } + + // Ordering (for std::map, sorting, etc.) + constexpr bool operator<(const UUID& other) const { + return (high < other.high) || (high == other.high && low < other.low); + } + + // Hash support for UUID (for unordered_map compatibility) + struct Hash { + std::size_t operator()(const UUID& uuid) const noexcept { + return std::hash{}(uuid.high) ^ + (std::hash{}(uuid.low) << 1); + } + }; + + // UUID format: 8-4-4-4-12 + std::string toString() const { + std::string buffer(36, ' '); + std::snprintf( + buffer.data(), + buffer.size() + 1, + "%08x-%04x-%04x-%04x-%012llx", + getTimeLow(), + getTimeMid(), + getTimeHighAndVersion(), + getVariantAndClockSeq(), + (unsigned long long)getNode()); + return buffer; + } + + constexpr uint32_t getTimeLow() const { + return (uint32_t)(high >> 32); + } + + constexpr uint16_t getTimeMid() const { + return (uint16_t)(high >> 16); + } + + constexpr uint16_t getTimeHighAndVersion() const { + return (uint16_t)high; + } + + constexpr uint16_t getVariantAndClockSeq() const { + return (uint16_t)(low >> 48); + } + + constexpr uint64_t getNode() const { + return low & 0xFFFFFFFFFFFF; + } + + private: + uint64_t high; + uint64_t low; +}; + +/// Base interface that all JSI interfaces inherit from. Users should not try to +/// manipulate this base type directly, and should use castInterface to get the +/// appropriate subtype. +struct JSI_EXPORT ICast { + /// If the current object can be cast into the interface specified by \p + /// interfaceUUID, return a pointer to the object. Otherwise, return a null + /// pointer. + /// The returned interface has the same lifetime as the underlying object. It + /// does not need to be released when not needed. + virtual ICast* castInterface(const UUID& interfaceUUID) = 0; + + protected: + /// Interfaces are not destructible, thus the destructor is intentionally + /// protected to prevent delete calls on the interface. + /// Additionally, the destructor is non-virtual to reduce the vtable + /// complexity from inheritance. + ~ICast() = default; +}; +#endif + +/// Base class for buffers of data or bytecode that need to be passed to the +/// runtime. The buffer is expected to be fully immutable, so the result of +/// size(), data(), and the contents of the pointer returned by data() must not +/// change after construction. +class JSI_EXPORT Buffer { + public: + virtual ~Buffer(); + virtual size_t size() const = 0; + virtual const uint8_t* data() const = 0; +}; + +class JSI_EXPORT StringBuffer : public Buffer { + public: + StringBuffer(std::string s) : s_(std::move(s)) {} + size_t size() const override { + return s_.size(); + } + const uint8_t* data() const override { + return reinterpret_cast(s_.data()); + } + + private: + std::string s_; +}; + +#if JSI_VERSION >= 9 +/// Base class for buffers of data that need to be passed to the runtime. The +/// result of size() and data() must not change after construction. However, the +/// region pointed to by data() may be modified by the user or the runtime. The +/// user must ensure that access to the contents of the buffer is properly +/// synchronised. +class JSI_EXPORT MutableBuffer { + public: + virtual ~MutableBuffer(); + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; +#endif + +/// PreparedJavaScript is a base class representing JavaScript which is in a +/// form optimized for execution, in a runtime-specific way. Construct one via +/// jsi::Runtime::prepareJavaScript(). +/// ** This is an experimental API that is subject to change. ** +class JSI_EXPORT PreparedJavaScript { + protected: + PreparedJavaScript() = default; + + public: + virtual ~PreparedJavaScript() = 0; +}; + +class Runtime; +class Pointer; +class PropNameID; +class Symbol; +#if JSI_VERSION >= 6 +class BigInt; +#endif +class String; +class Object; +class WeakObject; +class Array; +class ArrayBuffer; +class Function; +class Value; +class Instrumentation; +class Scope; +class JSIException; +class JSError; + +/// A function which has this type can be registered as a function +/// callable from JavaScript using Function::createFromHostFunction(). +/// When the function is called, args will point to the arguments, and +/// count will indicate how many arguments are passed. The function +/// can return a Value to the caller, or throw an exception. If a C++ +/// exception is thrown, a JS Error will be created and thrown into +/// JS; if the C++ exception extends std::exception, the Error's +/// message will be whatever what() returns. Note that it is undefined whether +/// HostFunctions may or may not be called in strict mode; that is `thisVal` +/// can be any value - it will not necessarily be coerced to an object or +/// or set to the global object. +using HostFunctionType = std::function< + Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>; + +/// An object which implements this interface can be registered as an +/// Object with the JS runtime. +class JSI_EXPORT HostObject { + public: + // The C++ object's dtor will be called when the GC finalizes this + // object. (This may be as late as when the Runtime is shut down.) + // You have no control over which thread it is called on. This will + // be called from inside the GC, so it is unsafe to do any VM + // operations which require a Runtime&. Derived classes' dtors + // should also avoid doing anything expensive. Calling the dtor on + // a jsi object is explicitly ok. If you want to do JS operations, + // or any nontrivial work, you should add it to a work queue, and + // manage it externally. + virtual ~HostObject(); + + // When JS wants a property with a given name from the HostObject, + // it will call this method. If it throws an exception, the call + // will throw a JS \c Error object. By default this returns undefined. + // \return the value for the property. + virtual Value get(Runtime&, const PropNameID& name); + + // When JS wants to set a property with a given name on the HostObject, + // it will call this method. If it throws an exception, the call will + // throw a JS \c Error object. By default this throws a type error exception + // mimicking the behavior of a frozen object in strict mode. + virtual void set(Runtime&, const PropNameID& name, const Value& value); + + // When JS wants a list of property names for the HostObject, it will + // call this method. If it throws an exception, the call will throw a + // JS \c Error object. The default implementation returns empty vector. + virtual std::vector getPropertyNames(Runtime& rt); +}; + +#if JSI_VERSION >= 7 +/// Native state (and destructor) that can be attached to any JS object +/// using setNativeState. +class JSI_EXPORT NativeState { + public: + virtual ~NativeState(); +}; +#endif + +/// Represents a JS runtime. Movable, but not copyable. Note that +/// this object may not be thread-aware, but cannot be used safely from +/// multiple threads at once. The application is responsible for +/// ensuring that it is used safely. This could mean using the +/// Runtime from a single thread, using a mutex, doing all work on a +/// serial queue, etc. This restriction applies to the methods of +/// this class, and any method in the API which take a Runtime& as an +/// argument. Destructors (all but ~Scope), operators, or other methods +/// which do not take Runtime& as an argument are safe to call from any +/// thread, but it is still forbidden to make write operations on a single +/// instance of any class from more than one thread. In addition, to +/// make shutdown safe, destruction of objects associated with the Runtime +/// must be destroyed before the Runtime is destroyed, or from the +/// destructor of a managed HostObject or HostFunction. Informally, this +/// means that the main source of unsafe behavior is to hold a jsi object +/// in a non-Runtime-managed object, and not clean it up before the Runtime +/// is shut down. If your lifecycle is such that avoiding this is hard, +/// you will probably need to do use your own locks. +#if JSI_VERSION >= 20 +class JSI_EXPORT Runtime : public ICast { +#else +class JSI_EXPORT Runtime { +#endif + public: + virtual ~Runtime(); + +#if JSI_VERSION >= 20 + ICast* castInterface(const UUID& interfaceUUID) override; +#endif + + /// Evaluates the given JavaScript \c buffer. \c sourceURL is used + /// to annotate the stack trace if there is an exception. The + /// contents may be utf8-encoded JS source code, or binary bytecode + /// whose format is specific to the implementation. If the input + /// format is unknown, or evaluation causes an error, a JSIException + /// will be thrown. + /// Note this function should ONLY be used when there isn't another means + /// through the JSI API. For example, it will be much slower to use this to + /// call a global function than using the JSI APIs to read the function + /// property from the global object and then calling it explicitly. + virtual Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) = 0; + + /// Prepares to evaluate the given JavaScript \c buffer by processing it into + /// a form optimized for execution. This may include pre-parsing, compiling, + /// etc. If the input is invalid (for example, cannot be parsed), a + /// JSIException will be thrown. The resulting object is tied to the + /// particular concrete type of Runtime from which it was created. It may be + /// used (via evaluatePreparedJavaScript) in any Runtime of the same concrete + /// type. + /// The PreparedJavaScript object may be passed to multiple VM instances, so + /// they can all share and benefit from the prepared script. + /// As with evaluateJavaScript(), using JavaScript code should be avoided + /// when the JSI API is sufficient. + virtual std::shared_ptr prepareJavaScript( + const std::shared_ptr& buffer, + std::string sourceURL) = 0; + + /// Evaluates a PreparedJavaScript. If evaluation causes an error, a + /// JSIException will be thrown. + /// As with evaluateJavaScript(), using JavaScript code should be avoided + /// when the JSI API is sufficient. + virtual Value evaluatePreparedJavaScript( + const std::shared_ptr& js) = 0; + +#if JSI_VERSION >= 12 + /// Queues a microtask in the JavaScript VM internal Microtask (a.k.a. Job in + /// ECMA262) queue, to be executed when the host drains microtasks in + /// its event loop implementation. + /// + /// \param callback a function to be executed as a microtask. + virtual void queueMicrotask(const jsi::Function& callback) = 0; +#endif + +#if JSI_VERSION >= 4 + /// Drain the JavaScript VM internal Microtask (a.k.a. Job in ECMA262) queue. + /// + /// \param maxMicrotasksHint a hint to tell an implementation that it should + /// make a best effort not execute more than the given number. It's default + /// to -1 for infinity (unbounded execution). + /// \return true if the queue is drained or false if there is more work to do. + /// + /// When there were exceptions thrown from the execution of microtasks, + /// implementations shall discard the exceptional jobs. An implementation may + /// \throw a \c JSError object to signal the hosts to handle. In that case, an + /// implementation may or may not suspend the draining. + /// + /// Hosts may call this function again to resume the draining if it was + /// suspended due to either exceptions or the \p maxMicrotasksHint bound. + /// E.g. a host may repetitively invoke this function until the queue is + /// drained to implement the "microtask checkpoint" defined in WHATWG HTML + /// event loop: https://html.spec.whatwg.org/C#perform-a-microtask-checkpoint. + /// + /// Note that error propagation is only a concern if a host needs to implement + /// `queueMicrotask`, a recent API that allows enqueueing arbitrary functions + /// (hence may throw) as microtasks. Exceptions from ECMA-262 Promise Jobs are + /// handled internally to VMs and are never propagated to hosts. + /// + /// This API offers some queue management to hosts at its best effort due to + /// different behaviors and limitations imposed by different VMs and APIs. By + /// the time this is written, An implementation may swallow exceptions (JSC), + /// may not pause (V8), and may not support bounded executions. + virtual bool drainMicrotasks(int maxMicrotasksHint = -1) = 0; +#endif + + /// \return the global object + virtual Object global() = 0; + + /// \return a short printable description of the instance. It should + /// at least include some human-readable indication of the runtime + /// implementation. This should only be used by logging, debugging, + /// and other developer-facing callers. + virtual std::string description() = 0; + + /// \return whether or not the underlying runtime supports debugging via the + /// Chrome remote debugging protocol. + /// + /// NOTE: the API for determining whether a runtime is debuggable and + /// registering a runtime with the debugger is still in flux, so please don't + /// use this API unless you know what you're doing. + virtual bool isInspectable() = 0; + + /// \return an interface to extract metrics from this \c Runtime. The default + /// implementation of this function returns an \c Instrumentation instance + /// which returns no metrics. + virtual Instrumentation& instrumentation(); + +#if JSI_VERSION >= 20 + /// Stores the pointer \p data with the \p uuid in the runtime. This can be + /// used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership of the held object. + void setRuntimeData(const UUID& uuid, const std::shared_ptr& data); + + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + std::shared_ptr getRuntimeData(const UUID& uuid); +#endif + + protected: + friend class Pointer; + friend class PropNameID; + friend class Symbol; +#if JSI_VERSION >= 6 + friend class BigInt; +#endif + friend class String; + friend class Object; + friend class WeakObject; + friend class Array; + friend class ArrayBuffer; + friend class Function; + friend class Value; + friend class Scope; + friend class JSError; + +#if JSI_VERSION >= 20 + /// Stores the pointer \p data with the \p uuid in the runtime. This can be + /// used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership by calling \p deleter. + virtual void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)); + + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + virtual const void* getRuntimeDataImpl(const UUID& uuid); +#endif + + // Potential optimization: avoid the cloneFoo() virtual dispatch, + // and instead just fix the number of fields, and copy them, since + // in practice they are trivially copyable. Sufficient use of + // rvalue arguments/methods would also reduce the number of clones. + + struct PointerValue { + virtual void invalidate() JSI_NOEXCEPT_15 = 0; + + protected: + virtual ~PointerValue() = default; + }; + + virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0; +#if JSI_VERSION >= 6 + virtual PointerValue* cloneBigInt(const Runtime::PointerValue* pv) = 0; +#endif + virtual PointerValue* cloneString(const Runtime::PointerValue* pv) = 0; + virtual PointerValue* cloneObject(const Runtime::PointerValue* pv) = 0; + virtual PointerValue* clonePropNameID(const Runtime::PointerValue* pv) = 0; + + virtual PropNameID createPropNameIDFromAscii( + const char* str, + size_t length) = 0; + virtual PropNameID createPropNameIDFromUtf8( + const uint8_t* utf8, + size_t length) = 0; +#if JSI_VERSION >= 19 + virtual PropNameID createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length); +#endif + virtual PropNameID createPropNameIDFromString(const String& str) = 0; +#if JSI_VERSION >= 5 + virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0; +#endif + virtual std::string utf8(const PropNameID&) = 0; + virtual bool compare(const PropNameID&, const PropNameID&) = 0; + + virtual std::string symbolToString(const Symbol&) = 0; + +#if JSI_VERSION >= 8 + virtual BigInt createBigIntFromInt64(int64_t) = 0; + virtual BigInt createBigIntFromUint64(uint64_t) = 0; + virtual bool bigintIsInt64(const BigInt&) = 0; + virtual bool bigintIsUint64(const BigInt&) = 0; + virtual uint64_t truncate(const BigInt&) = 0; + virtual String bigintToString(const BigInt&, int) = 0; +#endif + + virtual String createStringFromAscii(const char* str, size_t length) = 0; + virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0; +#if JSI_VERSION >= 19 + virtual String createStringFromUtf16(const char16_t* utf16, size_t length); +#endif + virtual std::string utf8(const String&) = 0; + + // \return a \c Value created from a utf8-encoded JSON string. The default + // implementation creates a \c String and invokes JSON.parse. +#if JSI_VERSION >= 2 + virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length); +#endif + + virtual Object createObject() = 0; + virtual Object createObject(std::shared_ptr ho) = 0; + virtual std::shared_ptr getHostObject(const jsi::Object&) = 0; + virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0; + +#if JSI_VERSION >= 18 + // Creates a new Object with the custom prototype + virtual Object createObjectWithPrototype(const Value& prototype); +#endif + +#if JSI_VERSION >= 7 + virtual bool hasNativeState(const jsi::Object&) = 0; + virtual std::shared_ptr getNativeState(const jsi::Object&) = 0; + virtual void setNativeState( + const jsi::Object&, + std::shared_ptr state) = 0; +#endif + +#if JSI_VERSION >= 17 + virtual void setPrototypeOf(const Object& object, const Value& prototype); + virtual Value getPrototypeOf(const Object& object); +#endif + + virtual Value getProperty(const Object&, const PropNameID& name) = 0; + virtual Value getProperty(const Object&, const String& name) = 0; + +#if JSI_VERSION >= 21 + virtual Value getProperty(const Object&, const Value& name); +#endif + + virtual bool hasProperty(const Object&, const PropNameID& name) = 0; + virtual bool hasProperty(const Object&, const String& name) = 0; + +#if JSI_VERSION >= 21 + virtual bool hasProperty(const Object&, const Value& name); +#endif + + virtual void setPropertyValue( + JSI_CONST_10 Object&, + const PropNameID& name, + const Value& value) = 0; + virtual void setPropertyValue( + JSI_CONST_10 Object&, + const String& name, + const Value& value) = 0; + +#if JSI_VERSION >= 21 + virtual void + setPropertyValue(const Object&, const Value& name, const Value& value); + + virtual void deleteProperty(const Object&, const PropNameID& name); + virtual void deleteProperty(const Object&, const String& name); + virtual void deleteProperty(const Object&, const Value& name); +#endif + + virtual bool isArray(const Object&) const = 0; + virtual bool isArrayBuffer(const Object&) const = 0; + virtual bool isFunction(const Object&) const = 0; + virtual bool isHostObject(const jsi::Object&) const = 0; + virtual bool isHostFunction(const jsi::Function&) const = 0; + virtual Array getPropertyNames(const Object&) = 0; + + virtual WeakObject createWeakObject(const Object&) = 0; + virtual Value lockWeakObject(JSI_NO_CONST_3 JSI_CONST_10 WeakObject&) = 0; + + virtual Array createArray(size_t length) = 0; +#if JSI_VERSION >= 9 + virtual ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) = 0; +#endif + virtual size_t size(const Array&) = 0; + virtual size_t size(const ArrayBuffer&) = 0; + virtual uint8_t* data(const ArrayBuffer&) = 0; + virtual Value getValueAtIndex(const Array&, size_t i) = 0; + virtual void + setValueAtIndexImpl(JSI_CONST_10 Array&, size_t i, const Value& value) = 0; + + virtual Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) = 0; + virtual Value call( + const Function&, + const Value& jsThis, + const Value* args, + size_t count) = 0; + virtual Value + callAsConstructor(const Function&, const Value* args, size_t count) = 0; + + // Private data for managing scopes. + struct ScopeState; + virtual ScopeState* pushScope(); + virtual void popScope(ScopeState*); + + virtual bool strictEquals(const Symbol& a, const Symbol& b) const = 0; +#if JSI_VERSION >= 6 + virtual bool strictEquals(const BigInt& a, const BigInt& b) const = 0; +#endif + virtual bool strictEquals(const String& a, const String& b) const = 0; + virtual bool strictEquals(const Object& a, const Object& b) const = 0; + + virtual bool instanceOf(const Object& o, const Function& f) = 0; + +#if JSI_VERSION >= 11 + /// See Object::setExternalMemoryPressure. + virtual void setExternalMemoryPressure( + const jsi::Object& obj, + size_t amount) = 0; +#endif + +#if JSI_VERSION >= 14 + virtual std::u16string utf16(const String& str); + virtual std::u16string utf16(const PropNameID& sym); +#endif + +#if JSI_VERSION >= 16 + /// 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)); +#endif + + // These exist so derived classes can access the private parts of + // Value, Symbol, String, and Object, which are all friends of Runtime. + template + static T make(PointerValue* pv); +#if JSI_VERSION >= 3 + static PointerValue* getPointerValue(Pointer& pointer); +#endif + static const PointerValue* getPointerValue(const Pointer& pointer); + static const PointerValue* getPointerValue(const Value& value); + + friend class ::FBJSRuntime; + template + friend class RuntimeDecorator; +}; + +// Base class for pointer-storing types. +class JSI_EXPORT Pointer { + protected: + explicit Pointer(Pointer&& other) JSI_NOEXCEPT_15 : ptr_(other.ptr_) { + other.ptr_ = nullptr; + } + + ~Pointer() { + if (ptr_) { + ptr_->invalidate(); + } + } + + Pointer& operator=(Pointer&& other) JSI_NOEXCEPT_15; + + friend class Runtime; + friend class Value; + + explicit Pointer(Runtime::PointerValue* ptr) : ptr_(ptr) {} + + typename Runtime::PointerValue* ptr_; +}; + +/// Represents something that can be a JS property key. Movable, not copyable. +class JSI_EXPORT PropNameID : public Pointer { + public: + using Pointer::Pointer; + + PropNameID(Runtime& runtime, const PropNameID& other) + : Pointer(runtime.clonePropNameID(other.ptr_)) {} + + PropNameID(PropNameID&& other) = default; + PropNameID& operator=(PropNameID&& other) = default; + + /// Create a JS property name id from ascii values. The data is + /// copied. + static PropNameID forAscii(Runtime& runtime, const char* str, size_t length) { + return runtime.createPropNameIDFromAscii(str, length); + } + + /// Create a property name id from a nul-terminated C ascii name. The data is + /// copied. + static PropNameID forAscii(Runtime& runtime, const char* str) { + return forAscii(runtime, str, strlen(str)); + } + + /// Create a PropNameID from a C++ string. The string is copied. + static PropNameID forAscii(Runtime& runtime, const std::string& str) { + return forAscii(runtime, str.c_str(), str.size()); + } + + /// Create a PropNameID from utf8 values. The data is copied. + /// Results are undefined if \p utf8 contains invalid code points. + static PropNameID + forUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) { + return runtime.createPropNameIDFromUtf8(utf8, length); + } + + /// Create a PropNameID from utf8-encoded octets stored in a + /// std::string. The string data is transformed and copied. + /// Results are undefined if \p utf8 contains invalid code points. + static PropNameID forUtf8(Runtime& runtime, const std::string& utf8) { + return runtime.createPropNameIDFromUtf8( + reinterpret_cast(utf8.data()), utf8.size()); + } + +#if JSI_VERSION >= 19 + /// Given a series of UTF-16 encoded code units, create a PropNameId. The + /// input may contain unpaired surrogates, which will be interpreted as a code + /// point of the same value. + static PropNameID + forUtf16(Runtime& runtime, const char16_t* utf16, size_t length) { + return runtime.createPropNameIDFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a PropNameId. The input may contain unpaired surrogates, which + /// will be interpreted as a code point of the same value. + static PropNameID forUtf16(Runtime& runtime, const std::u16string& str) { + return runtime.createPropNameIDFromUtf16(str.data(), str.size()); + } +#endif + + /// Create a PropNameID from a JS string. + static PropNameID forString(Runtime& runtime, const jsi::String& str) { + return runtime.createPropNameIDFromString(str); + } + +#if JSI_VERSION >= 5 + /// Create a PropNameID from a JS symbol. + static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym) { + return runtime.createPropNameIDFromSymbol(sym); + } +#endif + + // Creates a vector of PropNameIDs constructed from given arguments. + template + static std::vector names(Runtime& runtime, Args&&... args); + + // Creates a vector of given PropNameIDs. + template + static std::vector names(PropNameID (&&propertyNames)[N]); + + /// Copies the data in a PropNameID as utf8 into a C++ string. + std::string utf8(Runtime& runtime) const { + return runtime.utf8(*this); + } + +#if JSI_VERSION >= 14 + /// Copies the data in a PropNameID as utf16 into a C++ string. + std::u16string utf16(Runtime& runtime) const { + return runtime.utf16(*this); + } +#endif + +#if JSI_VERSION >= 16 + /// 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 + 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); + }); + } +#endif + + static bool compare( + Runtime& runtime, + const jsi::PropNameID& a, + const jsi::PropNameID& b) { + return runtime.compare(a, b); + } + + friend class Runtime; + friend class Value; +}; + +/// Represents a JS Symbol (es6). Movable, not copyable. +/// TODO T40778724: this is a limited implementation sufficient for +/// the debugger not to crash when a Symbol is a property in an Object +/// or element in an array. Complete support for creating will come +/// later. +class JSI_EXPORT Symbol : public Pointer { + public: + using Pointer::Pointer; + + Symbol(Symbol&& other) = default; + Symbol& operator=(Symbol&& other) = default; + + /// \return whether a and b refer to the same symbol. + static bool strictEquals(Runtime& runtime, const Symbol& a, const Symbol& b) { + return runtime.strictEquals(a, b); + } + + /// Converts a Symbol into a C++ string as JS .toString would. The output + /// will look like \c Symbol(description) . + std::string toString(Runtime& runtime) const { + return runtime.symbolToString(*this); + } + + friend class Runtime; + friend class Value; +}; + +#if JSI_VERSION >= 6 +/// Represents a JS BigInt. Movable, not copyable. +class JSI_EXPORT BigInt : public Pointer { + public: + using Pointer::Pointer; + + BigInt(BigInt&& other) = default; + BigInt& operator=(BigInt&& other) = default; + +#if JSI_VERSION >= 8 + /// Create a BigInt representing the signed 64-bit \p value. + static BigInt fromInt64(Runtime& runtime, int64_t value) { + return runtime.createBigIntFromInt64(value); + } + + /// Create a BigInt representing the unsigned 64-bit \p value. + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + return runtime.createBigIntFromUint64(value); + } + + /// \return whether a === b. + static bool strictEquals(Runtime& runtime, const BigInt& a, const BigInt& b) { + return runtime.strictEquals(a, b); + } + + /// \returns This bigint truncated to a signed 64-bit integer. + int64_t getInt64(Runtime& runtime) const { + return runtime.truncate(*this); + } + + /// \returns Whether this bigint can be losslessly converted to int64_t. + bool isInt64(Runtime& runtime) const { + return runtime.bigintIsInt64(*this); + } + + /// \returns This bigint truncated to a signed 64-bit integer. Throws a + /// JSIException if the truncation is lossy. + int64_t asInt64(Runtime& runtime) const; + + /// \returns This bigint truncated to an unsigned 64-bit integer. + uint64_t getUint64(Runtime& runtime) const { + return runtime.truncate(*this); + } + + /// \returns Whether this bigint can be losslessly converted to uint64_t. + bool isUint64(Runtime& runtime) const { + return runtime.bigintIsUint64(*this); + } + + /// \returns This bigint truncated to an unsigned 64-bit integer. Throws a + /// JSIException if the truncation is lossy. + uint64_t asUint64(Runtime& runtime) const; + + /// \returns this BigInt converted to a String in base \p radix. Throws a + /// JSIException if radix is not in the [2, 36] range. + inline String toString(Runtime& runtime, int radix = 10) const; +#endif + + friend class Runtime; + friend class Value; +}; +#endif + +/// Represents a JS String. Movable, not copyable. +class JSI_EXPORT String : public Pointer { + public: + using Pointer::Pointer; + + String(String&& other) = default; + String& operator=(String&& other) = default; + + /// Create a JS string from ascii values. The string data is + /// copied. + static String + createFromAscii(Runtime& runtime, const char* str, size_t length) { + return runtime.createStringFromAscii(str, length); + } + + /// Create a JS string from a nul-terminated C ascii string. The + /// string data is copied. + static String createFromAscii(Runtime& runtime, const char* str) { + return createFromAscii(runtime, str, strlen(str)); + } + + /// Create a JS string from a C++ string. The string data is + /// copied. + static String createFromAscii(Runtime& runtime, const std::string& str) { + return createFromAscii(runtime, str.c_str(), str.size()); + } + + /// Create a JS string from utf8-encoded octets. The string data is + /// transformed and copied. Results are undefined if \p utf8 contains invalid + /// code points. + static String + createFromUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) { + return runtime.createStringFromUtf8(utf8, length); + } + + /// Create a JS string from utf8-encoded octets stored in a + /// std::string. The string data is transformed and copied. Results are + /// undefined if \p utf8 contains invalid code points. + static String createFromUtf8(Runtime& runtime, const std::string& utf8) { + return runtime.createStringFromUtf8( + reinterpret_cast(utf8.data()), utf8.length()); + } + +#if JSI_VERSION >= 19 + /// Given a series of UTF-16 encoded code units, create a JS String. The input + /// may contain unpaired surrogates, which will be interpreted as a code point + /// of the same value. + static String + createFromUtf16(Runtime& runtime, const char16_t* utf16, size_t length) { + return runtime.createStringFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a JS String. The input may contain unpaired surrogates, which will + /// be interpreted as a code point of the same value. + static String createFromUtf16(Runtime& runtime, const std::u16string& utf16) { + return runtime.createStringFromUtf16(utf16.data(), utf16.length()); + } +#endif + + /// \return whether a and b contain the same characters. + static bool strictEquals(Runtime& runtime, const String& a, const String& b) { + return runtime.strictEquals(a, b); + } + + /// Copies the data in a JS string as utf8 into a C++ string. + std::string utf8(Runtime& runtime) const { + return runtime.utf8(*this); + } + +#if JSI_VERSION >= 14 + /// Copies the data in a JS string as utf16 into a C++ string. + std::u16string utf16(Runtime& runtime) const { + return runtime.utf16(*this); + } +#endif + +#if JSI_VERSION >= 16 + /// 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 + 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); + }); + } +#endif + + friend class Runtime; + friend class Value; +}; + +class Array; +class Function; + +/// Represents a JS Object. Movable, not copyable. +class JSI_EXPORT Object : public Pointer { + public: + using Pointer::Pointer; + + Object(Object&& other) = default; + Object& operator=(Object&& other) = default; + + /// Creates a new Object instance, like '{}' in JS. + explicit Object(Runtime& runtime) : Object(runtime.createObject()) {} + + static Object createFromHostObject( + Runtime& runtime, + std::shared_ptr ho) { + return runtime.createObject(ho); + } + +#if JSI_VERSION >= 18 + /// Creates a new Object with the custom prototype + static Object create(Runtime& runtime, const Value& prototype) { + return runtime.createObjectWithPrototype(prototype); + } +#endif + + /// \return whether this and \c obj are the same JSObject or not. + static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) { + return runtime.strictEquals(a, b); + } + + /// \return the result of `this instanceOf ctor` in JS. + bool instanceOf(Runtime& rt, const Function& ctor) JSI_CONST_10 { + return rt.instanceOf(*this, ctor); + } + +#if JSI_VERSION >= 17 + /// Sets \p prototype as the prototype of the object. The prototype must be + /// either an Object or null. If the prototype was not set successfully, this + /// method will throw. + void setPrototype(Runtime& runtime, const Value& prototype) const { + return runtime.setPrototypeOf(*this, prototype); + } + + /// \return the prototype of the object + inline Value getPrototype(Runtime& runtime) const; +#endif + + /// \return the property of the object with the given ascii name. + /// If the name isn't a property on the object, returns the + /// undefined value. + Value getProperty(Runtime& runtime, const char* name) const; + + /// \return the property of the object with the String name. + /// If the name isn't a property on the object, returns the + /// undefined value. + Value getProperty(Runtime& runtime, const String& name) const; + + /// \return the property of the object with the given JS PropNameID + /// name. If the name isn't a property on the object, returns the + /// undefined value. + Value getProperty(Runtime& runtime, const PropNameID& name) const; + +#if JSI_VERSION >= 21 + /// \return the Property of the object with the given JS Value name. If the + /// name isn't a property on the object, returns the undefined value.This + /// attempts to convert the JS Value to convert to a property key. If the + /// conversion fails, this method may throw. + Value getProperty(Runtime& runtime, const Value& name) const; +#endif + + /// \return true if and only if the object has a property with the + /// given ascii name. + bool hasProperty(Runtime& runtime, const char* name) const; + + /// \return true if and only if the object has a property with the + /// given String name. + bool hasProperty(Runtime& runtime, const String& name) const; + + /// \return true if and only if the object has a property with the + /// given PropNameID name. + bool hasProperty(Runtime& runtime, const PropNameID& name) const; + +#if JSI_VERSION >= 21 + /// \return true if and only if the object has a property with the given + /// JS Value name. This attempts to convert the JS Value to convert to a + /// property key. If the conversion fails, this method may throw. + bool hasProperty(Runtime& runtime, const Value& name) const; +#endif + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. + template + void setProperty(Runtime& runtime, const char* name, T&& value) JSI_CONST_10; + + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. + template + void setProperty(Runtime& runtime, const String& name, T&& value) + JSI_CONST_10; + + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. + template + void setProperty(Runtime& runtime, const PropNameID& name, T&& value) + JSI_CONST_10; + +#if JSI_VERSION >= 21 + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. This takes a JS Value as the property name, and + /// attempts to convert to a property key. If the conversion fails, this + /// method may throw. + template + void setProperty(Runtime& runtime, const Value& name, T&& value) const; + + /// Delete the property with the given ascii name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const char* name) const; + + /// Delete the property with the given String name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const String& name) const; + + /// Delete the property with the given PropNameID name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const PropNameID& name) const; + + /// Delete the property with the given Value name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const Value& name) const; +#endif + + /// \return true iff JS \c Array.isArray() would return \c true. If + /// so, then \c getArray() will succeed. + bool isArray(Runtime& runtime) const { + return runtime.isArray(*this); + } + + /// \return true iff the Object is an ArrayBuffer. If so, then \c + /// getArrayBuffer() will succeed. + bool isArrayBuffer(Runtime& runtime) const { + return runtime.isArrayBuffer(*this); + } + + /// \return true iff the Object is callable. If so, then \c + /// getFunction will succeed. + bool isFunction(Runtime& runtime) const { + return runtime.isFunction(*this); + } + + /// \return true iff the Object was initialized with \c createFromHostObject + /// and the HostObject passed is of type \c T. If returns \c true then + /// \c getHostObject will succeed. + template + bool isHostObject(Runtime& runtime) const; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will assert. + Array getArray(Runtime& runtime) const&; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will assert. + Array getArray(Runtime& runtime) &&; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will throw + /// JSIException. + Array asArray(Runtime& runtime) const&; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will throw + /// JSIException. + Array asArray(Runtime& runtime) &&; + + /// \return an ArrayBuffer instance which refers to the same underlying + /// object. If \c isArrayBuffer() would return false, this will assert. + ArrayBuffer getArrayBuffer(Runtime& runtime) const&; + + /// \return an ArrayBuffer instance which refers to the same underlying + /// object. If \c isArrayBuffer() would return false, this will assert. + ArrayBuffer getArrayBuffer(Runtime& runtime) &&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will assert. + Function getFunction(Runtime& runtime) const&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will assert. + Function getFunction(Runtime& runtime) &&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will throw + /// JSIException. + Function asFunction(Runtime& runtime) const&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will throw + /// JSIException. + Function asFunction(Runtime& runtime) &&; + + /// \return a shared_ptr which refers to the same underlying + /// \c HostObject that was used to create this object. If \c isHostObject + /// is false, this will assert. Note that this does a type check and will + /// assert if the underlying HostObject isn't of type \c T + template + std::shared_ptr getHostObject(Runtime& runtime) const; + + /// \return a shared_ptr which refers to the same underlying + /// \c HostObject that was used to create this object. If \c isHostObject + /// is false, this will throw. + template + std::shared_ptr asHostObject(Runtime& runtime) const; + +#if JSI_VERSION >= 7 + /// \return whether this object has native state of type T previously set by + /// \c setNativeState. + template + bool hasNativeState(Runtime& runtime) const; + + /// \return a shared_ptr to the state previously set by \c setNativeState. + /// If \c hasNativeState is false, this will assert. Note that this does a + /// type check and will assert if the native state isn't of type \c T + template + std::shared_ptr getNativeState(Runtime& runtime) const; + + /// Set the internal native state property of this object, overwriting any old + /// value. Creates a new shared_ptr to the object managed by \p state, which + /// will live until the value at this property becomes unreachable. + /// + /// Throws a type error if this object is a proxy or host object. + void setNativeState(Runtime& runtime, std::shared_ptr state) + const; +#endif + + /// \return same as \c getProperty(name).asObject(), except with + /// a better exception message. + Object getPropertyAsObject(Runtime& runtime, const char* name) const; + + /// \return similar to \c + /// getProperty(name).getObject().getFunction(), except it will + /// throw JSIException instead of asserting if the property is + /// not an object, or the object is not callable. + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + /// \return an Array consisting of all enumerable property names in + /// the object and its prototype chain. All values in the return + /// will be isString(). (This is probably not optimal, but it + /// works. I only need it in one place.) + Array getPropertyNames(Runtime& runtime) const; + +#if JSI_VERSION >= 11 + /// Inform the runtime that there is additional memory associated with a given + /// JavaScript object that is not visible to the GC. This can be used if an + /// object is known to retain some native memory, and may be used to guide + /// decisions about when to run garbage collection. + /// This method may be invoked multiple times on an object, and subsequent + /// calls will overwrite any previously set value. Once the object is garbage + /// collected, the associated external memory will be considered freed and may + /// no longer factor into GC decisions. + void setExternalMemoryPressure(Runtime& runtime, size_t amt) const; +#endif + + protected: + void setPropertyValue( + Runtime& runtime, + const String& name, + const Value& value) JSI_CONST_10 { + return runtime.setPropertyValue(*this, name, value); + } + + void setPropertyValue( + Runtime& runtime, + const PropNameID& name, + const Value& value) JSI_CONST_10 { + return runtime.setPropertyValue(*this, name, value); + } + +#if JSI_VERSION >= 21 + void setPropertyValue(Runtime& runtime, const Value& name, const Value& value) + const { + return runtime.setPropertyValue(*this, name, value); + } +#endif + + friend class Runtime; + friend class Value; +}; + +/// Represents a weak reference to a JS Object. If the only reference +/// to an Object are these, the object is eligible for GC. Method +/// names are inspired by C++ weak_ptr. Movable, not copyable. +class JSI_EXPORT WeakObject : public Pointer { + public: + using Pointer::Pointer; + + WeakObject(WeakObject&& other) = default; + WeakObject& operator=(WeakObject&& other) = default; + + /// Create a WeakObject from an Object. + WeakObject(Runtime& runtime, const Object& o) + : WeakObject(runtime.createWeakObject(o)) {} + + /// \return a Value representing the underlying Object if it is still valid; + /// otherwise returns \c undefined. Note that this method has nothing to do + /// with threads or concurrency. The name is based on std::weak_ptr::lock() + /// which serves a similar purpose. + Value lock(Runtime& runtime) JSI_CONST_10; + + friend class Runtime; +}; + +/// Represents a JS Object which can be efficiently used as an array +/// with integral indices. +class JSI_EXPORT Array : public Object { + public: + Array(Array&&) = default; + /// Creates a new Array instance, with \c length undefined elements. + Array(Runtime& runtime, size_t length) : Array(runtime.createArray(length)) {} + + Array& operator=(Array&&) = default; + + /// \return the size of the Array, according to its length property. + /// (C++ naming convention) + size_t size(Runtime& runtime) const { + return runtime.size(*this); + } + + /// \return the size of the Array, according to its length property. + /// (JS naming convention) + size_t length(Runtime& runtime) const { + return size(runtime); + } + + /// \return the property of the array at index \c i. If there is no + /// such property, returns the undefined value. If \c i is out of + /// range [ 0..\c length ] throws a JSIException. + Value getValueAtIndex(Runtime& runtime, size_t i) const; + + /// Sets the property of the array at index \c i. The argument + /// value behaves as with Object::setProperty(). If \c i is out of + /// range [ 0..\c length ] throws a JSIException. + template + void setValueAtIndex(Runtime& runtime, size_t i, T&& value) JSI_CONST_10; + + /// There is no current API for changing the size of an array once + /// created. We'll probably need that eventually. + + /// Creates a new Array instance from provided values + template + static Array createWithElements(Runtime&, Args&&... args); + + /// Creates a new Array instance from initializer list. + static Array createWithElements( + Runtime& runtime, + std::initializer_list elements); + + private: + friend class Object; + friend class Value; + friend class Runtime; + + void setValueAtIndexImpl(Runtime& runtime, size_t i, const Value& value) + JSI_CONST_10 { + return runtime.setValueAtIndexImpl(*this, i, value); + } + + Array(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents a JSArrayBuffer +class JSI_EXPORT ArrayBuffer : public Object { + public: + ArrayBuffer(ArrayBuffer&&) = default; + ArrayBuffer& operator=(ArrayBuffer&&) = default; + +#if JSI_VERSION >= 9 + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {} +#endif + + /// \return the size of the ArrayBuffer storage. This is not affected by + /// overriding the byteLength property. + /// (C++ naming convention) + size_t size(Runtime& runtime) const { + return runtime.size(*this); + } + + size_t length(Runtime& runtime) const { + return runtime.size(*this); + } + + uint8_t* data(Runtime& runtime) JSI_CONST_10 { + return runtime.data(*this); + } + + private: + friend class Object; + friend class Value; + friend class Runtime; + + ArrayBuffer(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents a JS Object which is guaranteed to be Callable. +class JSI_EXPORT Function : public Object { + public: + Function(Function&&) = default; + Function& operator=(Function&&) = default; + + /// Create a function which, when invoked, calls C++ code. If the + /// function throws an exception, a JS Error will be created and + /// thrown. + /// \param name the name property for the function. + /// \param paramCount the length property for the function, which + /// may not be the number of arguments the function is passed. + /// \note The std::function's dtor will be called when the GC finalizes this + /// function. As with HostObject, this may be as late as when the Runtime is + /// shut down, and may occur on an arbitrary thread. If the function contains + /// any captured values, you are responsible for ensuring that their + /// destructors are safe to call on any thread. + static Function createFromHostFunction( + Runtime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func); + + /// Calls the function with \c count \c args. The \c this value of the JS + /// function will not be set by the C++ caller, similar to calling + /// Function.prototype.apply(undefined, args) in JS. + /// \b Note: as with Function.prototype.apply, \c this may not always be + /// \c undefined in the function itself. If the function is non-strict, + /// \c this will be set to the global object. + Value call(Runtime& runtime, const Value* args, size_t count) const; + + /// Calls the function with a \c std::initializer_list of Value + /// arguments. The \c this value of the JS function will not be set by the + /// C++ caller, similar to calling Function.prototype.apply(undefined, args) + /// in JS. + /// \b Note: as with Function.prototype.apply, \c this may not always be + /// \c undefined in the function itself. If the function is non-strict, + /// \c this will be set to the global object. + Value call(Runtime& runtime, std::initializer_list args) const; + + /// Calls the function with any number of arguments similarly to + /// Object::setProperty(). The \c this value of the JS function will not be + /// set by the C++ caller, similar to calling + /// Function.prototype.call(undefined, ...args) in JS. + /// \b Note: as with Function.prototype.call, \c this may not always be + /// \c undefined in the function itself. If the function is non-strict, + /// \c this will be set to the global object. + template + Value call(Runtime& runtime, Args&&... args) const; + + /// Calls the function with \c count \c args and \c jsThis value passed + /// as the \c this value. + Value callWithThis( + Runtime& Runtime, + const Object& jsThis, + const Value* args, + size_t count) const; + + /// Calls the function with a \c std::initializer_list of Value + /// arguments and \c jsThis passed as the \c this value. + Value callWithThis( + Runtime& runtime, + const Object& jsThis, + std::initializer_list args) const; + + /// Calls the function with any number of arguments similarly to + /// Object::setProperty(), and with \c jsThis passed as the \c this value. + template + Value callWithThis(Runtime& runtime, const Object& jsThis, Args&&... args) + const; + + /// Calls the function as a constructor with \c count \c args. Equivalent + /// to calling `new Func` where `Func` is the js function reqresented by + /// this. + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) + const; + + /// Same as above `callAsConstructor`, except use an initializer_list to + /// supply the arguments. + Value callAsConstructor(Runtime& runtime, std::initializer_list args) + const; + + /// Same as above `callAsConstructor`, but automatically converts/wraps + /// any argument with a jsi Value. + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const; + + /// Returns whether this was created with Function::createFromHostFunction. + /// If true then you can use getHostFunction to get the underlying + /// HostFunctionType. + bool isHostFunction(Runtime& runtime) const { + return runtime.isHostFunction(*this); + } + + /// Returns the underlying HostFunctionType iff isHostFunction returns true + /// and asserts otherwise. You can use this to use std::function<>::target + /// to get the object that was passed to create the HostFunctionType. + /// + /// Note: The reference returned is borrowed from the JS object underlying + /// \c this, and thus only lasts as long as the object underlying + /// \c this does. + HostFunctionType& getHostFunction(Runtime& runtime) const { + assert(isHostFunction(runtime)); + return runtime.getHostFunction(*this); + } + + private: + friend class Object; + friend class Value; + friend class Runtime; + + Function(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents any JS Value (undefined, null, boolean, number, symbol, +/// string, or object). Movable, or explicitly copyable (has no copy +/// ctor). +class JSI_EXPORT Value { + public: + /// Default ctor creates an \c undefined JS value. + Value() JSI_NOEXCEPT_15 : Value(UndefinedKind) {} + + /// Creates a \c null JS value. + /* implicit */ Value(std::nullptr_t) : kind_(NullKind) {} + + /// Creates a boolean JS value. + /* implicit */ Value(bool b) : Value(BooleanKind) { + data_.boolean = b; + } + + /// Creates a number JS value. + /* implicit */ Value(double d) : Value(NumberKind) { + data_.number = d; + } + + /// Creates a number JS value. + /* implicit */ Value(int i) : Value(NumberKind) { + data_.number = i; + } + + /// Moves a Symbol, String, or Object rvalue into a new JS value. + template < + typename T, + typename = std::enable_if_t< + std::is_base_of::value || +#if JSI_VERSION >= 6 + std::is_base_of::value || +#endif + std::is_base_of::value || + std::is_base_of::value>> + /* implicit */ Value(T&& other) : Value(kindOf(other)) { + new (&data_.pointer) T(std::move(other)); + } + + /// Value("foo") will treat foo as a bool. This makes doing that a + /// compile error. + template + Value(const char*) { + static_assert( + !std::is_same::value, + "Value cannot be constructed directly from const char*"); + } + + Value(Value&& other) JSI_NOEXCEPT_15; + + /// Copies a Symbol lvalue into a new JS value. + Value(Runtime& runtime, const Symbol& sym) : Value(SymbolKind) { + new (&data_.pointer) Symbol(runtime.cloneSymbol(sym.ptr_)); + } + +#if JSI_VERSION >= 6 + /// Copies a BigInt lvalue into a new JS value. + Value(Runtime& runtime, const BigInt& bigint) : Value(BigIntKind) { + new (&data_.pointer) BigInt(runtime.cloneBigInt(bigint.ptr_)); + } +#endif + + /// Copies a String lvalue into a new JS value. + Value(Runtime& runtime, const String& str) : Value(StringKind) { + new (&data_.pointer) String(runtime.cloneString(str.ptr_)); + } + + /// Copies a Object lvalue into a new JS value. + Value(Runtime& runtime, const Object& obj) : Value(ObjectKind) { + new (&data_.pointer) Object(runtime.cloneObject(obj.ptr_)); + } + + /// Creates a JS value from another Value lvalue. + Value(Runtime& runtime, const Value& value); + + /// Value(rt, "foo") will treat foo as a bool. This makes doing + /// that a compile error. + template + Value(Runtime&, const char*) { + static_assert( + !std::is_same::value, + "Value cannot be constructed directly from const char*"); + } + + ~Value(); + // \return the undefined \c Value. + static Value undefined() { + return Value(); + } + + // \return the null \c Value. + static Value null() { + return Value(nullptr); + } + + // \return a \c Value created from a utf8-encoded JSON string. + static Value + createFromJsonUtf8(Runtime& runtime, const uint8_t* json, size_t length) +#if JSI_VERSION >= 2 + { + return runtime.createValueFromJsonUtf8(json, length); + } +#else + ; +#endif + + /// \return according to the Strict Equality Comparison algorithm, see: + /// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison + static bool strictEquals(Runtime& runtime, const Value& a, const Value& b); + + Value& operator=(Value&& other) JSI_NOEXCEPT_15 { + this->~Value(); + new (this) Value(std::move(other)); + return *this; + } + + bool isUndefined() const { + return kind_ == UndefinedKind; + } + + bool isNull() const { + return kind_ == NullKind; + } + + bool isBool() const { + return kind_ == BooleanKind; + } + + bool isNumber() const { + return kind_ == NumberKind; + } + + bool isString() const { + return kind_ == StringKind; + } + +#if JSI_VERSION >= 6 + bool isBigInt() const { + return kind_ == BigIntKind; + } +#endif + + bool isSymbol() const { + return kind_ == SymbolKind; + } + + bool isObject() const { + return kind_ == ObjectKind; + } + + /// \return the boolean value, or asserts if not a boolean. + bool getBool() const { + assert(isBool()); + return data_.boolean; + } + + /// \return the boolean value, or throws JSIException if not a + /// boolean. + bool asBool() const; + + /// \return the number value, or asserts if not a number. + double getNumber() const { + assert(isNumber()); + return data_.number; + } + + /// \return the number value, or throws JSIException if not a + /// number. + double asNumber() const; + + /// \return the Symbol value, or asserts if not a symbol. + Symbol getSymbol(Runtime& runtime) const& { + assert(isSymbol()); + return Symbol(runtime.cloneSymbol(data_.pointer.ptr_)); + } + + /// \return the Symbol value, or asserts if not a symbol. + /// Can be used on rvalue references to avoid cloning more symbols. + Symbol getSymbol(Runtime&) && { + assert(isSymbol()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the Symbol value, or throws JSIException if not a + /// symbol + Symbol asSymbol(Runtime& runtime) const&; + Symbol asSymbol(Runtime& runtime) &&; + +#if JSI_VERSION >= 6 + /// \return the BigInt value, or asserts if not a bigint. + BigInt getBigInt(Runtime& runtime) const& { + assert(isBigInt()); + return BigInt(runtime.cloneBigInt(data_.pointer.ptr_)); + } + + /// \return the BigInt value, or asserts if not a bigint. + /// Can be used on rvalue references to avoid cloning more bigints. + BigInt getBigInt(Runtime&) && { + assert(isBigInt()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the BigInt value, or throws JSIException if not a + /// bigint + BigInt asBigInt(Runtime& runtime) const&; + BigInt asBigInt(Runtime& runtime) &&; +#endif + + /// \return the String value, or asserts if not a string. + String getString(Runtime& runtime) const& { + assert(isString()); + return String(runtime.cloneString(data_.pointer.ptr_)); + } + + /// \return the String value, or asserts if not a string. + /// Can be used on rvalue references to avoid cloning more strings. + String getString(Runtime&) && { + assert(isString()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the String value, or throws JSIException if not a + /// string. + String asString(Runtime& runtime) const&; + String asString(Runtime& runtime) &&; + + /// \return the Object value, or asserts if not an object. + Object getObject(Runtime& runtime) const& { + assert(isObject()); + return Object(runtime.cloneObject(data_.pointer.ptr_)); + } + + /// \return the Object value, or asserts if not an object. + /// Can be used on rvalue references to avoid cloning more objects. + Object getObject(Runtime&) && { + assert(isObject()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the Object value, or throws JSIException if not an + /// object. + Object asObject(Runtime& runtime) const&; + Object asObject(Runtime& runtime) &&; + + // \return a String like JS .toString() would do. + String toString(Runtime& runtime) const; + + private: + friend class Runtime; + + enum ValueKind { + UndefinedKind, + NullKind, + BooleanKind, + NumberKind, + SymbolKind, +#if JSI_VERSION >= 6 + BigIntKind, +#endif + StringKind, + ObjectKind, + PointerKind = SymbolKind, + }; + + union Data { + // Value's ctor and dtor will manage the lifecycle of the contained Data. + Data() { + static_assert( + sizeof(Data) == sizeof(uint64_t), + "Value data should fit in a 64-bit register"); + } + ~Data() {} + + // scalars + bool boolean; + double number; + // pointers + Pointer pointer; // Symbol, String, Object, Array, Function + }; + + Value(ValueKind kind) : kind_(kind) {} + + constexpr static ValueKind kindOf(const Symbol&) { + return SymbolKind; + } +#if JSI_VERSION >= 6 + constexpr static ValueKind kindOf(const BigInt&) { + return BigIntKind; + } +#endif + constexpr static ValueKind kindOf(const String&) { + return StringKind; + } + constexpr static ValueKind kindOf(const Object&) { + return ObjectKind; + } + + ValueKind kind_; + Data data_; + + // In the future: Value becomes NaN-boxed. See T40538354. +}; + +/// Not movable and not copyable RAII marker advising the underlying +/// JavaScript VM to track resources allocated since creation until +/// destruction so that they can be recycled eagerly when the Scope +/// goes out of scope instead of floating in the air until the next +/// garbage collection or any other delayed release occurs. +/// +/// This API should be treated only as advice, implementations can +/// choose to ignore the fact that Scopes are created or destroyed. +/// +/// This class is an exception to the rule allowing destructors to be +/// called without proper synchronization (see Runtime documentation). +/// The whole point of this class is to enable all sorts of clean ups +/// when the destructor is called and this proper synchronization is +/// required at that time. +/// +/// Instances of this class are intended to be created as automatic stack +/// variables in which case destructor calls don't require any additional +/// locking, provided that the lock (if any) is managed with RAII helpers. +class JSI_EXPORT Scope { + public: + explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {} + ~Scope() { + rt_.popScope(prv_); + } + + Scope(const Scope&) = delete; + Scope(Scope&&) = delete; + + Scope& operator=(const Scope&) = delete; + Scope& operator=(Scope&&) = delete; + + template + static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) { + Scope s(rt); + return f(); + } + + private: + Runtime& rt_; + Runtime::ScopeState* prv_; +}; + +/// Base class for jsi exceptions +class JSI_EXPORT JSIException : public std::exception { + protected: + JSIException() {} + JSIException(std::string what) : what_(std::move(what)) {} + + public: + JSIException(const JSIException&) = default; + + virtual const char* what() const noexcept override { + return what_.c_str(); + } + + virtual ~JSIException() override; + + protected: + std::string what_; +}; + +/// This exception will be thrown by API functions on errors not related to +/// JavaScript execution. +class JSI_EXPORT JSINativeException : public JSIException { + public: + JSINativeException(std::string what) : JSIException(std::move(what)) {} + + JSINativeException(const JSINativeException&) = default; + + virtual ~JSINativeException(); +}; + +/// This exception will be thrown by API functions whenever a JS +/// operation causes an exception as described by the spec, or as +/// otherwise described. +class JSI_EXPORT JSError : public JSIException { + public: + /// Creates a JSError referring to provided \c value + JSError(Runtime& r, Value&& value); + + /// Creates a JSError referring to new \c Error instance capturing current + /// JavaScript stack. The error message property is set to given \c message. + JSError(Runtime& rt, std::string message); + + /// Creates a JSError referring to new \c Error instance capturing current + /// JavaScript stack. The error message property is set to given \c message. + JSError(Runtime& rt, const char* message) + : JSError(rt, std::string(message)) {} + + /// Creates a JSError referring to a JavaScript Object having message and + /// stack properties set to provided values. + JSError(Runtime& rt, std::string message, std::string stack); + + /// Creates a JSError referring to provided value and what string + /// set to provided message. This argument order is a bit weird, + /// but necessary to avoid ambiguity with the above. + JSError(std::string what, Runtime& rt, Value&& value); + + /// Creates a JSError referring to the provided value, message and stack. This + /// constructor does not take a Runtime parameter, and therefore cannot result + /// in recursively invoking the JSError constructor. + JSError(Value&& value, std::string message, std::string stack); + + JSError(const JSError&) = default; + + virtual ~JSError(); + + const std::string& getStack() const { + return stack_; + } + + const std::string& getMessage() const { + return message_; + } + + const jsi::Value& value() const { + assert(value_); + return *value_; + } + + // TODO: (vmoroz) Can we remove it considering that we have the new JSError + // constructor? + // In V8's case, creating an Error object in JS doesn't record the callstack. + // To preserve it, we need a way to manually add the stack here and on the JS + // side. + void setStack(std::string stack) { + stack_ = std::move(stack); + what_ = message_ + "\n\n" + stack_; + } + + private: + // This initializes the value_ member and does some other + // validation, so it must be called by every branch through the + // constructors. + void setValue(Runtime& rt, Value&& value); + + // This needs to be on the heap, because throw requires the object + // be copyable, and Value is not. + std::shared_ptr value_; + std::string message_; + std::string stack_; +}; + +/// Helper function to cast the object pointed to by \p ptr into an interface +/// specified by \c U. If cast is successful, return a pointer to the object +/// as a raw pointer of \c U. Otherwise, return nullptr. +/// The returned interface same lifetime as the object referenced by \p ptr. +#if JSI_VERSION >= 20 +template +U* castInterface(T* ptr) { + if (ptr) { + return static_cast(ptr->castInterface(U::uuid)); + } + return nullptr; +} + +/// Helper function to cast the object managed by the shared_ptr \p ptr into an +/// interface specified by \c U. If the cast is successful, return a shared_ptr +/// of type \c U to the object. Otherwise, return an empty pointer. +/// The returned shared_ptr shares ownership of the object with \p ptr. +template +std::shared_ptr dynamicInterfaceCast(T&& ptr) { + auto* p = ptr->castInterface(U::uuid); + U* res = static_cast(p); + if (res) { + return std::shared_ptr(std::forward(ptr), res); + } + return nullptr; +} +#endif + +} // namespace jsi +} // namespace facebook + +#include diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/threadsafe.h b/vnext/Microsoft.ReactNative.Cxx/JSI/threadsafe.h new file mode 100644 index 00000000000..cb10a335f6c --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/threadsafe.h @@ -0,0 +1,79 @@ +/* + * 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 + +#include +#include + +namespace facebook { +namespace jsi { + +class ThreadSafeRuntime : public Runtime { + public: + virtual void lock() const = 0; + virtual void unlock() const = 0; + virtual Runtime& getUnsafeRuntime() = 0; +}; + +namespace detail { + +template +struct WithLock { + L lock; + WithLock(R& r) : lock(r) {} + void before() { + lock.lock(); + } + void after() { + lock.unlock(); + } +}; + +// The actual implementation of a given ThreadSafeRuntime. It's parameterized +// by: +// +// - R: The actual Runtime type that this wraps +// - L: A lock type that has three members: +// - L(R& r) // ctor +// - void lock() +// - void unlock() +template +class ThreadSafeRuntimeImpl final + : public WithRuntimeDecorator, R, ThreadSafeRuntime> { + public: + template + ThreadSafeRuntimeImpl(Args&&... args) + : WithRuntimeDecorator, R, ThreadSafeRuntime>( + unsafe_, + lock_), + unsafe_(std::forward(args)...), + lock_(unsafe_) {} + + R& getUnsafeRuntime() override { + return WithRuntimeDecorator, R, ThreadSafeRuntime>::plain(); + } + + void lock() const override { + lock_.before(); + } + + void unlock() const override { + lock_.after(); + } + + private: + R unsafe_; + mutable WithLock lock_; +}; + +} // namespace detail + +} // namespace jsi +} // namespace facebook diff --git a/vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.cpp b/vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.cpp new file mode 100644 index 00000000000..8517e43a826 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.cpp @@ -0,0 +1,3695 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#define NAPI_EXPERIMENTAL + +#include "NodeApiJsiRuntime.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// JSI version defines set of features available in the API. +// Each significant API change must be under a new version. +// These macros must be defined in jsi.h, but define them here too +// in case if this code is used with unmodified jsi.h. +#ifndef JSI_VERSION +#define JSI_VERSION 10 +#endif + +#ifndef JSI_NO_CONST_3 +#if JSI_VERSION >= 3 +#define JSI_NO_CONST_3 +#else +#define JSI_NO_CONST_3 const +#endif +#endif + +#ifndef JSI_CONST_10 +#if JSI_VERSION >= 10 +#define JSI_CONST_10 const +#else +#define JSI_CONST_10 +#endif +#endif + +using namespace facebook; +using namespace std::string_view_literals; + +// We use macros to report errors. +// Macros provide more flexibility to show assert and provide failure context. + +#if defined(__clang__) || defined(__GNUC__) +#define CRASH_NOW() __builtin_trap() +#elif defined(_MSC_VER) +#include +#define CRASH_NOW() __fastfail(/*FAST_FAIL_FATAL_APP_EXIT*/ 7) +#else +#define CRASH_NOW() *((volatile int *)0) = 1 +#endif + +// Check condition and crash process if it fails. +#define CHECK_ELSE_CRASH(condition, message) \ + do { \ + if (!(condition)) { \ + assert(false && "Failed: " #condition && (message)); \ + CRASH_NOW(); \ + } \ + } while (false) + +// Check condition and throw native exception if it fails. +#define CHECK_ELSE_THROW(condition, message) \ + do { \ + if (!(condition)) { \ + throwNativeException(message); \ + } \ + } while (false) + +// Check Node-API result and and throw JS exception if it is not napi_ok. +#define CHECK_NAPI(...) \ + do { \ + napi_status temp_error_code_ = (__VA_ARGS__); \ + if (temp_error_code_ != napi_status::napi_ok) { \ + runtime.throwJSException(temp_error_code_); \ + } \ + } while (false) + +// Check Node-API result and and crash if it is not napi_ok. +#define CHECK_NAPI_ELSE_CRASH(expression) \ + do { \ + napi_status temp_error_code_ = (expression); \ + if (temp_error_code_ != napi_status::napi_ok) { \ + CHECK_ELSE_CRASH(false, "Failed: " #expression); \ + } \ + } while (false) + +// Check Node-API result and return it when it is an error. +#define NAPI_CALL(expression) \ + do { \ + napi_status temp_error_code_ = (expression); \ + if (temp_error_code_ != napi_status::napi_ok) { \ + return temp_error_code_; \ + } \ + } while (false) + +#ifdef __cpp_lib_span +#include +#endif // __cpp_lib_span + +namespace Microsoft::NodeApiJsi { + +namespace { + +#ifdef __cpp_lib_span +using std::span; +#else +/** + * @brief A span of values that can be used to pass arguments to a function. + * + * This should be replaced with std::span once C++20 is supported. + */ +template +class span { + public: + constexpr span() noexcept : data_{nullptr}, size_{0} {} + constexpr span(T *data, size_t size) noexcept : data_{data}, size_{size} {} + template + constexpr span(T (&arr)[N]) noexcept : data_{arr}, size_{N} {} + + [[nodiscard]] constexpr T *data() const noexcept { + return data_; + } + + [[nodiscard]] constexpr size_t size() const noexcept { + return size_; + } + + [[nodiscard]] constexpr T *begin() const noexcept { + return data_; + } + + [[nodiscard]] constexpr T *end() const noexcept { + return *(data_ + size_); + } + + const T &operator[](size_t index) const noexcept { + return *(data_ + index); + } + + private: + T *data_; + size_t size_; +}; +#endif // __cpp_lib_span + +// To be used as a key in a unordered_map. +class StringKey { + public: + explicit StringKey(std::string &&string) noexcept; + explicit StringKey(std::string_view view) noexcept; + explicit StringKey(const char *data, size_t length) noexcept; + StringKey(StringKey &&other) noexcept; + StringKey &operator=(StringKey &&other) noexcept; + StringKey(const StringKey &other) = delete; + StringKey &operator=(const StringKey &other) = delete; + ~StringKey(); + + std::string_view getStringView() const; + bool equalTo(const StringKey &other) const; + size_t hash() const; + + struct Hash { + size_t operator()(const StringKey &key) const; + }; + + struct EqualTo { + bool operator()(const StringKey &left, const StringKey &right) const; + }; + + private: + enum class Type { + String, + View, + }; + + private: + union { + std::string string_; + std::string_view view_; + }; + Type type_{Type::String}; + size_t hash_; +}; + +struct NodeApiAttachTag { +} attachTag; + +// Implementation of N-API JSI Runtime +class NodeApiJsiRuntime : public jsi::Runtime { + public: + NodeApiJsiRuntime( + napi_env env, + JSRuntimeApi *jsrApi, + std::function onDelete) noexcept; + ~NodeApiJsiRuntime() override; + + jsi::Value evaluateJavaScript( + const std::shared_ptr &buffer, + const std::string &sourceURL) override; + std::shared_ptr prepareJavaScript( + const std::shared_ptr &buffer, + std::string sourceURL) override; + jsi::Value evaluatePreparedJavaScript( + const std::shared_ptr &js) override; +#if JSI_VERSION >= 12 + void queueMicrotask(const jsi::Function &callback) override; +#endif +#if JSI_VERSION >= 4 + bool drainMicrotasks(int maxMicrotasksHint = -1) override; +#endif + jsi::Object global() override; + std::string description() override; + bool isInspectable() override; + + protected: + PointerValue *cloneSymbol(const PointerValue *pointerValue) override; +#if JSI_VERSION >= 6 + PointerValue *cloneBigInt(const PointerValue *pointerValue) override; +#endif + PointerValue *cloneString(const PointerValue *pointerValue) override; + PointerValue *cloneObject(const PointerValue *pointerValue) override; + PointerValue *clonePropNameID(const PointerValue *pointerValue) override; + + jsi::PropNameID createPropNameIDFromAscii(const char *str, size_t length) + override; + jsi::PropNameID createPropNameIDFromUtf8(const uint8_t *utf8, size_t length) + override; + jsi::PropNameID createPropNameIDFromString(const jsi::String &str) override; +#if JSI_VERSION >= 5 + jsi::PropNameID createPropNameIDFromSymbol(const jsi::Symbol &sym) override; +#endif + std::string utf8(const jsi::PropNameID &id) override; + bool compare(const jsi::PropNameID &lhs, const jsi::PropNameID &rhs) override; + + std::string symbolToString(const jsi::Symbol &s) override; + +#if JSI_VERSION >= 8 + jsi::BigInt createBigIntFromInt64(int64_t value) override; + jsi::BigInt createBigIntFromUint64(uint64_t value) override; + bool bigintIsInt64(const jsi::BigInt &value) override; + bool bigintIsUint64(const jsi::BigInt &value) override; + uint64_t truncate(const jsi::BigInt &value) override; + jsi::String bigintToString(const jsi::BigInt &value, int radix) override; +#endif + + jsi::String createStringFromAscii(const char *str, size_t length) override; + jsi::String createStringFromUtf8(const uint8_t *utf8, size_t length) override; + std::string utf8(const jsi::String &str) override; + + jsi::Object createObject() override; + jsi::Object createObject(std::shared_ptr ho) override; + std::shared_ptr getHostObject(const jsi::Object &) override; + jsi::HostFunctionType &getHostFunction(const jsi::Function &) override; + +#if JSI_VERSION >= 7 + bool hasNativeState(const jsi::Object &value) override; + std::shared_ptr getNativeState( + const jsi::Object &value) override; + void setNativeState( + const jsi::Object &value, + std::shared_ptr state) override; +#endif + + jsi::Value getProperty(const jsi::Object &obj, const jsi::PropNameID &name) + override; + jsi::Value getProperty(const jsi::Object &obj, const jsi::String &name) + override; + +#if JSI_VERSION >= 21 + jsi::Value getProperty(const jsi::Object &obj, const jsi::Value &name) + override; +#endif + + bool hasProperty(const jsi::Object &obj, const jsi::PropNameID &name) + override; + bool hasProperty(const jsi::Object &obj, const jsi::String &name) override; + +#if JSI_VERSION >= 21 + bool hasProperty(const jsi::Object &obj, const jsi::Value &name) override; +#endif + + void setPropertyValue( + JSI_CONST_10 jsi::Object &obj, + const jsi::PropNameID &name, + const jsi::Value &value) override; + void setPropertyValue( + JSI_CONST_10 jsi::Object &obj, + const jsi::String &name, + const jsi::Value &value) override; + +#if JSI_VERSION >= 21 + void setPropertyValue( + const jsi::Object &obj, + const jsi::Value &name, + const jsi::Value &value) override; + + void deleteProperty(const jsi::Object &obj, const jsi::PropNameID &name) + override; + void deleteProperty(const jsi::Object &obj, const jsi::String &name) override; + + void deleteProperty(const jsi::Object &obj, const jsi::Value &name) override; +#endif + + bool isArray(const jsi::Object &obj) const override; + bool isArrayBuffer(const jsi::Object &obj) const override; + bool isFunction(const jsi::Object &obj) const override; + bool isHostObject(const jsi::Object &obj) const override; + bool isHostFunction(const jsi::Function &func) const override; + jsi::Array getPropertyNames(const jsi::Object &obj) override; + + jsi::WeakObject createWeakObject(const jsi::Object &obj) override; + jsi::Value lockWeakObject( + JSI_NO_CONST_3 JSI_CONST_10 jsi::WeakObject &weakObj) override; + + jsi::Array createArray(size_t length) override; +#if JSI_VERSION >= 9 + jsi::ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) override; +#endif + size_t size(const jsi::Array &arr) override; + size_t size(const jsi::ArrayBuffer &arrBuf) override; + uint8_t *data(const jsi::ArrayBuffer &arrBuff) override; + jsi::Value getValueAtIndex(const jsi::Array &arr, size_t index) override; + void setValueAtIndexImpl( + JSI_CONST_10 jsi::Array &arr, + size_t index, + const jsi::Value &value) override; + + jsi::Function createFunctionFromHostFunction( + const jsi::PropNameID &name, + unsigned int paramCount, + jsi::HostFunctionType func) override; + jsi::Value call( + const jsi::Function &func, + const jsi::Value &jsThis, + const jsi::Value *args, + size_t count) override; + jsi::Value callAsConstructor( + const jsi::Function &func, + const jsi::Value *args, + size_t count) override; + + ScopeState *pushScope() override; + void popScope(ScopeState *) override; + + bool strictEquals(const jsi::Symbol &a, const jsi::Symbol &b) const override; +#if JSI_VERSION >= 6 + bool strictEquals(const jsi::BigInt &a, const jsi::BigInt &b) const override; +#endif + bool strictEquals(const jsi::String &a, const jsi::String &b) const override; + bool strictEquals(const jsi::Object &a, const jsi::Object &b) const override; + + bool instanceOf(const jsi::Object &obj, const jsi::Function &func) override; + +#if JSI_VERSION >= 11 + void setExternalMemoryPressure(const jsi::Object &obj, size_t amount) + override; +#endif + + private: + // RAII class to open and close the environment scope. + class NodeApiScope { + public: + NodeApiScope(const NodeApiJsiRuntime &runtime) noexcept; + NodeApiScope(NodeApiJsiRuntime &runtime) noexcept; + ~NodeApiScope() noexcept; + + NodeApiScope(const NodeApiScope &) = delete; + NodeApiScope &operator=(const NodeApiScope &) = delete; + + private: + NodeApiJsiRuntime &runtime_; + NodeApiEnvScope envScope_; + ScopeState *scopeState_{}; + }; + + // RAII class to open and close the environment scope. + class NodeApiPointerValueScope { + public: + NodeApiPointerValueScope(NodeApiJsiRuntime &runtime) noexcept; + ~NodeApiPointerValueScope() noexcept; + + NodeApiPointerValueScope(const NodeApiPointerValueScope &) = delete; + NodeApiPointerValueScope &operator=(const NodeApiPointerValueScope &) = + delete; + + private: + NodeApiJsiRuntime &runtime_; + }; + + // Sets the variable in the constructor and then restores its value in the + // destructor. + template + class AutoRestore { + public: + AutoRestore(T &varRef, T value); + ~AutoRestore(); + + AutoRestore(const AutoRestore &) = delete; + AutoRestore &operator=(const AutoRestore &) = delete; + + private: + T &varRef_; + T oldValue_; + }; + + enum class NodeApiPointerValueKind : uint8_t { + Object, + WeakObject, + String, + StringPropNameID, + Symbol, + BigInt, + }; + + class NodeApiRefCountedPointerValue; + + class NodeApiRefCount { + public: + static void incRefCount(std::atomic &value) noexcept { + int refCount = value.fetch_add(1, std::memory_order_relaxed) + 1; + CHECK_ELSE_CRASH(refCount > 1, "The ref count cannot bounce from zero."); + CHECK_ELSE_CRASH( + refCount < std::numeric_limits::max(), + "The ref count is too big."); + } + + static bool decRefCount(std::atomic &value) noexcept { + int refCount = value.fetch_sub(1, std::memory_order_release) - 1; + CHECK_ELSE_CRASH(refCount >= 0, "The ref count must not be negative."); + if (refCount == 0) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + return false; + } + + static bool isZero(std::atomic &value) noexcept { + return value.load(std::memory_order_relaxed) == 0; + } + }; + + // A smart pointer for types that implement intrusive ref count using + // methods incRefCount and decRefCount. + template + class NodeApiRefCountedPtr final { + public: + NodeApiRefCountedPtr() noexcept = default; + + explicit NodeApiRefCountedPtr(T *ptr, NodeApiAttachTag) noexcept + : ptr_(ptr) {} + + NodeApiRefCountedPtr(const NodeApiRefCountedPtr &other) noexcept + : ptr_(other.ptr_) { + if (ptr_ != nullptr) { + ptr_->incRefCount(); + } + } + + NodeApiRefCountedPtr(NodeApiRefCountedPtr &&other) + : ptr_(std::exchange(other.ptr_, nullptr)) {} + + ~NodeApiRefCountedPtr() noexcept { + if (ptr_ != nullptr) { + ptr_->decRefCount(); + } + } + + NodeApiRefCountedPtr &operator=(std::nullptr_t) noexcept { + if (ptr_ != nullptr) { + ptr_->decRefCount(); + } + ptr_ = nullptr; + return *this; + } + + NodeApiRefCountedPtr &operator=( + const NodeApiRefCountedPtr &other) noexcept { + if (this != &other) { + NodeApiRefCountedPtr temp(std::move(*this)); + ptr_ = other.ptr_; + if (ptr_ != nullptr) { + ptr_->incRefCount(); + } + } + return *this; + } + + NodeApiRefCountedPtr &operator=(NodeApiRefCountedPtr &&other) noexcept { + if (this != &other) { + NodeApiRefCountedPtr temp(std::move(*this)); + ptr_ = std::exchange(other.ptr_, nullptr); + } + return *this; + } + + T *operator->() const noexcept { + return ptr_; + } + + T *get() const noexcept { + return ptr_; + } + + explicit operator bool() const noexcept { + return ptr_ != nullptr; + } + + T *release() noexcept { + return std::exchange(ptr_, nullptr); + } + + private: + T *ptr_{}; + }; + + // Removes the `napi_value value_` field. + class NodeApiStackValueDeleter { + public: + void operator()(NodeApiRefCountedPointerValue *ptr) const noexcept; + }; + + // Removes the NodeApiRefCountedPointerValue instance and ignores the + // `napi_ref ref_` field. + class NodeApiRefDeleter { + public: + void operator()(NodeApiRefCountedPointerValue *ptr) const noexcept; + }; + + using NodeApiRefHolder = NodeApiRefCountedPtr; + using NodeApiStackValuePtr = + std::unique_ptr; + using NodeApiRefPtr = + std::unique_ptr; + + // NodeApiPendingDeletions helps to delete PointerValues in a thread safe way + // from the JS thread. According to JSI spec the PointerValue's release method + // can be called from any thread, while Node-API can only manage objects in + // the JS thread. So, when a PointerValue's ref count goes to zero after + // calling the release method, the PointerValue is added into the + // pointerValuesToDelete_ vector, and then NodeApiJsiRuntime deletes them + // later from the JS thread. Note that the napi_delete_reference can only be + // called before the napi_env is destroyed. Thus, we remove the pointer to + // napi_env as soon as NodeApiJsiRuntime destructor starts. + class NodeApiPendingDeletions { + public: + // Create new instance of NodeApiPendingDeletions. + static NodeApiRefCountedPtr create() noexcept { + return NodeApiRefCountedPtr( + new NodeApiPendingDeletions(), attachTag); + } + + // Add PointerValues to delete from JS thread. The method can be called from + // any thread. + void addPointerValueToDelete(NodeApiRefPtr pointerValueToDelete) noexcept { + std::scoped_lock lock{mutex_}; + pointerValuesToDeletePool_[poolSelector_].push_back( + std::move(pointerValueToDelete)); + } + + // Delete all PointerValues scheduled for deletion along with their napi_ref + // instances. It must be called from a JS thread. + void deletePointerValues(NodeApiJsiRuntime &runtime) noexcept { + { + // TODO: Does it affect the performance to take the lock every time? + // Should we use an atomic variable? + std::scoped_lock lock{mutex_}; + if (pointerValuesToDeletePool_[poolSelector_].empty()) { + return; + } + + // Switch the pool entries. + poolSelector_ = poolSelector_ ^ 1; + } + + std::vector &deleteInJSThread = + pointerValuesToDeletePool_[poolSelector_ ^ 1]; + for (auto &pointerValue : deleteInJSThread) { + pointerValue.release()->deleteNodeApiRef(runtime); + } + deleteInJSThread.resize(0); + } + + private: + friend class NodeApiRefCountedPtr; + + NodeApiPendingDeletions() noexcept = default; + + void incRefCount() noexcept { + NodeApiRefCount::incRefCount(refCount_); + } + + void decRefCount() noexcept { + if (NodeApiRefCount::decRefCount(refCount_)) { + delete this; + } + } + + private: + mutable std::atomic refCount_{1}; + std::recursive_mutex mutex_; + // One of the vectors is used from different threads under the mutex_ lock + // to add items, while another is from JS thread to remove items. Since we + // never change the capacity of the vectors, it should help avoiding memory + // allocations at some point. + std::vector pointerValuesToDeletePool_[2]{ + std::vector(), + std::vector()}; + // Index of the pool to access under mutex. + int32_t poolSelector_{0}; + }; + + // NodeApiPointerValue is used by jsi::Pointer derived classes. + struct NodeApiPointerValue : PointerValue { + virtual NodeApiRefCountedPointerValue *clone( + NodeApiJsiRuntime &runtime) const noexcept = 0; + virtual napi_value getValue(NodeApiJsiRuntime &runtime) noexcept = 0; + virtual NodeApiPointerValueKind getKind() const noexcept = 0; + }; + + // NodeApiStackOnlyPointerValue helps to avoid memory allocation in some + // scenarios. It is used by the JsiValueView, JsiValueViewArgs, and + // PropNameIDView classes to keep temporary PointerValues on the call stack + // when we call functions. Note that the clone() method return a new instance + // of the NodeApiRefCountedPointerValue. + class NodeApiStackOnlyPointerValue final : public NodeApiPointerValue { + public: + NodeApiStackOnlyPointerValue( + napi_value value, + NodeApiPointerValueKind pointerKind) noexcept; + + void invalidate() noexcept override; + NodeApiRefCountedPointerValue *clone( + NodeApiJsiRuntime &runtime) const noexcept override; + napi_value getValue(NodeApiJsiRuntime &runtime) noexcept override; + NodeApiPointerValueKind getKind() const noexcept override; + + NodeApiStackOnlyPointerValue(const NodeApiStackOnlyPointerValue &) = delete; + NodeApiStackOnlyPointerValue &operator=( + const NodeApiStackOnlyPointerValue &) = delete; + + private: + napi_value value_{}; + NodeApiPointerValueKind pointerKind_{NodeApiPointerValueKind::Object}; + }; + + // TODO: Use arena allocator for NodeApiRefCountedPointerValue. + + // NodeApiRefCountedPointerValue is a ref counted implementation of + // PointerValue that is allocated in the heap. + // + // Its lifetime is controlled by the atomic `refCount_` field. Since the + // `refCount_` can be changed from any thread, we do not remove the instance + // immediately when the `refCount_` becomes zero. Instead, we add it to the + // `NodeApiJsiRuntime::pendingDeletions_` list and delete it later from the JS + // thread. If `node_value value_` field is not null, then the + // `NodeApiRefCountedPointerValue` instance is also referenced from the + // `NodeApiJsiRuntime::stackValues_` list. + // + // The `NodeApiJsiRuntime::pendingDeletions_` is responsible for deleting + // `napi_ref ref_` and it deletes `NodeApiRefCountedPointerValue` instance if + // the `node_value value_` field is null. While + // `NodeApiJsiRuntime::stackValues_` is responsible for deleting + // `NodeApiRefCountedPointerValue` instance if `node_value value_` field is + // not null and `napi_ref ref_` is null. In case if + // `NodeApiJsiRuntime::pendingDeletions_` or `NodeApiJsiRuntime::stackValues_` + // cannot delete the instance, they set their "owned" `value_` or `ref_` + // fields to null. + // + // Some NodeApiRefCountedPointerValue are created with napi_value and may + // never get napi_ref. When stack scope is closed we check the `refCount_`. If + // it is not zero, then we ensure that it has an associated `napi_ref` or we + // create one. + // + // All methods except for invalidate() and decRefCount() must be called from + // the JS thread. + class NodeApiRefCountedPointerValue final : public NodeApiPointerValue { + friend class NodeApiStackValueDeleter; + friend class NodeApiRefDeleter; + friend class NodeApiRefCountedPtr; + + public: + // Creates new NodeApiRefCountedPointerValue and adds it to the + // NodeApiJsiRuntime::stackValues_. + static NodeApiRefHolder make( + NodeApiJsiRuntime &runtime, + napi_value value, + NodeApiPointerValueKind pointerKind, + int32_t initialRefCount = 1); + + // Calls `make` method and forces creation of `napi_ref`. + static NodeApiRefHolder makeNodeApiRef( + NodeApiJsiRuntime &runtime, + napi_value value, + NodeApiPointerValueKind pointerKind, + int32_t initialRefCount = 1); + + void invalidate() noexcept override; + NodeApiRefCountedPointerValue *clone( + NodeApiJsiRuntime &runtime) const noexcept override; + napi_value getValue(NodeApiJsiRuntime &runtime) noexcept override; + NodeApiPointerValueKind getKind() const noexcept override; + + // Returns true if the refCount_ is not zero. + bool usedByJsiPointer() const noexcept; + + // Removes `napi_value value_` field. + // In case if `refCount_` is not null, it ensures existence of the `napi_ref + // ref_` field. + void deleteStackValue(NodeApiJsiRuntime &runtime) noexcept; + + // Removes napi_ref and deletes `NodeApiRefCountedPointerValue` if `value_` + // is null. + void deleteNodeApiRef(NodeApiJsiRuntime &runtime) noexcept; + + NodeApiRefCountedPointerValue(const NodeApiRefCountedPointerValue &) = + delete; + NodeApiRefCountedPointerValue &operator=( + const NodeApiRefCountedPointerValue &) = delete; + + private: + NodeApiRefCountedPointerValue( + NodeApiJsiRuntime &runtime, + napi_value value, + NodeApiPointerValueKind pointerKind, + int32_t initialRefCount) noexcept; + + void incRefCount() const noexcept; + void decRefCount() const noexcept; + + // Creates `napi_ref ref_` field for the `napi_value value_` field. + NodeApiRefCountedPointerValue *createNodeApiRef(NodeApiJsiRuntime &runtime); + + private: + NodeApiRefCountedPtr pendingDeletions_; + napi_value value_{}; + napi_ref ref_{}; + mutable std::atomic refCount_{1}; + const NodeApiPointerValueKind pointerKind_{NodeApiPointerValueKind::Object}; + bool canBeDeletedFromStack_{false}; + + static constexpr char kPrimitivePropertyName[] = "X"; + }; + + // SmallBuffer keeps InplaceSize elements in place in the class, and uses heap + // memory for more elements. + template + class SmallBuffer { + public: + SmallBuffer(size_t size) noexcept; + + T *data() noexcept; + size_t size() const noexcept; + + SmallBuffer(const SmallBuffer &) = delete; + SmallBuffer &operator=(const SmallBuffer &) = delete; + + private: + size_t size_{}; + std::array stackData_{}; + std::unique_ptr heapData_{}; + }; + + // The number of arguments that we keep on stack. We use heap if we have more + // arguments. + constexpr static size_t MaxStackArgCount = 8; + + // NodeApiValueArgs helps optimize passing arguments to Node-API functions. + // If number of arguments is below or equal to MaxStackArgCount, they are kept + // on the call stack, otherwise arguments are allocated on the heap. + class NodeApiValueArgs { + public: + NodeApiValueArgs(NodeApiJsiRuntime &runtime, span args); + operator span(); + + NodeApiValueArgs(const NodeApiValueArgs &) = delete; + NodeApiValueArgs &operator=(const NodeApiValueArgs &) = delete; + + private: + SmallBuffer args_; + }; + + // Helps to use the stack storage for a temporary conversion from napi_value + // to jsi::Value. It also helps to avoid a conversion to a relatively + // expensive napi_ref. + class JsiValueView { + public: + union StoreType { + NodeApiStackOnlyPointerValue value_; + std::array bytes_; + StoreType() {} + ~StoreType() {} + }; + + public: + JsiValueView(NodeApiJsiRuntime *runtime, napi_value jsValue); + operator const jsi::Value &() const noexcept; + + static jsi::Value + initValue(NodeApiJsiRuntime *runtime, napi_value jsValue, StoreType *store); + + JsiValueView(const JsiValueView &) = delete; + JsiValueView &operator=(const JsiValueView &) = delete; + + private: + StoreType pointerStore_; + jsi::Value value_{}; + }; + + // Helps to use stack storage for passing arguments that must be temporarily + // converted from napi_value to jsi::Value. It helps to avoid conversion to a + // relatively expensive napi_ref. + class JsiValueViewArgs { + public: + JsiValueViewArgs( + NodeApiJsiRuntime *runtime, + span args) noexcept; + const jsi::Value *data() noexcept; + size_t size() const noexcept; + + JsiValueViewArgs(const JsiValueViewArgs &); + JsiValueViewArgs &operator=(const JsiValueViewArgs &); + + private: + using StoreType = JsiValueView::StoreType; + SmallBuffer pointerStore_{0}; + SmallBuffer args_{0}; + }; + + // Helps to use stack storage for a temporary conversion from napi_value to + // jsi::PropNameID. It helps to avoid conversions to a relatively expensive + // napi_ref. + class PropNameIDView { + public: + PropNameIDView(NodeApiJsiRuntime *runtime, napi_value propertyId) noexcept; + operator const jsi::PropNameID &() const noexcept; + + PropNameIDView(const PropNameIDView &); + PropNameIDView &operator=(const PropNameIDView &); + + private: + using StoreType = JsiValueView::StoreType; + StoreType pointerStore_{}; + jsi::PropNameID const propertyId_; + }; + + // Wraps up the jsi::HostFunctionType along with the NodeApiJsiRuntime. + class HostFunctionWrapper { + public: + HostFunctionWrapper( + jsi::HostFunctionType &&hostFunction, + NodeApiJsiRuntime &runtime); + + jsi::HostFunctionType &hostFunction() noexcept; + NodeApiJsiRuntime &runtime() noexcept; + + HostFunctionWrapper(const HostFunctionWrapper &) = delete; + HostFunctionWrapper &operator=(const HostFunctionWrapper &) = delete; + + private: + jsi::HostFunctionType hostFunction_; + NodeApiJsiRuntime &runtime_; + }; + + // Wraps up the jsr_prepared_script. + class NodeApiPreparedJavaScript final : public jsi::PreparedJavaScript { + public: + NodeApiPreparedJavaScript( + napi_env env, + jsr_prepared_script script, + std::string sourceURL) + : env_(env), script_(script), sourceURL_(std::move(sourceURL)) {} + + ~NodeApiPreparedJavaScript() override { + JSRuntimeApi::current()->jsr_delete_prepared_script(env_, script_); + } + + jsr_prepared_script getScript() const { + return script_; + } + + const std::string &sourceURL() const { + return sourceURL_; + } + + NodeApiPreparedJavaScript(const NodeApiPreparedJavaScript &) = delete; + NodeApiPreparedJavaScript &operator=(const NodeApiPreparedJavaScript &) = + delete; + + private: + napi_env env_; + jsr_prepared_script script_; + std::string sourceURL_; + }; + + private: // Error-handling utility methods + template + jsi::JSError makeJSError(Args &&...args); + [[noreturn]] void throwJSException(napi_status errorCode) const; + [[noreturn]] void throwNativeException(char const *errorMessage) const; + void rewriteErrorMessage(napi_value jsError) const; + template + auto runInMethodContext(char const *methodName, TLambda lambda); + template + napi_value handleCallbackExceptions(TLambda lambda) const noexcept; + bool setException(napi_value error) const noexcept; + bool setException(std::string_view message) const noexcept; + + private: // Shared Node-API call helpers + napi_valuetype typeOf(napi_value value) const; + bool strictEquals(napi_value left, napi_value right) const; + napi_value getUndefined() const; + napi_value getNull() const; + napi_value getGlobal() const; + napi_value getBoolean(bool value) const; + bool getValueBool(napi_value value) const; + napi_value createInt32(int32_t value) const; + napi_value createUInt32(uint32_t value) const; + napi_value createDouble(double value) const; + double getValueDouble(napi_value value) const; + napi_value createStringLatin1(std::string_view value) const; + napi_value createStringUtf8(std::string_view value) const; + napi_value createStringUtf8(const uint8_t *data, size_t length) const; + std::string stringToStdString(napi_value stringValue) const; + napi_value getPropertyIdFromName(std::string_view value) const; + napi_value getPropertyIdFromName(const uint8_t *data, size_t length) const; + napi_value getPropertyIdFromName(napi_value str) const; + napi_value getPropertyIdFromSymbol(napi_value sym) const; + std::string propertyIdToStdString(napi_value propertyId); + napi_value createSymbol(std::string_view symbolDescription) const; + std::string symbolToStdString(napi_value symbolValue); + napi_value callFunction( + napi_value thisArg, + napi_value function, + span args = {}) const; + napi_value constructObject(napi_value constructor, span args = {}) + const; + bool instanceOf(napi_value object, napi_value constructor) const; + napi_value createNodeApiObject() const; + bool hasProperty(napi_value object, napi_value propertyId) const; + napi_value getProperty(napi_value object, napi_value propertyId) const; + void setProperty(napi_value object, napi_value propertyId, napi_value value) + const; + bool deleteProperty(napi_value object, napi_value propertyId) const; + void setProperty( + napi_value object, + napi_value propertyId, + napi_value value, + napi_property_attributes attrs) const; + napi_value createNodeApiArray(size_t length) const; + bool isArray(napi_value value) const; + size_t getArrayLength(napi_value value) const; + napi_value getElement(napi_value arr, size_t index) const; + void setElement(napi_value array, uint32_t index, napi_value value) const; + static napi_value __cdecl jsiHostFunctionCallback( + napi_env env, + napi_callback_info info) noexcept; + napi_value createExternalFunction( + napi_value name, + int32_t paramCount, + napi_callback callback, + void *callbackData); + napi_value createExternalObject( + void *data, + node_api_nogc_finalize finalizeCallback) const; + template + napi_value createExternalObject(std::unique_ptr &&data) const; + void *getExternalData(napi_value object) const; + const std::shared_ptr &getJsiHostObject(napi_value obj); + napi_value getHostObjectProxyHandler(); + template < + napi_value (NodeApiJsiRuntime::*trapMethod)(span), + size_t argCount> + void setProxyTrap(napi_value handler, napi_value propertyName); + napi_value hostObjectHasTrap(span args); + napi_value hostObjectGetTrap(span args); + napi_value hostObjectSetTrap(span args); + napi_value hostObjectOwnKeysTrap(span args); + napi_value hostObjectGetOwnPropertyDescriptorTrap(span args); + + private: // Miscellaneous utility methods + span toSpan(const jsi::Buffer &buffer); + jsi::Value toJsiValue(napi_value value) const; + napi_value getNodeApiValue(const jsi::Value &value) const; + napi_value getNodeApiValue(const jsi::Pointer &ptr) const; + napi_value getNodeApiValue(const NodeApiRefHolder &ref) const; + NodeApiRefCountedPointerValue *cloneNodeApiPointerValue( + const PointerValue *pointerValue); + std::optional toArrayIndex( + std::string::const_iterator first, + std::string::const_iterator last); + + template < + typename T, + std::enable_if_t, int> = 0> + T makeJsiPointer(napi_value value) const; + template < + typename T, + std::enable_if_t, int> = 0> + T makeJsiPointer(napi_value value) const; + template < + typename T, + std::enable_if_t, int> = 0> + T makeJsiPointer(napi_value value) const; +#if JSI_VERSION >= 6 + template < + typename T, + std::enable_if_t, int> = 0> + T makeJsiPointer(napi_value value) const; +#endif + template < + typename TTo, + typename TFrom, + std::enable_if_t, int> = 0, + std::enable_if_t, int> = 0> + TTo cloneAs(const TFrom &pointer) const; + NodeApiRefHolder makeNodeApiRef( + napi_value value, + NodeApiPointerValueKind pointerKind, + int32_t initialRefCount = 1); + + void addStackValue(NodeApiStackValuePtr stackPointer); + void pushPointerValueScope() noexcept; + void popPointerValueScope() noexcept; + + napi_env getEnv() const noexcept { + return env_; + } + + private: // Fields + napi_env env_{}; + JSRuntimeApi *jsrApi_; + std::function onDelete_; + std::string sourceURL_; + + // Property ID cache to improve execution speed. + struct PropertyId { + NodeApiRefHolder Error; + NodeApiRefHolder Object; + NodeApiRefHolder Proxy; + NodeApiRefHolder Symbol; + NodeApiRefHolder byteLength; + NodeApiRefHolder configurable; + NodeApiRefHolder enumerable; + NodeApiRefHolder get; + NodeApiRefHolder getOwnPropertyDescriptor; + NodeApiRefHolder has; + NodeApiRefHolder hostFunctionSymbol; + NodeApiRefHolder hostObjectSymbol; + NodeApiRefHolder length; + NodeApiRefHolder message; + NodeApiRefHolder ownKeys; + NodeApiRefHolder propertyIsEnumerable; + NodeApiRefHolder prototype; + NodeApiRefHolder set; + NodeApiRefHolder stack; + NodeApiRefHolder toString; + NodeApiRefHolder value; + NodeApiRefHolder writable; + } propertyId_; + + // Cache of commonly used values. + struct CachedValue { + NodeApiRefHolder Error; + NodeApiRefHolder Global; + NodeApiRefHolder HostObjectProxyHandler; + NodeApiRefHolder ProxyConstructor; + NodeApiRefHolder SymbolToString; + } cachedValue_; + + bool hasPendingJSError_{false}; + + std::vector stackScopes_; + std::vector stackValues_; + + // TODO: implement GC for propNameIDs_ + std::unordered_map< + StringKey, + NodeApiRefHolder, + StringKey::Hash, + StringKey::EqualTo> + propNameIDs_; + + NodeApiJsiRuntime &runtime{*this}; + NodeApiRefCountedPtr pendingDeletions_{ + NodeApiPendingDeletions::create()}; +}; + +//===================================================================================================================== +// StringKey implementation +//===================================================================================================================== + +StringKey::StringKey(std::string &&string) noexcept + : string_(std::move(string)), + type_(Type::String), + hash_(std::hash{}(string_)) {} + +StringKey::StringKey(std::string_view view) noexcept + : view_(view), + type_(Type::View), + hash_(std::hash{}(view_)) {} + +StringKey::StringKey(const char *data, size_t length) noexcept + : view_(data, length), + type_(Type::View), + hash_(std::hash{}(view_)) {} + +StringKey::StringKey(StringKey &&other) noexcept + : type_(other.type_), hash_(std::exchange(other.hash_, 0)) { + if (type_ == Type::String) { + ::new (std::addressof(string_)) std::string(std::move(other.string_)); + } else { + ::new (std::addressof(view_)) + std::string_view(std::exchange(other.view_, std::string_view())); + } +} + +StringKey &StringKey::operator=(StringKey &&other) noexcept { + if (this != &other) { + this->~StringKey(); + ::new (this) StringKey(std::move(other)); + } + return *this; +} + +StringKey::~StringKey() { + if (type_ == Type::String) { + std::addressof(string_)->~basic_string(); + } else { + std::addressof(view_)->~basic_string_view(); + } +} + +std::string_view StringKey::getStringView() const { + return (type_ == Type::String) ? std::string_view(string_) : view_; +} + +bool StringKey::equalTo(const StringKey &other) const { + return getStringView().compare(other.getStringView()) == 0; +} + +size_t StringKey::hash() const { + return hash_; +} + +size_t StringKey::Hash::operator()(const StringKey &key) const { + return key.hash(); +} + +bool StringKey::EqualTo::operator()( + const StringKey &left, + const StringKey &right) const { + return left.equalTo(right); +} + +//===================================================================================================================== +// NodeApiJsiRuntime implementation +//===================================================================================================================== + +NodeApiJsiRuntime::NodeApiJsiRuntime( + napi_env env, + JSRuntimeApi *jsrApi, + std::function onDelete) noexcept + : env_(env), jsrApi_(jsrApi), onDelete_(std::move(onDelete)) { + NodeApiScope scope{*this}; + propertyId_.Error = makeNodeApiRef( + getPropertyIdFromName("Error"), NodeApiPointerValueKind::String); + propertyId_.Object = makeNodeApiRef( + getPropertyIdFromName("Object"), NodeApiPointerValueKind::String); + propertyId_.Proxy = makeNodeApiRef( + getPropertyIdFromName("Proxy"), NodeApiPointerValueKind::String); + propertyId_.Symbol = makeNodeApiRef( + getPropertyIdFromName("Symbol"), NodeApiPointerValueKind::String); + propertyId_.byteLength = makeNodeApiRef( + getPropertyIdFromName("byteLength"), NodeApiPointerValueKind::String); + propertyId_.configurable = makeNodeApiRef( + getPropertyIdFromName("configurable"), NodeApiPointerValueKind::String); + propertyId_.enumerable = makeNodeApiRef( + getPropertyIdFromName("enumerable"), NodeApiPointerValueKind::String); + propertyId_.get = makeNodeApiRef( + getPropertyIdFromName("get"), NodeApiPointerValueKind::String); + propertyId_.getOwnPropertyDescriptor = makeNodeApiRef( + getPropertyIdFromName("getOwnPropertyDescriptor"), + NodeApiPointerValueKind::String); + propertyId_.has = makeNodeApiRef( + getPropertyIdFromName("has"), NodeApiPointerValueKind::String); + propertyId_.hostFunctionSymbol = makeNodeApiRef( + createSymbol("hostFunctionSymbol"), NodeApiPointerValueKind::Symbol); + propertyId_.hostObjectSymbol = makeNodeApiRef( + createSymbol("hostObjectSymbol"), NodeApiPointerValueKind::Symbol); + propertyId_.length = makeNodeApiRef( + getPropertyIdFromName("length"), NodeApiPointerValueKind::String); + propertyId_.message = makeNodeApiRef( + getPropertyIdFromName("message"), NodeApiPointerValueKind::String); + propertyId_.ownKeys = makeNodeApiRef( + getPropertyIdFromName("ownKeys"), NodeApiPointerValueKind::String); + propertyId_.propertyIsEnumerable = makeNodeApiRef( + getPropertyIdFromName("propertyIsEnumerable"), + NodeApiPointerValueKind::String); + propertyId_.prototype = makeNodeApiRef( + getPropertyIdFromName("prototype"), NodeApiPointerValueKind::String); + propertyId_.set = makeNodeApiRef( + getPropertyIdFromName("set"), NodeApiPointerValueKind::String); + propertyId_.stack = makeNodeApiRef( + getPropertyIdFromName("stack"), NodeApiPointerValueKind::String); + propertyId_.toString = makeNodeApiRef( + getPropertyIdFromName("toString"), NodeApiPointerValueKind::String); + propertyId_.value = makeNodeApiRef( + getPropertyIdFromName("value"), NodeApiPointerValueKind::String); + propertyId_.writable = makeNodeApiRef( + getPropertyIdFromName("writable"), NodeApiPointerValueKind::String); + + cachedValue_.Global = + makeNodeApiRef(getGlobal(), NodeApiPointerValueKind::Object); + cachedValue_.Error = makeNodeApiRef( + getProperty( + getNodeApiValue(cachedValue_.Global), + getNodeApiValue(propertyId_.Error)), + NodeApiPointerValueKind::Object); +} + +NodeApiJsiRuntime::~NodeApiJsiRuntime() { + if (onDelete_) { + onDelete_(); + } +} + +jsi::Value NodeApiJsiRuntime::evaluateJavaScript( + const std::shared_ptr &buffer, + const std::string &sourceURL) { + return evaluatePreparedJavaScript(prepareJavaScript(buffer, sourceURL)); +} + +std::shared_ptr +NodeApiJsiRuntime::prepareJavaScript( + const std::shared_ptr &sourceBuffer, + std::string sourceURL) { + NodeApiScope scope{*this}; + jsr_prepared_script script{}; + napi_status status = jsrApi_->jsr_create_prepared_script( + env_, + sourceBuffer->data(), + sourceBuffer->size(), + [](void * /*data*/, void *deleterData) { + delete reinterpret_cast *>( + deleterData); + }, + new std::shared_ptr(sourceBuffer), + sourceURL.c_str(), + &script); + CHECK_NAPI(status); // Not for the call to keep better automated formatting. + return std::make_shared( + env_, script, std::move(sourceURL)); +} + +jsi::Value NodeApiJsiRuntime::evaluatePreparedJavaScript( + const std::shared_ptr &js) { + NodeApiScope scope{*this}; + auto preparedScript = + static_cast(js.get()); + AutoRestore sourceURLScope{ + sourceURL_, preparedScript->sourceURL()}; + napi_value result{}; + CHECK_NAPI(jsrApi_->jsr_prepared_script_run( + env_, preparedScript->getScript(), &result)); + return toJsiValue(result); +} + +#if JSI_VERSION >= 12 +void NodeApiJsiRuntime::queueMicrotask(const jsi::Function &callback) { + NodeApiScope scope{*this}; + napi_value callbackValue = getNodeApiValue(callback); + CHECK_NAPI(jsrApi_->jsr_queue_microtask(env_, callbackValue)); +} +#endif + +#if JSI_VERSION >= 4 +bool NodeApiJsiRuntime::drainMicrotasks(int maxMicrotasksHint) { + bool result{}; + CHECK_NAPI(jsrApi_->jsr_drain_microtasks(env_, maxMicrotasksHint, &result)); + return result; +} +#endif + +jsi::Object NodeApiJsiRuntime::global() { + return make(cachedValue_.Global->clone(*this)); +} + +std::string NodeApiJsiRuntime::description() { + const char *desc{}; + CHECK_NAPI(jsrApi_->jsr_get_description(env_, &desc)); + return desc; +} + +bool NodeApiJsiRuntime::isInspectable() { + bool result{}; + CHECK_NAPI(jsrApi_->jsr_is_inspectable(env_, &result)); + return result; +} + +jsi::Runtime::PointerValue *NodeApiJsiRuntime::cloneSymbol( + const jsi::Runtime::PointerValue *pointerValue) { + return cloneNodeApiPointerValue(pointerValue); +} + +#if JSI_VERSION >= 6 +jsi::Runtime::PointerValue *NodeApiJsiRuntime::cloneBigInt( + const jsi::Runtime::PointerValue *pointerValue) { + return cloneNodeApiPointerValue(pointerValue); +} +#endif + +jsi::Runtime::PointerValue *NodeApiJsiRuntime::cloneString( + const jsi::Runtime::PointerValue *pointerValue) { + return cloneNodeApiPointerValue(pointerValue); +} + +jsi::Runtime::PointerValue *NodeApiJsiRuntime::cloneObject( + const jsi::Runtime::PointerValue *pointerValue) { + return cloneNodeApiPointerValue(pointerValue); +} + +jsi::Runtime::PointerValue *NodeApiJsiRuntime::clonePropNameID( + const jsi::Runtime::PointerValue *pointerValue) { + return cloneNodeApiPointerValue(pointerValue); +} + +jsi::PropNameID NodeApiJsiRuntime::createPropNameIDFromAscii( + const char *str, + size_t length) { + NodeApiScope scope{*this}; + StringKey keyName{str, length}; + auto it = propNameIDs_.find(keyName); + if (it != propNameIDs_.end()) { + return make(it->second->clone(*this)); + } + + napi_value obj = createNodeApiObject(); + napi_value propName{}; + CHECK_NAPI(jsrApi_->napi_create_string_latin1(env_, str, length, &propName)); + CHECK_NAPI(jsrApi_->napi_set_property(env_, obj, propName, getUndefined())); + napi_value props{}; + CHECK_NAPI(jsrApi_->napi_get_all_property_names( + env_, + obj, + napi_key_own_only, + napi_key_skip_symbols, + napi_key_numbers_to_strings, + &props)); + napi_value propNameId = getElement(props, 0); + NodeApiRefHolder propNameRef = + makeNodeApiRef(propNameId, NodeApiPointerValueKind::StringPropNameID, 2); + jsi::PropNameID result = make(propNameRef.get()); + propNameIDs_.try_emplace( + StringKey(std::string(keyName.getStringView())), std::move(propNameRef)); + return result; +} + +jsi::PropNameID NodeApiJsiRuntime::createPropNameIDFromUtf8( + const uint8_t *utf8, + size_t length) { + NodeApiScope scope{*this}; + StringKey keyName{reinterpret_cast(utf8), length}; + auto it = propNameIDs_.find(keyName); + if (it != propNameIDs_.end()) { + return make(it->second->clone(*this)); + } + + napi_value obj = createNodeApiObject(); + napi_value propName{}; + CHECK_NAPI(jsrApi_->napi_create_string_utf8( + env_, reinterpret_cast(utf8), length, &propName)); + CHECK_NAPI(jsrApi_->napi_set_property(env_, obj, propName, getUndefined())); + napi_value props{}; + CHECK_NAPI(jsrApi_->napi_get_all_property_names( + env_, + obj, + napi_key_own_only, + napi_key_skip_symbols, + napi_key_numbers_to_strings, + &props)); + napi_value propNameId = getElement(props, 0); + NodeApiRefHolder propNameRef = + makeNodeApiRef(propNameId, NodeApiPointerValueKind::StringPropNameID, 2); + jsi::PropNameID result = make(propNameRef.get()); + propNameIDs_.try_emplace( + StringKey(std::string(keyName.getStringView())), std::move(propNameRef)); + return result; +} + +jsi::PropNameID NodeApiJsiRuntime::createPropNameIDFromString( + const jsi::String &str) { + NodeApiScope scope{*this}; + const NodeApiPointerValue *pv = + static_cast(getPointerValue(str)); + if (pv->getKind() == NodeApiPointerValueKind::StringPropNameID) { + return make(pv->clone(*this)); + } + + StringKey keyName(utf8(str)); + auto it = propNameIDs_.find(keyName); + if (it != propNameIDs_.end()) { + return make(it->second->clone(*this)); + } + + napi_value napiStr = const_cast(pv)->getValue(*this); + + napi_value obj = createNodeApiObject(); + setProperty(obj, napiStr, getUndefined()); + napi_value props{}; + CHECK_NAPI(jsrApi_->napi_get_all_property_names( + env_, + obj, + napi_key_own_only, + napi_key_skip_symbols, + napi_key_numbers_to_strings, + &props)); + napi_value propNameId = getElement(props, 0); + NodeApiRefHolder propNameRef = + makeNodeApiRef(propNameId, NodeApiPointerValueKind::StringPropNameID, 2); + jsi::PropNameID result = make(propNameRef.get()); + propNameIDs_.try_emplace( + StringKey(std::string(keyName.getStringView())), std::move(propNameRef)); + return result; +} + +#if JSI_VERSION >= 5 +jsi::PropNameID NodeApiJsiRuntime::createPropNameIDFromSymbol( + const jsi::Symbol &sym) { + // TODO: Should we ensure uniqueness of symbols? + return cloneAs(sym); +} +#endif + +std::string NodeApiJsiRuntime::utf8(const jsi::PropNameID &id) { + NodeApiScope scope{*this}; + return propertyIdToStdString(getNodeApiValue(id)); +} + +bool NodeApiJsiRuntime::compare( + const jsi::PropNameID &lhs, + const jsi::PropNameID &rhs) { + NodeApiScope scope{*this}; + return getPointerValue(lhs) == getPointerValue(rhs) || + strictEquals(getNodeApiValue(lhs), getNodeApiValue(rhs)); +} + +std::string NodeApiJsiRuntime::symbolToString(const jsi::Symbol &sym) { + NodeApiScope scope{*this}; + return symbolToStdString(getNodeApiValue(sym)); +} + +#if JSI_VERSION >= 8 +jsi::BigInt NodeApiJsiRuntime::createBigIntFromInt64(int64_t val) { + NodeApiScope scope{*this}; + napi_value bigint{}; + CHECK_NAPI(jsrApi_->napi_create_bigint_int64(env_, val, &bigint)); + return makeJsiPointer(bigint); +} + +jsi::BigInt NodeApiJsiRuntime::createBigIntFromUint64(uint64_t val) { + NodeApiScope scope{*this}; + napi_value bigint{}; + CHECK_NAPI(jsrApi_->napi_create_bigint_uint64(env_, val, &bigint)); + return makeJsiPointer(bigint); +} + +bool NodeApiJsiRuntime::bigintIsInt64(const jsi::BigInt &bigint) { + NodeApiScope scope{*this}; + napi_value value = getNodeApiValue(bigint); + bool lossless{false}; + int64_t result{}; + CHECK_NAPI( + jsrApi_->napi_get_value_bigint_int64(env_, value, &result, &lossless)); + return lossless; +} + +bool NodeApiJsiRuntime::bigintIsUint64(const jsi::BigInt &bigint) { + NodeApiScope scope{*this}; + napi_value value = getNodeApiValue(bigint); + bool lossless{false}; + uint64_t result{}; + CHECK_NAPI( + jsrApi_->napi_get_value_bigint_uint64(env_, value, &result, &lossless)); + return lossless; +} + +uint64_t NodeApiJsiRuntime::truncate(const jsi::BigInt &bigint) { + NodeApiScope scope{*this}; + napi_value value = getNodeApiValue(bigint); + bool lossless{false}; + uint64_t result{}; + CHECK_NAPI( + jsrApi_->napi_get_value_bigint_uint64(env_, value, &result, &lossless)); + return result; +} + +inline uint32_t constexpr maxCharsPerDigitInRadix(int32_t radix) { + // To compute the lower bound of bits in a BigIntDigitType "covered" by a + // char. For power of 2 radixes, it is known (exactly) that each character + // covers log2(radix) bits. For non-power of 2 radixes, a lower bound is + // log2(greatest power of 2 that is less than radix). + uint32_t minNumBitsPerChar = radix < 4 ? 1 + : radix < 8 ? 2 + : radix < 16 ? 3 + : radix < 32 ? 4 + : 5; + + // With minNumBitsPerChar being the lower bound estimate of how many bits each + // char can represent, the upper bound of how many chars "fit" in a bigint + // digit is ceil(sizeofInBits(bigint digit) / minNumBitsPerChar). + uint32_t numCharsPerDigits = + static_cast(sizeof(uint64_t)) / (1 << minNumBitsPerChar); + + return numCharsPerDigits; +} + +// Return the high 32 bits of a 64 bit value. +constexpr inline uint32_t Hi_32(uint64_t Value) { + return static_cast(Value >> 32); +} + +// Return the low 32 bits of a 64 bit value. +constexpr inline uint32_t Lo_32(uint64_t Value) { + return static_cast(Value); +} + +// Make a 64-bit integer from a high / low pair of 32-bit integers. +constexpr inline uint64_t Make_64(uint32_t High, uint32_t Low) { + return ((uint64_t)High << 32) | (uint64_t)Low; +} + +jsi::String NodeApiJsiRuntime::bigintToString( + const jsi::BigInt &bigint, + int32_t radix) { + NodeApiScope scope{*this}; + if (radix < 2 || radix > 36) { + throw makeJSError("Invalid radix ", radix, " to BigInt.toString"); + } + + napi_value value = getNodeApiValue(bigint); + size_t wordCount{}; + CHECK_NAPI(jsrApi_->napi_get_value_bigint_words( + env_, value, nullptr, &wordCount, nullptr)); + uint64_t stackWords[8]{}; + std::unique_ptr heapWords; + uint64_t *words = stackWords; + if (wordCount > std::size(stackWords)) { + heapWords = std::unique_ptr(new uint64_t[wordCount]); + words = heapWords.get(); + } + int32_t signBit{}; + CHECK_NAPI(jsrApi_->napi_get_value_bigint_words( + env_, value, &signBit, &wordCount, words)); + + if (wordCount == 0) { + return createStringFromAscii("0", 1); + } + + // avoid trashing the heap by pre-allocating the largest possible string + // returned by this function. The "1" below is to account for a possible "-" + // sign. + std::string digits; + digits.reserve(1 + wordCount * maxCharsPerDigitInRadix(radix)); + + // Use 32-bit values for calculations to get 64-bit results. + // For the little-endian machines we just cast the words array. + // TODO: Add support for big-endian. + uint32_t *halfWords = reinterpret_cast(words); + size_t count = wordCount * 2; + for (size_t i = count; i > 0 && halfWords[i - 1] == 0; --i) { + --count; + } + + uint32_t divisor = static_cast(radix); + uint32_t remainder = 0; + uint64_t word0 = words[0]; + + do { + // We rewrite the halfWords array as we divide it by radix. + if (count <= 2) { + remainder = word0 % divisor; + word0 = word0 / divisor; + } else { + for (size_t i = count; i > 0; --i) { + uint64_t partialDividend = Make_64(remainder, halfWords[i - 1]); + if (partialDividend == 0) { + halfWords[i] = 0; + remainder = 0; + if (i == count) { + if (--count == 2) { + word0 = words[0]; + } + } + } else if (partialDividend < divisor) { + halfWords[i] = 0; + remainder = Lo_32(partialDividend); + if (i == count) { + if (--count == 2) { + word0 = words[0]; + } + } + } else if (partialDividend == divisor) { + halfWords[i] = 1; + remainder = 0; + } else { + halfWords[i] = Lo_32(partialDividend / divisor); + remainder = Lo_32(partialDividend % divisor); + } + } + } + + if (remainder < 10) { + digits.push_back(static_cast('0' + remainder)); + } else { + digits.push_back(static_cast('a' + remainder - 10)); + } + } while (count > 2 || word0 != 0); + + if (signBit) { + digits.push_back('-'); + } + + std::reverse(digits.begin(), digits.end()); + return createStringFromAscii(digits.data(), digits.size()); +} +#endif + +jsi::String NodeApiJsiRuntime::createStringFromAscii( + const char *str, + size_t length) { + NodeApiScope scope{*this}; + return makeJsiPointer(createStringLatin1({str, length})); +} + +jsi::String NodeApiJsiRuntime::createStringFromUtf8( + const uint8_t *str, + size_t length) { + NodeApiScope scope{*this}; + return makeJsiPointer(createStringUtf8(str, length)); +} + +std::string NodeApiJsiRuntime::utf8(const jsi::String &str) { + NodeApiScope scope{*this}; + return stringToStdString(getNodeApiValue(str)); +} + +jsi::Object NodeApiJsiRuntime::createObject() { + NodeApiScope scope{*this}; + return makeJsiPointer(createNodeApiObject()); +} + +jsi::Object NodeApiJsiRuntime::createObject( + std::shared_ptr hostObject) { + NodeApiScope scope{*this}; + // The hostObjectHolder keeps the hostObject as external data. + // Then, the hostObjectHolder is wrapped up by a Proxy object to provide + // access to the hostObject's get, set, and getPropertyNames methods. There is + // a special symbol property ID, 'hostObjectSymbol', used to access the + // hostObjectWrapper from the Proxy. + napi_value hostObjectHolder = createExternalObject( + std::make_unique>( + std::move(hostObject))); + napi_value obj = createNodeApiObject(); + setProperty( + obj, getNodeApiValue(propertyId_.hostObjectSymbol), hostObjectHolder); + if (!cachedValue_.ProxyConstructor) { + cachedValue_.ProxyConstructor = makeNodeApiRef( + getProperty( + getNodeApiValue(cachedValue_.Global), + getNodeApiValue(propertyId_.Proxy)), + NodeApiPointerValueKind::Object); + } + napi_value args[] = {obj, getHostObjectProxyHandler()}; + napi_value proxy = + constructObject(getNodeApiValue(cachedValue_.ProxyConstructor), args); + return makeJsiPointer(proxy); +} + +std::shared_ptr NodeApiJsiRuntime::getHostObject( + const jsi::Object &obj) { + NodeApiScope scope{*this}; + return getJsiHostObject(getNodeApiValue(obj)); +} + +jsi::HostFunctionType &NodeApiJsiRuntime::getHostFunction( + const jsi::Function &func) { + NodeApiScope scope{*this}; + napi_value hostFunctionHolder = getProperty( + getNodeApiValue(func), getNodeApiValue((propertyId_.hostFunctionSymbol))); + if (typeOf(hostFunctionHolder) == napi_valuetype::napi_external) { + return static_cast( + getExternalData(hostFunctionHolder)) + ->hostFunction(); + } else { + throw jsi::JSINativeException( + "getHostFunction() can only be called with HostFunction."); + } +} + +#if JSI_VERSION >= 7 +bool NodeApiJsiRuntime::hasNativeState(const jsi::Object &obj) { + NodeApiScope scope{*this}; + void *nativeState{}; + napi_status status = + jsrApi_->napi_unwrap(env_, getNodeApiValue(obj), &nativeState); + return status == napi_ok && nativeState != nullptr; +} + +std::shared_ptr NodeApiJsiRuntime::getNativeState( + const jsi::Object &obj) { + NodeApiScope scope{*this}; + void *nativeState{}; + CHECK_NAPI(jsrApi_->napi_unwrap(env_, getNodeApiValue(obj), &nativeState)); + if (nativeState != nullptr) { + return *reinterpret_cast *>(nativeState); + } else { + return std::shared_ptr(); + } +} + +void NodeApiJsiRuntime::setNativeState( + const jsi::Object &obj, + std::shared_ptr state) { + NodeApiScope scope{*this}; + if (hasNativeState(obj)) { + void *nativeState{}; + CHECK_NAPI( + jsrApi_->napi_remove_wrap(env_, getNodeApiValue(obj), &nativeState)); + if (nativeState != nullptr) { + std::shared_ptr oldState{std::move( + *reinterpret_cast *>(nativeState))}; + } + } + + if (state) { + CHECK_NAPI(jsrApi_->napi_wrap( + env_, + getNodeApiValue(obj), + new std::shared_ptr(std::move(state)), + [](node_api_nogc_env /*env*/, void *data, void * /*finalize_hint*/) { + std::shared_ptr oldState{std::move( + *reinterpret_cast *>(data))}; + }, + nullptr, + nullptr)); + } +} +#endif + +jsi::Value NodeApiJsiRuntime::getProperty( + const jsi::Object &obj, + const jsi::PropNameID &name) { + NodeApiScope scope{*this}; + return toJsiValue(getProperty(getNodeApiValue(obj), getNodeApiValue(name))); +} + +jsi::Value NodeApiJsiRuntime::getProperty( + const jsi::Object &obj, + const jsi::String &name) { + NodeApiScope scope{*this}; + return toJsiValue(getProperty(getNodeApiValue(obj), getNodeApiValue(name))); +} + +#if JSI_VERSION >= 21 +jsi::Value NodeApiJsiRuntime::getProperty( + const jsi::Object &obj, + const jsi::Value &name) { + NodeApiScope scope{*this}; + return toJsiValue(getProperty(getNodeApiValue(obj), getNodeApiValue(name))); +} +#endif + +bool NodeApiJsiRuntime::hasProperty( + const jsi::Object &obj, + const jsi::PropNameID &name) { + NodeApiScope scope{*this}; + return hasProperty(getNodeApiValue(obj), getNodeApiValue(name)); +} + +bool NodeApiJsiRuntime::hasProperty( + const jsi::Object &obj, + const jsi::String &name) { + NodeApiScope scope{*this}; + return hasProperty(getNodeApiValue(obj), getNodeApiValue(name)); +} + +#if JSI_VERSION >= 21 +bool NodeApiJsiRuntime::hasProperty( + const jsi::Object &obj, + const jsi::Value &name) { + NodeApiScope scope{*this}; + return hasProperty(getNodeApiValue(obj), getNodeApiValue(name)); +} +#endif + +void NodeApiJsiRuntime::setPropertyValue( + JSI_CONST_10 jsi::Object &obj, + const jsi::PropNameID &name, + const jsi::Value &value) { + NodeApiScope scope{*this}; + setProperty( + getNodeApiValue(obj), getNodeApiValue(name), getNodeApiValue(value)); +} + +void NodeApiJsiRuntime::setPropertyValue( + JSI_CONST_10 jsi::Object &obj, + const jsi::String &name, + const jsi::Value &value) { + NodeApiScope scope{*this}; + setProperty( + getNodeApiValue(obj), getNodeApiValue(name), getNodeApiValue(value)); +} + +#if JSI_VERSION >= 21 +void NodeApiJsiRuntime::setPropertyValue( + const jsi::Object &obj, + const jsi::Value &name, + const jsi::Value &value) { + NodeApiScope scope{*this}; + setProperty( + getNodeApiValue(obj), getNodeApiValue(name), getNodeApiValue(value)); +} + +void NodeApiJsiRuntime::deleteProperty( + const jsi::Object &obj, + const jsi::PropNameID &name) { + NodeApiScope scope{*this}; + bool res = deleteProperty(getNodeApiValue(obj), getNodeApiValue(name)); + if (!res) { + throw jsi::JSError(*this, "Failed to delete property"); + } +} + +void NodeApiJsiRuntime::deleteProperty( + const jsi::Object &obj, + const jsi::String &name) { + NodeApiScope scope{*this}; + bool res = deleteProperty(getNodeApiValue(obj), getNodeApiValue(name)); + if (!res) { + throw jsi::JSError(*this, "Failed to delete property"); + } +} + +void NodeApiJsiRuntime::deleteProperty( + const jsi::Object &obj, + const jsi::Value &name) { + NodeApiScope scope{*this}; + bool res = deleteProperty(getNodeApiValue(obj), getNodeApiValue(name)); + if (!res) { + throw jsi::JSError(*this, "Failed to delete property"); + } +} +#endif + +bool NodeApiJsiRuntime::isArray(const jsi::Object &obj) const { + NodeApiScope scope{*this}; + return isArray(getNodeApiValue(obj)); +} + +bool NodeApiJsiRuntime::isArrayBuffer(const jsi::Object &obj) const { + NodeApiScope scope{*this}; + bool result{}; + CHECK_NAPI(jsrApi_->napi_is_arraybuffer(env_, getNodeApiValue(obj), &result)); + return result; +} + +bool NodeApiJsiRuntime::isFunction(const jsi::Object &obj) const { + NodeApiScope scope{*this}; + return typeOf(getNodeApiValue(obj)) == napi_valuetype::napi_function; +} + +bool NodeApiJsiRuntime::isHostObject(const jsi::Object &obj) const { + NodeApiScope scope{*this}; + napi_value hostObjectHolder = getProperty( + getNodeApiValue(obj), getNodeApiValue(propertyId_.hostObjectSymbol)); + if (typeOf(hostObjectHolder) == napi_valuetype::napi_external) { + return getExternalData(hostObjectHolder) != nullptr; + } else { + return false; + } +} + +bool NodeApiJsiRuntime::isHostFunction(const jsi::Function &func) const { + NodeApiScope scope{*this}; + napi_value hostFunctionHolder = getProperty( + getNodeApiValue(func), getNodeApiValue(propertyId_.hostFunctionSymbol)); + if (typeOf(hostFunctionHolder) == napi_valuetype::napi_external) { + return getExternalData(hostFunctionHolder) != nullptr; + } else { + return false; + } +} + +jsi::Array NodeApiJsiRuntime::getPropertyNames(const jsi::Object &obj) { + NodeApiScope scope{*this}; + napi_value properties; + CHECK_NAPI(jsrApi_->napi_get_all_property_names( + env_, + getNodeApiValue(obj), + napi_key_collection_mode::napi_key_include_prototypes, + napi_key_filter(napi_key_enumerable | napi_key_skip_symbols), + napi_key_numbers_to_strings, + &properties)); + return makeJsiPointer(properties).asArray(*this); +} + +jsi::WeakObject NodeApiJsiRuntime::createWeakObject(const jsi::Object &obj) { + NodeApiScope scope{*this}; + return make(NodeApiRefCountedPointerValue::make( + *const_cast(this), + getNodeApiValue(obj), + NodeApiPointerValueKind::WeakObject) + .release()); +} + +jsi::Value NodeApiJsiRuntime::lockWeakObject( + JSI_NO_CONST_3 JSI_CONST_10 jsi::WeakObject &weakObject) { + NodeApiScope scope{*this}; + napi_value value = getNodeApiValue(weakObject); + if (value) { + return toJsiValue(value); + } else { + return jsi::Value::undefined(); + } +} + +jsi::Array NodeApiJsiRuntime::createArray(size_t length) { + NodeApiScope scope{*this}; + return makeJsiPointer(createNodeApiArray(length)).asArray(*this); +} + +#if JSI_VERSION >= 9 +jsi::ArrayBuffer NodeApiJsiRuntime::createArrayBuffer( + std::shared_ptr buffer) { + NodeApiScope scope{*this}; + napi_value result{}; + void *data = buffer->data(); + size_t size = buffer->size(); + CHECK_NAPI(jsrApi_->napi_create_external_arraybuffer( + env_, + data, + size, + [](node_api_nogc_env /*env*/, void * /*data*/, void *finalizeHint) { + std::shared_ptr buffer{ + std::move(*reinterpret_cast *>( + finalizeHint))}; + }, + new std::shared_ptr{std::move(buffer)}, + &result)); + return makeJsiPointer(result).getArrayBuffer(*this); +} +#endif + +size_t NodeApiJsiRuntime::size(const jsi::Array &arr) { + NodeApiScope scope{*this}; + return getArrayLength(getNodeApiValue(arr)); +} + +size_t NodeApiJsiRuntime::size(const jsi::ArrayBuffer &arrBuf) { + NodeApiScope scope{*this}; + size_t result{}; + CHECK_NAPI(jsrApi_->napi_get_arraybuffer_info( + env_, getNodeApiValue(arrBuf), nullptr, &result)); + return result; +} + +uint8_t *NodeApiJsiRuntime::data(const jsi::ArrayBuffer &arrBuf) { + NodeApiScope scope{*this}; + uint8_t *result{}; + CHECK_NAPI(jsrApi_->napi_get_arraybuffer_info( + env_, + getNodeApiValue(arrBuf), + reinterpret_cast(&result), + nullptr)); + return result; +} + +jsi::Value NodeApiJsiRuntime::getValueAtIndex( + const jsi::Array &arr, + size_t index) { + NodeApiScope scope{*this}; + return toJsiValue(getElement(getNodeApiValue(arr), index)); +} + +void NodeApiJsiRuntime::setValueAtIndexImpl( + JSI_CONST_10 jsi::Array &arr, + size_t index, + const jsi::Value &value) { + NodeApiScope scope{*this}; + setElement( + getNodeApiValue(arr), + static_cast(index), + getNodeApiValue(value)); +} + +jsi::Function NodeApiJsiRuntime::createFunctionFromHostFunction( + const jsi::PropNameID &name, + unsigned int paramCount, + jsi::HostFunctionType func) { + NodeApiScope scope{*this}; + auto hostFunctionWrapper = + std::make_unique(std::move(func), *this); + napi_value function = createExternalFunction( + getNodeApiValue(name), + static_cast(paramCount), + jsiHostFunctionCallback, + hostFunctionWrapper.get()); + + const napi_value hostFunctionHolder = + createExternalObject(std::move(hostFunctionWrapper)); + setProperty( + function, + getNodeApiValue(propertyId_.hostFunctionSymbol), + hostFunctionHolder, + napi_property_attributes::napi_default); + return makeJsiPointer(function).getFunction(*this); +} + +jsi::Value NodeApiJsiRuntime::call( + const jsi::Function &func, + const jsi::Value &jsThis, + const jsi::Value *args, + size_t count) { + NodeApiScope scope{*this}; + return toJsiValue(callFunction( + getNodeApiValue(jsThis), + getNodeApiValue(func), + NodeApiValueArgs(*this, span(args, count)))); +} + +jsi::Value NodeApiJsiRuntime::callAsConstructor( + const jsi::Function &func, + const jsi::Value *args, + size_t count) { + NodeApiScope scope{*this}; + return toJsiValue(constructObject( + getNodeApiValue(func), + NodeApiValueArgs(*this, span(args, count)))); +} + +jsi::Runtime::ScopeState *NodeApiJsiRuntime::pushScope() { + napi_handle_scope result{}; + CHECK_NAPI(jsrApi_->napi_open_handle_scope(env_, &result)); + pushPointerValueScope(); + return reinterpret_cast(result); +} + +void NodeApiJsiRuntime::popScope(jsi::Runtime::ScopeState *state) { + popPointerValueScope(); + CHECK_NAPI(jsrApi_->napi_close_handle_scope( + env_, reinterpret_cast(state))); +} + +bool NodeApiJsiRuntime::strictEquals(const jsi::Symbol &a, const jsi::Symbol &b) + const { + NodeApiScope scope{*this}; + return strictEquals(getNodeApiValue(a), getNodeApiValue(b)); +} + +#if JSI_VERSION >= 6 +bool NodeApiJsiRuntime::strictEquals(const jsi::BigInt &a, const jsi::BigInt &b) + const { + NodeApiScope scope{*this}; + return strictEquals(getNodeApiValue(a), getNodeApiValue(b)); +} +#endif + +bool NodeApiJsiRuntime::strictEquals(const jsi::String &a, const jsi::String &b) + const { + NodeApiScope scope{*this}; + return strictEquals(getNodeApiValue(a), getNodeApiValue(b)); +} + +bool NodeApiJsiRuntime::strictEquals(const jsi::Object &a, const jsi::Object &b) + const { + NodeApiScope scope{*this}; + return strictEquals(getNodeApiValue(a), getNodeApiValue(b)); +} + +bool NodeApiJsiRuntime::instanceOf( + const jsi::Object &obj, + const jsi::Function &func) { + NodeApiScope scope{*this}; + return instanceOf(getNodeApiValue(obj), getNodeApiValue(func)); +} + +#if JSI_VERSION >= 11 +void NodeApiJsiRuntime::setExternalMemoryPressure( + const jsi::Object & /*obj*/, + size_t /*amount*/) { + // TODO: implement +} +#endif + +//===================================================================================================================== +// NodeApiJsiRuntime::NodeApiScope implementation +//===================================================================================================================== + +NodeApiJsiRuntime::NodeApiScope::NodeApiScope( + const NodeApiJsiRuntime &runtime) noexcept + : NodeApiScope(const_cast(runtime)) {} + +NodeApiJsiRuntime::NodeApiScope::NodeApiScope( + NodeApiJsiRuntime &runtime) noexcept + : runtime_(runtime), + envScope_(runtime_.getEnv()), + scopeState_(runtime_.pushScope()) { + runtime_.pushPointerValueScope(); +} + +NodeApiJsiRuntime::NodeApiScope::~NodeApiScope() noexcept { + runtime_.popPointerValueScope(); + runtime_.popScope(scopeState_); +} + +//===================================================================================================================== +// NodeApiJsiRuntime::NodeApiPointerValueScope implementation +//===================================================================================================================== + +NodeApiJsiRuntime::NodeApiPointerValueScope::NodeApiPointerValueScope( + NodeApiJsiRuntime &runtime) noexcept + : runtime_(runtime) { + runtime_.pushPointerValueScope(); +} + +NodeApiJsiRuntime::NodeApiPointerValueScope:: + ~NodeApiPointerValueScope() noexcept { + runtime_.popPointerValueScope(); +} + +//===================================================================================================================== +// NodeApiJsiRuntime::AutoRestore implementation +//===================================================================================================================== + +template +NodeApiJsiRuntime::AutoRestore::AutoRestore(T &varRef, T newValue) + : varRef_{varRef}, oldValue_{std::exchange(varRef, newValue)} {} + +template +NodeApiJsiRuntime::AutoRestore::~AutoRestore() { + varRef_ = oldValue_; +} + +//===================================================================================================================== +// NodeApiJsiRuntime::NodeApiStackOnlyPointerValue implementation +//===================================================================================================================== + +NodeApiJsiRuntime::NodeApiStackOnlyPointerValue::NodeApiStackOnlyPointerValue( + napi_value value, + NodeApiPointerValueKind pointerKind) noexcept + : value_(value), pointerKind_(pointerKind) {} + +// Intentionally do nothing since the value is allocated on the stack. +void NodeApiJsiRuntime::NodeApiStackOnlyPointerValue::invalidate() noexcept {} + +NodeApiJsiRuntime::NodeApiRefCountedPointerValue * +NodeApiJsiRuntime::NodeApiStackOnlyPointerValue::clone( + NodeApiJsiRuntime &runtime) const noexcept { + return NodeApiRefCountedPointerValue::make(runtime, value_, pointerKind_) + .release(); +} + +napi_value NodeApiJsiRuntime::NodeApiStackOnlyPointerValue::getValue( + NodeApiJsiRuntime & /*runtime*/) noexcept { + return value_; +} + +NodeApiJsiRuntime::NodeApiPointerValueKind +NodeApiJsiRuntime::NodeApiStackOnlyPointerValue::getKind() const noexcept { + return pointerKind_; +} + +//===================================================================================================================== +// NodeApiJsiRuntime::NodeApiPointerValue implementation +//===================================================================================================================== + +NodeApiJsiRuntime::NodeApiRefCountedPointerValue::NodeApiRefCountedPointerValue( + NodeApiJsiRuntime &runtime, + napi_value value, + NodeApiJsiRuntime::NodeApiPointerValueKind pointerKind, + int32_t initialRefCount) noexcept + : pendingDeletions_(runtime.pendingDeletions_), + value_(value), + refCount_(initialRefCount), + pointerKind_(pointerKind) {} + +/*static*/ NodeApiJsiRuntime::NodeApiRefHolder +NodeApiJsiRuntime::NodeApiRefCountedPointerValue::make( + NodeApiJsiRuntime &runtime, + napi_value value, + NodeApiJsiRuntime::NodeApiPointerValueKind pointerKind, + int32_t initialRefCount) { + NodeApiRefHolder result{ + new NodeApiRefCountedPointerValue( + runtime, value, pointerKind, initialRefCount), + attachTag}; + runtime.addStackValue(NodeApiStackValuePtr{result.get()}); + return result; +} + +/*static*/ NodeApiJsiRuntime::NodeApiRefHolder +NodeApiJsiRuntime::NodeApiRefCountedPointerValue::makeNodeApiRef( + NodeApiJsiRuntime &runtime, + napi_value value, + NodeApiPointerValueKind pointerKind, + int32_t initialRefCount) { + NodeApiRefHolder result{make(runtime, value, pointerKind, initialRefCount)}; + result->createNodeApiRef(runtime); + return result; +} + +void NodeApiJsiRuntime::NodeApiRefCountedPointerValue::invalidate() noexcept { + decRefCount(); +} + +NodeApiJsiRuntime::NodeApiRefCountedPointerValue * +NodeApiJsiRuntime::NodeApiRefCountedPointerValue::clone( + NodeApiJsiRuntime & /*runtime*/) const noexcept { + incRefCount(); + return const_cast(this); +} + +napi_value NodeApiJsiRuntime::NodeApiRefCountedPointerValue::getValue( + NodeApiJsiRuntime &runtime) noexcept { + if (value_ != nullptr) { + return value_; + } + + if (ref_ == nullptr) { + return nullptr; + } + + JSRuntimeApi *jsrApi = JSRuntimeApi::current(); + if (pointerKind_ == NodeApiPointerValueKind::Object || + pointerKind_ == NodeApiPointerValueKind::WeakObject) { + CHECK_NAPI_ELSE_CRASH( + jsrApi->napi_get_reference_value(runtime.getEnv(), ref_, &value_)); + } else { + napi_value obj{}; + CHECK_NAPI_ELSE_CRASH( + jsrApi->napi_get_reference_value(runtime.getEnv(), ref_, &obj)); + // TODO: Should we use an interned property key? + CHECK_NAPI_ELSE_CRASH(jsrApi->napi_get_named_property( + runtime.getEnv(), obj, kPrimitivePropertyName, &value_)); + } + + if (value_ != nullptr) { + runtime.addStackValue(NodeApiStackValuePtr(this)); + } + + return value_; +} + +NodeApiJsiRuntime::NodeApiPointerValueKind +NodeApiJsiRuntime::NodeApiRefCountedPointerValue::getKind() const noexcept { + return pointerKind_; +} + +bool NodeApiJsiRuntime::NodeApiRefCountedPointerValue::usedByJsiPointer() + const noexcept { + return !NodeApiRefCount::isZero(refCount_); +} + +void NodeApiJsiRuntime::NodeApiRefCountedPointerValue::deleteStackValue( + NodeApiJsiRuntime &runtime) noexcept { + CHECK_ELSE_CRASH(value_, "value_ must not be null"); + if (canBeDeletedFromStack_) { + delete this; + return; + } + + if (usedByJsiPointer() && ref_ == nullptr) { + createNodeApiRef(runtime); + } + + value_ = nullptr; +} + +void NodeApiJsiRuntime::NodeApiRefCountedPointerValue::deleteNodeApiRef( + NodeApiJsiRuntime &runtime) noexcept { + if (ref_ != nullptr) { + CHECK_NAPI_ELSE_CRASH( + JSRuntimeApi::current()->napi_delete_reference(runtime.getEnv(), ref_)); + ref_ = nullptr; + } + + if (value_ != nullptr) { + canBeDeletedFromStack_ = true; + } else { + delete this; + } +} + +void NodeApiJsiRuntime::NodeApiStackValueDeleter::operator()( + NodeApiRefCountedPointerValue *ptr) const noexcept { + if (ptr == nullptr) { + return; + } + + ptr->value_ = nullptr; + if (ptr->canBeDeletedFromStack_) { + delete ptr; + } +} + +void NodeApiJsiRuntime::NodeApiRefDeleter::operator()( + NodeApiRefCountedPointerValue *ptr) const noexcept { + if (ptr == nullptr) { + return; + } + + if (ptr->value_ != nullptr) { + ptr->canBeDeletedFromStack_ = true; + } else { + delete ptr; + } +} + +void NodeApiJsiRuntime::NodeApiRefCountedPointerValue::incRefCount() + const noexcept { + NodeApiRefCount::incRefCount(refCount_); +} + +void NodeApiJsiRuntime::NodeApiRefCountedPointerValue::decRefCount() + const noexcept { + if (NodeApiRefCount::decRefCount(refCount_)) { + pendingDeletions_->addPointerValueToDelete( + NodeApiRefPtr(const_cast(this))); + } +} + +NodeApiJsiRuntime::NodeApiRefCountedPointerValue * +NodeApiJsiRuntime::NodeApiRefCountedPointerValue::createNodeApiRef( + NodeApiJsiRuntime &runtime) { + JSRuntimeApi *jsrApi = JSRuntimeApi::current(); + CHECK_ELSE_CRASH(value_ != nullptr, "value_ must not be null"); + CHECK_ELSE_CRASH(ref_ == nullptr, "ref_ must be null"); + if (pointerKind_ == NodeApiPointerValueKind::Object) { + CHECK_NAPI_ELSE_CRASH( + jsrApi->napi_create_reference(runtime.getEnv(), value_, 1, &ref_)); + } else if (pointerKind_ != NodeApiPointerValueKind::WeakObject) { + napi_value obj{}; + CHECK_NAPI_ELSE_CRASH(jsrApi->napi_create_object(runtime.getEnv(), &obj)); + CHECK_NAPI_ELSE_CRASH(jsrApi->napi_set_named_property( + runtime.getEnv(), obj, kPrimitivePropertyName, value_)); + CHECK_NAPI_ELSE_CRASH( + jsrApi->napi_create_reference(runtime.getEnv(), obj, 1, &ref_)); + } else { + CHECK_NAPI_ELSE_CRASH( + jsrApi->napi_create_reference(runtime.getEnv(), value_, 0, &ref_)); + } + return this; +} + +//===================================================================================================================== +// NodeApiJsiRuntime::SmallBuffer implementation +//===================================================================================================================== + +template +NodeApiJsiRuntime::SmallBuffer::SmallBuffer( + size_t size) noexcept + : size_{size}, + heapData_{size_ > InplaceSize ? std::make_unique(size_) : nullptr} {} + +template +T *NodeApiJsiRuntime::SmallBuffer::data() noexcept { + return heapData_ ? heapData_.get() : stackData_.data(); +} + +template +size_t NodeApiJsiRuntime::SmallBuffer::size() const noexcept { + return size_; +} + +//===================================================================================================================== +// NodeApiJsiRuntime::NodeApiValueArgs implementation +//===================================================================================================================== + +NodeApiJsiRuntime::NodeApiValueArgs::NodeApiValueArgs( + NodeApiJsiRuntime &runtime, + span args) + : args_{args.size()} { + napi_value *jsArgs = args_.data(); + for (size_t i = 0; i < args.size(); ++i) { + jsArgs[i] = runtime.getNodeApiValue(args[i]); + } +} + +NodeApiJsiRuntime::NodeApiValueArgs::operator span() { + return span{args_.data(), args_.size()}; +} + +//===================================================================================================================== +// NodeApiJsiRuntime::JsiValueView implementation +//===================================================================================================================== + +NodeApiJsiRuntime::JsiValueView::JsiValueView( + NodeApiJsiRuntime *runtime, + napi_value jsValue) + : value_{initValue(runtime, jsValue, std::addressof(pointerStore_))} {} + +NodeApiJsiRuntime::JsiValueView::operator const jsi::Value &() const noexcept { + return value_; +} + +/*static*/ jsi::Value NodeApiJsiRuntime::JsiValueView::initValue( + NodeApiJsiRuntime *runtime, + napi_value value, + StoreType *store) { + switch (runtime->typeOf(value)) { + case napi_valuetype::napi_undefined: + return jsi::Value::undefined(); + case napi_valuetype::napi_null: + return jsi::Value::null(); + case napi_valuetype::napi_boolean: + return jsi::Value{runtime->getValueBool(value)}; + case napi_valuetype::napi_number: + return jsi::Value{runtime->getValueDouble(value)}; + case napi_valuetype::napi_string: + return make(new (store) NodeApiStackOnlyPointerValue( + value, NodeApiPointerValueKind::String)); + case napi_valuetype::napi_symbol: + return make(new (store) NodeApiStackOnlyPointerValue( + value, NodeApiPointerValueKind::Symbol)); + case napi_valuetype::napi_object: + case napi_valuetype::napi_function: + case napi_valuetype::napi_external: + return make(new (store) NodeApiStackOnlyPointerValue( + value, NodeApiPointerValueKind::Object)); +#if JSI_VERSION >= 8 + case napi_valuetype::napi_bigint: + return make(new (store) NodeApiStackOnlyPointerValue( + value, NodeApiPointerValueKind::BigInt)); +#endif + default: + throw jsi::JSINativeException("Unexpected value type"); + } +} + +//===================================================================================================================== +// NodeApiJsiRuntime::JsiValueViewArgs implementation +//===================================================================================================================== + +NodeApiJsiRuntime::JsiValueViewArgs::JsiValueViewArgs( + NodeApiJsiRuntime *runtime, + span args) noexcept + : pointerStore_{args.size()}, args_{args.size()} { + JsiValueView::StoreType *pointerStore = pointerStore_.data(); + jsi::Value *jsiArgs = args_.data(); + for (size_t i = 0; i < args_.size(); ++i) { + jsiArgs[i] = JsiValueView::initValue( + runtime, args[i], std::addressof(pointerStore[i])); + } +} + +jsi::Value const *NodeApiJsiRuntime::JsiValueViewArgs::data() noexcept { + return args_.data(); +} + +size_t NodeApiJsiRuntime::JsiValueViewArgs::size() const noexcept { + return args_.size(); +} + +//===================================================================================================================== +// NodeApiJsiRuntime::PropNameIDView implementation +//===================================================================================================================== + +// TODO: account for symbol +NodeApiJsiRuntime::PropNameIDView::PropNameIDView( + NodeApiJsiRuntime * /*runtime*/, + napi_value propertyId) noexcept + : propertyId_{make( + new (std::addressof(pointerStore_)) NodeApiStackOnlyPointerValue( + propertyId, + NodeApiPointerValueKind::StringPropNameID))} {} + +NodeApiJsiRuntime::PropNameIDView::operator jsi::PropNameID const &() + const noexcept { + return propertyId_; +} + +//===================================================================================================================== +// NodeApiJsiRuntime::HostFunctionWrapper implementation +//===================================================================================================================== + +NodeApiJsiRuntime::HostFunctionWrapper::HostFunctionWrapper( + jsi::HostFunctionType &&type, + NodeApiJsiRuntime &runtime) + : hostFunction_{std::move(type)}, runtime_{runtime} {} + +jsi::HostFunctionType & +NodeApiJsiRuntime::HostFunctionWrapper::hostFunction() noexcept { + return hostFunction_; +} + +NodeApiJsiRuntime &NodeApiJsiRuntime::HostFunctionWrapper::runtime() noexcept { + return runtime_; +} + +//===================================================================================================================== +// NodeApiJsiRuntime implementation +//===================================================================================================================== + +template +jsi::JSError NodeApiJsiRuntime::makeJSError(Args &&...args) { + std::ostringstream errorStream; + ((errorStream << std::forward(args)), ...); + return jsi::JSError(*this, errorStream.str()); +} + +[[noreturn]] void NodeApiJsiRuntime::throwJSException( + napi_status status) const { + auto formatStatusError = [](napi_status status) -> std::string { + // TODO: (vmoroz) use a more sophisticated error formatting. + std::ostringstream errorStream; + errorStream << "A call to Node-API returned error code 0x" << std::hex + << static_cast(status) << '.'; + return errorStream.str(); + }; + + NodeApiScope scope{*this}; + // Retrieve the exception value and clear as we will rethrow it as a C++ + // exception. + napi_value jsError{}; + CHECK_NAPI_ELSE_CRASH( + jsrApi_->napi_get_and_clear_last_exception(env_, &jsError)); + napi_valuetype jsErrorType; + CHECK_NAPI_ELSE_CRASH(jsrApi_->napi_typeof(env_, jsError, &jsErrorType)); + if (jsErrorType == napi_undefined) { + throw jsi::JSINativeException(formatStatusError(status).c_str()); + } + jsi::Value jsiJSError = toJsiValue(jsError); + + std::string msg = "No message"; + std::string stack = "No stack"; + if (jsErrorType == napi_string) { + // If the exception is a string, use it as the message. + msg = stringToStdString(jsError); + } else if (jsErrorType == napi_object) { + // If the exception is an object try to retrieve its message and stack + // properties. + + /// Attempt to retrieve a string property \p sym from \c jsError and store + /// it in \p out. Ignore any catchable errors and non-string properties. + auto getStrProp = [this, jsError](const char *sym, std::string &out) { + napi_value value{}; + napi_status propStatus = + jsrApi_->napi_get_named_property(env_, jsError, sym, &value); + if (propStatus != napi_ok) { + // An exception was thrown while retrieving the property, if it is + // catchable, suppress it. Otherwise, rethrow this exception without + // trying to invoke any more JavaScript. + napi_value newJSError{}; + CHECK_NAPI_ELSE_CRASH( + jsrApi_->napi_get_and_clear_last_exception(env_, &newJSError)); + napi_valuetype newJSErrorType; + CHECK_NAPI_ELSE_CRASH( + jsrApi_->napi_typeof(env_, newJSError, &newJSErrorType)); + + if (propStatus != napi_cannot_run_js) + return; + + // An uncatchable error occurred, it is unsafe to do anything that + // might execute more JavaScript. + if (newJSErrorType != napi_undefined) { + throw jsi::JSError( + toJsiValue(newJSError), + "Uncatchable exception thrown while creating error", + "No stack"); + } else { + std::ostringstream errorStream; + errorStream << "A call to Node-API returned error code 0x" << std::hex + << propStatus << '.'; + throw jsi::JSINativeException(errorStream.str().c_str()); + } + } + + // If the property is a string, update out. Otherwise ignore it. + napi_valuetype valueType; + CHECK_NAPI_ELSE_CRASH(jsrApi_->napi_typeof(env_, value, &valueType)); + if (valueType == napi_string) { + out = stringToStdString(value); + } + }; + + getStrProp("message", msg); + getStrProp("stack", stack); + } + + // Use the constructor of jsi::JSError that cannot run additional + // JS, since that may then result in additional exceptions and infinite + // recursion. + throw jsi::JSError(std::move(jsiJSError), msg, stack); +} + +// Throws jsi::JSINativeException with a message. +[[noreturn]] void NodeApiJsiRuntime::throwNativeException( + char const *errorMessage) const { + throw jsi::JSINativeException(errorMessage); +} + +// Rewrites error messages to match the JSI unit test expectations. +void NodeApiJsiRuntime::rewriteErrorMessage(napi_value jsError) const { + // The code below must work correctly even if the 'message' getter throws. + // In case when it throws, we ignore that exception. + napi_value message{}; + napi_status status = jsrApi_->napi_get_property( + env_, jsError, getNodeApiValue(propertyId_.message), &message); + if (status != napi_ok) { + // If the 'message' property getter throws, then we clear the exception and + // ignore it. + napi_value ignoreJSError{}; + jsrApi_->napi_get_and_clear_last_exception(env_, &ignoreJSError); + } else if (typeOf(message) == napi_string) { + // JSI unit tests expect V8- or JSC-like messages for the stack overflow. + std::string messageStr = stringToStdString(message); + if (messageStr == "Out of stack space" || + messageStr.find("Maximum call stack") != std::string::npos) { + setProperty( + jsError, + getNodeApiValue(propertyId_.message), + createStringUtf8("RangeError : Maximum call stack size exceeded"sv)); + } + } + + // Make sure that the call stack has the current URL + if (!sourceURL_.empty()) { + napi_value stack{}; + status = jsrApi_->napi_get_property( + env_, jsError, getNodeApiValue(propertyId_.stack), &stack); + if (status != napi_ok) { + // If the 'stack' property getter throws, then we clear the exception and + // ignore it. + napi_value ignoreJSError{}; + jsrApi_->napi_get_and_clear_last_exception(env_, &ignoreJSError); + } else if (typeOf(message) == napi_string) { + // JSI unit tests expect URL to be part of the call stack. + std::string stackStr = stringToStdString(stack); + if (stackStr.find(sourceURL_) == std::string::npos) { + stackStr += sourceURL_ + '\n' + stackStr; + setProperty( + jsError, + getNodeApiValue(propertyId_.stack), + createStringUtf8(stackStr.c_str())); + } + } + } +} + +// Evaluates lambda and augments exception messages with the method's name. +template +auto NodeApiJsiRuntime::runInMethodContext( + char const *methodName, + TLambda lambda) { + try { + return lambda(); + } catch (jsi::JSError const &) { + throw; // do not augment the JSError exceptions. + } catch (std::exception const &ex) { + throwNativeException( + (std::string{"Exception in "} + methodName + ": " + ex.what()).c_str()); + } catch (...) { + throwNativeException( + (std::string{"Exception in "} + methodName + ": ").c_str()); + } +} + +// Evaluates lambda and converts all exceptions to Node-API errors. +template +napi_value NodeApiJsiRuntime::handleCallbackExceptions( + TLambda lambda) const noexcept { + try { + try { + return lambda(); + } catch (jsi::JSError const &jsError) { + // This block may throw exceptions + setException(getNodeApiValue(jsError.value())); + } + } catch (std::exception const &ex) { + setException(ex.what()); + } catch (...) { + setException("Unexpected error"); + } + + return getUndefined(); +} + +// Throws JavaScript exception using Node-API. +bool NodeApiJsiRuntime::setException(napi_value error) const noexcept { + // This method must not throw. We return false in case of error. + return jsrApi_->napi_throw(env_, error) == napi_status::napi_ok; +} + +// Throws JavaScript error exception with the provided message using Node-API. +bool NodeApiJsiRuntime::setException(std::string_view message) const noexcept { + // This method must not throw. We return false in case of error. + return jsrApi_->napi_throw_error(env_, "Unknown", message.data()) == + napi_status::napi_ok; +} + +// Gets type of the napi_value. +napi_valuetype NodeApiJsiRuntime::typeOf(napi_value value) const { + napi_valuetype result{}; + CHECK_NAPI(jsrApi_->napi_typeof(env_, value, &result)); + return result; +} + +// Returns true if two napi_values are strict equal per JavaScript rules. +bool NodeApiJsiRuntime::strictEquals(napi_value left, napi_value right) const { + bool result{false}; + CHECK_NAPI(jsrApi_->napi_strict_equals(env_, left, right, &result)); + return result; +} + +// Gets the napi_value for the JavaScript's undefined value. +napi_value NodeApiJsiRuntime::getUndefined() const { + napi_value result{nullptr}; + CHECK_NAPI(jsrApi_->napi_get_undefined(env_, &result)); + return result; +} + +// Gets the napi_value for the JavaScript's null value. +napi_value NodeApiJsiRuntime::getNull() const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_get_null(env_, &result)); + return result; +} + +// Gets the napi_value for the JavaScript's global object. +napi_value NodeApiJsiRuntime::getGlobal() const { + napi_value result{nullptr}; + CHECK_NAPI(jsrApi_->napi_get_global(env_, &result)); + return result; +} + +// Gets the napi_value for the JavaScript's true and false values. +napi_value NodeApiJsiRuntime::getBoolean(bool value) const { + napi_value result{nullptr}; + CHECK_NAPI(jsrApi_->napi_get_boolean(env_, value, &result)); + return result; +} + +// Gets value of the Boolean napi_value. +bool NodeApiJsiRuntime::getValueBool(napi_value value) const { + bool result{false}; + CHECK_NAPI(jsrApi_->napi_get_value_bool(env_, value, &result)); + return result; +} + +// Creates napi_value with an int32_t value. +napi_value NodeApiJsiRuntime::createInt32(int32_t value) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_int32(env_, value, &result)); + return result; +} + +// Creates napi_value with an int32_t value. +napi_value NodeApiJsiRuntime::createUInt32(uint32_t value) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_uint32(env_, value, &result)); + return result; +} + +// Creates napi_value with a double value. +napi_value NodeApiJsiRuntime::createDouble(double value) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_double(env_, value, &result)); + return result; +} + +// Gets value of the Double napi_value. +double NodeApiJsiRuntime::getValueDouble(napi_value value) const { + double result{0}; + CHECK_NAPI(jsrApi_->napi_get_value_double(env_, value, &result)); + return result; +} + +// Creates a napi_value string from the extended ASCII symbols that correspond +// to the Latin1 encoding. Each character is a byte-sized value from 0 to 255. +napi_value NodeApiJsiRuntime::createStringLatin1(std::string_view value) const { + CHECK_ELSE_THROW(value.data(), "Cannot convert a nullptr to a JS string."); + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_string_latin1( + env_, value.data(), value.size(), &result)); + return result; +} + +// Creates a napi_value string from a UTF-8 string. +napi_value NodeApiJsiRuntime::createStringUtf8(std::string_view value) const { + CHECK_ELSE_THROW(value.data(), "Cannot convert a nullptr to a JS string."); + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_string_utf8( + env_, value.data(), value.size(), &result)); + return result; +} + +// Creates a napi_value string from a UTF-8 string. Use data and length instead +// of string_view. +napi_value NodeApiJsiRuntime::createStringUtf8( + const uint8_t *data, + size_t length) const { + return createStringUtf8({reinterpret_cast(data), length}); +} + +// Gets std::string from the napi_value string. +std::string NodeApiJsiRuntime::stringToStdString(napi_value stringValue) const { + std::string result; + CHECK_ELSE_THROW( + typeOf(stringValue) == napi_valuetype::napi_string, + "Cannot convert a non JS string Node-API Value to a std::string."); + size_t strLength{}; + CHECK_NAPI(jsrApi_->napi_get_value_string_utf8( + env_, stringValue, nullptr, 0, &strLength)); + result.assign(strLength, '\0'); + size_t copiedLength{}; + CHECK_NAPI(jsrApi_->napi_get_value_string_utf8( + env_, stringValue, &result[0], result.length() + 1, &copiedLength)); + CHECK_ELSE_THROW(result.length() == copiedLength, "Unexpected string length"); + return result; +} + +// Gets or creates a unique string value from an UTF-8 string_view. +napi_value NodeApiJsiRuntime::getPropertyIdFromName( + std::string_view value) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_string_utf8( + env_, value.data(), value.size(), &result)); + return result; +} + +// Gets or creates a unique string value from an UTF-8 data/length range. +napi_value NodeApiJsiRuntime::getPropertyIdFromName( + const uint8_t *data, + size_t length) const { + return getPropertyIdFromName({reinterpret_cast(data), length}); +} + +// Gets or creates a unique string value from napi_value string. +napi_value NodeApiJsiRuntime::getPropertyIdFromName(napi_value str) const { + return str; +} + +// Gets or creates a unique string value from napi_value symbol. +napi_value NodeApiJsiRuntime::getPropertyIdFromSymbol(napi_value sym) const { + return sym; +} + +// Converts property id value to std::string. +std::string NodeApiJsiRuntime::propertyIdToStdString(napi_value propertyId) { + if (typeOf(propertyId) == napi_symbol) { + return symbolToStdString(propertyId); + } + + return stringToStdString(propertyId); +} + +// Creates a JavaScript symbol napi_value. +napi_value NodeApiJsiRuntime::createSymbol( + std::string_view symbolDescription) const { + napi_value result{}; + napi_value description = createStringUtf8(symbolDescription); + CHECK_NAPI(jsrApi_->napi_create_symbol(env_, description, &result)); + return result; +} + +// Calls Symbol.toString() and returns it as std::string. +std::string NodeApiJsiRuntime::symbolToStdString(napi_value symbolValue) { + if (!cachedValue_.SymbolToString) { + napi_value symbolCtor = getProperty( + getNodeApiValue(cachedValue_.Global), + getNodeApiValue(propertyId_.Symbol)); + napi_value symbolPrototype = + getProperty(symbolCtor, getNodeApiValue(propertyId_.prototype)); + cachedValue_.SymbolToString = makeNodeApiRef( + getProperty(symbolPrototype, getNodeApiValue(propertyId_.toString)), + NodeApiPointerValueKind::Object); + } + napi_value jsString = callFunction( + symbolValue, getNodeApiValue(cachedValue_.SymbolToString), {}); + return stringToStdString(jsString); +} + +// Calls a JavaScript function. +napi_value NodeApiJsiRuntime::callFunction( + napi_value thisArg, + napi_value function, + span args) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_call_function( + env_, thisArg, function, args.size(), args.data(), &result)); + return result; +} + +// Constructs a new JavaScript Object using a constructor function. +napi_value NodeApiJsiRuntime::constructObject( + napi_value constructor, + span args) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_new_instance( + env_, constructor, args.size(), args.data(), &result)); + return result; +} + +// Returns true if object was constructed using the provided constructor. +bool NodeApiJsiRuntime::instanceOf(napi_value object, napi_value constructor) + const { + bool result{false}; + CHECK_NAPI(jsrApi_->napi_instanceof(env_, object, constructor, &result)); + return result; +} + +// Creates new JavaScript Object. +napi_value NodeApiJsiRuntime::createNodeApiObject() const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_object(env_, &result)); + return result; +} + +// Returns true if the object has a property with the provided property ID. +bool NodeApiJsiRuntime::hasProperty(napi_value object, napi_value propertyId) + const { + bool result{}; + CHECK_NAPI(jsrApi_->napi_has_property(env_, object, propertyId, &result)); + return result; +} + +// Gets object property value. +napi_value NodeApiJsiRuntime::getProperty( + napi_value object, + napi_value propertyId) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_get_property(env_, object, propertyId, &result)); + return result; +} + +// Sets object property value. +void NodeApiJsiRuntime::setProperty( + napi_value object, + napi_value propertyId, + napi_value value) const { + CHECK_NAPI(jsrApi_->napi_set_property(env_, object, propertyId, value)); +} + +// Deletes object property value. +bool NodeApiJsiRuntime::deleteProperty(napi_value object, napi_value propertyId) + const { + bool result{}; + CHECK_NAPI(jsrApi_->napi_delete_property(env_, object, propertyId, &result)); + return result; +} + +// Sets object property value with the provided property accessibility +// attributes. +void NodeApiJsiRuntime::setProperty( + napi_value object, + napi_value propertyId, + napi_value value, + napi_property_attributes attrs) const { + napi_property_descriptor descriptor{}; + descriptor.name = propertyId; + descriptor.value = value; + descriptor.attributes = attrs; + CHECK_NAPI(jsrApi_->napi_define_properties(env_, object, 1, &descriptor)); +} + +// Creates a new JavaScript Array with the provided length. +napi_value NodeApiJsiRuntime::createNodeApiArray(size_t length) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_array_with_length(env_, length, &result)); + return result; +} + +bool NodeApiJsiRuntime::isArray(napi_value value) const { + bool result{}; + CHECK_NAPI(jsrApi_->napi_is_array(env_, value, &result)); + return result; +} + +size_t NodeApiJsiRuntime::getArrayLength(napi_value value) const { + uint32_t result{}; + CHECK_NAPI(jsrApi_->napi_get_array_length(env_, value, &result)); + return result; +} + +napi_value NodeApiJsiRuntime::getElement(napi_value arr, size_t index) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_get_element( + env_, arr, static_cast(index), &result)); + return result; +} + +// Sets array element. +void NodeApiJsiRuntime::setElement( + napi_value array, + uint32_t index, + napi_value value) const { + CHECK_NAPI(jsrApi_->napi_set_element(env_, array, index, value)); +} + +// The Node-API external function callback used for the JSI host function +// implementation. +/*static*/ napi_value __cdecl NodeApiJsiRuntime::jsiHostFunctionCallback( + napi_env env, + napi_callback_info info) noexcept { + HostFunctionWrapper *hostFuncWrapper{}; + size_t argc{}; + CHECK_NAPI_ELSE_CRASH( + JSRuntimeApi::current()->napi_get_cb_info( + env, + info, + &argc, + nullptr, + nullptr, + reinterpret_cast(&hostFuncWrapper))); + CHECK_ELSE_CRASH(hostFuncWrapper, "Cannot find the host function"); + NodeApiJsiRuntime &runtime = hostFuncWrapper->runtime(); + NodeApiPointerValueScope scope{runtime}; + + return runtime.handleCallbackExceptions( + [&env, &info, &argc, &runtime, &hostFuncWrapper]() { + SmallBuffer napiArgs(argc); + napi_value thisArg{}; + CHECK_NAPI_ELSE_CRASH( + JSRuntimeApi::current()->napi_get_cb_info( + env, info, &argc, napiArgs.data(), &thisArg, nullptr)); + CHECK_ELSE_CRASH(napiArgs.size() == argc, "Wrong argument count"); + const JsiValueView jsiThisArg{&runtime, thisArg}; + JsiValueViewArgs jsiArgs( + &runtime, span(napiArgs.data(), napiArgs.size())); + + const jsi::HostFunctionType &hostFunc = hostFuncWrapper->hostFunction(); + return runtime.runInMethodContext( + "HostFunction", [&hostFunc, &runtime, &jsiThisArg, &jsiArgs]() { + return runtime.getNodeApiValue(hostFunc( + runtime, jsiThisArg, jsiArgs.data(), jsiArgs.size())); + }); + }); +} + +// Creates an external function. +napi_value NodeApiJsiRuntime::createExternalFunction( + napi_value name, + int32_t paramCount, + napi_callback callback, + void *callbackData) { + std::string funcName = stringToStdString(name); + napi_value function{}; + CHECK_NAPI(jsrApi_->napi_create_function( + env_, + funcName.data(), + funcName.length(), + callback, + callbackData, + &function)); + setProperty( + function, + getNodeApiValue(propertyId_.length), + createInt32(paramCount), + napi_property_attributes::napi_default); + + return function; +} + +// Creates an object that wraps up external data. +napi_value NodeApiJsiRuntime::createExternalObject( + void *data, + node_api_nogc_finalize finalizeCallback) const { + napi_value result{}; + CHECK_NAPI(jsrApi_->napi_create_external( + env_, data, finalizeCallback, nullptr, &result)); + return result; +} + +// Wraps up std::unique_ptr as an external object. +template +napi_value NodeApiJsiRuntime::createExternalObject( + std::unique_ptr &&data) const { + node_api_nogc_finalize finalize = [](node_api_nogc_env /*env*/, + void *dataToDestroy, + void * /*finalizerHint*/) { + // We wrap dataToDestroy in a unique_ptr to avoid calling delete explicitly. + std::unique_ptr dataDeleter{static_cast(dataToDestroy)}; + }; + napi_value object = createExternalObject(data.get(), finalize); + + // We only call data.release() after the createExternalObject succeeds. + // Otherwise, when createExternalObject fails and an exception is thrown, + // the memory that data used to own will be leaked. + data.release(); + return object; +} + +// Gets external data wrapped by an external object. +void *NodeApiJsiRuntime::getExternalData(napi_value object) const { + void *result{}; + CHECK_NAPI(jsrApi_->napi_get_value_external(env_, object, &result)); + return result; +} + +// Gets JSI host object wrapped into a napi_value object. +const std::shared_ptr &NodeApiJsiRuntime::getJsiHostObject( + napi_value obj) { + const napi_value hostObjectHolder = + getProperty(obj, getNodeApiValue(propertyId_.hostObjectSymbol)); + + if (typeOf(hostObjectHolder) == napi_valuetype::napi_external) { + if (void *data = getExternalData(hostObjectHolder)) { + return *static_cast *>(data); + } + } + + throw jsi::JSINativeException("Cannot get HostObjects."); +} + +// Gets cached or creates Proxy handler to implement the JSI host object. +napi_value NodeApiJsiRuntime::getHostObjectProxyHandler() { + if (!cachedValue_.HostObjectProxyHandler) { + const napi_value handler = createNodeApiObject(); + setProxyTrap<&NodeApiJsiRuntime::hostObjectHasTrap, 2>( + handler, getNodeApiValue(propertyId_.has)); + setProxyTrap<&NodeApiJsiRuntime::hostObjectGetTrap, 3>( + handler, getNodeApiValue(propertyId_.get)); + setProxyTrap<&NodeApiJsiRuntime::hostObjectSetTrap, 4>( + handler, getNodeApiValue(propertyId_.set)); + setProxyTrap<&NodeApiJsiRuntime::hostObjectOwnKeysTrap, 1>( + handler, getNodeApiValue(propertyId_.ownKeys)); + setProxyTrap<&NodeApiJsiRuntime::hostObjectGetOwnPropertyDescriptorTrap, 2>( + handler, getNodeApiValue(propertyId_.getOwnPropertyDescriptor)); + cachedValue_.HostObjectProxyHandler = + makeNodeApiRef(handler, NodeApiPointerValueKind::Object); + } + + return getNodeApiValue(cachedValue_.HostObjectProxyHandler); +} + +// Sets Proxy trap method as a pointer to NodeApiJsiRuntime instance method. +template < + napi_value (NodeApiJsiRuntime::*trapMethod)(span), + size_t argCount> +void NodeApiJsiRuntime::setProxyTrap( + napi_value handler, + napi_value propertyName) { + napi_callback proxyTrap = [](napi_env env, napi_callback_info info) noexcept { + NodeApiJsiRuntime *runtime{}; + napi_value args[argCount]{}; + size_t actualArgCount{argCount}; + CHECK_NAPI_ELSE_CRASH( + JSRuntimeApi::current()->napi_get_cb_info( + env, + info, + &actualArgCount, + args, + nullptr, + reinterpret_cast(&runtime))); + CHECK_ELSE_CRASH( + actualArgCount == argCount, "proxy trap requires argCount arguments."); + NodeApiPointerValueScope scope{*runtime}; + return runtime->handleCallbackExceptions([&runtime, &args]() { + return (runtime->*trapMethod)(span(args, argCount)); + }); + }; + + setProperty( + handler, + propertyName, + createExternalFunction(propertyName, argCount, proxyTrap, this)); +} + +// The host object Proxy 'has' trap implementation. +napi_value NodeApiJsiRuntime::hostObjectHasTrap(span args) { + // args[0] - the Proxy target object. + // args[1] - the name of the property to check. + napi_value propertyName = args[1]; + const auto &hostObject = getJsiHostObject(args[0]); + return runInMethodContext( + "HostObject::has", [&hostObject, &propertyName, this]() { + // std::vector ownKeys = + // hostObject->getPropertyNames(*this); for (jsi::PropNameID &ownKey : + // ownKeys) { + // if (strictEquals(propertyName, getNodeApiValue(ownKey))) { + return getBoolean(true); + // } + // } + // return getBoolean(false); + }); +} + +// The host object Proxy 'get' trap implementation. +napi_value NodeApiJsiRuntime::hostObjectGetTrap(span args) { + // args[0] - the Proxy target object. + // args[1] - the name of the property to set. + // args[2] - the Proxy object (unused). + napi_value target = args[0]; + napi_value propertyName = args[1]; + bool isTargetOwnProp{}; + CHECK_NAPI(jsrApi_->napi_has_own_property( + env_, target, propertyName, &isTargetOwnProp)); + if (isTargetOwnProp) { + return getProperty(target, propertyName); + } + const auto &hostObject = getJsiHostObject(args[0]); + PropNameIDView propertyId{this, propertyName}; + return runInMethodContext( + "HostObject::get", [&hostObject, &propertyId, this]() { + return getNodeApiValue(hostObject->get(*this, propertyId)); + }); +} + +// The host object Proxy 'set' trap implementation. +napi_value NodeApiJsiRuntime::hostObjectSetTrap(span args) { + // args[0] - the Proxy target object. + // args[1] - the name of the property to set. + // args[2] - the new value of the property to set. + // args[3] - the Proxy object (unused). + const auto &hostObject = getJsiHostObject(args[0]); + PropNameIDView propertyId{this, args[1]}; + JsiValueView value{this, args[2]}; + runInMethodContext( + "HostObject::set", [&hostObject, &propertyId, &value, this]() { + hostObject->set(*this, propertyId, value); + }); + return getUndefined(); +} + +// The host object Proxy 'ownKeys' trap implementation. +napi_value NodeApiJsiRuntime::hostObjectOwnKeysTrap(span args) { + // args[0] - the Proxy target object. + napi_value target = args[0]; + + napi_value targetOwnKeys{}; + CHECK_NAPI(jsrApi_->napi_get_all_property_names( + env_, + target, + napi_key_own_only, + napi_key_all_properties, + napi_key_numbers_to_strings, + &targetOwnKeys)); + CHECK_ELSE_THROW(isArray(targetOwnKeys), "Expected an array"); + size_t targetOwnKeysLength = getArrayLength(targetOwnKeys); + + const auto &hostObject = getJsiHostObject(target); + std::vector hostOwnKeys = runInMethodContext( + "HostObject::getPropertyNames", + [&hostObject, this]() { return hostObject->getPropertyNames(*this); }); + + std::vector ownKeys; + std::unordered_set uniqueOwnKeys; + // Minus one hostObjectSymbol key. + ownKeys.reserve(targetOwnKeysLength - 1 + hostOwnKeys.size()); + uniqueOwnKeys.reserve(targetOwnKeysLength - 1 + hostOwnKeys.size()); + + // Read all target own keys. + if (targetOwnKeysLength > 1) { + auto addPropNameId = + [this, &uniqueOwnKeys, &ownKeys](jsi::PropNameID &&propNameId) { + const PointerValue *pv = getPointerValue(propNameId); + auto inserted = uniqueOwnKeys.insert(pv); + CHECK_ELSE_THROW(inserted.second, "Target has non-unique keys"); + ownKeys.push_back(std::move(propNameId)); + }; + for (size_t i = 0; i < targetOwnKeysLength; ++i) { + napi_value key = getElement(targetOwnKeys, i); + napi_valuetype keyType = typeOf(key); + if (keyType == napi_string) { + addPropNameId( + createPropNameIDFromString(makeJsiPointer(key))); +#if JSI_VERSION >= 8 + } else if (keyType == napi_symbol) { + if (strictEquals(key, getNodeApiValue(propertyId_.hostObjectSymbol))) { + continue; + } + addPropNameId( + createPropNameIDFromSymbol(makeJsiPointer(key))); +#endif + } else { + throwNativeException("Unexpected key type"); + } + } + } + + // Read all unique host own keys. + for (jsi::PropNameID &propNameId : hostOwnKeys) { + const PointerValue *pv = getPointerValue(propNameId); + auto inserted = uniqueOwnKeys.insert(pv); + if (inserted.second) { + ownKeys.push_back(std::move(propNameId)); + } + } + + // Put indexed properties before named ones. + struct Index { + uint32_t index; + napi_value value; + }; + std::vector indexKeys; + std::vector nonIndexKeys; + nonIndexKeys.reserve(ownKeys.size()); + for (const jsi::PropNameID &key : ownKeys) { + napi_value napiKey = getNodeApiValue(key); + napi_valuetype valueType = typeOf(napiKey); + if (valueType == napi_string) { + std::string keyStr = stringToStdString(napiKey); + std::optional indexKey = + toArrayIndex(keyStr.begin(), keyStr.end()); + if (indexKey.has_value()) { + indexKeys.push_back(Index{indexKey.value(), napiKey}); + continue; + } + } + nonIndexKeys.push_back(napiKey); + } + + std::sort( + indexKeys.begin(), + indexKeys.end(), + [](const Index &left, const Index &right) { + return left.index < right.index; + }); + + napi_value ownKeyArray = createNodeApiArray(0); + uint32_t index = 0; + for (const Index &indexKey : indexKeys) { + setElement(ownKeyArray, index++, indexKey.value); + } + for (napi_value napiKey : nonIndexKeys) { + setElement(ownKeyArray, index++, napiKey); + } + + return ownKeyArray; +} + +// The host object Proxy 'getOwnPropertyDescriptor' trap implementation. +napi_value NodeApiJsiRuntime::hostObjectGetOwnPropertyDescriptorTrap( + span args) { + // args[0] - the Proxy target object. + // args[1] - the property + const auto &hostObject = getJsiHostObject(args[0]); + PropNameIDView propertyId{this, args[1]}; + + return runInMethodContext( + "HostObject::getOwnPropertyDescriptor", + [&hostObject, &propertyId, this]() { + auto getPropDescriptor = [](napi_value name, napi_value value) { + return napi_property_descriptor{ + nullptr, + name, + nullptr, + nullptr, + nullptr, + value, + napi_default_jsproperty, + nullptr}; + }; + napi_value trueValue = getBoolean(true); + napi_property_descriptor properties[]{ + getPropDescriptor( + getNodeApiValue(propertyId_.value), + getNodeApiValue(hostObject->get(*this, propertyId))), + getPropDescriptor(getNodeApiValue(propertyId_.writable), trueValue), + getPropDescriptor( + getNodeApiValue(propertyId_.enumerable), trueValue), + getPropDescriptor( + getNodeApiValue(propertyId_.configurable), trueValue)}; + napi_value descriptor = createNodeApiObject(); + CHECK_NAPI(jsrApi_->napi_define_properties( + env_, descriptor, std::size(properties), properties)); + return descriptor; + }); +} + +// Converts jsi::Bufer to span. +span NodeApiJsiRuntime::toSpan(const jsi::Buffer &buffer) { + return span(buffer.data(), buffer.size()); +} + +// Creates jsi::Value from napi_value. +jsi::Value NodeApiJsiRuntime::toJsiValue(napi_value value) const { + switch (typeOf(value)) { + case napi_valuetype::napi_undefined: + return jsi::Value::undefined(); + case napi_valuetype::napi_null: + return jsi::Value::null(); + case napi_valuetype::napi_boolean: + return jsi::Value{getValueBool(value)}; + case napi_valuetype::napi_number: + return jsi::Value{getValueDouble(value)}; + case napi_valuetype::napi_string: + return jsi::Value{makeJsiPointer(value)}; + case napi_valuetype::napi_symbol: + return jsi::Value{makeJsiPointer(value)}; + case napi_valuetype::napi_object: + case napi_valuetype::napi_function: + case napi_valuetype::napi_external: + return jsi::Value{makeJsiPointer(value)}; +#if JSI_VERSION >= 6 + case napi_valuetype::napi_bigint: + return jsi::Value{makeJsiPointer(value)}; +#endif + default: + throw jsi::JSINativeException("Unexpected value type"); + } +} + +napi_value NodeApiJsiRuntime::getNodeApiValue(const jsi::Value &value) const { + if (value.isUndefined()) { + return getUndefined(); + } else if (value.isNull()) { + return getNull(); + } else if (value.isBool()) { + return getBoolean(value.getBool()); + } else if (value.isNumber()) { + return createDouble(value.getNumber()); + } else if (value.isSymbol()) { + return getNodeApiValue( + value.getSymbol(*const_cast(this))); + } else if (value.isString()) { + return getNodeApiValue( + value.getString(*const_cast(this))); + } else if (value.isObject()) { + return getNodeApiValue( + value.getObject(*const_cast(this))); +#if JSI_VERSION >= 8 + } else if (value.isBigInt()) { + return getNodeApiValue( + value.getBigInt(*const_cast(this))); +#endif + } else { + throw jsi::JSINativeException("Unexpected jsi::Value type"); + } +} + +napi_value NodeApiJsiRuntime::getNodeApiValue(const jsi::Pointer &ptr) const { + return const_cast( + static_cast(getPointerValue(ptr))) + ->getValue(*const_cast(this)); +} + +napi_value NodeApiJsiRuntime::getNodeApiValue( + const NodeApiRefHolder &ref) const { + return ref->getValue(*const_cast(this)); +} + +NodeApiJsiRuntime::NodeApiRefCountedPointerValue * +NodeApiJsiRuntime::cloneNodeApiPointerValue(const PointerValue *pointerValue) { + return static_cast(pointerValue)->clone(*this); +} + +// Adopted from Hermes code. +std::optional NodeApiJsiRuntime::toArrayIndex( + std::string::const_iterator first, + std::string::const_iterator last) { + // Empty string is invalid. + if (first == last) + return std::nullopt; + + // Leading 0 is special. + if (*first == '0') { + ++first; + // Just "0"? + if (first == last) + return 0; + // Leading 0 is invalid otherwise. + return std::nullopt; + } + + uint32_t res = 0; + do { + auto ch = *first; + if (ch < '0' || ch > '9') + return std::nullopt; + uint64_t tmp = (uint64_t)res * 10 + (ch - '0'); + // Check for overflow. + if (tmp & ((uint64_t)0xFFFFFFFFu << 32)) + return std::nullopt; + res = (uint32_t)tmp; + } while (++first != last); + + // 0xFFFFFFFF is not a valid array index. + if (res == 0xFFFFFFFFu) + return std::nullopt; + + return res; +} + +template , int>> +T NodeApiJsiRuntime::makeJsiPointer(napi_value value) const { + return make(NodeApiRefCountedPointerValue::make( + *const_cast(this), + value, + NodeApiPointerValueKind::Object) + .release()); +} + +template , int>> +T NodeApiJsiRuntime::makeJsiPointer(napi_value value) const { + return make(NodeApiRefCountedPointerValue::make( + *const_cast(this), + value, + NodeApiPointerValueKind::String) + .release()); +} + +template , int>> +T NodeApiJsiRuntime::makeJsiPointer(napi_value value) const { + return make(NodeApiRefCountedPointerValue::make( + *const_cast(this), + value, + NodeApiPointerValueKind::Symbol) + .release()); +} + +#if JSI_VERSION >= 6 +template , int>> +T NodeApiJsiRuntime::makeJsiPointer(napi_value value) const { + return make(NodeApiRefCountedPointerValue::make( + *const_cast(this), + value, + NodeApiPointerValueKind::BigInt) + .release()); +} +#endif + +template < + typename TTo, + typename TFrom, + std::enable_if_t, int>, + std::enable_if_t, int>> +TTo NodeApiJsiRuntime::cloneAs(const TFrom &pointer) const { + return make(static_cast( + getPointerValue(pointer)) + ->clone(*const_cast(this))); +} + +NodeApiJsiRuntime::NodeApiRefHolder NodeApiJsiRuntime::makeNodeApiRef( + napi_value value, + NodeApiPointerValueKind pointerKind, + int32_t initialRefCount) { + return NodeApiRefCountedPointerValue::make( + *this, value, pointerKind, initialRefCount); +} + +void NodeApiJsiRuntime::addStackValue(NodeApiStackValuePtr stackPointer) { + stackValues_.push_back(std::move(stackPointer)); +} + +void NodeApiJsiRuntime::pushPointerValueScope() noexcept { + stackScopes_.push_back(stackValues_.size()); +} + +void NodeApiJsiRuntime::popPointerValueScope() noexcept { + CHECK_ELSE_CRASH(!stackScopes_.empty(), "There are no scopes to pop"); + size_t newStackSize = stackScopes_.back(); + auto beginIterator = stackValues_.begin() + newStackSize; + stackScopes_.pop_back(); + std::for_each( + beginIterator, stackValues_.end(), [this](NodeApiStackValuePtr &ptr) { + ptr.release()->deleteStackValue(*this); + }); + stackValues_.resize(newStackSize); + + pendingDeletions_->deletePointerValues(*this); +} + +} // namespace + +std::unique_ptr makeNodeApiJsiRuntime( + napi_env env, + JSRuntimeApi *jsrApi, + std::function onDelete) noexcept { + return std::make_unique(env, jsrApi, std::move(onDelete)); +} + +} // namespace Microsoft::NodeApiJsi + +EXTERN_C_START + +// Default implementation of jsr_get_description if it is not provided by JS +// engine. It returns "NodeApiJsiRuntime" string. +napi_status NAPI_CDECL +default_jsr_get_description(napi_env /*env*/, const char **result) { + if (result != nullptr) { + *result = "NodeApiJsiRuntime"; + } + return napi_ok; +} + +// Default implementation of jsr_queue_microtask if it is not provided by JS +// engine. It does nothing. +napi_status NAPI_CDECL +default_jsr_queue_microtask(napi_env /*env*/, napi_value /*callback*/) { + return napi_generic_failure; +} + +// Default implementation of jsr_drain_microtasks if it is not provided by JS +// engine. It does nothing. +napi_status NAPI_CDECL default_jsr_drain_microtasks( + napi_env /*env*/, + int32_t /*max_count_hint*/, + bool *result) { + if (result != nullptr) { + *result = true; // All tasks are drained + } + return napi_ok; +} + +// Default implementation of jsr_is_inspectable if it is not provided by JS +// engine. It always returns false. +napi_status NAPI_CDECL +default_jsr_is_inspectable(napi_env /*env*/, bool *result) { + if (result != nullptr) { + *result = false; + } + return napi_ok; +} + +// Default implementation of jsr_open_napi_env_scope if it is not provided by JS +// engine. +napi_status NAPI_CDECL default_jsr_open_napi_env_scope( + napi_env /*env*/, + jsr_napi_env_scope * /*scope*/) { + return napi_ok; +} + +// Default implementation of jsr_close_napi_env_scope if it is not provided by +// JS engine. +napi_status NAPI_CDECL default_jsr_close_napi_env_scope( + napi_env /*env*/, + jsr_napi_env_scope /*scope*/) { + return napi_ok; +} + +// TODO: Ensure that we either load all three functions or use their default +// versions and never mix and match. + +// Default implementation of jsr_create_prepared_script if it is not provided by +// JS engine. It return napi_ref as a jsr_prepared_script that wraps up an +// object with a "script" property string. +napi_status NAPI_CDECL default_jsr_create_prepared_script( + napi_env env, + const uint8_t *script_data, + size_t script_length, + jsr_data_delete_cb script_delete_cb, + void *deleter_data, + const char * /*source_url*/, + jsr_prepared_script *result) { + Microsoft::NodeApiJsi::JSRuntimeApi *jsrApi = + Microsoft::NodeApiJsi::JSRuntimeApi::current(); + napi_value script{}, obj{}; + // Do not use NAPI_CALL - we must finalize the buffer right after we attempted + // the string creation. + napi_status status = jsrApi->napi_create_string_utf8( + env, reinterpret_cast(script_data), script_length, &script); + if (script_delete_cb != nullptr) { + script_delete_cb(const_cast(script_data), deleter_data); + } + NAPI_CALL(status); + NAPI_CALL(jsrApi->napi_create_object(env, &obj)); + NAPI_CALL(jsrApi->napi_set_named_property(env, obj, "script", script)); + return jsrApi->napi_create_reference( + env, obj, 1, reinterpret_cast(result)); +} + +// Default implementation of jsr_delete_prepared_script if it is not provided by +// JS engine. It deletes prepared_script as a napi_ref. +napi_status NAPI_CDECL default_jsr_delete_prepared_script( + napi_env env, + jsr_prepared_script prepared_script) { + Microsoft::NodeApiJsi::JSRuntimeApi *jsrApi = + Microsoft::NodeApiJsi::JSRuntimeApi::current(); + return jsrApi->napi_delete_reference( + env, reinterpret_cast(prepared_script)); +} + +// Default implementation of jsr_prepared_script_run if it is not provided by JS +// engine. It interprets prepared_script as a napi_ref to an object with a +// "script" property string. +napi_status NAPI_CDECL default_jsr_prepared_script_run( + napi_env env, + jsr_prepared_script prepared_script, + napi_value *result) { + Microsoft::NodeApiJsi::JSRuntimeApi *jsrApi = + Microsoft::NodeApiJsi::JSRuntimeApi::current(); + napi_value obj{}, script{}; + NAPI_CALL(jsrApi->napi_get_reference_value( + env, reinterpret_cast(prepared_script), &obj)); + NAPI_CALL(jsrApi->napi_get_named_property(env, obj, "script", &script)); + return jsrApi->napi_run_script(env, script, result); +} + +EXTERN_C_END diff --git a/vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.h b/vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.h new file mode 100644 index 00000000000..139e386316b --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/NodeApiJsiRuntime.h @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#ifndef NODEAPIJSIRUNTIME_H_ +#define NODEAPIJSIRUNTIME_H_ + +#include +#include +#include +#include "ApiLoaders/JSRuntimeApi.h" + +namespace Microsoft::NodeApiJsi { + +std::unique_ptr makeNodeApiJsiRuntime( + napi_env env, + JSRuntimeApi *jsrApi, + std::function onDelete) noexcept; + +struct NodeApiEnvScope { + NodeApiEnvScope(napi_env env) : env_(env) { + JSRuntimeApi::current()->jsr_open_napi_env_scope(env, &scope_); + } + + NodeApiEnvScope(const NodeApiEnvScope &) = delete; + NodeApiEnvScope &operator=(const NodeApiEnvScope &) = delete; + + ~NodeApiEnvScope() { + JSRuntimeApi::current()->jsr_close_napi_env_scope(env_, scope_); + } + + private: + napi_env env_{}; + jsr_napi_env_scope scope_{}; +}; +} // namespace Microsoft::NodeApiJsi + +#endif // !NODEAPIJSIRUNTIME_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/CallInvoker.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/CallInvoker.h new file mode 100644 index 00000000000..452493c27e3 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/CallInvoker.h @@ -0,0 +1,61 @@ +/* + * 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 "SchedulerPriority.h" + +#include +#include + +namespace facebook::jsi { +class Runtime; +} + +namespace facebook::react { + +using CallFunc = std::function; + +/** + * An interface for a generic native-to-JS call invoker. See BridgeJSCallInvoker + * for an implementation. + */ +class CallInvoker { + public: + virtual void invokeAsync(CallFunc &&func) noexcept = 0; + virtual void invokeAsync(SchedulerPriority /*priority*/, CallFunc &&func) noexcept + { + // When call with priority is not implemented, fall back to a regular async + // execution + invokeAsync(std::move(func)); + } + virtual void invokeSync(CallFunc &&func) = 0; + + // Backward compatibility only, prefer the CallFunc methods instead + virtual void invokeAsync(std::function &&func) noexcept + { + invokeAsync([func = std::move(func)](jsi::Runtime &) { func(); }); + } + + virtual void invokeSync(std::function &&func) + { + invokeSync([func = std::move(func)](jsi::Runtime &) { func(); }); + } + + virtual ~CallInvoker() = default; +}; + +using NativeMethodCallFunc = std::function; + +class NativeMethodCallInvoker { + public: + virtual void invokeAsync(const std::string &methodName, NativeMethodCallFunc &&func) noexcept = 0; + virtual void invokeSync(const std::string &methodName, NativeMethodCallFunc &&func) = 0; + virtual ~NativeMethodCallInvoker() = default; +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/SchedulerPriority.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/SchedulerPriority.h new file mode 100644 index 00000000000..2027d95c766 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/SchedulerPriority.h @@ -0,0 +1,20 @@ +/* + * 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 + +namespace facebook::react { + +enum class SchedulerPriority : int { + ImmediatePriority = 1, + UserBlockingPriority = 2, + NormalPriority = 3, + LowPriority = 4, + IdlePriority = 5, +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.cpp b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.cpp new file mode 100644 index 00000000000..193773b6441 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.cpp @@ -0,0 +1,64 @@ +/* + * 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 "TurboModule.h" +#include + +namespace facebook::react { + +TurboModuleMethodValueKind getTurboModuleMethodValueKind( + jsi::Runtime& rt, + const jsi::Value* value) { + if ((value == nullptr) || value->isUndefined() || value->isNull()) { + return VoidKind; + } else if (value->isBool()) { + return BooleanKind; + } else if (value->isNumber()) { + return NumberKind; + } else if (value->isString()) { + return StringKind; + } else if (value->isObject()) { + auto object = value->asObject(rt); + if (object.isArray(rt)) { + return ArrayKind; + } else if (object.isFunction(rt)) { + return FunctionKind; + } + return ObjectKind; + } + react_native_assert(false && "Unsupported jsi::Value kind"); + return VoidKind; +} + +TurboModule::TurboModule( + std::string name, + std::shared_ptr jsInvoker) + : name_(std::move(name)), jsInvoker_(std::move(jsInvoker)) {} + +void TurboModule::emitDeviceEvent( + const std::string& eventName, + ArgFactory&& argFactory) { + jsInvoker_->invokeAsync([eventName, argFactory = std::move(argFactory)]( + jsi::Runtime& rt) { + jsi::Value emitter = rt.global().getProperty(rt, "__rctDeviceEventEmitter"); + if (!emitter.isUndefined()) { + jsi::Object emitterObject = emitter.asObject(rt); + // TODO: consider caching these + jsi::Function emitFunction = + emitterObject.getPropertyAsFunction(rt, "emit"); + std::vector args; + args.emplace_back(jsi::String::createFromAscii(rt, eventName.c_str())); + if (argFactory) { + argFactory(rt, args); + } + emitFunction.callWithThis( + rt, emitterObject, (const jsi::Value*)args.data(), args.size()); + } + }); +} + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.h new file mode 100644 index 00000000000..2256c0cbfe6 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.h @@ -0,0 +1,153 @@ +/* + * 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 +#include +#include + +#include + +#include +#include + +namespace facebook::react { + +/** + * For now, support the same set of return types as existing impl. + * This can be improved to support richer typed objects. + */ +enum TurboModuleMethodValueKind { + VoidKind, + BooleanKind, + NumberKind, + StringKind, + ObjectKind, + ArrayKind, + FunctionKind, + PromiseKind, +}; + +/** + * Determines TurboModuleMethodValueKind based on the jsi::Value type. + */ +TurboModuleMethodValueKind getTurboModuleMethodValueKind(jsi::Runtime &rt, const jsi::Value *value); + +class TurboCxxModule; +class TurboModuleBinding; + +/** + * Base HostObject class for every module to be exposed to JS + */ +class JSI_EXPORT TurboModule : public jsi::HostObject { + public: + TurboModule(std::string name, std::shared_ptr jsInvoker); + + // DO NOT OVERRIDE - it will become final in a future release. + // This method provides automatic caching of properties on the TurboModule's + // JS representation. To customize lookup of properties, override `create`. + // Note: keep this method declared inline to avoid conflicts + // between RTTI and non-RTTI compilation units + jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &propName) override + { + auto prop = create(runtime, propName); + // If we have a JS wrapper, cache the result of this lookup + // We don't cache misses, to allow for methodMap_ to dynamically be + // extended + // [Windows] Reenable once https://github.com/microsoft/react-native-windows/issues/14128 is fixed +#ifndef WINAPI_FAMILY + if (jsRepresentation_ && !prop.isUndefined()) { + jsRepresentation_->lock(runtime).asObject(runtime).setProperty(runtime, propName, prop); + } +#endif + return prop; + } + + std::vector getPropertyNames(jsi::Runtime &runtime) override + { + std::vector result; + result.reserve(methodMap_.size()); + for (auto it = methodMap_.cbegin(); it != methodMap_.cend(); ++it) { + result.push_back(jsi::PropNameID::forUtf8(runtime, it->first)); + } + return result; + } + + protected: + const std::string name_; + std::shared_ptr jsInvoker_; + + struct MethodMetadata { + size_t argCount; + jsi::Value (*invoker)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count); + }; + std::unordered_map methodMap_; + + friend class TurboModuleTestFixtureInternal; + std::unordered_map> eventEmitterMap_; + + using ArgFactory = std::function &args)>; + + /** + * Calls RCTDeviceEventEmitter.emit to JavaScript, with given event name and + * an optional list of arguments. + * If present, argFactory is a callback used to construct extra arguments, + * e.g. + * + * emitDeviceEvent(rt, "myCustomEvent", + * [](jsi::Runtime& rt, std::vector& args) { + * args.emplace_back(jsi::Value(true)); + * args.emplace_back(jsi::Value(42)); + * }); + */ + void emitDeviceEvent(const std::string &eventName, ArgFactory &&argFactory = nullptr); + + // Backwards compatibility version + void emitDeviceEvent( + jsi::Runtime & /*runtime*/, + + const std::string &eventName, + ArgFactory &&argFactory = nullptr) + { + emitDeviceEvent(eventName, std::move(argFactory)); + } + + virtual jsi::Value create(jsi::Runtime &runtime, const jsi::PropNameID &propName) + { + std::string propNameUtf8 = propName.utf8(runtime); + if (auto methodIter = methodMap_.find(propNameUtf8); methodIter != methodMap_.end()) { + const MethodMetadata &meta = methodIter->second; + return jsi::Function::createFromHostFunction( + runtime, + propName, + static_cast(meta.argCount), + [this, meta]( + jsi::Runtime &rt, [[maybe_unused]] const jsi::Value &thisVal, const jsi::Value *args, size_t count) { + return meta.invoker(rt, *this, args, count); + }); + } else if (auto eventEmitterIter = eventEmitterMap_.find(propNameUtf8); + eventEmitterIter != eventEmitterMap_.end()) { + return eventEmitterIter->second->get(runtime, jsInvoker_); + } else { + // Neither Method nor EventEmitter were found, let JS decide what to do + return jsi::Value::undefined(); + } + } + + private: + friend class TurboModuleBinding; + std::unique_ptr jsRepresentation_; +}; + +/** + * An app/platform-specific provider function to get an instance of a module + * given a name. + */ +using TurboModuleProviderFunctionType = std::function(const std::string &name)>; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.cpp b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.cpp new file mode 100644 index 00000000000..b881694f96a --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.cpp @@ -0,0 +1,52 @@ +/* + * 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 "TurboModuleUtils.h" + +namespace facebook::react { + +Promise::Promise(jsi::Runtime& rt, jsi::Function resolve, jsi::Function reject) + : LongLivedObject(rt), + resolve_(std::move(resolve)), + reject_(std::move(reject)) {} + +void Promise::resolve(const jsi::Value& result) { + resolve_.call(runtime_, result); +} + +void Promise::reject(const std::string& message) { + jsi::Object error(runtime_); + error.setProperty( + runtime_, "message", jsi::String::createFromUtf8(runtime_, message)); + reject_.call(runtime_, error); +} + +jsi::Value createPromiseAsJSIValue( + jsi::Runtime& rt, + PromiseSetupFunctionType&& func) { + jsi::Function JSPromise = rt.global().getPropertyAsFunction(rt, "Promise"); + jsi::Function fn = jsi::Function::createFromHostFunction( + rt, + jsi::PropNameID::forAscii(rt, "fn"), + 2, + [func = std::move(func)]( + jsi::Runtime& rt2, + const jsi::Value& /*thisVal*/, + const jsi::Value* args, + size_t /*count*/) { + jsi::Function resolve = args[0].getObject(rt2).getFunction(rt2); + jsi::Function reject = args[1].getObject(rt2).getFunction(rt2); + auto wrapper = std::make_shared( + rt2, std::move(resolve), std::move(reject)); + func(rt2, wrapper); + return jsi::Value::undefined(); + }); + + return JSPromise.callAsConstructor(rt, fn); +} + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.h new file mode 100644 index 00000000000..0c436aa13d7 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.h @@ -0,0 +1,31 @@ +/* + * 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 +#include +#include +#include +#include + +namespace facebook::react { + +struct Promise : public LongLivedObject { + Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject); + + void resolve(const jsi::Value &result); + void reject(const std::string &message); + + jsi::Function resolve_; + jsi::Function reject_; +}; + +using PromiseSetupFunctionType = std::function)>; +jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, PromiseSetupFunctionType &&func); + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/AString.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/AString.h new file mode 100644 index 00000000000..b5d48354ef2 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/AString.h @@ -0,0 +1,44 @@ +/* + * 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 + +#include +#include + +namespace facebook::react { + +template <> +struct Bridging { + static std::string fromJs(jsi::Runtime &rt, const jsi::String &value) + { + return value.utf8(rt); + } + + static jsi::String toJs(jsi::Runtime &rt, const std::string &value) + { + return jsi::String::createFromUtf8(rt, value); + } +}; + +template <> +struct Bridging { + static jsi::String toJs(jsi::Runtime &rt, std::string_view value) + { + return jsi::String::createFromUtf8(rt, reinterpret_cast(value.data()), value.length()); + } +}; + +template <> +struct Bridging : Bridging {}; + +template +struct Bridging : Bridging {}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Array.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Array.h new file mode 100644 index 00000000000..89108201ce0 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Array.h @@ -0,0 +1,134 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +namespace array_detail { + +template +struct BridgingStatic { + static jsi::Array toJs(jsi::Runtime &rt, const T &array, const std::shared_ptr &jsInvoker) + { + return toJs(rt, array, jsInvoker, std::make_index_sequence{}); + } + + private: + template + static jsi::Array toJs( + facebook::jsi::Runtime &rt, + const T &array, + const std::shared_ptr &jsInvoker, + std::index_sequence /*unused*/) + { + return jsi::Array::createWithElements(rt, bridging::toJs(rt, std::get(array), jsInvoker)...); + } +}; + +template +struct BridgingDynamic { + static jsi::Array toJs(jsi::Runtime &rt, const T &list, const std::shared_ptr &jsInvoker) + { + jsi::Array result(rt, list.size()); + size_t index = 0; + + for (const auto &item : list) { + result.setValueAtIndex(rt, index++, bridging::toJs(rt, item, jsInvoker)); + } + + return result; + } +}; + +} // namespace array_detail + +template +struct Bridging> : array_detail::BridgingStatic, N> { + static std::array + fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr &jsInvoker) + { + size_t length = array.length(rt); + + std::array result; + for (size_t i = 0; i < length; i++) { + result[i] = bridging::fromJs(rt, array.getValueAtIndex(rt, i), jsInvoker); + } + + return result; + } +}; + +template +struct Bridging> : array_detail::BridgingStatic, 2> { + static std::pair + fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr &jsInvoker) + { + return std::make_pair( + bridging::fromJs(rt, array.getValueAtIndex(rt, 0), jsInvoker), + bridging::fromJs(rt, array.getValueAtIndex(rt, 1), jsInvoker)); + } +}; + +template +struct Bridging> : array_detail::BridgingStatic, sizeof...(Types)> {}; + +template +struct Bridging> : array_detail::BridgingDynamic> {}; + +template +struct Bridging> : array_detail::BridgingDynamic> {}; + +template +struct Bridging> : array_detail::BridgingDynamic> {}; + +template +struct Bridging> : array_detail::BridgingDynamic> { + static std::vector + fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr &jsInvoker) + { + size_t length = array.length(rt); + + std::vector vector; + vector.reserve(length); + + for (size_t i = 0; i < length; i++) { + vector.push_back(bridging::fromJs(rt, array.getValueAtIndex(rt, i), jsInvoker)); + } + + return vector; + } +}; + +template +struct Bridging> : array_detail::BridgingDynamic> { + static std::set + fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr &jsInvoker) + { + size_t length = array.length(rt); + + std::set set; + for (size_t i = 0; i < length; i++) { + set.insert(bridging::fromJs(rt, array.getValueAtIndex(rt, i), jsInvoker)); + } + + return set; + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Base.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Base.h new file mode 100644 index 00000000000..fcae10f42de --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Base.h @@ -0,0 +1,139 @@ +/* + * 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 +#include + +#include +#include +#include + +namespace facebook::react { + +class CallInvoker; + +template +struct Bridging; + +template <> +struct Bridging { + // Highly generic code may result in "casting" to void. + static void fromJs(jsi::Runtime & /*unused*/, const jsi::Value & /*unused*/) {} +}; + +namespace bridging { +namespace detail { + +template +struct function_wrapper; + +template +struct function_wrapper { + using type = std::function; +}; + +template +struct function_wrapper { + using type = std::function; +}; + +template +struct bridging_wrapper { + using type = remove_cvref_t; +}; + +// Convert lambda types to move-only function types since we can't specialize +// Bridging templates for arbitrary lambdas. +template +struct bridging_wrapper::operator())>> + : function_wrapper::operator())> {}; + +} // namespace detail + +template +using bridging_t = typename detail::bridging_wrapper::type; + +template + requires is_jsi_v +auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr & /*unused*/) + -> decltype(static_cast(std::move(convert(rt, std::forward(value))))) + +{ + return static_cast(std::move(convert(rt, std::forward(value)))); +} + +template +auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr & /*unused*/) + -> decltype(Bridging>::fromJs(rt, convert(rt, std::forward(value)))) +{ + return Bridging>::fromJs(rt, convert(rt, std::forward(value))); +} + +template +auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr &jsInvoker) + -> decltype(Bridging>::fromJs(rt, convert(rt, std::forward(value)), jsInvoker)) +{ + return Bridging>::fromJs(rt, convert(rt, std::forward(value)), jsInvoker); +} + +template + requires is_jsi_v +auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr & /*unused*/ = nullptr) -> remove_cvref_t +{ + return convert(rt, std::forward(value)); +} + +template +auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr & /*unused*/ = nullptr) + -> decltype(Bridging>::toJs(rt, std::forward(value))) +{ + return Bridging>::toJs(rt, std::forward(value)); +} + +template +auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr &jsInvoker) + -> decltype(Bridging>::toJs(rt, std::forward(value), jsInvoker)) +{ + return Bridging>::toJs(rt, std::forward(value), jsInvoker); +} + +template +inline constexpr bool supportsFromJs = false; + +template +inline constexpr bool supportsFromJs< + T, + Arg, + std::void_t(std::declval(), std::declval(), nullptr))>> = true; + +template +inline constexpr bool supportsFromJs< + T, + jsi::Value, + std::void_t(std::declval(), std::declval(), nullptr))>> = true; + +template +inline constexpr bool supportsToJs = false; + +template +inline constexpr bool supportsToJs< + JSReturnT, + ReturnT, + std::void_t(), std::declval(), nullptr))>> = + std::is_convertible_v(), std::declval(), nullptr)), ReturnT>; + +template +inline constexpr bool supportsToJs< + ReturnT, + jsi::Value, + std::void_t(), std::declval(), nullptr))>> = + std::is_convertible_v(), std::declval(), nullptr)), jsi::Value>; + +} // namespace bridging +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bool.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bool.h new file mode 100644 index 00000000000..10bb7500abd --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bool.h @@ -0,0 +1,27 @@ +/* + * 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 + +namespace facebook::react { + +template <> +struct Bridging { + static bool fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value) + { + return value.asBool(); + } + + static bool toJs(jsi::Runtime & /*unused*/, bool value) + { + return value; + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bridging.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bridging.h new file mode 100644 index 00000000000..87ae9cb4f4b --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bridging.h @@ -0,0 +1,22 @@ +/* + * 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 +#include +#include +#include +// #include // [Windows] Line causes Error C1083 Cannot open include file: 'double-conversion/double-conversion.h' #11644 +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/CallbackWrapper.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/CallbackWrapper.h new file mode 100644 index 00000000000..a6c5083ef26 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/CallbackWrapper.h @@ -0,0 +1,68 @@ +/* + * 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 + +#include + +#include + +namespace facebook::react { + +class CallInvoker; + +// Helper for passing jsi::Function arg to other methods. +class CallbackWrapper : public LongLivedObject { + private: + CallbackWrapper(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr jsInvoker) + : LongLivedObject(runtime), callback_(std::move(callback)), jsInvoker_(std::move(jsInvoker)) + { + } + + jsi::Function callback_; + std::shared_ptr jsInvoker_; + + public: + static std::weak_ptr + createWeak(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr jsInvoker) + { + auto wrapper = + std::shared_ptr(new CallbackWrapper(std::move(callback), runtime, std::move(jsInvoker))); + LongLivedObjectCollection::get(runtime).add(wrapper); + return wrapper; + } + + // Delete the enclosed jsi::Function + void destroy() + { + allowRelease(); + } + + jsi::Function &callback() noexcept + { + return callback_; + } + + jsi::Runtime &runtime() noexcept + { + return runtime_; + } + + CallInvoker &jsInvoker() noexcept + { + return *(jsInvoker_); + } + + std::shared_ptr jsInvokerPtr() noexcept + { + return jsInvoker_; + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Class.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Class.h new file mode 100644 index 00000000000..16bc0cf0aba --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Class.h @@ -0,0 +1,75 @@ +/* + * 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 + +namespace facebook::react::bridging { + +template +JSReturnT callFromJs( + jsi::Runtime &rt, + ReturnT (ClassT::*method)(jsi::Runtime &, ArgsT...), + const std::shared_ptr &jsInvoker, + ClassT *instance, + JSArgsT &&...args) +{ + static_assert(sizeof...(ArgsT) == sizeof...(JSArgsT), "Incorrect arguments length"); + static_assert((supportsFromJs && ...), "Incompatible arguments"); + if constexpr (std::is_void_v) { + static_assert(std::is_void_v, "Method must return void when JSReturnT is void"); + } + + if constexpr (std::is_void_v) { + (instance->*method)(rt, fromJs(rt, std::forward(args), jsInvoker)...); + + } else if constexpr (std::is_void_v) { + static_assert(std::is_same_v, "Void functions may only return undefined"); + + (instance->*method)(rt, fromJs(rt, std::forward(args), jsInvoker)...); + return jsi::Value(); + + } else if constexpr (is_jsi_v || supportsToJs) { + static_assert(supportsToJs, "Incompatible return type"); + + return toJs(rt, (instance->*method)(rt, fromJs(rt, std::forward(args), jsInvoker)...), jsInvoker); + } else if constexpr (is_optional_jsi_v) { + static_assert( + is_optional_v ? supportsToJs + : supportsToJs, + "Incompatible return type"); + + auto result = + toJs(rt, (instance->*method)(rt, fromJs(rt, std::forward(args), jsInvoker)...), jsInvoker); + + if constexpr (std::is_same_v) { + if (result.isNull() || result.isUndefined()) { + return std::nullopt; + } + } + + return convert(rt, std::move(result)); + } else { + static_assert(std::is_convertible_v, "Incompatible return type"); + return (instance->*method)(rt, fromJs(rt, std::forward(args), jsInvoker)...); + } +} + +template +constexpr size_t getParameterCount(ReturnT (* /*unused*/)(ArgsT...)) +{ + return sizeof...(ArgsT); +} + +template +constexpr size_t getParameterCount(ReturnT (Class::* /*unused*/)(ArgsT...)) +{ + return sizeof...(ArgsT); +} + +} // namespace facebook::react::bridging diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Convert.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Convert.h new file mode 100644 index 00000000000..51622c555ca --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Convert.h @@ -0,0 +1,177 @@ +/* + * 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 + +#include +#include + +namespace facebook::react::bridging { + +// std::remove_cvref_t is not available until C++20. +template +using remove_cvref_t = std::remove_cv_t>; + +template +inline constexpr bool is_jsi_v = std::is_same_v> || + std::is_same_v> || std::is_base_of_v>; + +template +struct is_optional : std::false_type {}; + +template +struct is_optional> : std::true_type {}; + +template +inline constexpr bool is_optional_v = is_optional::value; + +template +inline constexpr bool is_optional_jsi_v = false; + +template +inline constexpr bool is_optional_jsi_v>> = + is_jsi_v; + +template +struct Converter; + +template +struct ConverterBase { + using BaseT = remove_cvref_t; + + ConverterBase(jsi::Runtime &rt, T &&value) : rt_(rt), value_(std::forward(value)) {} + + operator BaseT() && + { + if constexpr (std::is_lvalue_reference_v) { + // Copy the reference into a Value that then can be moved from. + auto value = jsi::Value(rt_, value_); + + if constexpr (std::is_same_v) { + return std::move(value); + } else if constexpr (std::is_same_v) { + return std::move(value).getString(rt_); + } else if constexpr (std::is_same_v) { + return std::move(value).getObject(rt_); + } else if constexpr (std::is_same_v) { + return std::move(value).getObject(rt_).getArray(rt_); + } else if constexpr (std::is_same_v) { + return std::move(value).getObject(rt_).getFunction(rt_); + } + } else { + return std::move(value_); + } + } + + template < + typename U, + std::enable_if_t< + std::is_lvalue_reference_v && + // Ensure non-reference type can be converted to the desired type. + std::is_convertible_v, U>, + int> = 0> + operator U() && + { + return Converter(rt_, std::move(*this).operator BaseT()); + } + + template && std::is_same_v, int> = 0> + operator U() && = delete; // Prevent unwanted upcasting of JSI values. + + protected: + jsi::Runtime &rt_; + T value_; +}; + +template +struct Converter : public ConverterBase { + using ConverterBase::ConverterBase; +}; + +template <> +struct Converter : public ConverterBase { + using ConverterBase::ConverterBase; + + operator jsi::String() && + { + return std::move(value_).asString(rt_); + } + + operator jsi::Object() && + { + return std::move(value_).asObject(rt_); + } + + operator jsi::Array() && + { + return std::move(value_).asObject(rt_).asArray(rt_); + } + + operator jsi::Function() && + { + return std::move(value_).asObject(rt_).asFunction(rt_); + } +}; + +template <> +struct Converter : public ConverterBase { + using ConverterBase::ConverterBase; + + operator jsi::Array() && + { + return std::move(value_).asArray(rt_); + } + + operator jsi::Function() && + { + return std::move(value_).asFunction(rt_); + } +}; + +template +struct Converter> : public ConverterBase { + Converter(jsi::Runtime &rt, std::optional value) + : ConverterBase(rt, value ? std::move(*value) : jsi::Value::null()) + { + } + + operator std::optional() && + { + if (value_.isNull() || value_.isUndefined()) { + return {}; + } + return std::move(value_); + } +}; + +template , int> = 0> +auto convert(jsi::Runtime &rt, T &&value) +{ + return Converter(rt, std::forward(value)); +} + +template || std::is_scalar_v, int> = 0> +auto convert(jsi::Runtime &rt, std::optional value) +{ + return Converter>(rt, std::move(value)); +} + +template , int> = 0> +auto convert(jsi::Runtime & /*rt*/, T &&value) +{ + return value; +} + +template +auto convert(jsi::Runtime & /*rt*/, Converter &&converter) +{ + return std::move(converter); +} + +} // namespace facebook::react::bridging diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Error.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Error.h new file mode 100644 index 00000000000..621fd0bfb08 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Error.h @@ -0,0 +1,56 @@ +/* + * 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 + +namespace facebook::react { + +class Error { + public: + // TODO (T114055466): Retain stack trace (at least caller location) + Error(std::string message) : message_(std::move(message)) {} + + Error(const char *message) : Error(std::string(message)) {} + + const std::string &message() const + { + return message_; + } + + private: + std::string message_; +}; + +template <> +struct Bridging { + static jsi::JSError fromJs(jsi::Runtime &rt, const jsi::Value &value) + { + return jsi::JSError(rt, jsi::Value(rt, value)); + } + + static jsi::JSError fromJs(jsi::Runtime &rt, jsi::Value &&value) + { + return jsi::JSError(rt, std::move(value)); + } + + static jsi::Value toJs(jsi::Runtime &rt, std::string message) + { + return jsi::Value(rt, jsi::JSError(rt, std::move(message)).value()); + } +}; + +template <> +struct Bridging { + static jsi::Value toJs(jsi::Runtime &rt, const Error &error) + { + return jsi::Value(rt, jsi::JSError(rt, error.message()).value()); + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/EventEmitter.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/EventEmitter.h new file mode 100644 index 00000000000..3a76abcc36c --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/EventEmitter.h @@ -0,0 +1,136 @@ +/* + * 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 +#include +#include +#include +#include + +#define FRIEND_TEST(test_case_name, test_name) friend class test_case_name##_##test_name##_Test + +namespace facebook::react { + +class EventSubscription { + public: + explicit EventSubscription(std::function remove) : remove_(std::move(remove)) {} + ~EventSubscription() = default; + EventSubscription(EventSubscription &&) noexcept = default; + EventSubscription &operator=(EventSubscription &&) noexcept = default; + EventSubscription(const EventSubscription &) = delete; + EventSubscription &operator=(const EventSubscription &) = delete; + + void remove() + { + remove_(); + } + + private: + friend Bridging; + std::function remove_; +}; + +template <> +struct Bridging { + static EventSubscription + fromJs(jsi::Runtime &rt, const jsi::Object &value, const std::shared_ptr &jsInvoker) + { + auto listener = bridging::fromJs>(rt, value.getProperty(rt, "remove"), jsInvoker); + return EventSubscription([listener = std::move(listener)]() mutable { listener(); }); + } + + static jsi::Object + toJs(jsi::Runtime &rt, const EventSubscription &eventSubscription, const std::shared_ptr &jsInvoker) + { + auto result = jsi::Object(rt); + result.setProperty(rt, "remove", bridging::toJs(rt, eventSubscription.remove_, jsInvoker)); + return result; + } +}; + +class IAsyncEventEmitter { + public: + IAsyncEventEmitter() noexcept = default; + virtual ~IAsyncEventEmitter() noexcept = default; + IAsyncEventEmitter(IAsyncEventEmitter &&) noexcept = default; + IAsyncEventEmitter &operator=(IAsyncEventEmitter &&) noexcept = default; + IAsyncEventEmitter(const IAsyncEventEmitter &) = delete; + IAsyncEventEmitter &operator=(const IAsyncEventEmitter &) = delete; + + virtual jsi::Object get(jsi::Runtime &rt, const std::shared_ptr &jsInvoker) const = 0; +}; + +template +class AsyncEventEmitter : public IAsyncEventEmitter { + static_assert(sizeof...(Args) <= 1, "AsyncEventEmitter must have at most one argument"); + + public: + AsyncEventEmitter() : state_(std::make_shared()) + { + listen_ = [state = state_](AsyncCallback listener) { + std::lock_guard lock(state->mutex); + auto listenerId = state->listenerId++; + state->listeners.emplace(listenerId, std::move(listener)); + return EventSubscription([state, listenerId]() { + std::lock_guard innerLock(state->mutex); + state->listeners.erase(listenerId); + }); + }; + } + ~AsyncEventEmitter() override = default; + AsyncEventEmitter(AsyncEventEmitter &&) noexcept = default; + AsyncEventEmitter &operator=(AsyncEventEmitter &&) noexcept = default; + AsyncEventEmitter(const AsyncEventEmitter &) = delete; + AsyncEventEmitter &operator=(const AsyncEventEmitter &) = delete; + + void emit(std::function &&converter) + { + std::lock_guard lock(state_->mutex); + for (auto &[_, listener] : state_->listeners) { + listener.call([converter](jsi::Runtime &rt, jsi::Function &jsFunction) { jsFunction.call(rt, converter(rt)); }); + } + } + + void emit(Args... value) + { + std::lock_guard lock(state_->mutex); + for (const auto &[_, listener] : state_->listeners) { + listener.call(static_cast(value)...); + } + } + + jsi::Object get(jsi::Runtime &rt, const std::shared_ptr &jsInvoker) const override + { + return bridging::toJs(rt, listen_, jsInvoker); + } + + private: + friend Bridging; + FRIEND_TEST(BridgingTest, eventEmitterTest); + + struct SharedState { + std::mutex mutex; + std::unordered_map> listeners; + size_t listenerId{}; + }; + + std::function)> listen_; + std::shared_ptr state_; +}; + +template +struct Bridging> { + static jsi::Object + toJs(jsi::Runtime &rt, const AsyncEventEmitter &eventEmitter, const std::shared_ptr &jsInvoker) + { + return eventEmitter.get(rt, jsInvoker); + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Function.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Function.h new file mode 100644 index 00000000000..eb5d08749b5 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Function.h @@ -0,0 +1,263 @@ +/* + * 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 +#include + +#include +#include + +namespace facebook::react { + +template +class SyncCallback; + +template +class AsyncCallback { + public: + AsyncCallback(jsi::Runtime &runtime, jsi::Function function, std::shared_ptr jsInvoker) + : callback_(std::make_shared>(runtime, std::move(function), std::move(jsInvoker))) + { + } + + void operator()(Args... args) const noexcept + { + call(std::forward(args)...); + } + + void call(Args... args) const noexcept + { + callWithArgs(std::nullopt, std::forward(args)...); + } + + void callWithPriority(SchedulerPriority priority, Args... args) const noexcept + { + callWithArgs(priority, std::forward(args)...); + } + + void call(std::function &&callImpl) const noexcept + { + callWithFunction(std::nullopt, std::move(callImpl)); + } + + void callWithPriority(SchedulerPriority priority, std::function &&callImpl) + const noexcept + { + callWithFunction(priority, std::move(callImpl)); + } + + private: + friend Bridging; + + std::shared_ptr> callback_; + + void callWithArgs(std::optional priority, Args... args) const noexcept + { + if (auto wrapper = callback_->wrapper_.lock()) { + auto fn = [callback = callback_, + argsPtr = std::make_shared>(std::make_tuple(std::forward(args)...))]( + jsi::Runtime &) { callback->apply(std::move(*argsPtr)); }; + + auto &jsInvoker = wrapper->jsInvoker(); + if (priority) { + jsInvoker.invokeAsync(*priority, std::move(fn)); + } else { + jsInvoker.invokeAsync(std::move(fn)); + } + } + } + + void callWithFunction( + std::optional priority, + std::function &&callImpl) const noexcept + { + if (auto wrapper = callback_->wrapper_.lock()) { + // Capture callback_ and not wrapper_. If callback_ is deallocated or the + // JSVM is shutdown before the async task is scheduled, the underlying + // function will have been deallocated. + auto fn = [callback = callback_, callImpl = std::move(callImpl)](jsi::Runtime &rt) { + if (auto wrapper2 = callback->wrapper_.lock()) { + callImpl(rt, wrapper2->callback()); + } + }; + + auto &jsInvoker = wrapper->jsInvoker(); + if (priority) { + jsInvoker.invokeAsync(*priority, std::move(fn)); + } else { + jsInvoker.invokeAsync(std::move(fn)); + } + } + } +}; + +// You must ensure that when invoking this you're located on the JS thread, or +// have exclusive control of the JS VM context. If you cannot ensure this, use +// AsyncCallback instead. +template +class SyncCallback { + public: + SyncCallback(jsi::Runtime &rt, jsi::Function function, std::shared_ptr jsInvoker) + : wrapper_(CallbackWrapper::createWeak(std::move(function), rt, std::move(jsInvoker))) + { + } + + // Disallow copying, as we can no longer safely destroy the callback + // from the destructor if there's multiple copies + SyncCallback(const SyncCallback &) = delete; + SyncCallback &operator=(const SyncCallback &) = delete; + + // Allow move + SyncCallback(SyncCallback &&other) noexcept : wrapper_(std::move(other.wrapper_)) {} + + SyncCallback &operator=(SyncCallback &&other) noexcept + { + wrapper_ = std::move(other.wrapper_); + return *this; + } + + ~SyncCallback() + { + if (auto wrapper = wrapper_.lock()) { + wrapper->destroy(); + } + } + + R operator()(Args... args) const + { + return call(std::forward(args)...); + } + + R call(Args... args) const + { + auto wrapper = wrapper_.lock(); + + // If the wrapper has been deallocated, we can no longer provide a return + // value consistently, so our only option is to throw + if (!wrapper) { + if constexpr (std::is_void_v) { + return; + } else { + throw std::runtime_error("Failed to call invalidated sync callback"); + } + } + + auto &callback = wrapper->callback(); + auto &rt = wrapper->runtime(); + auto jsInvoker = wrapper->jsInvokerPtr(); + + if constexpr (std::is_void_v) { + callback.call(rt, bridging::toJs(rt, std::forward(args), jsInvoker)...); + } else { + return bridging::fromJs( + rt, callback.call(rt, bridging::toJs(rt, std::forward(args), jsInvoker)...), jsInvoker); + } + } + + private: + friend AsyncCallback; + friend Bridging; + + R apply(std::tuple &&args) const + { + return apply(std::move(args), std::index_sequence_for{}); + } + + template + R apply(std::tuple &&args, std::index_sequence /*unused*/) const + { + return call(std::move(std::get(args))...); + } + + // Held weakly so lifetime is managed by LongLivedObjectCollection. + std::weak_ptr wrapper_; +}; + +template +struct Bridging> { + static AsyncCallback + fromJs(jsi::Runtime &rt, jsi::Function &&value, const std::shared_ptr &jsInvoker) + { + return AsyncCallback(rt, std::move(value), jsInvoker); + } + + static jsi::Function toJs(jsi::Runtime &rt, const AsyncCallback &value) + { + return value.callback_->function_.getFunction(rt); + } +}; + +template +struct Bridging> { + static SyncCallback + fromJs(jsi::Runtime &rt, jsi::Function &&value, const std::shared_ptr &jsInvoker) + { + return SyncCallback(rt, std::move(value), jsInvoker); + } + + static jsi::Function toJs(jsi::Runtime &rt, const SyncCallback &value) + { + return value.function_.getFunction(rt); + } +}; + +template +struct Bridging> { + using Func = std::function; + using IndexSequence = std::index_sequence_for; + + static constexpr size_t kArgumentCount = sizeof...(Args); + + static jsi::Function toJs(jsi::Runtime &rt, Func fn, const std::shared_ptr &jsInvoker) + { + return jsi::Function::createFromHostFunction( + rt, + jsi::PropNameID::forAscii(rt, "BridgedFunction"), + kArgumentCount, + [fn = std::make_shared(std::move(fn)), jsInvoker]( + jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value { + if (count < kArgumentCount) { + throw jsi::JSError(rt, "Incorrect number of arguments"); + } + + if constexpr (std::is_void_v) { + callFromJs(*fn, rt, args, jsInvoker, IndexSequence{}); + return jsi::Value(); + } else { + return bridging::toJs(rt, callFromJs(*fn, rt, args, jsInvoker, IndexSequence{}), jsInvoker); + } + }); + } + + private: + template + static R callFromJs( + Func &fn, + jsi::Runtime &rt, + const jsi::Value *args, + const std::shared_ptr &jsInvoker, + std::index_sequence /*unused*/) + { + return fn(bridging::fromJs(rt, args[Index], jsInvoker)...); + } +}; + +template +struct Bridging< + std::function, + std::enable_if_t, std::function>>> + : Bridging> {}; + +template +struct Bridging : Bridging> {}; + +template +struct Bridging : Bridging> {}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/HighResTimeStamp.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/HighResTimeStamp.h new file mode 100644 index 00000000000..83807d54e87 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/HighResTimeStamp.h @@ -0,0 +1,41 @@ +/* + * 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 +#include + +namespace facebook::react { + +template <> +struct Bridging { + static HighResTimeStamp fromJs(jsi::Runtime & /*rt*/, const jsi::Value &jsiValue) + { + return HighResTimeStamp::fromDOMHighResTimeStamp(jsiValue.asNumber()); + } + + static double toJs(jsi::Runtime & /*rt*/, const HighResTimeStamp &value) + { + return value.toDOMHighResTimeStamp(); + } +}; + +template <> +struct Bridging { + static HighResDuration fromJs(jsi::Runtime & /*rt*/, const jsi::Value &jsiValue) + { + return HighResDuration::fromDOMHighResTimeStamp(jsiValue.asNumber()); + } + + static double toJs(jsi::Runtime & /*rt*/, const HighResDuration &value) + { + return value.toDOMHighResTimeStamp(); + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.cpp b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.cpp new file mode 100644 index 00000000000..9c6d7c38d86 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.cpp @@ -0,0 +1,63 @@ +/* + * 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 "LongLivedObject.h" +#include + +namespace facebook::react { + +// LongLivedObjectCollection + +LongLivedObjectCollection& LongLivedObjectCollection::get( + jsi::Runtime& runtime) { + static std::unordered_map> + instances; + static std::mutex instancesMutex; + + std::scoped_lock lock(instancesMutex); + void* key = static_cast(&runtime); + auto entry = instances.find(key); + if (entry == instances.end()) { + entry = + instances.emplace(key, std::make_shared()) + .first; + } + return *(entry->second); +} + +void LongLivedObjectCollection::add(std::shared_ptr so) { + std::scoped_lock lock(collectionMutex_); + collection_.insert(std::move(so)); +} + +void LongLivedObjectCollection::remove(const LongLivedObject* o) { + std::scoped_lock lock(collectionMutex_); + for (auto p = collection_.begin(); p != collection_.end(); p++) { + if (p->get() == o) { + collection_.erase(p); + break; + } + } +} + +void LongLivedObjectCollection::clear() { + std::scoped_lock lock(collectionMutex_); + collection_.clear(); +} + +size_t LongLivedObjectCollection::size() const { + std::scoped_lock lock(collectionMutex_); + return collection_.size(); +} + +// LongLivedObject + +void LongLivedObject::allowRelease() { + LongLivedObjectCollection::get(runtime_).remove(this); +} + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.h new file mode 100644 index 00000000000..1e3ff3f97bc --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.h @@ -0,0 +1,61 @@ +/* + * 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 +#include +#include +#include + +namespace facebook::react { + +/** + * A simple wrapper class that can be registered to a collection that keep it + * alive for extended period of time. This object can be removed from the + * collection when needed. + * + * The subclass of this class must be created using std::make_shared(). + * After creation, add it to the `LongLivedObjectCollection`. When done with the + * object, call `allowRelease()` to reclaim its memory. + * + * When using LongLivedObject to keep JS values alive, ensure you only hold weak + * references to the object outside the JS thread to avoid accessing deallocated + * values when the JS VM is shutdown. + */ +class LongLivedObject { + public: + virtual void allowRelease(); + + protected: + explicit LongLivedObject(jsi::Runtime &runtime) : runtime_(runtime) {} + virtual ~LongLivedObject() = default; + jsi::Runtime &runtime_; +}; + +/** + * A singleton, thread-safe, write-only collection for the `LongLivedObject`s. + */ +class LongLivedObjectCollection { + public: + static LongLivedObjectCollection &get(jsi::Runtime &runtime); + + LongLivedObjectCollection() = default; + LongLivedObjectCollection(const LongLivedObjectCollection &) = delete; + void operator=(const LongLivedObjectCollection &) = delete; + + void add(std::shared_ptr o); + void remove(const LongLivedObject *o); + void clear(); + size_t size() const; + + private: + std::unordered_set> collection_; + mutable std::mutex collectionMutex_; +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Number.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Number.h new file mode 100644 index 00000000000..2fb0c8a9b0d --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Number.h @@ -0,0 +1,66 @@ +/* + * 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 + +namespace facebook::react { + +template <> +struct Bridging { + static double fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value) + { + return value.asNumber(); + } + + static double toJs(jsi::Runtime & /*unused*/, double value) + { + return value; + } +}; + +template <> +struct Bridging { + static float fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value) + { + return (float)value.asNumber(); + } + + static float toJs(jsi::Runtime & /*unused*/, float value) + { + return value; + } +}; + +template <> +struct Bridging { + static int32_t fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value) + { + return (int32_t)value.asNumber(); + } + + static int32_t toJs(jsi::Runtime & /*unused*/, int32_t value) + { + return value; + } +}; + +template <> +struct Bridging { + static uint32_t fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value) + { + return (uint32_t)value.asNumber(); + } + + static jsi::Value toJs(jsi::Runtime & /*unused*/, uint32_t value) + { + return double(value); + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Object.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Object.h new file mode 100644 index 00000000000..0ccd3a24e1e --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Object.h @@ -0,0 +1,86 @@ +/* + * 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 +#include + +#include +#include + +namespace facebook::react { + +template <> +struct Bridging { + static jsi::WeakObject fromJs(jsi::Runtime &rt, const jsi::Object &value) + { + return jsi::WeakObject(rt, value); + } + + static jsi::Value toJs(jsi::Runtime &rt, jsi::WeakObject &value) + { + return value.lock(rt); + } +}; + +template +struct Bridging, std::enable_if_t>> { + static std::shared_ptr fromJs(jsi::Runtime &rt, const jsi::Object &value) + { + return value.getHostObject(rt); + } + + static jsi::Object toJs(jsi::Runtime &rt, std::shared_ptr value) + { + return jsi::Object::createFromHostObject(rt, std::move(value)); + } +}; + +namespace map_detail { + +template +struct Bridging { + static T fromJs(jsi::Runtime &rt, const jsi::Object &value, const std::shared_ptr &jsInvoker) + { + T result; + auto propertyNames = value.getPropertyNames(rt); + auto length = propertyNames.length(rt); + + for (size_t i = 0; i < length; i++) { + auto propertyName = propertyNames.getValueAtIndex(rt, i); + + result.emplace( + bridging::fromJs(rt, propertyName, jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, propertyName.asString(rt)), jsInvoker)); + } + + return result; + } + + static jsi::Object toJs(jsi::Runtime &rt, const T &map, const std::shared_ptr &jsInvoker) + { + auto resultObject = jsi::Object(rt); + + for (const auto &[key, value] : map) { + resultObject.setProperty(rt, jsi::PropNameID::forUtf8(rt, key), bridging::toJs(rt, value, jsInvoker)); + } + + return resultObject; + } +}; + +} // namespace map_detail + +template +struct Bridging> : map_detail::Bridging> {}; + +template +struct Bridging> + : map_detail::Bridging> {}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Promise.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Promise.h new file mode 100644 index 00000000000..a1f17f813ad --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Promise.h @@ -0,0 +1,110 @@ +/* + * 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 +#include +#include + +#include +#include + +namespace facebook::react { + +template +class AsyncPromise { + static_assert(sizeof...(T) <= 1, "AsyncPromise must have at most one argument"); + + public: + AsyncPromise(jsi::Runtime &rt, const std::shared_ptr &jsInvoker) + : state_(std::make_shared()) + { + auto constructor = rt.global().getPropertyAsFunction(rt, "Promise"); + + auto promise = constructor.callAsConstructor( + rt, + bridging::toJs( + rt, + // Safe to capture this since this is called synchronously. + [this](AsyncCallback resolve, const AsyncCallback &reject) { + state_->resolve = std::move(resolve); + state_->reject = std::move(reject); + }, + jsInvoker)); + + auto promiseHolder = std::make_shared(rt, promise.asObject(rt)); + LongLivedObjectCollection::get(rt).add(promiseHolder); + + // The shared state can retain the promise holder weakly now. + state_->promiseHolder = promiseHolder; + } + + void resolve(T... value) + { + std::lock_guard lock(state_->mutex); + + if (state_->resolve) { + state_->resolve->call(std::forward(value)...); + state_->resolve.reset(); + state_->reject.reset(); + } + } + + void reject(Error error) + { + std::lock_guard lock(state_->mutex); + + if (state_->reject) { + state_->reject->call(std::move(error)); + state_->reject.reset(); + state_->resolve.reset(); + } + } + + jsi::Object get(jsi::Runtime &rt) const + { + if (auto holder = state_->promiseHolder.lock()) { + return jsi::Value(rt, holder->promise).asObject(rt); + } else { + throw jsi::JSError(rt, "Failed to get invalidated promise"); + } + } + + private: + struct PromiseHolder : LongLivedObject { + PromiseHolder(jsi::Runtime &runtime, jsi::Object p) : LongLivedObject(runtime), promise(std::move(p)) {} + + jsi::Object promise; + }; + + struct SharedState { + ~SharedState() + { + if (auto holder = promiseHolder.lock()) { + holder->allowRelease(); + } + } + + std::mutex mutex; + std::weak_ptr promiseHolder; + std::optional> resolve; + std::optional> reject; + }; + + std::shared_ptr state_; +}; + +template +struct Bridging> { + static jsi::Object toJs(jsi::Runtime &rt, const AsyncPromise &promise) + { + return promise.get(rt); + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Value.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Value.h new file mode 100644 index 00000000000..deb99ad5d39 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Value.h @@ -0,0 +1,98 @@ +/* + * 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 + +#include +#include + +namespace facebook::react { + +template <> +struct Bridging { + static std::nullptr_t fromJs(jsi::Runtime &rt, const jsi::Value &value) + { + if (value.isNull() || value.isUndefined()) { + return nullptr; + } else { + throw jsi::JSError(rt, "Cannot convert value to nullptr"); + } + } + + static std::nullptr_t toJs(jsi::Runtime & /*unused*/, std::nullptr_t) + { + return nullptr; + } +}; + +template +struct Bridging> { + static std::optional + fromJs(jsi::Runtime &rt, const jsi::Value &value, const std::shared_ptr &jsInvoker) + { + if (value.isNull() || value.isUndefined()) { + return {}; + } + return bridging::fromJs(rt, value, jsInvoker); + } + + template + static std::optional + fromJs(jsi::Runtime &rt, const std::optional &value, const std::shared_ptr &jsInvoker) + { + if (value) { + return bridging::fromJs(rt, *value, jsInvoker); + } + return {}; + } + + static jsi::Value toJs(jsi::Runtime &rt, const std::optional &value, const std::shared_ptr &jsInvoker) + { + if (value) { + return bridging::toJs(rt, *value, jsInvoker); + } + return jsi::Value::null(); + } +}; + +template +struct Bridging, std::enable_if_t>> { + static jsi::Value toJs(jsi::Runtime &rt, const std::shared_ptr &ptr, const std::shared_ptr &jsInvoker) + { + if (ptr) { + return bridging::toJs(rt, *ptr, jsInvoker); + } + return jsi::Value::null(); + } +}; + +template +struct Bridging> { + static jsi::Value toJs(jsi::Runtime &rt, const std::unique_ptr &ptr, const std::shared_ptr &jsInvoker) + { + if (ptr) { + return bridging::toJs(rt, *ptr, jsInvoker); + } + return jsi::Value::null(); + } +}; + +template +struct Bridging> { + static jsi::Value + toJs(jsi::Runtime &rt, const std::weak_ptr &weakPtr, const std::shared_ptr &jsInvoker) + { + if (auto ptr = weakPtr.lock()) { + return bridging::toJs(rt, *ptr, jsInvoker); + } + return jsi::Value::null(); + } +}; + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/timing/primitives.h b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/timing/primitives.h new file mode 100644 index 00000000000..0e5a93aedb8 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactCommon/react/timing/primitives.h @@ -0,0 +1,350 @@ +/* + * 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 +#include +#include + +namespace facebook::react { + +class HighResDuration; +class HighResTimeStamp; + +/* + * A class representing a duration of time with high precision. + * + * @see __docs__/README.md for more information. + */ +class HighResDuration { + friend class HighResTimeStamp; + friend constexpr HighResDuration operator-(const HighResTimeStamp &lhs, const HighResTimeStamp &rhs); + friend constexpr HighResTimeStamp operator+(const HighResTimeStamp &lhs, const HighResDuration &rhs); + friend constexpr HighResTimeStamp operator-(const HighResTimeStamp &lhs, const HighResDuration &rhs); + + public: + constexpr HighResDuration() : chronoDuration_(std::chrono::steady_clock::duration()) {} + + static constexpr HighResDuration zero() + { + return HighResDuration(std::chrono::steady_clock::duration::zero()); + } + + static constexpr HighResDuration fromChrono(std::chrono::steady_clock::duration chronoDuration) + { + return HighResDuration(chronoDuration); + } + + static constexpr HighResDuration fromNanoseconds(int64_t units) + { + return HighResDuration(std::chrono::nanoseconds(units)); + } + + static constexpr HighResDuration fromMilliseconds(int64_t units) + { + return HighResDuration(std::chrono::milliseconds(units)); + } + + constexpr int64_t toNanoseconds() const + { + return std::chrono::duration_cast(chronoDuration_).count(); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + static constexpr HighResDuration fromDOMHighResTimeStamp(double units) + { + auto nanoseconds = static_cast(units * 1e6); + return fromNanoseconds(nanoseconds); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + constexpr double toDOMHighResTimeStamp() const + { + return static_cast>(chronoDuration_).count(); + } + + constexpr HighResDuration operator+(const HighResDuration &rhs) const + { + return HighResDuration(chronoDuration_ + rhs.chronoDuration_); + } + + constexpr HighResDuration operator+(const std::chrono::steady_clock::duration &rhs) const + { + return HighResDuration(chronoDuration_ + rhs); + } + + constexpr HighResDuration operator-(const HighResDuration &rhs) const + { + return HighResDuration(chronoDuration_ - rhs.chronoDuration_); + } + + constexpr HighResDuration operator-(const std::chrono::steady_clock::duration &rhs) const + { + return HighResDuration(chronoDuration_ - rhs); + } + + constexpr HighResDuration &operator+=(const HighResDuration &rhs) + { + chronoDuration_ += rhs.chronoDuration_; + return *this; + } + + constexpr HighResDuration &operator+=(const std::chrono::steady_clock::duration &rhs) + { + chronoDuration_ += rhs; + return *this; + } + + constexpr HighResDuration &operator-=(const HighResDuration &rhs) + { + chronoDuration_ -= rhs.chronoDuration_; + return *this; + } + + constexpr HighResDuration &operator-=(const std::chrono::steady_clock::duration &rhs) + { + chronoDuration_ -= rhs; + return *this; + } + + constexpr bool operator==(const HighResDuration &rhs) const + { + return chronoDuration_ == rhs.chronoDuration_; + } + + constexpr bool operator==(const std::chrono::steady_clock::duration &rhs) const + { + return chronoDuration_ == rhs; + } + + constexpr bool operator!=(const HighResDuration &rhs) const + { + return chronoDuration_ != rhs.chronoDuration_; + } + + constexpr bool operator!=(const std::chrono::steady_clock::duration &rhs) const + { + return chronoDuration_ != rhs; + } + + constexpr bool operator<(const HighResDuration &rhs) const + { + return chronoDuration_ < rhs.chronoDuration_; + } + + constexpr bool operator<(const std::chrono::steady_clock::duration &rhs) const + { + return chronoDuration_ < rhs; + } + + constexpr bool operator<=(const HighResDuration &rhs) const + { + return chronoDuration_ <= rhs.chronoDuration_; + } + + constexpr bool operator<=(const std::chrono::steady_clock::duration &rhs) const + { + return chronoDuration_ <= rhs; + } + + constexpr bool operator>(const HighResDuration &rhs) const + { + return chronoDuration_ > rhs.chronoDuration_; + } + + constexpr bool operator>(const std::chrono::steady_clock::duration &rhs) const + { + return chronoDuration_ > rhs; + } + + constexpr bool operator>=(const HighResDuration &rhs) const + { + return chronoDuration_ >= rhs.chronoDuration_; + } + + constexpr bool operator>=(const std::chrono::steady_clock::duration &rhs) const + { + return chronoDuration_ >= rhs; + } + + constexpr operator std::chrono::steady_clock::duration() const + { + return chronoDuration_; + } + + private: + explicit constexpr HighResDuration(std::chrono::steady_clock::duration chronoDuration) + : chronoDuration_(chronoDuration) + { + } + + std::chrono::steady_clock::duration chronoDuration_; +}; + +/* + * A class representing a specific point in time with high precision. + * + * @see __docs__/README.md for more information. + */ +class HighResTimeStamp { + friend constexpr HighResDuration operator-(const HighResTimeStamp &lhs, const HighResTimeStamp &rhs); + friend constexpr HighResTimeStamp operator+(const HighResTimeStamp &lhs, const HighResDuration &rhs); + friend constexpr HighResTimeStamp operator-(const HighResTimeStamp &lhs, const HighResDuration &rhs); + + public: + HighResTimeStamp() noexcept : chronoTimePoint_(chronoNow()) {} + + static HighResTimeStamp now() noexcept + { + return HighResTimeStamp(chronoNow()); + } + + static HighResDuration unsafeOriginFromUnixTimeStamp() noexcept + { + static auto origin = computeUnsafeOriginFromUnixTimeStamp(); + return origin; + } + + static constexpr HighResTimeStamp min() noexcept + { + return HighResTimeStamp(std::chrono::steady_clock::time_point::min()); + } + + static constexpr HighResTimeStamp max() noexcept + { + return HighResTimeStamp(std::chrono::steady_clock::time_point::max()); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + static constexpr HighResTimeStamp fromDOMHighResTimeStamp(double units) + { + auto nanoseconds = static_cast(units * 1e6); + return HighResTimeStamp(std::chrono::steady_clock::time_point(std::chrono::nanoseconds(nanoseconds))); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + constexpr double toDOMHighResTimeStamp() const + { + return HighResDuration(chronoTimePoint_.time_since_epoch()).toDOMHighResTimeStamp(); + } + + // This method is expected to be used only when converting time stamps from + // external systems. + static constexpr HighResTimeStamp fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point chronoTimePoint) + { + return HighResTimeStamp(chronoTimePoint); + } + +#ifdef REACT_NATIVE_DEBUG + static void setTimeStampProviderForTesting(std::function &&timeStampProvider) + { + getTimeStampProvider() = std::move(timeStampProvider); + } +#endif + + // This method is provided for convenience, if you need to convert + // HighResTimeStamp to some common epoch with time stamps from other sources. + constexpr std::chrono::steady_clock::time_point toChronoSteadyClockTimePoint() const + { + return chronoTimePoint_; + } + + constexpr bool operator==(const HighResTimeStamp &rhs) const + { + return chronoTimePoint_ == rhs.chronoTimePoint_; + } + + constexpr bool operator!=(const HighResTimeStamp &rhs) const + { + return chronoTimePoint_ != rhs.chronoTimePoint_; + } + + constexpr bool operator<(const HighResTimeStamp &rhs) const + { + return chronoTimePoint_ < rhs.chronoTimePoint_; + } + + constexpr bool operator<=(const HighResTimeStamp &rhs) const + { + return chronoTimePoint_ <= rhs.chronoTimePoint_; + } + + constexpr bool operator>(const HighResTimeStamp &rhs) const + { + return chronoTimePoint_ > rhs.chronoTimePoint_; + } + + constexpr bool operator>=(const HighResTimeStamp &rhs) const + { + return chronoTimePoint_ >= rhs.chronoTimePoint_; + } + + constexpr HighResTimeStamp &operator+=(const HighResDuration &rhs) + { + chronoTimePoint_ += rhs.chronoDuration_; + return *this; + } + + constexpr HighResTimeStamp &operator-=(const HighResDuration &rhs) + { + chronoTimePoint_ -= rhs.chronoDuration_; + return *this; + } + + private: + explicit constexpr HighResTimeStamp(std::chrono::steady_clock::time_point chronoTimePoint) + : chronoTimePoint_(chronoTimePoint) + { + } + + std::chrono::steady_clock::time_point chronoTimePoint_; + + static HighResDuration computeUnsafeOriginFromUnixTimeStamp() noexcept + { + auto systemNow = std::chrono::system_clock::now(); + auto steadyNow = std::chrono::steady_clock::now(); + return HighResDuration(systemNow.time_since_epoch() - steadyNow.time_since_epoch()); + } + +#ifdef REACT_NATIVE_DEBUG + static std::function &getTimeStampProvider() + { + static std::function timeStampProvider = nullptr; + return timeStampProvider; + } + + static std::chrono::steady_clock::time_point chronoNow() + { + auto &timeStampProvider = getTimeStampProvider(); + return timeStampProvider != nullptr ? timeStampProvider() : std::chrono::steady_clock::now(); + } +#else + inline static std::chrono::steady_clock::time_point chronoNow() + { + return std::chrono::steady_clock::now(); + } +#endif +}; + +inline constexpr HighResDuration operator-(const HighResTimeStamp &lhs, const HighResTimeStamp &rhs) +{ + return HighResDuration(lhs.chronoTimePoint_ - rhs.chronoTimePoint_); +} + +inline constexpr HighResTimeStamp operator+(const HighResTimeStamp &lhs, const HighResDuration &rhs) +{ + return HighResTimeStamp(lhs.chronoTimePoint_ + rhs.chronoDuration_); +} + +inline constexpr HighResTimeStamp operator-(const HighResTimeStamp &lhs, const HighResDuration &rhs) +{ + return HighResTimeStamp(lhs.chronoTimePoint_ - rhs.chronoDuration_); +} + +} // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api.h b/vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api.h new file mode 100644 index 00000000000..84c91dbb6a5 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api.h @@ -0,0 +1,627 @@ +#ifndef SRC_JS_NATIVE_API_H_ +#define SRC_JS_NATIVE_API_H_ + +// This file needs to be compatible with C compilers. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +// Use INT_MAX, this should only be consumed by the pre-processor anyway. +#define NAPI_VERSION_EXPERIMENTAL 2147483647 +#ifndef NAPI_VERSION +#ifdef NAPI_EXPERIMENTAL +#define NAPI_VERSION NAPI_VERSION_EXPERIMENTAL +#else +// The baseline version for N-API. +// The NAPI_VERSION controls which version will be used by default when +// compiling a native addon. If the addon developer specifically wants to use +// functions available in a new version of N-API that is not yet ported in all +// LTS versions, they can set NAPI_VERSION knowing that they have specifically +// depended on that version. +#define NAPI_VERSION 8 +#endif +#endif + +#if defined(NAPI_EXPERIMENTAL) && \ + !defined(NODE_API_EXPERIMENTAL_NO_WARNING) && \ + !defined(NODE_WANT_INTERNALS) +#ifdef _MSC_VER +#pragma message("NAPI_EXPERIMENTAL is enabled. " \ + "Experimental features may be unstable.") +#else +#warning "NAPI_EXPERIMENTAL is enabled. " \ + "Experimental features may be unstable." +#endif +#endif + +#include "js_native_api_types.h" + +// If you need __declspec(dllimport), either include instead, or +// define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. +#ifndef NAPI_EXTERN +#ifdef _WIN32 +#define NAPI_EXTERN __declspec(dllexport) +#elif defined(__wasm__) +#define NAPI_EXTERN \ + __attribute__((visibility("default"))) \ + __attribute__((__import_module__("napi"))) +#else +#define NAPI_EXTERN __attribute__((visibility("default"))) +#endif +#endif + +#define NAPI_AUTO_LENGTH SIZE_MAX + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END +#endif + +EXTERN_C_START + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info( + node_api_basic_env env, const napi_extended_error_info** result); + +// Getters for defined singletons +NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_null(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_global(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env, + bool value, + napi_value* result); + +// Methods to create Primitive types/Objects +NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_array_with_length(napi_env env, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_double(napi_env env, + double value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_int32(napi_env env, + int32_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_uint32(napi_env env, + uint32_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_latin1( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result); +#if NAPI_VERSION >= 10 +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_latin1( + napi_env env, + char* str, + size_t length, + node_api_basic_finalize finalize_callback, + void* finalize_hint, + napi_value* result, + bool* copied); +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_external_string_utf16(napi_env env, + char16_t* str, + size_t length, + node_api_basic_finalize finalize_callback, + void* finalize_hint, + napi_value* result, + bool* copied); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_latin1( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf8( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf16( + napi_env env, const char16_t* str, size_t length, napi_value* result); +#endif // NAPI_VERSION >= 10 + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env, + napi_value description, + napi_value* result); +#if NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL +node_api_symbol_for(napi_env env, + const char* utf8description, + size_t length, + napi_value* result); +#endif // NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_range_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +#if NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_syntax_error( + napi_env env, napi_value code, napi_value msg, napi_value* result); +#endif // NAPI_VERSION >= 9 + +// Methods to get the native napi_value from Primitive type +NAPI_EXTERN napi_status NAPI_CDECL napi_typeof(napi_env env, + napi_value value, + napi_valuetype* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_double(napi_env env, + napi_value value, + double* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bool(napi_env env, + napi_value value, + bool* result); + +// Copies LATIN-1 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_latin1( + napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); + +// Copies UTF-8 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf8( + napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); + +// Copies UTF-16 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result); + +// Methods to coerce values +// These APIs may execute user scripts +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result); + +// Methods to work with Objects +NAPI_EXTERN napi_status NAPI_CDECL napi_get_prototype(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_property_names(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_named_property(napi_env env, + napi_value object, + const char* utf8name, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties); + +// Methods to work with Arrays +NAPI_EXTERN napi_status NAPI_CDECL napi_is_array(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result); + +// Methods to compare values +NAPI_EXTERN napi_status NAPI_CDECL napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result); + +// Methods to work with Functions +NAPI_EXTERN napi_status NAPI_CDECL napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result); + +// Methods to work with napi_callbacks + +// Gets all callback info in a single call. (Ugly, but faster.) +NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info( + napi_env env, // [in] Node-API environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* this_arg, // [out] Receives the JS 'this' arg for the call + void** data); // [out] Receives the data pointer for the callback. + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_new_target( + napi_env env, napi_callback_info cbinfo, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback constructor, + void* data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result); + +// Methods to work with external data objects +NAPI_EXTERN napi_status NAPI_CDECL +napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external(napi_env env, + void* data, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, + napi_value value, + void** result); + +// Methods to control object lifespan + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result); + +// Deletes a reference. The referenced value is released, and may +// be GC'd unless there are other references to it. +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_reference(napi_env env, + napi_ref ref); + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its +// refcount is >0, and the referenced object is effectively "pinned". +// Calling this when the refcount is 0 and the object is unavailable +// results in an error. +NAPI_EXTERN napi_status NAPI_CDECL napi_reference_ref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Decrements the reference count, optionally returning the resulting count. +// If the result is 0 the reference is now weak and the object may be GC'd +// at any time if there are no other references. Calling this when the +// refcount is already 0 results in an error. +NAPI_EXTERN napi_status NAPI_CDECL napi_reference_unref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Attempts to get a referenced value. If the reference is weak, +// the value might no longer be available, in that case the call +// is still successful but the result is NULL. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_open_handle_scope(napi_env env, napi_handle_scope* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_close_handle_scope(napi_env env, napi_handle_scope scope); +NAPI_EXTERN napi_status NAPI_CDECL napi_open_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_close_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope scope); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result); + +// Methods to support error handling +NAPI_EXTERN napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_type_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_range_error(napi_env env, + const char* code, + const char* msg); +#if NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL node_api_throw_syntax_error(napi_env env, + const char* code, + const char* msg); +#endif // NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL napi_is_error(napi_env env, + napi_value value, + bool* result); + +// Methods to support catching exceptions +NAPI_EXTERN napi_status NAPI_CDECL napi_is_exception_pending(napi_env env, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_and_clear_last_exception(napi_env env, napi_value* result); + +// Methods to work with array buffers and typed arrays +NAPI_EXTERN napi_status NAPI_CDECL napi_is_arraybuffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info( + napi_env env, napi_value arraybuffer, void** data, size_t* byte_length); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_typedarray(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_dataview(napi_env env, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_dataview(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* bytelength, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +// version management +NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_basic_env env, + uint32_t* result); + +// Promises +NAPI_EXTERN napi_status NAPI_CDECL napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise); +NAPI_EXTERN napi_status NAPI_CDECL napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution); +NAPI_EXTERN napi_status NAPI_CDECL napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value rejection); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_promise(napi_env env, + napi_value value, + bool* is_promise); + +// Running a script +NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, + napi_value script, + napi_value* result); + +// Memory management +NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( + node_api_basic_env env, int64_t change_in_bytes, int64_t* adjusted_value); + +#if NAPI_VERSION >= 5 + +// Dates +NAPI_EXTERN napi_status NAPI_CDECL napi_create_date(napi_env env, + double time, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_is_date(napi_env env, + napi_value value, + bool* is_date); + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env, + napi_value value, + double* result); + +// Add finalizer for pointer +NAPI_EXTERN napi_status NAPI_CDECL +napi_add_finalizer(napi_env env, + napi_value js_object, + void* finalize_data, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); + +#endif // NAPI_VERSION >= 5 + +#ifdef NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_post_finalizer(node_api_basic_env env, + napi_finalize finalize_cb, + void* finalize_data, + void* finalize_hint); + +#endif // NAPI_EXPERIMENTAL + +#if NAPI_VERSION >= 6 + +// BigInt +NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_bigint_words(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t* result, + bool* lossless); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_uint64( + napi_env env, napi_value value, uint64_t* result, bool* lossless); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_value_bigint_words(napi_env env, + napi_value value, + int* sign_bit, + size_t* word_count, + uint64_t* words); + +// Object +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_all_property_names(napi_env env, + napi_value object, + napi_key_collection_mode key_mode, + napi_key_filter key_filter, + napi_key_conversion key_conversion, + napi_value* result); + +// Instance data +NAPI_EXTERN napi_status NAPI_CDECL +napi_set_instance_data(node_api_basic_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_instance_data(node_api_basic_env env, void** data); +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 7 +// ArrayBuffer detaching +NAPI_EXTERN napi_status NAPI_CDECL +napi_detach_arraybuffer(napi_env env, napi_value arraybuffer); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_is_detached_arraybuffer(napi_env env, napi_value value, bool* result); +#endif // NAPI_VERSION >= 7 + +#if NAPI_VERSION >= 8 +// Type tagging +NAPI_EXTERN napi_status NAPI_CDECL napi_type_tag_object( + napi_env env, napi_value value, const napi_type_tag* type_tag); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_check_object_type_tag(napi_env env, + napi_value value, + const napi_type_tag* type_tag, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_object_freeze(napi_env env, + napi_value object); +NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env, + napi_value object); +#endif // NAPI_VERSION >= 8 + +EXTERN_C_END + +#endif // SRC_JS_NATIVE_API_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api_types.h b/vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api_types.h new file mode 100644 index 00000000000..43e7bb77ff9 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/node-api/js_native_api_types.h @@ -0,0 +1,211 @@ +#ifndef SRC_JS_NATIVE_API_TYPES_H_ +#define SRC_JS_NATIVE_API_TYPES_H_ + +// This file needs to be compatible with C compilers. +// This is a public include file, and these includes have essentially +// became part of it's API. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) +typedef uint16_t char16_t; +#endif + +#ifndef NAPI_CDECL +#ifdef _WIN32 +#define NAPI_CDECL __cdecl +#else +#define NAPI_CDECL +#endif +#endif + +// JSVM API types are all opaque pointers for ABI stability +// typedef undefined structs instead of void* for compile time type safety +typedef struct napi_env__* napi_env; + +// We need to mark APIs which can be called during garbage collection (GC), +// meaning that they do not affect the state of the JS engine, and can +// therefore be called synchronously from a finalizer that itself runs +// synchronously during GC. Such APIs can receive either a `napi_env` or a +// `node_api_basic_env` as their first parameter, because we should be able to +// also call them during normal, non-garbage-collecting operations, whereas +// APIs that affect the state of the JS engine can only receive a `napi_env` as +// their first parameter, because we must not call them during GC. In lieu of +// inheritance, we use the properties of the const qualifier to accomplish +// this, because both a const and a non-const value can be passed to an API +// expecting a const value, but only a non-const value can be passed to an API +// expecting a non-const value. +// +// In conjunction with appropriate CFLAGS to warn us if we're passing a const +// (basic) environment into an API that expects a non-const environment, and +// the definition of basic finalizer function pointer types below, which +// receive a basic environment as their first parameter, and can thus only call +// basic APIs (unless the user explicitly casts the environment), we achieve +// the ability to ensure at compile time that we do not call APIs that affect +// the state of the JS engine from a synchronous (basic) finalizer. +#if !defined(NAPI_EXPERIMENTAL) || \ + (defined(NAPI_EXPERIMENTAL) && \ + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) +typedef struct napi_env__* node_api_nogc_env; +#else +typedef const struct napi_env__* node_api_nogc_env; +#endif +typedef node_api_nogc_env node_api_basic_env; + +typedef struct napi_value__* napi_value; +typedef struct napi_ref__* napi_ref; +typedef struct napi_handle_scope__* napi_handle_scope; +typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; +typedef struct napi_callback_info__* napi_callback_info; +typedef struct napi_deferred__* napi_deferred; + +typedef enum { + napi_default = 0, + napi_writable = 1 << 0, + napi_enumerable = 1 << 1, + napi_configurable = 1 << 2, + + // Used with napi_define_class to distinguish static properties + // from instance properties. Ignored by napi_define_properties. + napi_static = 1 << 10, + +#if NAPI_VERSION >= 8 + // Default for class methods. + napi_default_method = napi_writable | napi_configurable, + + // Default for object properties, like in JS obj[prop]. + napi_default_jsproperty = napi_writable | napi_enumerable | napi_configurable, +#endif // NAPI_VERSION >= 8 +} napi_property_attributes; + +typedef enum { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, + napi_bigint, +} napi_valuetype; + +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, + napi_bigint64_array, + napi_biguint64_array, +} napi_typedarray_type; + +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_escape_called_twice, + napi_handle_scope_mismatch, + napi_callback_scope_mismatch, + napi_queue_full, + napi_closing, + napi_bigint_expected, + napi_date_expected, + napi_arraybuffer_expected, + napi_detachable_arraybuffer_expected, + napi_would_deadlock, // unused + napi_no_external_buffers_allowed, + napi_cannot_run_js, +} napi_status; +// Note: when adding a new enum value to `napi_status`, please also update +// * `const int last_status` in the definition of `napi_get_last_error_info()' +// in file js_native_api_v8.cc. +// * `const char* error_messages[]` in file js_native_api_v8.cc with a brief +// message explaining the error. +// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly +// added value(s). + +typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, + napi_callback_info info); +typedef void(NAPI_CDECL* napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); + +#if !defined(NAPI_EXPERIMENTAL) || \ + (defined(NAPI_EXPERIMENTAL) && \ + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) +typedef napi_finalize node_api_nogc_finalize; +#else +typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env, + void* finalize_data, + void* finalize_hint); +#endif +typedef node_api_nogc_finalize node_api_basic_finalize; + +typedef struct { + // One of utf8name or name should be NULL. + const char* utf8name; + napi_value name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; + +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; + +#if NAPI_VERSION >= 6 +typedef enum { + napi_key_include_prototypes, + napi_key_own_only +} napi_key_collection_mode; + +typedef enum { + napi_key_all_properties = 0, + napi_key_writable = 1, + napi_key_enumerable = 1 << 1, + napi_key_configurable = 1 << 2, + napi_key_skip_strings = 1 << 3, + napi_key_skip_symbols = 1 << 4 +} napi_key_filter; + +typedef enum { + napi_key_keep_numbers, + napi_key_numbers_to_strings +} napi_key_conversion; +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 8 +typedef struct { + uint64_t lower; + uint64_t upper; +} napi_type_tag; +#endif // NAPI_VERSION >= 8 + +#endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/node-api/js_runtime_api.h b/vnext/Microsoft.ReactNative.Cxx/node-api/js_runtime_api.h new file mode 100644 index 00000000000..dd99622da1a --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/node-api/js_runtime_api.h @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef SRC_JS_RUNTIME_API_H_ +#define SRC_JS_RUNTIME_API_H_ + +#include "node_api.h" + +// +// Node-API extensions required for JavaScript engine hosting. +// +// It is a very early version of the APIs which we consider to be experimental. +// These APIs are not stable yet and are subject to change while we continue +// their development. After some time we will stabilize the APIs and make them +// "officially stable". +// + +#define JSR_API NAPI_EXTERN napi_status NAPI_CDECL + +EXTERN_C_START + +typedef struct jsr_runtime_s* jsr_runtime; +typedef struct jsr_config_s* jsr_config; +typedef struct jsr_prepared_script_s* jsr_prepared_script; +typedef struct jsr_napi_env_scope_s* jsr_napi_env_scope; + +typedef void(NAPI_CDECL* jsr_data_delete_cb)(void* data, void* deleter_data); + +//============================================================================= +// jsr_runtime +//============================================================================= + +JSR_API jsr_create_runtime(jsr_config config, jsr_runtime* runtime); +JSR_API jsr_delete_runtime(jsr_runtime runtime); +JSR_API jsr_runtime_get_node_api_env(jsr_runtime runtime, napi_env* env); + +//============================================================================= +// jsr_config +//============================================================================= + +JSR_API jsr_create_config(jsr_config* config); +JSR_API jsr_delete_config(jsr_config config); + +JSR_API jsr_config_enable_inspector(jsr_config config, bool value); +JSR_API jsr_config_set_inspector_runtime_name(jsr_config config, + const char* name); +JSR_API jsr_config_set_inspector_port(jsr_config config, uint16_t port); +JSR_API jsr_config_set_inspector_break_on_start(jsr_config config, bool value); + +JSR_API jsr_config_enable_gc_api(jsr_config config, bool value); + +JSR_API jsr_config_set_explicit_microtasks(jsr_config config, bool value); + +// A callback to process unhandled JS error +typedef void(NAPI_CDECL* jsr_unhandled_error_cb)(void* cb_data, + napi_env env, + napi_value error); + +JSR_API jsr_config_on_unhandled_error( + jsr_config config, + void* cb_data, + jsr_unhandled_error_cb unhandled_error_cb); + +//============================================================================= +// jsr_config task runner +//============================================================================= + +// A callback to run task +typedef void(NAPI_CDECL* jsr_task_run_cb)(void* task_data); + +// A callback to post task to the task runner +typedef void(NAPI_CDECL* jsr_task_runner_post_task_cb)( + void* task_runner_data, + void* task_data, + jsr_task_run_cb task_run_cb, + jsr_data_delete_cb task_data_delete_cb, + void* deleter_data); + +JSR_API jsr_config_set_task_runner( + jsr_config config, + void* task_runner_data, + jsr_task_runner_post_task_cb task_runner_post_task_cb, + jsr_data_delete_cb task_runner_data_delete_cb, + void* deleter_data); + +//============================================================================= +// jsr_config script cache +//============================================================================= + +typedef void(NAPI_CDECL* jsr_script_cache_load_cb)( + void* script_cache_data, + const char* source_url, + uint64_t source_hash, + const char* runtime_name, + uint64_t runtime_version, + const char* cache_tag, + const uint8_t** buffer, + size_t* buffer_size, + jsr_data_delete_cb* buffer_delete_cb, + void** deleter_data); + +typedef void(NAPI_CDECL* jsr_script_cache_store_cb)( + void* script_cache_data, + const char* source_url, + uint64_t source_hash, + const char* runtime_name, + uint64_t runtime_version, + const char* cache_tag, + const uint8_t* buffer, + size_t buffer_size, + jsr_data_delete_cb buffer_delete_cb, + void* deleter_data); + +JSR_API jsr_config_set_script_cache( + jsr_config config, + void* script_cache_data, + jsr_script_cache_load_cb script_cache_load_cb, + jsr_script_cache_store_cb script_cache_store_cb, + jsr_data_delete_cb script_cache_data_delete_cb, + void* deleter_data); + +//============================================================================= +// napi_env scope +//============================================================================= + +// Opens the napi_env scope in the current thread. +// Calling Node-API functions without the opened scope may cause a failure. +// The scope must be closed by the jsr_close_napi_env_scope call. +JSR_API jsr_open_napi_env_scope(napi_env env, jsr_napi_env_scope* scope); + +// Closes the napi_env scope in the current thread. It must match to the +// jsr_open_napi_env_scope call. +JSR_API jsr_close_napi_env_scope(napi_env env, jsr_napi_env_scope scope); + +//============================================================================= +// Additional functions to implement JSI +//============================================================================= + +// To implement JSI description() +JSR_API jsr_get_description(napi_env env, const char** result); + +// To implement JSI queueMicrotask() +JSR_API jsr_queue_microtask(napi_env env, napi_value callback); + +// To implement JSI drainMicrotasks() +JSR_API +jsr_drain_microtasks(napi_env env, int32_t max_count_hint, bool* result); + +// To implement JSI isInspectable() +JSR_API jsr_is_inspectable(napi_env env, bool* result); + +//============================================================================= +// Script preparing and running. +// +// Script is usually converted to byte code, or in other words - prepared - for +// execution. Then, we can run the prepared script. +//============================================================================= + +// Run script with source URL. +JSR_API jsr_run_script(napi_env env, + napi_value source, + const char* source_url, + napi_value* result); + +// Prepare the script for running. +JSR_API jsr_create_prepared_script(napi_env env, + const uint8_t* script_data, + size_t script_length, + jsr_data_delete_cb script_delete_cb, + void* deleter_data, + const char* source_url, + jsr_prepared_script* result); + +// Delete the prepared script. +JSR_API jsr_delete_prepared_script(napi_env env, + jsr_prepared_script prepared_script); + +// Run the prepared script. +JSR_API jsr_prepared_script_run(napi_env env, + jsr_prepared_script prepared_script, + napi_value* result); + +//============================================================================= +// Functions to support unit tests. +//============================================================================= + +// Provides a hint to run garbage collection. +// It is typically used for unit tests. +// It requires enabling GC by calling jsr_config_enable_gc_api. +JSR_API jsr_collect_garbage(napi_env env); + +// Checks if the environment has an unhandled promise rejection. +JSR_API jsr_has_unhandled_promise_rejection(napi_env env, bool* result); + +// Gets and clears the last unhandled promise rejection. +JSR_API jsr_get_and_clear_last_unhandled_promise_rejection(napi_env env, + napi_value* result); + +// Create new napi_env for the runtime. +JSR_API +jsr_create_node_api_env(napi_env root_env, int32_t api_version, napi_env* env); + +// Run task in the environment context. +JSR_API jsr_run_task(napi_env env, jsr_task_run_cb task_cb, void* data); + +// Initializes native module. +JSR_API jsr_initialize_native_module(napi_env env, + napi_addon_register_func register_module, + int32_t api_version, + napi_value* exports); + +EXTERN_C_END + +#endif // !SRC_JS_RUNTIME_API_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/node-api/node_api.h b/vnext/Microsoft.ReactNative.Cxx/node-api/node_api.h new file mode 100644 index 00000000000..4ebfbd46d81 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/node-api/node_api.h @@ -0,0 +1,270 @@ +#ifndef SRC_NODE_API_H_ +#define SRC_NODE_API_H_ + +#if defined(BUILDING_NODE_EXTENSION) && !defined(NAPI_EXTERN) +#ifdef _WIN32 +// Building native addon against node +#define NAPI_EXTERN __declspec(dllimport) +#elif defined(__wasm__) +#define NAPI_EXTERN __attribute__((__import_module__("napi"))) +#endif +#endif +#include "js_native_api.h" +#include "node_api_types.h" + +struct uv_loop_s; // Forward declaration. + +#ifdef _WIN32 +#define NAPI_MODULE_EXPORT __declspec(dllexport) +#else +#ifdef __EMSCRIPTEN__ +#define NAPI_MODULE_EXPORT \ + __attribute__((visibility("default"))) __attribute__((used)) +#else +#define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +#if defined(__GNUC__) +#define NAPI_NO_RETURN __attribute__((noreturn)) +#elif defined(_WIN32) +#define NAPI_NO_RETURN __declspec(noreturn) +#else +#define NAPI_NO_RETURN +#endif + +typedef napi_value(NAPI_CDECL* napi_addon_register_func)(napi_env env, + napi_value exports); +typedef int32_t(NAPI_CDECL* node_api_addon_get_api_version_func)(void); + +// Used by deprecated registration method napi_module_register. +typedef struct napi_module { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + napi_addon_register_func nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +} napi_module; + +#define NAPI_MODULE_VERSION 1 + +#define NAPI_MODULE_INITIALIZER_X(base, version) \ + NAPI_MODULE_INITIALIZER_X_HELPER(base, version) +#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version + +#ifdef __wasm__ +#define NAPI_MODULE_INITIALIZER_BASE napi_register_wasm_v +#else +#define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v +#endif + +#define NODE_API_MODULE_GET_API_VERSION_BASE node_api_module_get_api_version_v + +#define NAPI_MODULE_INITIALIZER \ + NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, NAPI_MODULE_VERSION) + +#define NODE_API_MODULE_GET_API_VERSION \ + NAPI_MODULE_INITIALIZER_X(NODE_API_MODULE_GET_API_VERSION_BASE, \ + NAPI_MODULE_VERSION) + +#define NAPI_MODULE_INIT() \ + EXTERN_C_START \ + NAPI_MODULE_EXPORT int32_t NODE_API_MODULE_GET_API_VERSION(void) { \ + return NAPI_VERSION; \ + } \ + NAPI_MODULE_EXPORT napi_value NAPI_MODULE_INITIALIZER(napi_env env, \ + napi_value exports); \ + EXTERN_C_END \ + napi_value NAPI_MODULE_INITIALIZER(napi_env env, napi_value exports) + +#define NAPI_MODULE(modname, regfunc) \ + NAPI_MODULE_INIT() { \ + return regfunc(env, exports); \ + } + +// Deprecated. Use NAPI_MODULE. +#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ + NAPI_MODULE(modname, regfunc) + +EXTERN_C_START + +// Deprecated. Replaced by symbol-based registration defined by NAPI_MODULE +// and NAPI_MODULE_INIT macros. +NAPI_EXTERN void NAPI_CDECL napi_module_register(napi_module* mod); + +NAPI_EXTERN NAPI_NO_RETURN void NAPI_CDECL +napi_fatal_error(const char* location, + size_t location_len, + const char* message, + size_t message_len); + +// Methods for custom handling of async operations +NAPI_EXTERN napi_status NAPI_CDECL +napi_async_init(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_context* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_async_destroy(napi_env env, napi_async_context async_context); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_make_callback(napi_env env, + napi_async_context async_context, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); + +// Methods to provide node::Buffer functionality with napi types +NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer(napi_env env, + size_t length, + void** data, + napi_value* result); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external_buffer(napi_env env, + size_t length, + void* data, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +#if NAPI_VERSION >= 10 + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_buffer_from_arraybuffer(napi_env env, + napi_value arraybuffer, + size_t byte_offset, + size_t byte_length, + napi_value* result); +#endif // NAPI_VERSION >= 10 + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_buffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length); + +// Methods to manage simple async operations +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_basic_env env, + napi_async_work work); +NAPI_EXTERN napi_status NAPI_CDECL +napi_cancel_async_work(node_api_basic_env env, napi_async_work work); + +// version management +NAPI_EXTERN napi_status NAPI_CDECL napi_get_node_version( + node_api_basic_env env, const napi_node_version** version); + +#if NAPI_VERSION >= 2 + +// Return the current libuv event loop for a given environment +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_uv_event_loop(node_api_basic_env env, struct uv_loop_s** loop); + +#endif // NAPI_VERSION >= 2 + +#if NAPI_VERSION >= 3 + +NAPI_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env, + napi_value err); + +NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook( + node_api_basic_env env, napi_cleanup_hook fun, void* arg); + +NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook( + node_api_basic_env env, napi_cleanup_hook fun, void* arg); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context context, + napi_callback_scope* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_close_callback_scope(napi_env env, napi_callback_scope scope); + +#endif // NAPI_VERSION >= 3 + +#if NAPI_VERSION >= 4 + +// Calling into JS from other threads +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_threadsafe_function(napi_env env, + napi_value func, + napi_value async_resource, + napi_value async_resource_name, + size_t max_queue_size, + size_t initial_thread_count, + void* thread_finalize_data, + napi_finalize thread_finalize_cb, + void* context, + napi_threadsafe_function_call_js call_js_cb, + napi_threadsafe_function* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_threadsafe_function_context( + napi_threadsafe_function func, void** result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_call_threadsafe_function(napi_threadsafe_function func, + void* data, + napi_threadsafe_function_call_mode is_blocking); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_acquire_threadsafe_function(napi_threadsafe_function func); + +NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function( + napi_threadsafe_function func, napi_threadsafe_function_release_mode mode); + +NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function( + node_api_basic_env env, napi_threadsafe_function func); + +NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function( + node_api_basic_env env, napi_threadsafe_function func); + +#endif // NAPI_VERSION >= 4 + +#if NAPI_VERSION >= 8 + +NAPI_EXTERN napi_status NAPI_CDECL +napi_add_async_cleanup_hook(node_api_basic_env env, + napi_async_cleanup_hook hook, + void* arg, + napi_async_cleanup_hook_handle* remove_handle); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle); + +#endif // NAPI_VERSION >= 8 + +#if NAPI_VERSION >= 9 + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_get_module_file_name(node_api_basic_env env, const char** result); + +#endif // NAPI_VERSION >= 9 + +EXTERN_C_END + +#endif // SRC_NODE_API_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/node-api/node_api_types.h b/vnext/Microsoft.ReactNative.Cxx/node-api/node_api_types.h new file mode 100644 index 00000000000..9c2f03f4d09 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/node-api/node_api_types.h @@ -0,0 +1,52 @@ +#ifndef SRC_NODE_API_TYPES_H_ +#define SRC_NODE_API_TYPES_H_ + +#include "js_native_api_types.h" + +typedef struct napi_callback_scope__* napi_callback_scope; +typedef struct napi_async_context__* napi_async_context; +typedef struct napi_async_work__* napi_async_work; + +#if NAPI_VERSION >= 3 +typedef void(NAPI_CDECL* napi_cleanup_hook)(void* arg); +#endif // NAPI_VERSION >= 3 + +#if NAPI_VERSION >= 4 +typedef struct napi_threadsafe_function__* napi_threadsafe_function; +#endif // NAPI_VERSION >= 4 + +#if NAPI_VERSION >= 4 +typedef enum { + napi_tsfn_release, + napi_tsfn_abort +} napi_threadsafe_function_release_mode; + +typedef enum { + napi_tsfn_nonblocking, + napi_tsfn_blocking +} napi_threadsafe_function_call_mode; +#endif // NAPI_VERSION >= 4 + +typedef void(NAPI_CDECL* napi_async_execute_callback)(napi_env env, void* data); +typedef void(NAPI_CDECL* napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); +#if NAPI_VERSION >= 4 +typedef void(NAPI_CDECL* napi_threadsafe_function_call_js)( + napi_env env, napi_value js_callback, void* context, void* data); +#endif // NAPI_VERSION >= 4 + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; + +#if NAPI_VERSION >= 8 +typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle; +typedef void(NAPI_CDECL* napi_async_cleanup_hook)( + napi_async_cleanup_hook_handle handle, void* data); +#endif // NAPI_VERSION >= 8 + +#endif // SRC_NODE_API_TYPES_H_ diff --git a/vnext/Microsoft.ReactNative.Cxx/stubs/glog/logging.h b/vnext/Microsoft.ReactNative.Cxx/stubs/glog/logging.h new file mode 100644 index 00000000000..94761f25fd2 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/stubs/glog/logging.h @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// TODO: Use actual glog +// TODO: Using actual glog non-trivial as it uses Desktop only APIs +// TODO: Maybe implement glog header in terms of Windows' +// TraceLoggingProvider.h? + +#pragma once +#include + +#define CHECK(b) !(b) && GlogStub::LogMessageFatal{}.stream() +#define CHECK_GE(a, b) !(a >= b) && GlogStub::LogMessageFatal{}.stream() +#define DCHECK(b) !(b) && GlogStub::LogMessageFatal{}.stream() + +#ifdef DEBUG +#define DCHECK_GT(v1, v2) CHECK((v1) > (v2)) +#else +#define DCHECK_GT(v1, v2) +#endif + +#ifdef DEBUG +#define DCHECK_LE(v1, v2) CHECK((v1) <= (v2)) +#else +#define DCHECK_LE(v1, v2) +#endif + +#ifdef DEBUG +#define DCHECK_GE(v1, v2) CHECK((v1) >= (v2)) +#else +#define DCHECK_GE(v1, v2) +#endif + +#ifdef DEBUG +#define DCHECK_EQ(v1, v2) CHECK((v1) == (v2)) +#else +#define DCHECK_EQ(v1, v2) +#endif + +namespace GlogStub { + +struct LogMessageFatal { +#pragma warning(suppress : 4722) // destructor does not return + [[noreturn]] ~LogMessageFatal() noexcept { + std::abort(); + } + + std::ostream &stream() { + return std::cerr; + } +}; + +#ifndef LOG + +struct NullBuffer : public std::streambuf { + // Put character on overflow. + // It is called by other functions in streambuf. + // By doing nothing in this function and not returning error, we effectively + // implement a streambuf that does nothing and just 'eats' the input. + int overflow(int c) override { + return c; + } +}; + +inline std::ostream &GetNullLog() noexcept { + static NullBuffer nullBuffer; + static std::ostream nullStream(&nullBuffer); + return nullStream; +} + +#define LOG(b) GlogStub::GetNullLog() +#define LOG_EVERY_N(severity, n) GlogStub::GetNullLog() + +typedef int LogSeverity; +inline void FlushLogFiles(LogSeverity) {} + +#define google GlogStub +static const int GLOG_INFO = 1; + +#endif + +} // namespace GlogStub