diff --git a/Photino.NET/PhotinoDllImports.cs b/Photino.NET/PhotinoDllImports.cs
index 645acefc..c6b43996 100644
--- a/Photino.NET/PhotinoDllImports.cs
+++ b/Photino.NET/PhotinoDllImports.cs
@@ -146,12 +146,17 @@ public partial class PhotinoWindow
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
static partial void Photino_NavigateToString(IntPtr instance, string content);
- [LibraryImport(DLL_NAME, SetLastError = true, StringMarshalling = StringMarshalling.Utf8)]
- [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
- static partial void Photino_NavigateToUrl(IntPtr instance, string url);
-
-
- //SET
+ [LibraryImport(DLL_NAME, SetLastError = true, StringMarshalling = StringMarshalling.Utf8)]
+ [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
+ static partial void Photino_NavigateToUrl(IntPtr instance, string url);
+
+ [LibraryImport(DLL_NAME, SetLastError = true, StringMarshalling = StringMarshalling.Utf8)]
+ [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
+ [return: MarshalAs(UnmanagedType.I1)]
+ private static partial bool Photino_ExecuteScript(IntPtr instance, string script);
+
+
+ //SET
[LibraryImport(DLL_NAME, SetLastError = true, StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
static partial void Photino_setWebView2RuntimePath_win32(IntPtr instance, string webView2RuntimePath);
diff --git a/Photino.NET/PhotinoNativeDelegates.cs b/Photino.NET/PhotinoNativeDelegates.cs
index ddab55db..37da313e 100644
--- a/Photino.NET/PhotinoNativeDelegates.cs
+++ b/Photino.NET/PhotinoNativeDelegates.cs
@@ -14,6 +14,7 @@ namespace Photino.NET;
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)] public delegate void CppMinimizedDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)] public delegate void CppMovedDelegate(int x, int y);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)] public delegate void CppWebMessageReceivedDelegate(string message);
+[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)] public delegate byte CppPopupRequestedDelegate(string url, string name, int x, int y, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)] public delegate IntPtr CppWebResourceRequestedDelegate(string url, out int outNumBytes, out string outContentType);
//These are sent in during the request
diff --git a/Photino.NET/PhotinoNativeParameters.cs b/Photino.NET/PhotinoNativeParameters.cs
index cd138b62..ba5ddcb2 100644
--- a/Photino.NET/PhotinoNativeParameters.cs
+++ b/Photino.NET/PhotinoNativeParameters.cs
@@ -79,6 +79,9 @@ internal struct PhotinoNativeParameters
///SET BY PHOTINIWINDOW CONSTRUCTOR
[MarshalAs(UnmanagedType.FunctionPtr)] internal CppWebMessageReceivedDelegate WebMessageReceivedHandler;
+ ///SET BY PHOTINOWINDOW CONSTRUCTOR
+ [MarshalAs(UnmanagedType.FunctionPtr)] internal CppPopupRequestedDelegate PopupRequestedHandler;
+
///OPTIONAL: Names of custom URL Schemes. e.g. 'app', 'custom'. Array length must be 16. Default is none.
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStr, SizeConst = 16)]
internal string[] CustomSchemeNames;
diff --git a/Photino.NET/PhotinoNetDelegates.cs b/Photino.NET/PhotinoNetDelegates.cs
index 098fa9e6..c426e7d6 100644
--- a/Photino.NET/PhotinoNetDelegates.cs
+++ b/Photino.NET/PhotinoNetDelegates.cs
@@ -192,9 +192,37 @@ public PhotinoWindow RegisterWebMessageReceivedHandler(EventHandler hand
///
internal void OnWebMessageReceived(string message)
{
+ if (TryHandleScriptExecutionCallback(message))
+ return;
+
WebMessageReceived?.Invoke(this, message);
}
+ public event EventHandler PopupRequested;
+
+ ///
+ /// Registers user-defined handler methods to receive callbacks when the browser requests a popup or new window.
+ ///
+ ///
+ /// Returns the current instance.
+ ///
+ ///
+ public PhotinoWindow RegisterPopupRequestedHandler(EventHandler handler)
+ {
+ PopupRequested += handler;
+ return this;
+ }
+
+ ///
+ /// Invokes registered user-defined handler methods when the browser requests a popup or new window.
+ ///
+ internal byte OnPopupRequested(string url, string name, int x, int y, int width, int height)
+ {
+ var args = new PopupRequestedEventArgs(this, url, name, x, y, width, height);
+ PopupRequested?.Invoke(this, args);
+ return (byte)(args.Handled ? 1 : 0);
+ }
+
public delegate bool NetClosingDelegate(object sender, EventArgs e);
public event NetClosingDelegate WindowClosing;
diff --git a/Photino.NET/PhotinoScriptExecution.cs b/Photino.NET/PhotinoScriptExecution.cs
new file mode 100644
index 00000000..ffb4e7f4
--- /dev/null
+++ b/Photino.NET/PhotinoScriptExecution.cs
@@ -0,0 +1,196 @@
+#nullable enable
+
+using System.Collections.Concurrent;
+using System.Text.Json;
+
+namespace Photino.NET;
+
+public partial class PhotinoWindow
+{
+ private const string ExecuteScriptCallbackMessageType = "__photino_execute_script_result";
+ private readonly ConcurrentDictionary> _scriptExecutionTasks = new();
+
+ ///
+ /// Executes JavaScript in the current page and waits for a JSON-serializable result.
+ ///
+ /// The JavaScript body to execute. Use a return statement to return a value.
+ /// The JSON-serializable result converted to a .NET primitive, list, dictionary, or null.
+ /// Thrown when the window is not initialized, the script fails, or the method is called synchronously on the UI thread.
+ public object? ExecuteScript(string script)
+ {
+ if (Environment.CurrentManagedThreadId == _managedThreadId)
+ throw new ApplicationException("ExecuteScript cannot be called synchronously on the Photino UI thread. Use ExecuteScriptAsync instead.");
+
+ return ExecuteScriptAsync(script).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Executes JavaScript in the current page and asynchronously waits for a JSON-serializable result.
+ ///
+ /// The JavaScript body to execute. Use a return statement to return a value.
+ /// A token that cancels waiting for the script result.
+ /// A task that completes with the JSON-serializable result converted to a .NET primitive, list, dictionary, or null.
+ /// Thrown when the window is not initialized or the script fails.
+ public async Task