@nativescript/windows-hermes 0.1.0-next.2026-08-03-30819078286 → 1.0.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/framework/__PROJECT_NAME__/App.xaml.cs +3 -2
  2. package/framework/__PROJECT_NAME__/CrashDiagnostics.cs +3 -3
  3. package/framework/__PROJECT_NAME__/RuntimeHost.cs +149 -24
  4. package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +35 -41
  5. package/framework/dotnet-bridge/BinaryProtocol.cs +18 -0
  6. package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +83 -8
  7. package/framework/dotnet-bridge/Bridge.Dispatch.cs +40 -35
  8. package/framework/dotnet-bridge/Bridge.JsDelegate.cs +115 -59
  9. package/framework/dotnet-bridge/Bridge.Proxy.cs +158 -230
  10. package/framework/dotnet-bridge/Bridge.TaskHelper.cs +2 -1
  11. package/framework/dotnet-bridge/Bridge.cs +45 -5
  12. package/framework/dotnet-bridge/DotNetBridge.csproj +8 -0
  13. package/framework/libs/x64/nativescript.dll +0 -0
  14. package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
  15. package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
  16. package/framework/tools/dotnet-tool-x64.exe +0 -0
  17. package/framework/tools/dotnet-tool.exe +0 -0
  18. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.dll +0 -0
  19. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.exe +0 -0
  20. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.pdb +0 -0
  21. package/framework/tools/nsbundle_pack-x64.exe +0 -0
  22. package/framework/tools/nsbundle_pack.exe +0 -0
  23. package/framework/tools/typings-generator-x64.exe +0 -0
  24. package/framework/tools/typings-generator.exe +0 -0
  25. package/package.json +1 -1
  26. package/framework/tools/sbg-arm64.exe +0 -0
  27. package/framework/tools/sbg-x64.exe +0 -0
  28. package/framework/tools/sbg.exe +0 -0
@@ -57,7 +57,7 @@ namespace __PROJECT_NAME__
57
57
  try
58
58
  {
59
59
  var panicLogPath = System.IO.Path.Combine(
60
- ApplicationData.Current.LocalFolder.Path, "nativescript-panic.log");
60
+ RuntimeHost.LocalFolderPath, "nativescript-panic.log");
61
61
  if (System.IO.File.Exists(panicLogPath))
62
62
  {
63
63
  var content = System.IO.File.ReadAllText(panicLogPath);
@@ -121,7 +121,8 @@ namespace __PROJECT_NAME__
121
121
  {
122
122
  StopPump();
123
123
  _runtimeHost.NotifyAppEvent((int)AppEventKind.Exit, null);
124
- ApplicationData.Current.LocalSettings.Values[LastLaunchArgsKey] = string.Empty;
124
+ try { ApplicationData.Current.LocalSettings.Values[LastLaunchArgsKey] = string.Empty; }
125
+ catch (InvalidOperationException) { } // unpackaged: no ApplicationData
125
126
  _runtimeHost.Dispose();
126
127
  }
127
128
 
@@ -101,7 +101,7 @@ namespace __PROJECT_NAME__
101
101
  // Include panic log from previous or current run
102
102
  try
103
103
  {
104
- var panicLog = Path.Combine(ApplicationData.Current.LocalFolder.Path, "nativescript-panic.log");
104
+ var panicLog = Path.Combine(RuntimeHost.LocalFolderPath, "nativescript-panic.log");
105
105
  if (File.Exists(panicLog))
106
106
  {
107
107
  var content = File.ReadAllText(panicLog, Encoding.UTF8).Trim();
@@ -130,7 +130,7 @@ namespace __PROJECT_NAME__
130
130
 
131
131
  public static string CrashLogPath()
132
132
  {
133
- try { return Path.Combine(ApplicationData.Current.LocalFolder.Path, "nativescript-crash.log"); }
133
+ try { return Path.Combine(RuntimeHost.LocalFolderPath, "nativescript-crash.log"); }
134
134
  catch { return null; }
135
135
  }
136
136
 
@@ -272,7 +272,7 @@ namespace __PROJECT_NAME__
272
272
  {
273
273
  try
274
274
  {
275
- var logPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "nativescript-crash.log");
275
+ var logPath = Path.Combine(RuntimeHost.LocalFolderPath, "nativescript-crash.log");
276
276
  File.AppendAllText(logPath, content, Encoding.UTF8);
277
277
  }
278
278
  catch { }
@@ -32,6 +32,20 @@ namespace __PROJECT_NAME__
32
32
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_set_local_folder))]
33
33
  private static extern void runtime_set_local_folder([MarshalAs(UnmanagedType.LPUTF8Str)] string localFolder);
34
34
 
35
+ // Escape hatch for apps packed with `nsbundle_pack --key-hex <hex>` (custom-key
36
+ // app.nsbundle containers). Must be called before runtime_init if used at all: apps
37
+ // packed with the default pepper (no --key-hex) never need to call this. Not wired to a
38
+ // default call site here; an app author supplies their own key material and calls this
39
+ // from Initialize() before runtime_init(...).
40
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_set_bundle_key))]
41
+ private static extern int runtime_set_bundle_key([MarshalAs(UnmanagedType.LPUTF8Str)] string keyHex);
42
+
43
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_read_protected_file))]
44
+ private static extern IntPtr runtime_read_protected_file([MarshalAs(UnmanagedType.LPUTF8Str)] string virtualPath);
45
+
46
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_free_protected_string))]
47
+ private static extern void runtime_free_protected_string(IntPtr ptr);
48
+
35
49
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_get_last_js_error))]
36
50
  private static extern IntPtr runtime_get_last_js_error();
37
51
 
@@ -39,6 +53,7 @@ namespace __PROJECT_NAME__
39
53
  private static extern void runtime_free_js_error(IntPtr ptr);
40
54
 
41
55
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_has_devtools))]
56
+ [return: MarshalAs(UnmanagedType.U1)]
42
57
  private static extern bool runtime_has_devtools();
43
58
 
44
59
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_pump_timers))]
@@ -55,6 +70,18 @@ namespace __PROJECT_NAME__
55
70
  }
56
71
 
57
72
  #if DEBUG
73
+ // Engine runtimes (@nativescript/windows-<engine>) only: a debug host always allows
74
+ // dev-server modules (Vite HMR). The classic runtime derives this from its devtools build
75
+ // and doesn't export it.
76
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_set_debug_build))]
77
+ private static extern void runtime_set_debug_build(int debug);
78
+
79
+ private static void TrySetDebugBuild()
80
+ {
81
+ try { runtime_set_debug_build(1); }
82
+ catch (EntryPointNotFoundException) { }
83
+ }
84
+
58
85
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(ns_set_log_to_console))]
59
86
  private static extern int ns_set_log_to_console(int enabled);
60
87
 
@@ -84,6 +111,21 @@ namespace __PROJECT_NAME__
84
111
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_free_string))]
85
112
  private static extern void runtime_free_string(IntPtr ptr);
86
113
 
114
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_devtools_wait_for_debugger))]
115
+ [return: MarshalAs(UnmanagedType.U1)]
116
+ private static extern bool runtime_devtools_wait_for_debugger(long runtime, uint timeoutMs);
117
+
118
+ // iOS uses 41000 and Android 42000.
119
+ private const ushort DevtoolsPort = 43000;
120
+ private const uint DebuggerWaitTimeoutMs = 30000;
121
+
122
+ // LocalFolder sentinel files shared with the CLI's WindowsApplicationManager.
123
+ private const string InspectorMarker = "ns-inspector";
124
+ private const string DebugBreakMarker = "ns-debugbreak";
125
+ private const string DebuggerStartedMarker = "ns-debugger-started";
126
+
127
+ private bool _waitForDebugger;
128
+
87
129
  public string DevtoolsFrontendUrl { get; private set; }
88
130
 
89
131
  private bool _devtoolsAvailable;
@@ -104,7 +146,7 @@ namespace __PROJECT_NAME__
104
146
  IntPtr urlPtr = IntPtr.Zero;
105
147
  try
106
148
  {
107
- urlPtr = runtime_devtools_start(_runtime, 42000);
149
+ urlPtr = runtime_devtools_start(_runtime, DevtoolsPort);
108
150
  if (urlPtr == IntPtr.Zero) return;
109
151
  var wsUrl = Marshal.PtrToStringUTF8(urlPtr);
110
152
  DevtoolsFrontendUrl = wsUrl != null
@@ -113,6 +155,8 @@ namespace __PROJECT_NAME__
113
155
  if (DevtoolsFrontendUrl != null)
114
156
  {
115
157
  _devtoolsAvailable = true;
158
+ // The server may fall back to the next free port.
159
+ WriteMarker(DebuggerStartedMarker, new Uri(wsUrl).Port.ToString());
116
160
  System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] {DevtoolsFrontendUrl}");
117
161
  }
118
162
  }
@@ -131,15 +175,41 @@ namespace __PROJECT_NAME__
131
175
  private long _runtime;
132
176
  private bool _initialized;
133
177
 
178
+ private static string _localFolderPath;
179
+
180
+ /// The app's local data folder: ApplicationData's LocalFolder for a packaged app, or
181
+ /// %LOCALAPPDATA%\<AssemblyName> for an unpackaged one (WindowsPackageType=None), which has
182
+ /// no package identity and therefore no ApplicationData.
183
+ internal static string LocalFolderPath
184
+ {
185
+ get
186
+ {
187
+ if (_localFolderPath != null) return _localFolderPath;
188
+ try
189
+ {
190
+ _localFolderPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
191
+ }
192
+ catch (InvalidOperationException)
193
+ {
194
+ var name = Assembly.GetEntryAssembly()?.GetName().Name ?? "NativeScriptApp";
195
+ _localFolderPath = Path.Combine(
196
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), name);
197
+ Directory.CreateDirectory(_localFolderPath);
198
+ }
199
+ return _localFolderPath;
200
+ }
201
+ }
202
+
134
203
  public void Initialize()
135
204
  {
136
205
  if (_initialized) return;
137
206
  bool attached = false;
138
207
  try { attached = AttachConsole(ATTACH_PARENT_PROCESS); } catch { }
139
208
  runtime_install_ctrlc_handler(0);
140
- runtime_set_local_folder(Windows.Storage.ApplicationData.Current.LocalFolder.Path);
209
+ runtime_set_local_folder(LocalFolderPath);
141
210
  _runtime = runtime_init(AppContext.BaseDirectory);
142
211
  #if DEBUG
212
+ TrySetDebugBuild();
143
213
  var trySet = TrySetLogToConsole(true);
144
214
  System.Diagnostics.Debug.WriteLine($"[NativeScript] TrySetLogToConsole returned={trySet}");
145
215
 
@@ -164,38 +234,85 @@ namespace __PROJECT_NAME__
164
234
  }
165
235
  }
166
236
  catch { }
167
- if (ConsumeDebugBreakMarker())
237
+ DeleteMarker(DebuggerStartedMarker);
238
+ var debugBreak = ConsumeMarker(DebugBreakMarker);
239
+ if (ConsumeMarker(InspectorMarker) || debugBreak)
240
+ {
168
241
  StartDevtoolsSafely();
242
+ _waitForDebugger = debugBreak && _devtoolsAvailable;
243
+ }
169
244
  #endif
170
245
  _initialized = true;
171
246
  }
172
247
 
173
248
  #if DEBUG
174
- /// <summary>
175
- /// Returns true and deletes the marker if the CLI wrote ns-debugbreak to LocalFolder,
176
- /// matching the Android sentinel-file pattern used by the NativeScript CLI.
177
- /// </summary>
178
- private static bool ConsumeDebugBreakMarker()
249
+ private static string MarkerPath(string name) =>
250
+ Path.Combine(LocalFolderPath, name);
251
+
252
+ private static bool ConsumeMarker(string name)
179
253
  {
180
254
  try
181
255
  {
182
- var markerPath = System.IO.Path.Combine(
183
- Windows.Storage.ApplicationData.Current.LocalFolder.Path,
184
- "ns-debugbreak");
185
- if (!System.IO.File.Exists(markerPath))
256
+ var markerPath = MarkerPath(name);
257
+ if (!File.Exists(markerPath))
186
258
  return false;
187
- System.IO.File.Delete(markerPath);
259
+ File.Delete(markerPath);
188
260
  return true;
189
261
  }
190
262
  catch { return false; }
191
263
  }
264
+
265
+ private static void WriteMarker(string name, string contents)
266
+ {
267
+ try { File.WriteAllText(MarkerPath(name), contents); }
268
+ catch (Exception ex)
269
+ {
270
+ System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] Could not write {name}: {ex.Message}");
271
+ }
272
+ }
273
+
274
+ private static void DeleteMarker(string name)
275
+ {
276
+ try { File.Delete(MarkerPath(name)); }
277
+ catch { }
278
+ }
192
279
  #endif
193
280
 
281
+ /// Reads a file that may live inside a sealed app.nsbundle instead of on disk: tries the
282
+ /// protected-VFS native call first (cheap no-op when no bundle is loaded), falls back to
283
+ /// the real filesystem. `path` is whatever candidate the caller already built for
284
+ /// `File.Exists`/`File.ReadAllText`: the native side strips it down to the bundle's
285
+ /// virtual (app/App-relative) path itself, so no extra bookkeeping is needed here.
286
+ private static string TryReadVirtual(string path)
287
+ {
288
+ IntPtr ptr = IntPtr.Zero;
289
+ try
290
+ {
291
+ ptr = runtime_read_protected_file(path);
292
+ return ptr == IntPtr.Zero ? null : Marshal.PtrToStringUTF8(ptr);
293
+ }
294
+ catch { return null; }
295
+ finally { if (ptr != IntPtr.Zero) runtime_free_protected_string(ptr); }
296
+ }
297
+
298
+ private static bool VExists(string path) => TryReadVirtual(path) != null || File.Exists(path);
299
+
300
+ private static string VReadAllText(string path) => TryReadVirtual(path) ?? File.ReadAllText(path);
301
+
194
302
  public void RunMainScript()
195
303
  {
196
304
  if (!_initialized)
197
305
  throw new InvalidOperationException("Runtime must be initialized before running scripts.");
198
306
 
307
+ #if DEBUG
308
+ if (_waitForDebugger)
309
+ {
310
+ // --debug-brk: hold the first script until DevTools attaches.
311
+ _waitForDebugger = false;
312
+ if (!runtime_devtools_wait_for_debugger(_runtime, DebuggerWaitTimeoutMs))
313
+ System.Diagnostics.Debug.WriteLine("[NativeScript DevTools] No debugger attached, continuing.");
314
+ }
315
+ #endif
199
316
  var entryPath = ResolveEntryScriptPath();
200
317
  if (entryPath == null)
201
318
  {
@@ -205,10 +322,14 @@ namespace __PROJECT_NAME__
205
322
 
206
323
  var dir = Path.GetDirectoryName(Path.GetFullPath(entryPath));
207
324
  var chunks = new List<string>();
208
- foreach (var chunkName in new[] { "runtime.js", "vendor.js" })
325
+ // An ES-module entry (Vite's bundle.mjs) imports its own chunks; webpack's classic
326
+ // runtime.js/vendor.js prelude must not run in front of it (a stale pair can be left
327
+ // in app/ after switching bundlers).
328
+ var isModuleEntry = entryPath.EndsWith(".mjs", StringComparison.OrdinalIgnoreCase);
329
+ foreach (var chunkName in isModuleEntry ? Array.Empty<string>() : new[] { "runtime.js", "vendor.js" })
209
330
  {
210
331
  var chunkPath = Path.Combine(dir, chunkName);
211
- if (File.Exists(chunkPath) &&
332
+ if (VExists(chunkPath) &&
212
333
  !string.Equals(chunkPath, Path.GetFullPath(entryPath), StringComparison.OrdinalIgnoreCase))
213
334
  {
214
335
  chunks.Add(chunkPath);
@@ -218,10 +339,14 @@ namespace __PROJECT_NAME__
218
339
 
219
340
  foreach (var scriptPath in chunks)
220
341
  {
221
- var script = File.ReadAllText(Path.GetFullPath(scriptPath));
342
+ var script = VReadAllText(Path.GetFullPath(scriptPath));
222
343
  try
223
344
  {
224
- runtime_runscript(_runtime, script, Path.GetFileName(scriptPath));
345
+ // Modules resolve their imports (chunks, ~/ paths) relative to their own
346
+ // location, so they need the full path; classic scripts keep the short name
347
+ // their stack traces and code-cache keys have always used.
348
+ var name = isModuleEntry ? Path.GetFullPath(scriptPath) : Path.GetFileName(scriptPath);
349
+ runtime_runscript(_runtime, script, name);
225
350
  }
226
351
  catch (Exception ex)
227
352
  {
@@ -260,7 +385,7 @@ namespace __PROJECT_NAME__
260
385
  foreach (var dir in appDirCandidates)
261
386
  {
262
387
  var candidate = Path.Combine(dir, "package.json");
263
- if (File.Exists(candidate))
388
+ if (VExists(candidate))
264
389
  {
265
390
  packageJsonPath = candidate;
266
391
  resolvedBaseDir = dir;
@@ -269,7 +394,7 @@ namespace __PROJECT_NAME__
269
394
  }
270
395
 
271
396
  // Also accept package.json at the project root (parent of bin/).
272
- if (packageJsonPath == null && File.Exists(Path.Combine(parentDir, "package.json")))
397
+ if (packageJsonPath == null && VExists(Path.Combine(parentDir, "package.json")))
273
398
  {
274
399
  packageJsonPath = Path.Combine(parentDir, "package.json");
275
400
  resolvedBaseDir = parentDir;
@@ -278,7 +403,7 @@ namespace __PROJECT_NAME__
278
403
  string Fallback() =>
279
404
  appDirCandidates
280
405
  .SelectMany(d => new[] { Path.Combine(d, "bundle.js"), Path.Combine(d, "bundle.mjs") })
281
- .FirstOrDefault(File.Exists);
406
+ .FirstOrDefault(VExists);
282
407
 
283
408
  if (packageJsonPath == null)
284
409
  return Fallback();
@@ -304,7 +429,7 @@ namespace __PROJECT_NAME__
304
429
 
305
430
  private static RuntimePackageConfig ParsePackageConfig(string packageJsonPath)
306
431
  {
307
- using var doc = JsonDocument.Parse(File.ReadAllText(packageJsonPath));
432
+ using var doc = JsonDocument.Parse(VReadAllText(packageJsonPath));
308
433
  var config = new RuntimePackageConfig();
309
434
  if (doc.RootElement.TryGetProperty("main", out var main) && main.ValueKind == JsonValueKind.String)
310
435
  config.Main = main.GetString();
@@ -321,11 +446,11 @@ namespace __PROJECT_NAME__
321
446
  foreach (var candidate in new[] { normalized, normalized + ".js", normalized + ".mjs" })
322
447
  {
323
448
  var direct = Path.IsPathRooted(candidate) ? candidate : Path.Combine(baseDir, candidate);
324
- if (File.Exists(direct)) return direct;
449
+ if (VExists(direct)) return direct;
325
450
  var appLower = Path.Combine(baseDir, "app", candidate);
326
- if (File.Exists(appLower)) return appLower;
451
+ if (VExists(appLower)) return appLower;
327
452
  var appUpper = Path.Combine(baseDir, "App", candidate);
328
- if (File.Exists(appUpper)) return appUpper;
453
+ if (VExists(appUpper)) return appUpper;
329
454
  }
330
455
  return null;
331
456
  }
@@ -25,19 +25,14 @@
25
25
  <NSWindowsRoot>$(MSBuildProjectDirectory)\..</NSWindowsRoot>
26
26
  </PropertyGroup>
27
27
 
28
- <!-- Static Binding Generator (SBG) output folder and helper properties for integrating generated proxies. -->
29
28
  <PropertyGroup>
30
- <NSGenDir>$(BaseIntermediateOutputPath)_ns_\gen</NSGenDir>
31
- <SBG_OUTPUT_DIR>$(MSBuildProjectDirectory)\$(NSGenDir)</SBG_OUTPUT_DIR>
32
29
  <!-- Path to the windows-runtime repository root. Can be overridden by setting the environment
33
30
  variable `NS_WINDOWS_RUNTIME_ROOT` (useful when building an app that lives outside the
34
31
  runtime repo). If not set, fall back to the project parent directory (legacy behavior). -->
35
32
  <WindowsRuntimeRepoRoot Condition="'$(WindowsRuntimeRepoRoot)'=='' and '$(NS_WINDOWS_RUNTIME_ROOT)' != ''">$(NS_WINDOWS_RUNTIME_ROOT)</WindowsRuntimeRepoRoot>
36
33
  <WindowsRuntimeRepoRoot Condition="'$(WindowsRuntimeRepoRoot)'==''">$(MSBuildProjectDirectory)\..</WindowsRuntimeRepoRoot>
37
- <SBGMetadataSource>$(SBG_OUTPUT_DIR)\sbg_metadata.json</SBGMetadataSource>
38
34
  <!-- Resolve pre-built tool binaries shipped inside the npm package tools/ directory -->
39
35
  <NSToolsDir>$(NSWindowsRoot)\tools</NSToolsDir>
40
- <SbgExe Condition="Exists('$(NSToolsDir)\sbg.exe')">$(NSToolsDir)\sbg.exe</SbgExe>
41
36
  <DotNetToolExe Condition="Exists('$(NSToolsDir)\dotnet-tool.exe')">$(NSToolsDir)\dotnet-tool.exe</DotNetToolExe>
42
37
  </PropertyGroup>
43
38
 
@@ -87,12 +82,32 @@
87
82
  <Content Include="Properties\Default.rd.xml" />
88
83
 
89
84
  <!-- App JS/resource files — copied to the output directory on every build -->
90
- <Content Include="app\**\*">
85
+ <!-- Sealed source-protected bundle when present, takes
86
+ priority over the plaintext app\ tree; produced by the nsbundle_pack packer tool. -->
87
+ <Content Include="app.nsbundle" Condition="Exists('app.nsbundle')">
88
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
89
+ </Content>
90
+ <Content Include="app\**\*" Condition="!Exists('app.nsbundle')">
91
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
92
+ </Content>
93
+
94
+ <!-- App_Resources: platform-specific assets (images, fonts, etc.). C# sources anywhere under
95
+ App_Resources\Windows (e.g. App_Resources\Windows\src\*.cs) and in plugins\<plugin>\ are
96
+ compiled into the app by the SDK's default Compile glob — the counterpart of Java/Kotlin in
97
+ App_Resources/Android/src and Objective-C/Swift in App_Resources/iOS/src — and JavaScript
98
+ reaches their types by namespace. Sources and MSBuild files aren't copied to the output. -->
99
+ <Content Include="App_Resources\**\*" Exclude="App_Resources\**\*.cs;App_Resources\**\*.csproj;App_Resources\**\*.props;App_Resources\**\*.targets" Condition="Exists('App_Resources')">
91
100
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
92
101
  </Content>
93
102
 
94
- <!-- App_Resources: platform-specific assets (images, fonts, etc.) -->
95
- <Content Include="App_Resources\**\*" Condition="Exists('App_Resources')">
103
+ <!-- The app's metadata filter (App_Resources/Windows/native-api-usage.json, which the CLI writes
104
+ out next to this project): the runtime reads it next to the exe. -->
105
+ <Content Include="$(NSWindowsRoot)\whitelist.mdg" Condition="Exists('$(NSWindowsRoot)\whitelist.mdg')">
106
+ <Link>whitelist.mdg</Link>
107
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
108
+ </Content>
109
+ <Content Include="$(NSWindowsRoot)\blacklist.mdg" Condition="Exists('$(NSWindowsRoot)\blacklist.mdg')">
110
+ <Link>blacklist.mdg</Link>
96
111
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
97
112
  </Content>
98
113
 
@@ -170,44 +185,23 @@
170
185
  SkipUnchangedFiles="true" />
171
186
  </Target>
172
187
 
173
- <!-- Run the Static Binding Generator (sbg) when project contains an sbg entry script
174
- or when pre-existing metadata is present. Generated proxies will be written
175
- into $(NSGenDir) and included in compilation. -->
176
- <Target Name="RunStaticBindingGenerator" BeforeTargets="CoreCompile">
177
- <MakeDir Directories="$(NSGenDir)" />
178
- <!-- Run dotnet-tool to auto-detect .NET usage and publish/copy DotNetBridge.
179
- Prefer the pre-built binary shipped in the npm package; fall back to cargo run for dev. -->
180
- <Exec Command="&quot;$(DotNetToolExe)&quot; --app-root &quot;$(MSBuildProjectDirectory)&quot; --dir App --sbg-output &quot;$(SBG_OUTPUT_DIR)&quot;"
188
+ <!-- A filter the app dropped must not stay in force from an earlier build's copy. -->
189
+ <Target Name="RemoveStaleMetadataFilter" BeforeTargets="CopyFilesToOutputDirectory">
190
+ <Delete Files="$(OutDir)whitelist.mdg" Condition="!Exists('$(NSWindowsRoot)\whitelist.mdg')" />
191
+ <Delete Files="$(OutDir)blacklist.mdg" Condition="!Exists('$(NSWindowsRoot)\blacklist.mdg')" />
192
+ </Target>
193
+
194
+ <!-- Run dotnet-tool to auto-detect .NET usage and publish/copy DotNetBridge. Prefer the pre-built
195
+ binary shipped in the npm package; fall back to cargo run for dev. JS subclasses of .NET and
196
+ WinRT classes need no generated code: the bridge creates their managed types at runtime. -->
197
+ <Target Name="RunDotNetTool" BeforeTargets="CoreCompile">
198
+ <Exec Command="&quot;$(DotNetToolExe)&quot; --app-root &quot;$(MSBuildProjectDirectory)&quot; --dir App"
181
199
  Condition="'$(DotNetToolExe)' != ''" />
182
200
  <Exec WorkingDirectory="$(WindowsRuntimeRepoRoot)"
183
- Command="cargo run --quiet --manifest-path &quot;$(WindowsRuntimeRepoRoot)\Cargo.toml&quot; -p dotnet-tool -- --app-root &quot;$(MSBuildProjectDirectory)&quot; --dir App --sbg-output &quot;$(SBG_OUTPUT_DIR)&quot;"
201
+ Command="cargo run --quiet --manifest-path &quot;$(WindowsRuntimeRepoRoot)\Cargo.toml&quot; -p dotnet-tool -- --app-root &quot;$(MSBuildProjectDirectory)&quot; --dir App"
184
202
  Condition="'$(DotNetToolExe)' == '' and Exists('$(WindowsRuntimeRepoRoot)\Cargo.toml')" />
185
- <!-- Run SBG to generate C# proxies from captured metadata.
186
- sbg_metadata.json is written by __nsProxyAutoCapture when the app runs
187
- with extensions that use .extend() / @Interfaces / @CSharpProxy.
188
- Search order: pre-built tools/ binary, then cargo run for dev. -->
189
- <Exec
190
- Command="&quot;$(SbgExe)&quot;"
191
- EnvironmentVariables="NSWINRT_AUTO_METADATA_PATH=$(SBGMetadataSource);SBG_OUTPUT_DIR=$(SBG_OUTPUT_DIR);SBG_METADATA_SOURCE=$(SBGMetadataSource);SBG_TARGET_FRAMEWORK=$(TargetFramework);SBG_TARGET_PLATFORM_MIN_VERSION=$(TargetPlatformMinVersion);SBG_USE_UWP=false;SBG_APP_CS_SOURCES_DIR=$(MSBuildProjectDirectory)"
192
- Condition="'$(SbgExe)' != '' and Exists('$(SBGMetadataSource)')" />
193
- <Exec WorkingDirectory="$(WindowsRuntimeRepoRoot)"
194
- Command="cargo run --quiet --manifest-path &quot;$(WindowsRuntimeRepoRoot)\Cargo.toml&quot; -p sbg"
195
- EnvironmentVariables="NSWINRT_AUTO_METADATA_PATH=$(SBGMetadataSource);SBG_OUTPUT_DIR=$(SBG_OUTPUT_DIR);SBG_METADATA_SOURCE=$(SBGMetadataSource);SBG_TARGET_FRAMEWORK=$(TargetFramework);SBG_TARGET_PLATFORM_MIN_VERSION=$(TargetPlatformMinVersion);SBG_USE_UWP=false;SBG_APP_CS_SOURCES_DIR=$(MSBuildProjectDirectory)"
196
- Condition="'$(SbgExe)' == '' and Exists('$(SBGMetadataSource)') and Exists('$(WindowsRuntimeRepoRoot)\Cargo.toml')" />
197
203
  </Target>
198
204
 
199
- <Target Name="CleanStaticBindingGeneratorOutput" AfterTargets="CoreClean">
200
- <RemoveDir Directories="$(NSGenDir)" Condition="Exists('$(NSGenDir)')" />
201
- </Target>
202
-
203
- <ItemGroup>
204
- <Compile Include="$(NSGenDir)\NSWinRTProxies\**\*.cs" Visible="false" Condition="Exists('$(NSGenDir)\NSWinRTProxies')" />
205
- <Compile Remove="$(NSGenDir)\NSWinRTProxies\obj\**\*.cs" Condition="Exists('$(NSGenDir)\NSWinRTProxies\obj')" />
206
- <Content Include="$(NSGenDir)\sbg-manifest.json" Link="sbg-manifest.json" CopyToOutputDirectory="PreserveNewest" Condition="Exists('$(NSGenDir)\sbg-manifest.json')" />
207
- <Content Include="$(NSGenDir)\NSWinRTProxies\bin\Release\**\NSWinRTProxies.dll" Link="NSWinRTProxies.dll" CopyToOutputDirectory="PreserveNewest" Condition="Exists('$(NSGenDir)\NSWinRTProxies\bin\Release')" />
208
- </ItemGroup>
209
-
210
-
211
205
  <!-- Ensure bridge files are included in the final `dotnet publish` output. -->
212
206
  <Target Name="CopyDotNetBridgeToPublishDir"
213
207
  AfterTargets="Publish"
@@ -17,6 +17,20 @@ internal readonly struct WinRtRef(long ptr)
17
17
  public readonly long Ptr = ptr;
18
18
  }
19
19
 
20
+ // A JS function passed where a delegate is expected (tag 0x0C): the runtime's callback id, wrapped
21
+ // in a delegate of the parameter's type when the call's arguments are coerced.
22
+ internal readonly struct JsFunctionRef(int id)
23
+ {
24
+ public readonly int Id = id;
25
+ }
26
+
27
+ // A plain JS object (tag 0x0D) as JSON, deserialized into the struct/record/class the parameter or
28
+ // return type expects (e.g. `{ Width: 120, Height: 40 }` for Windows.Foundation.Size).
29
+ internal sealed class JsJsonValue(string json)
30
+ {
31
+ public readonly string Json = json;
32
+ }
33
+
20
34
  internal ref struct BinReader(ReadOnlySpan<byte> buf)
21
35
  {
22
36
  private readonly ReadOnlySpan<byte> _buf = buf;
@@ -24,6 +38,8 @@ internal ref struct BinReader(ReadOnlySpan<byte> buf)
24
38
 
25
39
  public byte ReadByte() => _buf[_pos++];
26
40
 
41
+ public bool HasMore => _pos < _buf.Length;
42
+
27
43
  public int ReadI32()
28
44
  {
29
45
  var v = BinaryPrimitives.ReadInt32LittleEndian(_buf[_pos..]);
@@ -96,6 +112,8 @@ internal ref struct BinReader(ReadOnlySpan<byte> buf)
96
112
  0x05 => (object)ReadString16(),
97
113
  0x06 => (object)new HandleRef(ReadI32()),
98
114
  0x0A => (object)new WinRtRef(ReadI64()),
115
+ 0x0C => (object)new JsFunctionRef(ReadI32()),
116
+ 0x0D => (object)new JsJsonValue(ReadString32()),
99
117
  _ => null,
100
118
  };
101
119
  }