Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions eng/testing/tests.wasm.targets
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@
<_WasmVFSFilesToCopy Include="@(WasmFilesToIncludeInFileSystem)" />
<_WasmVFSFilesToCopy TargetPath="%(FileName)%(Extension)" Condition="'%(TargetPath)' == ''" />

<!-- the custom marshaler support requires the .csproj to include a list of custom marshalers and types
that will get included in the generated mono-config so that the runtime knows about them. Without this
any application or test relying on custom marshalers will break -->
<_WasmItemsToPass Include="@(WasmMarshaledType)" OriginalItemName__="WasmMarshaledType" />

<!-- Example of passing items to the project

<_WasmItemsToPass Include="@(BundleFiles)" OriginalItemName__="BundleFiles" ConditionToUse__="'$(Foo)' != 'true'" />
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ internal static partial class Runtime
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern string CancelPromise(int promiseJSHandle, out int exceptionalResult);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern int InvokeJSFunction(
string internedFunctionName, int argumentCount,
IntPtr type1, IntPtr arg1,
IntPtr type2, IntPtr arg2,
IntPtr type3, IntPtr arg3
);

// / <summary>
// / Execute the provided string in the JavaScript context
// / </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<linker>
<assembly fullname="System.Private.Runtime.InteropServices.JavaScript">
<!-- We need to preserve these marshalers during the library build, because whether they are used is not known -->
<!-- until end users' applications are built. At that point they can be linked out -->
<type fullname="System.Runtime.InteropServices.JavaScript.UriMarshaler" />
<type fullname="System.Runtime.InteropServices.JavaScript.DateTimeMarshaler" />
<type fullname="System.Runtime.InteropServices.JavaScript.DateTimeOffsetMarshaler" />
</assembly>
</linker>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<linker>
<assembly fullname="System.Private.Runtime.InteropServices.JavaScript">
<!-- HACK: Hard-coded preserve for all custom marshalers until we implement the msbuild task to generate this on-demand -->
<type fullname="System.Runtime.InteropServices.JavaScript.UriMarshaler" />
<type fullname="System.Runtime.InteropServices.JavaScript.DateTimeMarshaler" />
<type fullname="System.Runtime.InteropServices.JavaScript.DateTimeOffsetMarshaler" />
</assembly>
</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
<Compile Include="$(CommonPath)Interop\Browser\Interop.Runtime.cs" Link="Common\Interop\Browser\Interop.Runtime.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Codegen.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.CS.Owned.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.JS.Owned.cs" />
Expand All @@ -31,6 +32,9 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\HostObject.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Function.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSObject.References.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\DateTimeMarshaler.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\DateTimeOffsetMarshaler.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\UriMarshaler.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Runtime" />
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace System.Runtime.InteropServices.JavaScript
{
public static class DateTimeMarshaler
{
public static string JavaScriptToInterchangeTransform => @"
switch (typeof (value)) {
case 'number':
return value;
default:
if (value instanceof Date) {
return value.valueOf();
} else
throw new Error('Value must be a number (msecs since unix epoch), or a Date');
}
";
public static string InterchangeToJavaScriptTransform => "return new Date(value)";

public static DateTime FromJavaScript (double msecsSinceEpoch)
{
return DateTimeOffset.FromUnixTimeMilliseconds((long)msecsSinceEpoch).UtcDateTime;
}

public static double ToJavaScript (in DateTime dt)
{
return (double)new DateTimeOffset(dt).ToUnixTimeMilliseconds();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace System.Runtime.InteropServices.JavaScript
{
public static class DateTimeOffsetMarshaler
{
public static string JavaScriptToInterchangeTransform => DateTimeMarshaler.JavaScriptToInterchangeTransform;
public static string InterchangeToJavaScriptTransform => DateTimeMarshaler.InterchangeToJavaScriptTransform;

public static DateTimeOffset FromJavaScript (double msecsSinceEpoch)
{
return DateTimeOffset.FromUnixTimeMilliseconds((long)msecsSinceEpoch);
}

public static double ToJavaScript (in DateTimeOffset dto)
{
return (double)dto.ToUnixTimeMilliseconds();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ namespace System.Runtime.InteropServices.JavaScript
/// </summary>
public class JSException : Exception
{
public string? Stack;

public JSException(string msg) : base(msg) { }

public JSException(string msg, string? stack) : base(msg) {
Stack = stack;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal void AddInFlight()
InFlightCounter++;
if (InFlightCounter == 1)
{
Debug.Assert(InFlight == null);
Debug.Assert(InFlight == null, "InFlight == null");
InFlight = GCHandle.Alloc(this, GCHandleType.Normal);
}
}
Expand All @@ -61,12 +61,12 @@ internal void ReleaseInFlight()
{
lock (this)
{
Debug.Assert(InFlightCounter != 0);
Debug.Assert(InFlightCounter != 0, "InFlightCounter != 0");

InFlightCounter--;
if (InFlightCounter == 0)
{
Debug.Assert(InFlight.HasValue);
Debug.Assert(InFlight.HasValue, "InFlight.HasValue");
InFlight.Value.Free();
InFlight = null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Console = System.Diagnostics.Debug;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Runtime.InteropServices.JavaScript
{
public interface IJSObject
Expand All @@ -15,6 +20,30 @@ public interface IJSObject
/// </summary>
public partial class JSObject : IJSObject, IDisposable
{
[StructLayout(LayoutKind.Sequential)]
private struct InvokeRecord {
public object?[] Arguments;
// FIXME: Make this object? and update Invoke
public object Result;
public string? ErrorMessage;
public string? ErrorStack;
}

[StructLayout(LayoutKind.Sequential)]
private struct GetPropertyRecord {
public object Value;
public string? ErrorMessage;
public string? ErrorStack;
}

[StructLayout(LayoutKind.Sequential)]
private struct SetPropertyRecord {
public object? Value;
public string? ErrorMessage;
public string? ErrorStack;
public int CreateIfNotExists;
}

/// <summary>
/// Invoke a named method of the object, or throws a JSException on error.
/// </summary>
Expand All @@ -34,15 +63,23 @@ public partial class JSObject : IJSObject, IDisposable
/// valuews.
/// </para>
/// </returns>
public object Invoke(string method, params object?[] args)
// FIXME: This should be object?, but if we correct it lots of stuff breaks
public unsafe object Invoke(string method, params object?[] args)
{
AssertNotDisposed();

object res = Interop.Runtime.InvokeJSWithArgs(JSHandle, method, args, out int exception);
if (exception != 0)
throw new JSException((string)res);
Interop.Runtime.ReleaseInFlight(res);
return res;
var record = new InvokeRecord {
Arguments = args
};
var pRecord = (IntPtr)Unsafe.AsPointer(ref record);
var invokeResult = Runtime.InvokeJSFunctionByName("INTERNAL._JSObject_Invoke", (IntPtr)JSHandle, method, pRecord);
if (invokeResult != InvokeJSResult.Success)
throw new JSException($"Invoke result was {invokeResult}");
else if (record.ErrorMessage != null)
throw new JSException(record.ErrorMessage, record.ErrorStack);
else {
var result = record.Result;
Interop.Runtime.ReleaseInFlight(result);
return result;
}
}

public struct EventListenerOptions {
Expand Down Expand Up @@ -128,15 +165,21 @@ public void RemoveEventListener(string name, int listenerGCHandle, EventListener
/// valuews.
/// </para>
/// </returns>
public object GetObjectProperty(string name)
public unsafe object GetObjectProperty(string name)
{
AssertNotDisposed();

object propertyValue = Interop.Runtime.GetObjectProperty(JSHandle, name, out int exception);
if (exception != 0)
throw new JSException((string)propertyValue);
Interop.Runtime.ReleaseInFlight(propertyValue);
return propertyValue;
var record = new GetPropertyRecord {
};
var pRecord = (IntPtr)Unsafe.AsPointer(ref record);
var invokeResult = Runtime.InvokeJSFunctionByName("INTERNAL._JSObject_GetProperty", (IntPtr)JSHandle, name, pRecord);
if (invokeResult != InvokeJSResult.Success)
throw new JSException($"Invoke result was {invokeResult}");
else if (record.ErrorMessage != null)
throw new JSException(record.ErrorMessage, record.ErrorStack);
else {
var result = record.Value;
Interop.Runtime.ReleaseInFlight(result);
return result;
}
}

/// <summary>
Expand All @@ -149,14 +192,20 @@ public object GetObjectProperty(string name)
/// array that will be surfaced as a typed ArrayBuffer (byte[], sbyte[], short[], ushort[],
/// float[], double[]) </param>
/// <param name="createIfNotExists">Defaults to <see langword="true"/> and creates the property on the javascript object if not found, if set to <see langword="false"/> it will not create the property if it does not exist. If the property exists, the value is updated with the provided value.</param>
/// <param name="hasOwnProperty"></param>
public void SetObjectProperty(string name, object value, bool createIfNotExists = true, bool hasOwnProperty = false)
/// <param name="hasOwnProperty">does nothing</param>
// FIXME: hasOwnProperty is unused.
public unsafe void SetObjectProperty(string name, object value, bool createIfNotExists = true, bool hasOwnProperty = false)
{
AssertNotDisposed();

object setPropResult = Interop.Runtime.SetObjectProperty(JSHandle, name, value, createIfNotExists, hasOwnProperty, out int exception);
if (exception != 0)
throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}')");
var record = new SetPropertyRecord {
Value = value,
CreateIfNotExists = createIfNotExists ? 1 : 0
};
var pRecord = (IntPtr)Unsafe.AsPointer(ref record);
var invokeResult = Runtime.InvokeJSFunctionByName("INTERNAL._JSObject_SetProperty", (IntPtr)JSHandle, name, pRecord);
if (invokeResult != InvokeJSResult.Success)
throw new JSException($"Invoke result was {invokeResult}");
else if (record.ErrorMessage != null)
throw new JSException(record.ErrorMessage, record.ErrorStack);
}

/// <summary>
Expand Down
Loading