diff --git a/NetWebView2Lib.au3 b/NetWebView2Lib.au3 index 56d8b2a..8fee755 100644 --- a/NetWebView2Lib.au3 +++ b/NetWebView2Lib.au3 @@ -6,7 +6,7 @@ #Tidy_Parameters=/tcb=-1 -; NetWebView2Lib.au3 - Script Version: 2026.2.17.5 🚩 +; NetWebView2Lib.au3 - Script Version: 2026.2.23.9 🚩 #include #include @@ -29,6 +29,7 @@ Global $_g_bNetWebView2_DebugInfo = True ;~ Global $_g_bNetWebView2_DebugDev = False Global $_g_bNetWebView2_DebugDev = (@Compiled = 1) + #Region ; ENUMS ;~ Global Enum _ @@ -47,6 +48,12 @@ Global Enum _ ; $NETWEBVIEW2_MESSAGE__* are set by mainly by __NetWebView2_Event $NETWEBVIEW2_MESSAGE__SOURCE_CHANGED, _ ; #TODO https://learn.microsoft.com/en-us/microsoft-edge/webview2/get-started/wpf#step-7---navigation-events $NETWEBVIEW2_MESSAGE__CONTENT_LOADING, _ ; #TODO https://learn.microsoft.com/en-us/microsoft-edge/webview2/get-started/wpf#step-7---navigation-events $NETWEBVIEW2_MESSAGE__BASIC_AUTHENTICATION_REQUESTED, _ ; #TODO WHERE THIS SHOULD be Lower/Higher ? + $NETWEBVIEW2_MESSAGE__FRAME_NAV_STARTING, _ + $NETWEBVIEW2_MESSAGE__FRAME_NAV_COMPLETED, _ + $NETWEBVIEW2_MESSAGE__FRAME_CONTENT_LOADING, _ + $NETWEBVIEW2_MESSAGE__FRAME_DOM_CONTENT_LOADED, _ + $NETWEBVIEW2_MESSAGE__FRAME_WEB_MESSAGE_RECEIVED, _ + $NETWEBVIEW2_MESSAGE__FRAME_HTML_SOURCE, _ $NETWEBVIEW2_MESSAGE__CRITICAL_ERROR, _ $NETWEBVIEW2_MESSAGE__DOM_CONTENT_LOADED, _ ; #TODO https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/navigation-events $NETWEBVIEW2_MESSAGE__NAV_ERROR, _ @@ -1008,7 +1015,6 @@ Func _NetWebView2_SetBuiltInErrorPageEnabled($oWebV2M, $bEnabled) Return SetError(@error, 0, (@error ? False : True)) EndFunc ;==>_NetWebView2_SetBuiltInErrorPageEnabled - ; #FUNCTION# ==================================================================================================================== ; Name...........: _NetWebView2_SilentErrorHandler ; Description....: A generic COM Error Handler that silences errors. @@ -1438,7 +1444,9 @@ EndFunc ;==>__NetWebView2_freezer #EndRegion ; NetWebView2Lib UDF - #INTERNAL_USE_ONLY# -#Region ; NetWebView2Lib UDF - === EVENT HANDLERS === +#Region ; === NetWebView2Lib UDF === EVENT HANDLERS === + +#Region ; === EVENT HANDLERS === Error Handler === ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name ..........: __NetWebView2_COMErrFunc ; Description ...: @@ -1474,8 +1482,9 @@ Volatile Func __NetWebView2_fake_COMErrFunc($oError) ; COM Error Function used b ; this is only to silently handle _NetWebView2_IsRegisteredCOMObject() $oError = 0 ; Explicitly release the COM reference inside the volatile scopeEndFunc EndFunc ;==>__NetWebView2_fake_COMErrFunc +#EndRegion ; === NetWebView2Lib UDF === EVENT HANDLERS === Error Handler === - +#Region ; === EVENT HANDLERS === MessageReceived === ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name ..........: __NetWebView2_Events__OnMessageReceived ; Description ...: Handles native WebView2 events @@ -1663,6 +1672,10 @@ Volatile Func __NetWebView2_Events__OnMessageReceived($oWebV2M, $hGUI, $sMsg) __NetWebView2_Log(@ScriptLineNumber, $s_Prefix & " COMMAND:" & $sCommand, 1) __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__BROWSER_LOST_FOCUS) + Case "FRAME_HTML_SOURCE" + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix & " COMMAND:" & $sCommand, 1) + __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__FRAME_HTML_SOURCE) + Case "ERROR" __NetWebView2_Log(@ScriptLineNumber, $s_Prefix & " ! CRITICAL ERROR:" & $sData, 1) __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__CRITICAL_ERROR) @@ -1752,7 +1765,9 @@ Volatile Func __NetWebView2_JSEvents__OnMessageReceived($oWebV2M, $hGUI, $sMsg) EndIf EndFunc ;==>__NetWebView2_JSEvents__OnMessageReceived +#EndRegion ; === NetWebView2Lib UDF === EVENT HANDLERS === MessageReceived === +#Region ; === EVENT HANDLERS === Browser === ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name ..........: __NetWebView2_Events__OnBrowserGotFocus ; Description ...: @@ -2134,49 +2149,207 @@ Volatile Func __NetWebView2_Events__OnBasicAuthenticationRequested($oWebV2M, $hG ; Note: User should handle $oArgs.UserName / $oArgs.Password and call $oArgs.Complete() in their script. $oArgs = 0 EndFunc ;==>__NetWebView2_Events__OnBasicAuthenticationRequested +#EndRegion ; === NetWebView2Lib UDF === EVENT HANDLERS === Browser === -#Region ; NetWebView2Lib UDF - === EVENT HANDLERS === #TODO - +#Region ; === EVENT HANDLERS === Frame Related === +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: _NetWebView2_GetFrame +; Description ...: Returns a Frame Object (IWebView2Frame) for the specified index. +; Syntax ........: _NetWebView2_GetFrame($oWebV2M, $iIndex) +; Parameters ....: $oWebV2M - an object. +; $iIndex - an int value. +; Return values .: Frame Object or Null +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: +; Example .......: Yes +; =============================================================================================================================== +Func _NetWebView2_GetFrame($oWebV2M, $iIndex) + Local $oFrame = $oWebV2M.GetFrame($iIndex) + Return SetError(@error, @extended, $oFrame) +EndFunc ;==>_NetWebView2_GetFrame -#Region ; NetWebView2Lib UDF - === EVENT HANDLERS === FRAME RELATED -Volatile Func __NetWebView2_Events__OnFrameCreated() -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#framecreated +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameCreated +; Description ...: FrameCreated is raised when a new iframe is created. Handle this event to get access to CoreWebView2Frame objects. +; Syntax ........: __NetWebView2_Events__OnFrameCreated($oWebV2M, $hGUI, $oFrame) +; Parameters ....: $oWebV2M - an WebV2M object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.framecreated +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameCreated($oWebV2M, $hGUI, $oFrame) + Local Const $s_Prefix = "[EVENT: OnFrameCreated]: WebV2M: " & VarGetType($oWebV2M) & " GUI: " & $hGUI & " Frame: " & VarGetType($oFrame) + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) EndFunc ;==>__NetWebView2_Events__OnFrameCreated -Volatile Func __NetWebView2_Events__OnContentLoading() -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#contentloading -EndFunc ;==>__NetWebView2_Events__OnContentLoading +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameDestroyed +; Description ...: Destroyed event is raised when the iframe corresponding to this CoreWebView2Frame object is removed or the document containing that iframe is destroyed. +; Syntax ........: __NetWebView2_Events__OnFrameDestroyed($oWebV2M, $hGUI, $oFrame) +; Parameters ....: $oWebV2M - an object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.destroyed +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameDestroyed($oWebV2M, $hGUI, $oFrame) + Local Const $s_Prefix = "[EVENT: OnFrameDestroyed]: WebV2M: " & VarGetType($oWebV2M) & " GUI: " & $hGUI & " Frame: " & VarGetType($oFrame) + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) +EndFunc ;==>__NetWebView2_Events__OnFrameDestroyed -Volatile Func __NetWebView2_Events__OnDOMContentLoaded() -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#domcontentloaded -EndFunc ;==>__NetWebView2_Events__OnDOMContentLoaded +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameNameChanged +; Description ...: NameChanged is raised when the iframe changes its window.name property. +; Syntax ........: __NetWebView2_Events__OnFrameNameChanged($oWebV2M, $hGUI, $oFrame) +; Parameters ....: $oWebV2M - an object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.namechanged +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameNameChanged($oWebV2M, $hGUI, $oFrame) + Local Const $s_Prefix = "[EVENT: OnFrameNameChanged]: WebV2M: " & VarGetType($oWebV2M) & " GUI: " & $hGUI & " Frame: " & VarGetType($oFrame) + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) +EndFunc ;==>__NetWebView2_Events__OnFrameNameChanged -Volatile Func __NetWebView2_Events__OnDestroyed() -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#destroyed -EndFunc ;==>__NetWebView2_Events__OnDestroyed +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameNavigationStarting +; Description ...: Handles Frame NavigationStarting event +; Syntax ........: __NetWebView2_Events__OnFrameNavigationStarting($oWebV2M, $hGUI, $oFrame, $sUri) +; Parameters ....: $oWebV2M - an object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; $sUri - a string value. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.navigationstarting +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameNavigationStarting($oWebV2M, $hGUI, $oFrame, $sUri) + Local Const $s_Prefix = "[EVENT: OnFrameNavigationStarting]: WebV2M: " & VarGetType($oWebV2M) & " GUI:" & $hGUI & " Frame:" & VarGetType($oFrame) & " Uri:" & $sUri + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) + ; __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__FRAME_NAV_STARTING) ; Optional: Update status if needed +EndFunc ;==>__NetWebView2_Events__OnFrameNavigationStarting -Volatile Func __NetWebView2_Events__OnNameChanged() -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#namechanged -EndFunc ;==>__NetWebView2_Events__OnNameChanged +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameNavigationCompleted +; Description ...: Handles Frame NavigationCompleted event +; Syntax ........: __NetWebView2_Events__OnFrameNavigationCompleted($oWebV2M, $hGUI, $oFrame, $bIsSuccess, $iWebErrorStatus) +; Parameters ....: $oWebV2M - an object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; $bIsSuccess - a boolean value. +; $iWebErrorStatus - an integer value. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.navigationcompleted +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameNavigationCompleted($oWebV2M, $hGUI, $oFrame, $bIsSuccess, $iWebErrorStatus) + Local Const $s_Prefix = "[EVENT: OnFrameNavigationCompleted]: WebV2M: " & VarGetType($oWebV2M) & " GUI:" & $hGUI & " Frame:" & VarGetType($oFrame) & " Success:" & $bIsSuccess & " Status:" & $iWebErrorStatus + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) + ; __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__FRAME_NAV_COMPLETED) +EndFunc ;==>__NetWebView2_Events__OnFrameNavigationCompleted -;~ is this followed navigationcompleted are the same as __NetWebView2_Events__OnNavigationCompleted() ? -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#navigationcompleted +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameContentLoading +; Description ...: Handles Frame ContentLoading event +; Syntax ........: __NetWebView2_Events__OnFrameContentLoading($oWebV2M, $hGUI, $oFrame, $iNavigationId) +; Parameters ....: $oWebV2M - an object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; $iNavigationId - an integer value. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.contentloading +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameContentLoading($oWebV2M, $hGUI, $oFrame, $iNavigationId) + Local Const $s_Prefix = "[EVENT: OnFrameContentLoading]: WebV2M: " & VarGetType($oWebV2M) & " GUI:" & $hGUI & " Frame:" & VarGetType($oFrame) & " NavID:" & $iNavigationId + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) + ; __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__FRAME_CONTENT_LOADING) +EndFunc ;==>__NetWebView2_Events__OnFrameContentLoading -;~ is this followed navigationstarting are the same as __NetWebView2_Events__OnNavigationStarting() ? -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#navigationstarting +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameDOMContentLoaded +; Description ...: Handles Frame DOMContentLoaded event +; Syntax ........: __NetWebView2_Events__OnFrameDOMContentLoaded($oWebV2M, $hGUI, $oFrame, $iNavigationId) +; Parameters ....: $oWebV2M - an object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; $iNavigationId - an integer value. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.domcontentloaded +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameDOMContentLoaded($oWebV2M, $hGUI, $oFrame, $iNavigationId) + Local Const $s_Prefix = "[EVENT: OnFrameDOMContentLoaded]: WebV2M: " & VarGetType($oWebV2M) & " GUI:" & $hGUI & " Frame:" & VarGetType($oFrame) & " NavID:" & $iNavigationId + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) + ; __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__FRAME_DOM_CONTENT_LOADED) +EndFunc ;==>__NetWebView2_Events__OnFrameDOMContentLoaded -Volatile Func __NetWebView2_Events__OnScreenCaptureStarting() -;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#screencapturestarting -EndFunc ;==>__NetWebView2_Events__OnScreenCaptureStarting +; #INTERNAL_USE_ONLY# =========================================================================================================== +; Name ..........: __NetWebView2_Events__OnFrameWebMessageReceived +; Description ...: Handles Frame WebMessageReceived event +; Syntax ........: __NetWebView2_Events__OnFrameWebMessageReceived($oWebV2M, $hGUI, $oFrame, $sMessage) +; Parameters ....: $oWebV2M - an object. +; $hGUI - a handle value. +; $oFrame - an Frame object. +; $sMessage - a string value. +; Return values .: None +; Author ........: ioa747 +; Modified ......: +; Remarks .......: +; Related .......: +; Link ..........: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.webmessagereceived +; Example .......: No +; =============================================================================================================================== +Volatile Func __NetWebView2_Events__OnFrameWebMessageReceived($oWebV2M, $hGUI, $oFrame, $sMessage) + Local Const $s_Prefix = "[EVENT: OnFrameWebMessageReceived]: WebV2M: " & VarGetType($oWebV2M) & " GUI:" & $hGUI & " Frame:" & VarGetType($oFrame) & " Message:" & $sMessage + __NetWebView2_Log(@ScriptLineNumber, $s_Prefix, 1) + ; __NetWebView2_LastMessage_KEEPER($oWebV2M, $NETWEBVIEW2_MESSAGE__FRAME_WEB_MESSAGE_RECEIVED) +EndFunc ;==>__NetWebView2_Events__OnFrameWebMessageReceived +#EndRegion ; === NetWebView2Lib UDF === EVENT HANDLERS === Frame Related === +#Region ; === EVENT HANDLERS * #TODO === ;~ is this followed webmessagereceived are the same as __NetWebView2_Events__OnMessageReceived() ? ;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#webmessagereceived -#EndRegion ; NetWebView2Lib UDF - === EVENT HANDLERS === FRAME RELATED - -#EndRegion ; NetWebView2Lib UDF - === EVENT HANDLERS === #TODO - -#EndRegion ; NetWebView2Lib UDF - === EVENT HANDLERS === - - +;~ Volatile Func __NetWebView2_Events__OnScreenCaptureStarting() +;~ https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame?view=webview2-winrt-1.0.3595.46#screencapturestarting +;~ EndFunc ;==>__NetWebView2_Events__OnScreenCaptureStarting +#EndRegion ; === NetWebView2Lib UDF === EVENT HANDLERS * #TODO === +#EndRegion ; === NetWebView2Lib UDF === EVENT HANDLERS === diff --git a/README.md b/README.md index 0a75d59..f1d5503 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ https://www.autoitscript.com/forum/topic/213375-webview2autoit-autoit-webview2-c 1. **.NET Framework 4.8** or higher. 2. **Microsoft Edge WebView2 Runtime version 128.0.2739.15 or higher**. - * *The registration script will check for this and provide a download link if missing.* +* *The registration script will check for this and provide a download link if missing.* --- ### 📦 Deployment & Installation @@ -62,609 +62,566 @@ This project is provided "as-is". You are free to use, modify, and distribute it

+## 🚀 What's New in v2.1.0-alpha - Frame Support & Event Isolation - -## 🚀 What's New in v2.0.0-stable - Professional Naming & Absolute Stability - -This stable release marks the transition to a professional, standardized COM architecture and introduces high-performance infrastructure for production-grade applications. +This patch introduces first-class support for `iframe` interaction, allowing developers to target specific frames for script execution, messaging, and host object binding. ### ⚡ Key Features & Enhancements -#### **1. COM Standardizing (NetWebView2Lib)** -The library has transitioned to a consistent naming convention for all subsystems, improving discoverability and professional alignment. -- **New ProgIDs**: `NetWebView2Lib.WebView2Manager`, `NetWebView2Lib.WebView2Bridge`, and `NetWebView2Lib.WebView2Parser`. -- **Full Backward Compatibility**: Legacy ProgIDs (e.g., `NetWebView2.Manager`) continue to function via an inheritance-based compatibility layer. - -#### **2. Threading & Stability Mastery** -Identified and resolved complex threading deadlocks in asynchronous handlers. -- **UI-Thread Marshalling**: Explicit synchronization ensure that events like Context Menus, Downloads, and IFrame HTML extraction always safely interact with the AutoIt UI thread. -- **SDK Resilience**: Reflection-based URI retrieval for IFrames ensures compatibility across different versions of the WebView2 SDK. - -#### **3. Standardized SDK Locking (v1.0.2739.15)** -To ensure maximum reliability across the broadest possible range of Windows installations, we have standardized the core engine on a specific, high-stability SDK version. -- **Fixed Version**: Locked to **WebView2 SDK 1.0.2739.15** (Released August 26, 2024). -- **Runtime Requirement**: Requires **WebView2 Runtime 128.0.2739.15** or newer. -- **Strategic Choice**: This version provides a "Sweet Spot" of features—including full support for IFrame scraping, Basic Authentication, and Process Failure events—while maintaining compatibility with the vast majority of current "Evergreen" browser installations. -- **Strict Validation**: The registration utility (`Register_web2.au3`) has been updated to perform a mandatory check for this minimum runtime version, preventing obscure startup errors. - -#### **4. Enriched Verbose Logging** -A redesigned diagnostic system for easier debugging of complex workflows. -- **Branded & Traceable**: Every log line now includes the `+++[NetWebView2Lib]` prefix and the instance-specific `[HANDLE:0x...]`. -- **Comprehensive Event Tracing**: Detailed logs for Navigation, Resource Requests (with HTTP codes), Focus changes, and Web Messages. -- **Default Folder Sync**: Renamed the default crash folder to `FailureReportFolder` for better parity with internal WebView2 parameters. - -#### **4. High-Performance IFrame Scraping** -Bulk extraction methods (`GetFrameUrls`, `GetFrameNames`) allow for high-speed metadata retrieval from complex multi-frame layouts, essential for advanced web scraping. - ---- -#### 🖼️ IFrame HTML Extraction +#### **1. Professional Frame Support (WebView2Frame)** +IFrames are no longer just metadata. You can now obtain a dedicated COM object for any frame to interact with it directly. +- **`GetFrame(index)`**: returns an `IWebView2Frame` object. +- **Frame Methods**: `ExecuteScript`, `ExecuteScriptWithResult` (Thread-Safe Sync), `PostWebMessageAsJson`, `PostWebMessageAsString`. +- **Host Object Injection**: `AddHostObjectToScript` and `RemoveHostObjectFromScript` are now supported per frame. -We implemented a robust system to track and extract HTML from iframes, including cross-origin ones. +#### **2. Isolated Frame Events** +Listen to the lifecycle of specific frames without context ambiguity. +- **Events**: `OnFrameNavigationStarting`, `OnFrameNavigationCompleted`, `OnFrameContentLoading`, `OnFrameDOMContentLoaded`, `OnFrameWebMessageReceived`. +- **Targeting**: Every event provides the `frameName` to facilitate multi-frame coordination. -##### 1. Frame Tracking System +#### **3. Backward Compatibility Sync** +The existing bulk extraction methods (`GetFrameCount`, `GetFrameUrls`, etc.) remain fully functional and synchronized with the internal frame tracker. -A dynamic registration system was added to  -WebView2Manager.cs to track the lifecycle of all iframes. +#### **4. Refactoring & Structural Inheritance** -- **Initialization**: Automatically subscribes to `CoreWebView2.FrameCreated`. -- **Lifecycle Management**: Tracks `CoreWebView2Frame` objects in a thread-safe list and automatically removes them on `Destroyed`. +The project's internal architecture has been fully reorganized to ensure long-term scalability and clean COM interoperability. -##### 2. New IFrame API - -Six new COM methods were added to the  - -IWebViewActions interface (Standardized & Compatibility Layers): +- **Logic-Based Namespacing:** Files are now categorized into dedicated directories (`/Core`, `/Events`, `/Interfaces`, `/Utils`). This makes the codebase easier to navigate and ensures that the root namespace remains focused on the primary API. + +- **Partial Class Implementation:** The `WebView2Manager` is now distributed across specialized partial classes (e.g., `Main`, `Events`, `FrameMgmt`). This maintains a single, unified COM object for the user while keeping the source code modular and readable. + +- **Inheritance-Driven Event Wrappers:** We introduced `BaseWebViewEventArgs`, a parent class for all event wrappers. This leverages C# inheritance to streamline how data is passed to the host application. + -|Method|Description| -|---|---| -|GetFrameCount()|Returns the number of tracked iframes.| -|GetFrameUrl(index)|Returns the URL of a specific iframe.| -|GetFrameName(index)|Returns the Name of a specific iframe.| -|GetFrameUrls()|Returns the **entire array** of frame URLs as a pipe-separated string.| -|GetFrameNames()|Returns the **entire array** of frame names as a pipe-separated string.| -|GetFrameHtmlSource(index)|Asynchronously initiates HTML extraction for a frame.| +**Why this matters:** -#### 🩺Verbose Diagnostic Mode +- **Standardized Metadata:** Every event now automatically inherits common properties like `WindowHandle` and a reference to the `Manager` instance. + +- **DRY (Don't Repeat Yourself):** By using `: base(manager, hwnd)`, we eliminated boilerplate code. Updates to core event logic (like handle formatting) now only need to be made in one place to affect all event types. + +- **Developer Predictability:** Whether you are handling a `ZoomChanged` or a `NavigationStarting` event, the core properties remain consistent, making the library much more intuitive for AutoIt/COM scripting. -A new `Verbose` property was added to allow real-time diagnostic logging to the SciTE console (or any stdout listener). -The diagnostic logs use a distinctive prefix and include the instance handle for easier filtering in multi-window applications. -```autoit -$oWebV2M.Verbose = True ; Enable diagnostic logging +The project has been reorganized into logical directories: +``` +NETWEBVIEW2LIB\SRC +├───Core Main manager logic (partial classes) and core bridge/parser. +├───Events Standardized event argument wrappers. +├───Interfaces COM-visible interface definitions. +└───Utils Shared utility functions and assembly helpers. ``` - -When enabled, the console will show entries like:  -`+++[NetWebView2Lib][HANDLE:0x...][HH:mm:ss.fff] Message` - -`+++[NetWebView2Lib][HANDLE:0x004F1128][14:20:33.456] Initialize request: parent=[HANDLE:0x...], x=0, y=0, w=1024, h=768`  -`+++[NetWebView2Lib][HANDLE:0x004F1128][14:20:34.123] WebView2 Initialized successfully.`  -`+++[NetWebView2Lib][HANDLE:0x004F1128][14:20:35.789] Navigate: https://www.google.com` - -#### 🔄 Backward Compatibility (Legacy ProgIDs) - -To ensure stability for existing scripts not using the UDF, the following legacy ProgIDs are still supported through a compatibility layer: - -| Legacy Object | Legacy ProgID | v2.0 Standardized ProgID | -| :--------------- | :----------------------------- | :------------------------------- | -| `WebViewManager` | `NetWebView2.Manager` | `NetWebView2Lib.WebView2Manager` | -| `WebViewBridge` | `NetWebView2Lib.WebViewBridge` | `NetWebView2Lib.WebView2Bridge` | -| `JsonParser` | `NetJson.Parser` | `NetWebView2Lib.WebView2Parser` | - -> [!NOTE] -> Objects created with legacy ProgIDs will function exactly the same but will report their legacy class names via `ObjName()`. -

-## 📖 NetWebView2Lib Version 2.0.0-stable (Quick Reference) +## 📖 NetWebView2Lib Version 2.1.0-alpha (Quick Reference) -### WebView2Manager (ProgId: NetWebView2Lib.WebView2Manager) +### 🧊 WebView2Manager (ProgId: NetWebView2Lib.WebView2Manager) -#### ===Properties=== +#### ===🏷️ Properties=== -##### AreDevToolsEnabled +##### 🏷️AreDevToolsEnabled Determines whether the user is able to use the context menu or keyboard shortcuts to open the DevTools window. `object.AreDevToolsEnabled = Value` -##### AreDefaultContextMenusEnabled +##### 🏷️AreDefaultContextMenusEnabled Activates or Deactivates the contextual menus of the WebView2 browser. `object.AreDefaultContextMenusEnabled = Value` -##### AreDefaultScriptDialogsEnabled +##### 🏷️ AreDefaultScriptDialogsEnabled Determines whether the standard JavaScript dialogs (alert, confirm, prompt) are enabled. `object.AreDefaultScriptDialogsEnabled = Value` -##### AreBrowserAcceleratorKeysEnabled +##### 🏷️ AreBrowserAcceleratorKeysEnabled Determines whether browser-specific accelerator keys are enabled (e.g., Ctrl+P, F5, etc.). `object.AreBrowserAcceleratorKeysEnabled = Value` -##### IsStatusBarEnabled +##### 🏷️ IsStatusBarEnabled Determines whether the status bar is visible. `object.IsStatusBarEnabled = Value` -##### ZoomFactor +##### 🏷️ ZoomFactor Gets or sets the current zoom factor (e.g., 1.0 for 100%). `object.ZoomFactor = Value` -##### BackColor +##### 🏷️ BackColor Sets the background color of the WebView using a Hex string (e.g., "#FFFFFF" or "0xFFFFFF"). `object.BackColor = Value` -##### AreHostObjectsAllowed +##### 🏷️ AreHostObjectsAllowed Determines whether host objects (like the 'autoit' bridge) are accessible from JavaScript. `object.AreHostObjectsAllowed = Value` -##### Anchor +##### 🏷️ Anchor Determines how the control is anchored when the parent window is resized. `object.Anchor = Value` -##### BorderStyle +##### 🏷️ BorderStyle Note: Not supported natively by WebView2, provided for compatibility. `object.BorderStyle = Value` -##### AreBrowserPopupsAllowed +##### 🏷️ AreBrowserPopupsAllowed Determines whether new window requests are allowed or redirected to the same window. `object.AreBrowserPopupsAllowed = Value` -##### CustomMenuEnabled +##### 🏷️ CustomMenuEnabled Enables or disables custom context menu handling. `object.CustomMenuEnabled = Value` -##### AdditionalBrowserArguments +##### 🏷️ AdditionalBrowserArguments Sets additional command-line arguments to be passed to the Chromium engine during initialization. Must be set BEFORE calling Initialize(). `object.AdditionalBrowserArguments = Value` -##### HiddenPdfToolbarItems +##### 🏷️ HiddenPdfToolbarItems Controls the visibility of buttons in the PDF viewer toolbar using a bitwise combination of CoreWebView2PdfToolbarItems (e.g., 1=Save, 2=Print, 4=Search). `object.HiddenPdfToolbarItems = Value` -##### IsDownloadUIEnabled +##### 🏷️ IsDownloadUIEnabled Determines whether the browser's default download UI (shelf/bubble) is shown. `object.IsDownloadUIEnabled = Value` -##### HttpStatusCodeEventsEnabled +##### 🏷️ HttpStatusCodeEventsEnabled Enables or disables the `OnWebResourceResponseReceived` event entirely. `object.HttpStatusCodeEventsEnabled = Value` -##### HttpStatusCodeDocumentOnly +##### 🏷️ HttpStatusCodeDocumentOnly Determines whether `OnWebResourceResponseReceived` triggers for all resources (False) or only for the main document (True). Essential for preventing GUI deadlocks in AutoIt. `object.HttpStatusCodeDocumentOnly = Value` -##### Verbose +##### 🏷️ Verbose Enable diagnostic logging to console. (before `$object.Initialize()`) `object.Verbose = Value` -##### IsDownloadHandled +##### 🏷️ IsDownloadHandled Determines whether the download is handled by the application. If set to **True** during `OnDownloadStarting`, the internal Edge download is cancelled. `object.IsDownloadHandled = Value` -##### ActiveDownloadsList +##### 🏷️ ActiveDownloadsList Returns a pipe-separated string of all active download URIs. `object.ActiveDownloadsList` -##### IsZoomControlEnabled +##### 🏷️ IsZoomControlEnabled Determines whether user can zoom the page (Ctrl+MouseWheel, shortcuts). `object.IsZoomControlEnabled = Value` -##### IsBuiltInErrorPageEnabled +##### 🏷️ IsBuiltInErrorPageEnabled Control visibility of the browser's default error pages (e.g., connection lost). `object.IsBuiltInErrorPageEnabled = Value` -##### BrowserWindowHandle +##### 🏷️ BrowserWindowHandle Returns the internal window handle (HWND) of the WebView2 control. [Format: `[HANDLE:0x...]`] `object.BrowserWindowHandle` -##### ParentWindowHandle +##### 🏷️ ParentWindowHandle Returns the parent window handle provided during initialization. [Format: `[HANDLE:0x...]`] `object.ParentWindowHandle` -##### BlockedVirtualKeys +##### 🏷️ BlockedVirtualKeys A comma-separated list of Virtual Key codes to be blocked synchronously (e.g., "116,123"). `object.BlockedVirtualKeys = "116,123"` -##### FailureReportFolderPath +##### 🏷️ FailureReportFolderPath Sets or gets the path where the WebView2 browser stores crash reports (dump files). - **Default**: If NOT set by the user, the system automatically uses the `FailureReportFolderPath` subfolder within the `UserDataFolder` (assigned during `.Initialize`). - **Manual Override**: You can set a custom path **before** calling `.Initialize`. - **Example (Custom Path)**: `object.FailureReportFolderPath = "C:\MyCustomCrashDumps"` - **Example (Read current)**: `$sPath = object.FailureReportFolderPath` -##### Version +##### 🏷️ Version Allows AutoIt to verify the DLL version at runtime for compatibility checks. `object.Version` -#### ===Method=== +#### ===⚡Method=== -##### Initialize +##### ⚡ Initialize Initializes the WebView2 control within a parent window. `object.Initialize(ParentHandle As HWND, UserDataFolder As String, X As Integer, Y As Integer, Width As Integer, Height As Integer)` -##### Navigate +##### ⚡Navigate Navigates the browser to the specified URL. `object.Navigate(Url As String)` -##### NavigateToString +##### ⚡ NavigateToString Loads the provided HTML content directly into the browser. `object.NavigateToString(HtmlContent As String)` -##### ExecuteScript +##### ⚡ ExecuteScript **Type**: void (Fire-and-Forget) **Description**: Sends the command to the UI thread. No return value. **Use Case**: UI Actions (click, scroll, focus). `object.ExecuteScript(Script As String)` -##### Resize +##### ⚡ Resize Changes the dimensions of the WebView2 control. `object.Resize(Width As Integer, Height As Integer)` -##### Cleanup +##### ⚡ Cleanup Disposes of the WebView2 control and releases resources. `object.Cleanup()` -##### GetBridge +##### ⚡ GetBridge Returns the Bridge object for advanced AutoIt-JavaScript interaction. `object.GetBridge()` -##### ExportToPdf +##### ⚡ ExportToPdf Saves the current page as a PDF file. `object.ExportToPdf(FilePath As String)` -##### IsReady +##### ⚡ IsReady Checks if the WebView2 control is fully initialized and ready for use. `object.IsReady()` -##### SetContextMenuEnabled +##### ⚡ SetContextMenuEnabled Toggles between Native (true) and Custom (false) context menu modes. `object.SetContextMenuEnabled(Enabled As Boolean)` -##### LockWebView +##### ⚡ LockWebView Locks down the WebView by disabling context menus, dev tools, zoom control, default error pages, script dialogs, accelerator keys, and popups. `object.LockWebView()` -##### UnLockWebView +##### ⚡ UnLockWebView Re-enables the features previously restricted by `LockWebView()` (ContextMenus, DevTools, Zoom, ErrorPages, Dialogs, Keys, Popups). `object.UnLockWebView()` -##### DisableBrowserFeatures +##### ⚡ DisableBrowserFeatures Disables major browser features for a controlled environment (Unified with `LockWebView`). `object.DisableBrowserFeatures()` -##### GoBack +##### ⚡ GoBack Navigates back to the previous page in history. `object.GoBack()` -##### GoForward +##### ⚡ GoForward Navigates forward to the next page in history. `object.GoForward()` -##### ResetZoom +##### ⚡ ResetZoom Resets the zoom factor to the default 100%. `object.ResetZoom()` -##### InjectCss +##### ⚡ InjectCss Injects a block of CSS code into the current page. `object.InjectCss(CssCode As String)` -##### ClearInjectedCss +##### ⚡ ClearInjectedCss Removes any CSS previously injected via InjectCss. `object.ClearInjectedCss()` -##### ToggleAuditHighlights +##### ⚡ ToggleAuditHighlights Toggles visual highlights on common web elements for auditing purposes. `object.ToggleAuditHighlights(Enable As Boolean)` -##### SetAdBlock +##### ⚡ SetAdBlock Enables or disables the built-in ad blocker. `object.SetAdBlock(Active As Boolean)` -##### AddBlockRule +##### ⚡ AddBlockRule Adds a domain pattern to the ad block list. `object.AddBlockRule(Domain As String)` -##### ClearBlockRules +##### ⚡ ClearBlockRules Clears all active ad block rules. `object.ClearBlockRules()` -##### GetHtmlSource +##### ⚡ GetHtmlSource Asynchronously retrieves the full HTML source (sent via OnMessageReceived with 'HTML_SOURCE|'). `object.GetHtmlSource()` -##### GetFrameCount +##### ⚡ GetFrameCount Returns the number of currently tracked iframes. `object.GetFrameCount()` -##### GetFrameUrl +##### ⚡ GetFrameUrl Returns the URL of the specified frame index. `object.GetFrameUrl(Index As Integer)` -##### GetFrameName +##### ⚡ GetFrameName Returns the Name attribute of the specified frame index. `object.GetFrameName(Index As Integer)` -##### GetFrameUrls +##### ⚡ GetFrameUrls Returns a pipe-separated string of all tracked iframe URLs. `object.GetFrameUrls()` -##### GetFrameNames +##### ⚡ GetFrameNames Returns a pipe-separated string of all tracked iframe names. `object.GetFrameNames()` -##### GetFrameHtmlSource +##### ⚡ GetFrameHtmlSource Asynchronously retrieves the HTML of the frame at the specified index (sent via OnMessageReceived with 'FRAME_HTML_SOURCE|Index|'). `object.GetFrameHtmlSource(Index As Integer)` -##### GetSelectedText +##### ⚡ GetSelectedText Asynchronously retrieves the currently selected text (sent via OnMessageReceived with 'SELECTED_TEXT|'). `object.GetSelectedText()` -##### SetZoom +##### ⚡ SetZoom Sets the zoom factor (wrapper for ZoomFactor property). `object.SetZoom(Factor As Double)` -##### ParseJsonToInternal +##### ⚡ ParseJsonToInternal Parses a JSON string into the internal JSON storage. `object.ParseJsonToInternal(Json As String)` -##### GetInternalJsonValue +##### ⚡ GetInternalJsonValue Retrieves a value from the internal JSON storage using a path. `object.GetInternalJsonValue(Path As String)` -##### ClearBrowserData +##### ⚡ ClearBrowserData Clears all browsing data including cookies, cache, and history. `object.ClearBrowserData()` -##### Reload +##### ⚡ Reload Reloads the current page. `object.Reload()` -##### Stop +##### ⚡ Stop Stops any ongoing navigation or loading. `object.Stop()` -##### ShowPrintUI +##### ⚡ ShowPrintUI Opens the standard Print UI dialog. `object.ShowPrintUI()` -##### SetMuted +##### ⚡ SetMuted Mutes or unmutes the audio output of the browser. `object.SetMuted(Muted As Boolean)` -##### IsMuted +##### ⚡ IsMuted Returns true if the browser audio is currently muted. `object.IsMuted()` -##### SetUserAgent +##### ⚡ SetUserAgent Sets a custom User Agent string for the browser. `object.SetUserAgent(UserAgent As String)` -##### GetDocumentTitle +##### ⚡ GetDocumentTitle Returns the title of the current document. `object.GetDocumentTitle()` -##### GetSource +##### ⚡ GetSource Returns the current URL of the browser. `object.GetSource()` -##### SetScriptEnabled +##### ⚡ SetScriptEnabled Enables or disables JavaScript execution. `object.SetScriptEnabled(Enabled As Boolean)` -##### SetWebMessageEnabled +##### ⚡ SetWebMessageEnabled Enables or disables the Web Message communication system. `object.SetWebMessageEnabled(Enabled As Boolean)` -##### SetStatusBarEnabled +##### ⚡ SetStatusBarEnabled Enables or disables the browser status bar. `object.SetStatusBarEnabled(Enabled As Boolean)` -##### CapturePreview +##### ⚡ CapturePreview Captures a screenshot of the current view to a file. `object.CapturePreview(FilePath As String, Format As String)` -##### CallDevToolsProtocolMethod +##### ⚡ CallDevToolsProtocolMethod Calls a Chrome DevTools Protocol (CDP) method directly. `object.CallDevToolsProtocolMethod(MethodName As String, ParametersJson As String)` -##### GetCookies +##### ⚡ GetCookies Retrieves all cookies (results sent via OnMessageReceived as 'COOKIES_B64|'). `object.GetCookies(ChannelId As String)` -##### AddCookie +##### ⚡ AddCookie Adds or updates a cookie in the browser. `object.AddCookie(Name As String, Value As String, Domain As String, Path As String)` -##### DeleteCookie +##### ⚡ DeleteCookie Deletes a specific cookie. `object.DeleteCookie(Name As String, Domain As String, Path As String)` -##### DeleteAllCookies +##### ⚡ DeleteAllCookies Deletes all cookies from the current profile. `object.DeleteAllCookies()` -##### Print +##### ⚡ Print Opens the print dialog (via window.print()). `object.Print()` -##### AddExtension +##### ⚡ AddExtension Adds a browser extension from an unpacked folder. `object.AddExtension(ExtensionPath As String)` -##### RemoveExtension +##### ⚡ RemoveExtension Removes an extension by its ID. `object.RemoveExtension(ExtensionId As String)` -##### GetCanGoBack +##### ⚡ GetCanGoBack Returns true if navigating back is possible. `object.GetCanGoBack()` -##### GetCanGoForward +##### ⚡ GetCanGoForward Returns true if navigating forward is possible. `object.GetCanGoForward()` -##### GetBrowserProcessId +##### ⚡ GetBrowserProcessId Returns the Process ID (PID) of the browser process. `object.GetBrowserProcessId()` -##### EncodeURI +##### ⚡ EncodeURI URL-encodes a string. `object.EncodeURI(Value As String) -##### DecodeURI +##### ⚡ DecodeURI URL-decodes a string. `object.DecodeURI(Value As String)` -##### EncodeB64 +##### ⚡ EncodeB64 Encodes a string to Base64 (UTF-8). `object.EncodeB64(Value As String)` -##### DecodeB64 +##### ⚡ DecodeB64 Decodes a Base64 string back to **plain text** (UTF-8). `object.DecodeB64(Value As String)` -##### DecodeB64ToBinary +##### ⚡ DecodeB64ToBinary Decodes a Base64 string directly into a **raw byte array**. Optimized for memory-based binary processing (e.g., images, PDFs). `object.DecodeB64ToBinary(Base64Text As String)` -##### CapturePreviewAsBase64 +##### ⚡ CapturePreviewAsBase64 Captures a screenshot of the current page content and returns it as a Base64-encoded data URL. `object.CapturePreviewAsBase64(format)` -##### SetZoomFactor +##### ⚡ SetZoomFactor Sets the zoom factor for the control. `object.SetZoomFactor(Factor As Double)` -##### OpenDevToolsWindow +##### ⚡ OpenDevToolsWindow Opens the DevTools window for the current project. `object.OpenDevToolsWindow()` -##### WebViewSetFocus +##### ⚡ WebViewSetFocus Gives focus to the WebView control. `object.WebViewSetFocus()` -##### SetAutoResize +##### ⚡ SetAutoResize Enables or disables robust "Smart Anchor" resizing. Uses Win32 subclassing to perfectly sync with any parent window (AutoIt/Native). Sends "WINDOW_RESIZED" via OnMessageReceived on completion. `object.SetAutoResize(Enabled As Boolean)` -##### AddInitializationScript +##### ⚡ AddInitializationScript Registers a script that will run automatically every time a new page loads. Returns the unique **ScriptId** (string). `ResultString = object.AddInitializationScript(Script As String)` -##### RemoveInitializationScript +##### ⚡ RemoveInitializationScript Removes a script previously added via AddInitializationScript using its ScriptId. `object.RemoveInitializationScript(ScriptId As String)` -##### SetVirtualHostNameToFolderMapping +##### ⚡ SetVirtualHostNameToFolderMapping Maps a virtual host name (e.g., `app.local`) to a local folder path for local resource loading. `object.SetVirtualHostNameToFolderMapping(hostName As String, folderPath As String, accessKind As Integer)` -##### BindJsonToBrowser +##### ⚡ BindJsonToBrowser Binds the internal JSON data to a browser variable. `object.BindJsonToBrowser(VariableName As String)` -##### SyncInternalData +##### ⚡ SyncInternalData Syncs JSON data to internal parser and optionally binds it to a browser variable. `object.SyncInternalData(Json As String, BindToVariableName As String)` -##### ExecuteScriptOnPage +##### ⚡ ExecuteScriptOnPage **Type**: void (Async-Fire) **Description**: Starts asynchronously but does not wait. No return value. **Use Case**: Quick background actions. `object.ExecuteScriptOnPage(Script As String)` -##### ExecuteScriptWithResult +##### ⚡ ExecuteScriptWithResult **Type**: string (Synchronous/Blocking) **Description**: Uses Message Pump (DoEvents) to wait for the response (timeout 5s). **Special**: Performs automatic JSON Unescaping (removes extra quotes and fixes escape characters). **Use Case**: Scraping, retrieving variables from JS, checking DOM state. `object.ExecuteScriptWithResult(Script As String)` -##### ClearCache +##### ⚡ ClearCache Clears the browser cache (DiskCache and LocalStorage). `object.ClearCache()` -##### GetInnerText +##### ⚡ GetInnerText Asynchronously retrieves the entire visible text content of the document (sent via OnMessageReceived with 'Inner_Text|'). `object.GetInnerText()` -##### CaptureSnapshot +##### ⚡ CaptureSnapshot Captures page data using Chrome DevTools Protocol. Can return MHTML or other CDP formats based on the `cdpParameters` JSON string. `object.CaptureSnapshot(CdpParameters As String)` -##### SetDownloadPath +##### ⚡ SetDownloadPath Sets a global default folder or file path for all browser downloads. If a directory is provided, the filename is automatically appended by the library. Create the folder if it doesn't exist `object.SetDownloadPath(Path As String)` -##### CancelDownloads +##### ⚡ CancelDownloads Cancels active downloads. If `uri` is empty or omitted, cancels all active downloads. `object.CancelDownloads([Uri As String])` -##### ExportPageData +##### ⚡ ExportPageData [LEGACY] Consolidated into **CaptureSnapshot**. `object.ExportPageData(Format As Integer, FilePath As String)` -##### PrintToPdfStream +##### ⚡ PrintToPdfStream Captures the current page as a PDF and returns the content as a Base64-encoded string. `object.PrintToPdfStream()` -#### ===Events=== +#### ===🔔Events=== -##### OnMessageReceived +##### 🔔OnMessageReceived Fired when a message or notification is sent from the library to AutoIt. `object_OnMessageReceived(Sender As Object, ParentHandle As HWND, Message As String)` -##### OnWebResourceResponseReceived +##### 🔔 OnWebResourceResponseReceived Fired when a web resource response is received (useful for tracking HTTP Status Codes). `object_OnWebResourceResponseReceived(Sender As Object, ParentHandle As HWND, StatusCode As Integer, ReasonPhrase As String, RequestUrl As String)` -##### OnNavigationStarting +##### 🔔 OnNavigationStarting Fired when the browser starts navigating to a new URL. `object_OnNavigationStarting(Sender As Object, ParentHandle As HWND, Url As String)` -##### OnNavigationCompleted +##### 🔔 OnNavigationCompleted Fired when navigation has finished. `object_OnNavigationCompleted(Sender As Object, ParentHandle As HWND, IsSuccess As Boolean, WebErrorStatus As Integer)` -##### OnTitleChanged +##### 🔔 OnTitleChanged Fired when the document title changes. `object_OnTitleChanged(Sender As Object, ParentHandle As HWND, NewTitle As String)` -##### OnURLChanged +##### 🔔 OnURLChanged Fired when the current URL changes. `object_OnURLChanged(Sender As Object, ParentHandle As HWND, NewUrl As String)` -##### OnContextMenu +##### 🔔 OnContextMenu Fired when a custom context menu is requested (if SetContextMenuEnabled is false). `object_OnContextMenu(Sender As Object, ParentHandle As HWND, MenuData As String)` -##### OnZoomChanged +##### 🔔 OnZoomChanged Fired when the zoom factor is changed. `object_OnZoomChanged(Sender As Object, ParentHandle As HWND, Factor As Double)` -##### OnBrowserGotFocus +##### 🔔 OnBrowserGotFocus Fired when the browser receives focus. `object_OnBrowserGotFocus(Sender As Object, ParentHandle As HWND, Reason As Integer)` -##### OnBrowserLostFocus +##### 🔔 OnBrowserLostFocus Fired when the browser loses focus. `object_OnBrowserLostFocus(Sender As Object, ParentHandle As HWND, Reason As Integer)` -##### OnContextMenuRequested +##### 🔔 OnContextMenuRequested Fired when a context menu is requested (Simplified for AutoIt). `object_OnContextMenuRequested(Sender As Object, ParentHandle As HWND, LinkUrl As String, X As Integer, Y As Integer, SelectionText As String)` -##### OnDownloadStarting +##### 🔔 OnDownloadStarting Fired when a download is starting. Provides core metadata to allow decision making. Path overrides and UI suppression should be handled via the `DownloadResultPath` and `IsDownloadHandled` properties. `object_OnDownloadStarting(Sender As Object, ParentHandle As HWND, Uri As String, DefaultPath As String)` -##### OnDownloadStateChanged +##### 🔔 OnDownloadStateChanged Fired when a download state changes (e.g., Progress, Completed, Failed). `object_OnDownloadStateChanged(Sender As Object, ParentHandle As HWND, State As String, Uri As String, TotalBytes As Long, ReceivedBytes As Long)` -##### OnAcceleratorKeyPressed +##### 🔔 OnAcceleratorKeyPressed Fired when an accelerator key is pressed. Allows blocking browser shortcuts. `object_OnAcceleratorKeyPressed(Sender As Object, ParentHandle As HWND, Args As Object)` *Args properties: @@ -679,7 +636,7 @@ Fired when an accelerator key is pressed. Allows blocking browser shortcuts. IsKeyReleased (bool): True if the event is a key up. KeyEventLParam (int): Gets the LPARAM value that accompanied the window message* -##### OnProcessFailed +##### 🔔 OnProcessFailed Fired when a renderer or other browser process fails/crashes. `object_OnProcessFailed(Sender As Object, ParentHandle As HWND, Args As Object)` *Args properties: @@ -688,7 +645,7 @@ Fired when a renderer or other browser process fails/crashes. ExitCode (int): The exit code of the failed process. ProcessDescription (string): A description of the process.* -##### OnBasicAuthenticationRequested +##### 🔔 OnBasicAuthenticationRequested Fired when the browser requires basic authentication credentials for a URI. `object_OnBasicAuthenticationRequested(Sender As Object, ParentHandle As HWND, Args As Object)` *Args properties: @@ -701,135 +658,228 @@ Fired when the browser requires basic authentication credentials for a URI. Complete(): Notifies the browser that credentials have been set (supports asynchronous data gathering).* +#### ===🔔Frame Events=== + +##### 🔔 OnFrameCreated +Fired when a new iframe is created in the document. +`object_OnFrameCreated(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame)` + +##### 🔔 OnFrameDestroyed +Fired when an iframe is removed from the document. +`object_OnFrameDestroyed(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame)` + +##### 🔔 OnFrameNameChanged +Fired when an iframe's name attribute changes. +`object_OnFrameNameChanged(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame)` + +##### 🔔 OnFrameNavigationStarting +Fired when a frame starts navigating to a new URL. +`object_OnFrameNavigationStarting(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, Url As String)` + +##### 🔔 OnFrameNavigationCompleted +Fired when a frame navigation has finished. +`object_OnFrameNavigationCompleted(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, IsSuccess As Boolean, WebErrorStatus As Integer)` + +##### 🔔 OnFrameContentLoading +Fired when a frame starts loading content. +`object_OnFrameContentLoading(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, NavigationId As Long)` + +##### 🔔 OnFrameDOMContentLoaded +Fired when a frame's DOM content is fully loaded. +`object_OnFrameDOMContentLoaded(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, NavigationId As Long)` + +##### 🔔 OnFrameWebMessageReceived +Fired when a frame receives a message via `window.chrome.webview.postMessage`. +`object_OnFrameWebMessageReceived(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, Message As String)` + +##### 🔔 OnFrameProcessFailed 🚧 +Fired when a frame renderer or other process fails. +`object_OnFrameProcessFailed(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, Args As Object)` + +##### 🔔 OnFramePermissionRequested +Fired when a frame requests permission (e.g. Geolocation). +`object_OnFramePermissionRequested(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, Args As Object)` + +##### 🔔 OnFrameScreenCaptureStarting +Fired when a frame starts a screen capture session. +`object_OnFrameScreenCaptureStarting(Sender As Object, ParentHandle As HWND, Frame As IWebView2Frame, Args As Object)` + + +--- +### 🧊 WebView2Frame (ProgId: NetWebView2Lib.WebView2Frame) + +#### ===🏷️Properties=== + +##### 🏷️Name +Returns the name attribute of the frame. +`string object.Name` + +##### 🏷️ IsDestroyed +Checks if the frame is still valid and attached to the page. +`bool object.IsDestroyed()` + +#### ===⚡Methods=== + +##### ⚡ExecuteScript (DispId 2) +**Type**: void (Fire-and-Forget) +**Description**: Executes JavaScript within the context of the frame. +`object.ExecuteScript(Script As String)` + +##### ⚡ExecuteScriptWithResult (DispId 6) +**Type**: string (Synchronous/Blocking) +**Description**: Executes JavaScript in the frame and waits for the result (Thread-Safe). +`object.ExecuteScriptWithResult(Script As String)` + +##### ⚡PostWebMessageAsJson (DispId 3) +Sends a JSON message to the frame content. +`object.PostWebMessageAsJson(Json As String)` + +##### ⚡PostWebMessageAsString (DispId 4) +Sends a plain text message to the frame content. +`object.PostWebMessageAsString(Text As String)` + +##### ⚡AddHostObjectToScript (DispId 7) +Adds a host object (AutoIt object) to the frame's script environment. +`object.AddHostObjectToScript(Name As String, RawObject As Object)` + +##### ⚡RemoveHostObjectFromScript (DispId 8) +Removes a host object from the frame's script environment. +`object.RemoveHostObjectFromScript(Name As String)` + +##### ⚡IsDestroyed (DispId 5) +Checks if the frame is valid. +`bool object.IsDestroyed()` + --- -### WebView2Parser (ProgId: NetWebView2Lib.WebView2Parser) +### 🧊 WebView2Parser (ProgId: NetWebView2Lib.WebView2Parser) -#### ===Properties=== -##### Version +#### ===🏷️Properties=== +##### 🏷️ Version Allows AutoIt to verify the DLL version at runtime for compatibility checks. `object.Version` -#### ===Methods=== +#### ===⚡Methods=== -##### Parse +##### ⚡Parse Parses a JSON string. Automatically detects if it's an Object or an Array. `bool Parse(Json As String)` -##### GetTokenValue +##### ⚡GetTokenValue Retrieves a value by JSON path (e.g., "items[0].name"). `string GetTokenValue(Path As String)` -##### GetArrayLength +##### ⚡GetArrayLength Returns the count of elements if the JSON is an array (Legacy wrapper for GetTokenCount). `int GetArrayLength(Path As String)` -##### GetTokenCount +##### ⚡GetTokenCount Returns the count of elements (array items or object properties) at the specified path. `int GetTokenCount(Path As String)` -##### GetKeys +##### ⚡GetKeys Returns a delimited string of keys for the object at the specified path. `string GetKeys(Path As String, Delimiter As String)` -##### SetTokenValue +##### ⚡SetTokenValue Updates or adds a value at the specified path. Supports **Deep Creation** (automatic path creation) and **Smart Typing** (auto-detection of bool/null/numbers). `void SetTokenValue(Path As String, Value As String)` -##### LoadFromFile +##### ⚡LoadFromFile Loads JSON content directly from a file. `bool LoadFromFile(FilePath As String)` -##### SaveToFile +##### ⚡SaveToFile Saves the current JSON state back to a file. `bool SaveToFile(FilePath As String)` -##### Exists +##### ⚡Exists Checks if a path exists in the current JSON structure. `bool Exists(Path As String)` -##### Clear +##### ⚡Clear Clears the internal data. `void Clear()` -##### GetJson +##### ⚡GetJson Returns the full JSON string. `string GetJson()` -##### EscapeString +##### ⚡EscapeString Escapes a string to be safe for use in JSON. `string EscapeString(PlainText As String)` -##### UnescapeString +##### ⚡UnescapeString Unescapes a JSON string back to plain text. `string UnescapeString(EscapedText As String)` -##### GetPrettyJson +##### ⚡GetPrettyJson Returns the JSON string with nice formatting (Indented). `string GetPrettyJson()` -##### GetMinifiedJson +##### ⚡GetMinifiedJson Minifies a JSON string (removes spaces and new lines). `string GetMinifiedJson()` -##### Merge +##### ⚡Merge Merges another JSON string into the current JSON structure. `bool Merge(JsonContent As String)` -##### MergeFromFile +##### ⚡MergeFromFile Merges JSON content from a file into the current JSON structure. `bool MergeFromFile(FilePath As String)` -##### GetTokenType +##### ⚡GetTokenType Returns the type of the token at the specified path (e.g., Object, Array, String). `string GetTokenType(Path As String)` -##### RemoveToken +##### ⚡RemoveToken Removes the token at the specified path. `bool RemoveToken(Path As String)` -##### Search +##### ⚡Search Searches the JSON structure using a JSONPath query and returns a JSON array of results. `string Search(Query As String)` -##### Flatten +##### ⚡Flatten Flattens the JSON structure into a single-level object with dot-notated paths. `string Flatten()` -##### CloneTo +##### ⚡CloneTo Clones the current JSON data to another named parser instance. `bool CloneTo(ParserName As String)` -##### FlattenToTable +##### ⚡FlattenToTable Flattens the JSON structure into a table-like string with specified delimiters. `string FlattenToTable(ColDelim As String, RowDelim As String)` -##### EncodeB64 +##### ⚡EncodeB64 Encodes a string to Base64 (UTF-8). `string EncodeB64(PlainText As String)` -##### DecodeB64ToBinary +##### ⚡DecodeB64ToBinary Converts a Base64-encoded string back into raw binary data (byte array). `Variant DecodeB64ToBinary(Base64Text As String)` -##### EncodeBinaryToB64 +##### ⚡EncodeBinaryToB64 Converts raw binary data (byte array) to a Base64-encoded string. `string EncodeBinaryToB64(BinaryData As Variant)` -##### DecodeB64 +##### ⚡DecodeB64 Decodes a Base64 string back to **plain text** (UTF-8). `string DecodeB64(Base64Text As String)` -##### DecodeB64ToFile +##### ⚡DecodeB64ToFile Decodes a Base64 string and saves the binary content directly to a file. `bool DecodeB64ToFile(Base64Text As String, FilePath As String)` -##### SortArray +##### ⚡SortArray Sorts a JSON array by a specific key. `bool SortArray(ArrayPath As String, Key As String, Descending As Boolean)` -##### SelectUnique +##### ⚡SelectUnique Removes duplicate objects from a JSON array based on a key's value. `bool SelectUnique(ArrayPath As String, Key As String)` --- + diff --git a/bin/NetWebView2Lib.dll b/bin/NetWebView2Lib.dll deleted file mode 100644 index 5d447f1..0000000 Binary files a/bin/NetWebView2Lib.dll and /dev/null differ diff --git a/bin/NetWebView2Lib.tlb b/bin/NetWebView2Lib.tlb deleted file mode 100644 index 9cc423e..0000000 Binary files a/bin/NetWebView2Lib.tlb and /dev/null differ diff --git a/bin/RegCleaner.au3 b/bin/RegCleaner.au3 index 650b22d..3b066b8 100644 --- a/bin/RegCleaner.au3 +++ b/bin/RegCleaner.au3 @@ -10,6 +10,8 @@ #include #include "..\NetWebView2Lib.au3" +; RegCleaner.au3 + Global $_g_s_ELEMENTs = '' _Cleaner() @@ -18,7 +20,7 @@ Func _Cleaner() ConsoleWrite("! MicrosoftEdgeWebview2 : version check: " & _NetWebView2_IsAlreadyInstalled() & ' ERR=' & @error & ' EXT=' & @extended & @CRLF) ; === Configuration === - Local $sSearchTerm = "NetWebView2" + Local $sSearchTerm = "NetWebView2Lib" ; NetWebView2Lib Local $aTargets[2] = ["HKEY_LOCAL_MACHINE64\SOFTWARE\Classes", "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes"] ; GUICreate diff --git a/bin/Register_web2.au3 b/bin/Register_web2.au3 index 6547e1a..66efeb2 100644 --- a/bin/Register_web2.au3 +++ b/bin/Register_web2.au3 @@ -8,12 +8,15 @@ #include #include "..\NetWebView2Lib.au3" +; Register_web2.au3 + _Register() Func _Register() ConsoleWrite("! MicrosoftEdgeWebview2 : version check: " & _NetWebView2_IsAlreadyInstalled() & ' ERR=' & @error & ' EXT=' & @extended & @CRLF) ; === Configuration === + Local $sDllName = "NetWebView2Lib.dll" Local $sNet4_x86 = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" Local $sNet4_x64 = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe" @@ -56,13 +59,14 @@ Func _Register() EndIf EndIf + ; === Registration 'NetWebView2Lib.dll' COM === If Not $bAbort Then - ; === Registration 'NetWebView2Lib.dll' COM === + Local $iExitCode ; Registration for x86 (32-bit) If FileExists($sNet4_x86) Then - $iExitCode = RunWait('"' & $sNet4_x86 & '" "' & @ScriptDir & '\' & $sDllName & '" /codebase /tlb', @ScriptDir, @SW_HIDE) + $iExitCode = RunWait('"' & $sNet4_x86 & '" "' & @ScriptDir & '\x86\' & $sDllName & '" /codebase /tlb', @ScriptDir, @SW_HIDE) If $iExitCode = 0 Then $sLog &= "[+] x86 Registration: SUCCESS" & @CRLF $bSuccess = True @@ -75,12 +79,9 @@ Func _Register() ; Registration for x64 (64-bit) If FileExists($sNet4_x64) Then - $iExitCode = RunWait('"' & $sNet4_x64 & '" "' & @ScriptDir & '\' & $sDllName & '" /codebase /tlb', @ScriptDir, @SW_HIDE) + $iExitCode = RunWait('"' & $sNet4_x64 & '" "' & @ScriptDir & '\x64\' & $sDllName & '" /codebase /tlb', @ScriptDir, @SW_HIDE) If $iExitCode = 0 Then $sLog &= "[+] x64 Registration: SUCCESS" & @CRLF - $sLog &= @CRLF - $sLog &= "Validation:" & @CRLF - $sLog &= " _NetWebView2_IsRegisteredCOMObject() =" & _NetWebView2_IsRegisteredCOMObject() & @CRLF $bSuccess = True Else $sLog &= "[-] x64 Registration: FAILED (Code: " & $iExitCode & ")" & @CRLF @@ -93,6 +94,10 @@ Func _Register() ; === Final Message === If $bSuccess Then ConsoleWrite("+ Registration Complete" & @CRLF) + $sLog &= @CRLF + $sLog &= "Validation:" & @CRLF + $sLog &= " _NetWebView2_IsRegisteredCOMObject() =" & _NetWebView2_IsRegisteredCOMObject() & @CRLF + ConsoleWrite($sLog & @CRLF) MsgBox($MB_ICONINFORMATION, "Registration Complete", $sLog) diff --git a/bin/Unregister.au3 b/bin/Unregister.au3 index 197c7af..9a6f583 100644 --- a/bin/Unregister.au3 +++ b/bin/Unregister.au3 @@ -7,6 +7,8 @@ #include #include "..\NetWebView2Lib.au3" +; Unregister.au3 + _Unregister() Func _Unregister() @@ -25,13 +27,13 @@ Func _Unregister() ; === Unregister x86 === If FileExists($sNet4_x86) Then - $iExitCode = RunWait('"' & $sNet4_x86 & '" /u "' & @ScriptDir & '\' & $sDllName & '"', @ScriptDir, @SW_HIDE) + $iExitCode = RunWait('"' & $sNet4_x86 & '" /u "' & @ScriptDir & '\x86\' & $sDllName & '"', @ScriptDir, @SW_HIDE) $sLog &= ($iExitCode = 0 ? "[+] x86 Unregistration: SUCCESS" : "[-] x86 Unregistration: FAILED") & @CRLF EndIf ; === Unregister x64 === If FileExists($sNet4_x64) Then - $iExitCode = RunWait('"' & $sNet4_x64 & '" /u "' & @ScriptDir & '\' & $sDllName & '"', @ScriptDir, @SW_HIDE) + $iExitCode = RunWait('"' & $sNet4_x64 & '" /u "' & @ScriptDir & '\x64\' & $sDllName & '"', @ScriptDir, @SW_HIDE) $sLog &= ($iExitCode = 0 ? "[+] x64 Unregistration: SUCCESS" : "[-] x64 Unregistration: FAILED") & @CRLF EndIf diff --git a/bin/Microsoft.Web.WebView2.Core.dll b/bin/x64/Microsoft.Web.WebView2.Core.dll similarity index 100% rename from bin/Microsoft.Web.WebView2.Core.dll rename to bin/x64/Microsoft.Web.WebView2.Core.dll diff --git a/bin/Microsoft.Web.WebView2.WinForms.dll b/bin/x64/Microsoft.Web.WebView2.WinForms.dll similarity index 100% rename from bin/Microsoft.Web.WebView2.WinForms.dll rename to bin/x64/Microsoft.Web.WebView2.WinForms.dll diff --git a/bin/Microsoft.Web.WebView2.Wpf.dll b/bin/x64/Microsoft.Web.WebView2.Wpf.dll similarity index 100% rename from bin/Microsoft.Web.WebView2.Wpf.dll rename to bin/x64/Microsoft.Web.WebView2.Wpf.dll diff --git a/bin/x64/NetWebView2Lib.dll b/bin/x64/NetWebView2Lib.dll new file mode 100644 index 0000000..5cd1480 Binary files /dev/null and b/bin/x64/NetWebView2Lib.dll differ diff --git a/bin/x64/NetWebView2Lib.tlb b/bin/x64/NetWebView2Lib.tlb new file mode 100644 index 0000000..db3ba96 Binary files /dev/null and b/bin/x64/NetWebView2Lib.tlb differ diff --git a/bin/Newtonsoft.Json.dll b/bin/x64/Newtonsoft.Json.dll similarity index 100% rename from bin/Newtonsoft.Json.dll rename to bin/x64/Newtonsoft.Json.dll diff --git a/bin/runtimes/win-x64/native/WebView2Loader.dll b/bin/x64/WebView2Loader.dll similarity index 100% rename from bin/runtimes/win-x64/native/WebView2Loader.dll rename to bin/x64/WebView2Loader.dll diff --git a/bin/x86/Microsoft.Web.WebView2.Core.dll b/bin/x86/Microsoft.Web.WebView2.Core.dll new file mode 100644 index 0000000..dc9f1c1 Binary files /dev/null and b/bin/x86/Microsoft.Web.WebView2.Core.dll differ diff --git a/bin/x86/Microsoft.Web.WebView2.WinForms.dll b/bin/x86/Microsoft.Web.WebView2.WinForms.dll new file mode 100644 index 0000000..eea7ee0 Binary files /dev/null and b/bin/x86/Microsoft.Web.WebView2.WinForms.dll differ diff --git a/bin/x86/Microsoft.Web.WebView2.Wpf.dll b/bin/x86/Microsoft.Web.WebView2.Wpf.dll new file mode 100644 index 0000000..236da9d Binary files /dev/null and b/bin/x86/Microsoft.Web.WebView2.Wpf.dll differ diff --git a/bin/x86/NetWebView2Lib.dll b/bin/x86/NetWebView2Lib.dll new file mode 100644 index 0000000..5cd1480 Binary files /dev/null and b/bin/x86/NetWebView2Lib.dll differ diff --git a/bin/x86/NetWebView2Lib.tlb b/bin/x86/NetWebView2Lib.tlb new file mode 100644 index 0000000..db3ba96 Binary files /dev/null and b/bin/x86/NetWebView2Lib.tlb differ diff --git a/bin/x86/Newtonsoft.Json.dll b/bin/x86/Newtonsoft.Json.dll new file mode 100644 index 0000000..3f6541a Binary files /dev/null and b/bin/x86/Newtonsoft.Json.dll differ diff --git a/bin/runtimes/win-x86/native/WebView2Loader.dll b/bin/x86/WebView2Loader.dll similarity index 100% rename from bin/runtimes/win-x86/native/WebView2Loader.dll rename to bin/x86/WebView2Loader.dll diff --git a/examples/018-BasicFramesDemo.au3 b/examples/018-BasicFramesDemo.au3 new file mode 100644 index 0000000..50e2014 --- /dev/null +++ b/examples/018-BasicFramesDemo.au3 @@ -0,0 +1,269 @@ +#WIP - this Example is imported from 1.5.0 UDF - and is in "WORK IN PROGRESS" state +#AutoIt3Wrapper_UseX64=y +#AutoIt3Wrapper_Run_AU3Check=Y +#AutoIt3Wrapper_AU3Check_Stop_OnWarning=y +#AutoIt3Wrapper_AU3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7 + +; 018-BasicFramesDemo.au3 + +#include +#include +#include "..\NetWebView2Lib.au3" + +Main() + +Func Main() + ConsoleWrite("! MicrosoftEdgeWebview2 : version check: " & _NetWebView2_IsAlreadyInstalled() & ' ERR=' & @error & ' EXT=' & @extended & @CRLF) + + Local $oMyError = ObjEvent("AutoIt.Error", __NetWebView2_COMErrFunc) + #forceref $oMyError + + ; Create the UI + Local $iHeight = 800 + Local $hGUI = GUICreate("WebView2 .NET Manager - Community Demo", 1100, $iHeight) + GUICtrlSetFont(-1, 9, 400, 0, "Segoe UI") + + WinMove($hGUI, '', Default, Default, 800, 440) + GUISetState(@SW_SHOW, $hGUI) + + ; Initialize WebView2 Manager and register events + Local $oWebV2M = _NetWebView2_CreateManager("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0", _ + "MyHook_", "--disable-gpu, --mute-audio") + If @error Then Return SetError(@error, @extended, $oWebV2M) + + ; Initialize JavaScript Bridge + Local $oJSBridge = _NetWebView2_GetBridge($oWebV2M, "_BridgeMyEventsHandler_") + If @error Then Return SetError(@error, @extended, $oWebV2M) + + Local $sProfileDirectory = @ScriptDir & "\NetWebView2Lib-UserDataFolder" + _NetWebView2_Initialize($oWebV2M, $hGUI, $sProfileDirectory, 0, 0, 0, 0, True, True, 1.2, "0x2B2B2B", False) + If @error Then Return SetError(@error, @extended, $oWebV2M) + + __Example_Log(@ScriptLineNumber, "After: _NetWebView2_Initialize()" & @CRLF) + + ; navigate to HTML string - full fill the object with your own offline content - without downloading any content + ConsoleWrite(@CRLF) + + __Example_Log(@ScriptLineNumber, "Before: _NetWebView2_NavigateToString()") + GUISetState(@SW_SHOW, $hGUI) + WinMove($hGUI, '', Default, Default, 1100, 800) + + #COMMENT This example is based on ==> ; https://github.com/Danp2/au3WebDriver/blob/1834e95206bd4a6ef6952c47a1f1192042f98c0b/wd_demo.au3#L588-L732 + #Region - Testing how to manage frames + + _NetWebView2_Navigate($oWebV2M, 'https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_iframe', $NETWEBVIEW2_MESSAGE__TITLE_CHANGED, "", 5000) + MsgBox($MB_TOPMOST, "TEST #" & @ScriptLineNumber, 1) +;~ _Demo_NavigateCheckBanner($sSession, "https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_iframe", '//*[@id="snigel-cmp-framework" and @class="snigel-cmp-framework"]') + If @error Then Return SetError(@error, @extended) + + #Region ; Example part 1 - testing NetWebView2Lib new methodes: .GetFrameCount() .GetFrameUrl($IDX_Frame) .GetFrameName($IDX_Frame) + ConsoleWrite("+ Example part 1 - testing NetWebView2Lib new methodes: .GetFrameCount() .GetFrameUrl($IDX_Frame) .GetFrameName($IDX_Frame)" & @CRLF) + + Local $iFrameCount = $oWebV2M.GetFrameCount() + ConsoleWrite(@CRLF) + ConsoleWrite("! " & @ScriptLineNumber & " : Frames=" & $iFrameCount & @CRLF) + For $IDX_Frame = 0 To $iFrameCount - 1 + ConsoleWrite("- IDX=" & $IDX_Frame & @CRLF) + ConsoleWrite("- URL=" & $oWebV2M.GetFrameUrl($IDX_Frame) & @CRLF) + ConsoleWrite("- NAME=" & $oWebV2M.GetFrameName($IDX_Frame) & @CRLF) + ConsoleWrite(@CRLF) + Next + #EndRegion ; Example part 1 - testing NetWebView2Lib new methodes: .GetFrameCount() .GetFrameUrl($IDX_Frame) .GetFrameName($IDX_Frame) + + #Region ; Example part 2 - testing NetWebView2Lib new methodes: .GetFrameCount() .GetFrameUrl($IDX_Frame) .GetFrameName($IDX_Frame) + ConsoleWrite("+ Example part 2 - testing NetWebView2Lib new methodes: .GetFrameCount() .GetFrameUrl($IDX_Frame) .GetFrameName($IDX_Frame)" & @CRLF) + + ConsoleWrite("! " & @ScriptLineNumber & " : GetFrameUrls() :" & @CRLF & $oWebV2M.GetFrameUrls() & @CRLF) + ConsoleWrite("! " & @ScriptLineNumber & " : GetFrameNames() :" & @CRLF & $oWebV2M.GetFrameNames() & @CRLF) + #EndRegion ; Example part 2 - testing NetWebView2Lib new methodes: .GetFrameCount() .GetFrameUrl($IDX_Frame) .GetFrameName($IDX_Frame) + + #Region ; Example part 3 - testing NetWebView2Lib new methodes .GetFrameHtmlSource($IDX_Frame) + ConsoleWrite("+ Example part 3 - testing NetWebView2Lib new methodes .GetFrameHtmlSource($IDX_Frame)" & @CRLF) + For $IDX_Frame = 0 To $iFrameCount - 1 + ConsoleWrite(@CRLF & "======================================================" & @CRLF) + Local $sHtmlSource = Fire_And_Wait($oWebV2M.GetFrameHtmlSource($IDX_Frame), 5000) ; pair with "FRAME_HTML_SOURCE" + ConsoleWrite("! " & @ScriptLineNumber & " : GetFrameHtmlSource(" & $IDX_Frame & ") :" & @CRLF & $sHtmlSource & @CRLF) + Next + ConsoleWrite(@CRLF & "======================================================" & @CRLF) + ConsoleWrite(@CRLF) + ConsoleWrite(@CRLF) + #EndRegion ; Example part 3 - testing NetWebView2Lib new methodes .GetFrameHtmlSource($IDX_Frame) + + + #Region ; Example part 4 - Direct Frame Interaction + + #cs NOT SUPPORTED YET + Local $oFrame0 = $oWebV2M.GetFrame(0) + Local $oFrame1 = $oWebV2M.GetFrame(1) + Local $oFrame2 = $oWebV2M.GetFrame(2) + Local $oFrame3 = $oWebV2M.GetFrame(3) + #CE NOT SUPPORTED YET + + Local $oFrame0 = $oWebV2M.GetFrame(0) + + ConsoleWrite("+ Example part 4 - Direct Frame Interaction via COM Object" & @CRLF) + If IsObj($oFrame0) Then + + ConsoleWrite("VarGetType($oFrame0)=" & VarGetType($oFrame0) & @CRLF) + ConsoleWrite("$oFrame0.Name=" & $oFrame0.Name & @CRLF & @CRLF) + + ; Direct script execution in the iframe without involving the central Manager + $oFrame0.ExecuteScript("document.body.style.backgroundColor = 'red';") + ConsoleWrite("> Executed background color change on Frame(0)" & @CRLF) + + ; Try getting the URL via JS + Local $sUrl = $oFrame0.ExecuteScriptWithResult("window.location.href") + ConsoleWrite("> Frame(0) URL via JS: " & $sUrl & @CRLF) + + Else + ConsoleWrite("! Error: $oFrame0 is not a valid COM Object" & @CRLF) + EndIf + + #EndRegion ; Example part 4 - Direct Frame Interaction + + + #EndRegion - Testing how to manage frames + + + ; Main Loop + While 1 + Switch GUIGetMsg() + Case $GUI_EVENT_CLOSE + ExitLoop + EndSwitch + WEnd + + GUIDelete($hGUI) + + + _NetWebView2_CleanUp($oWebV2M, $oJSBridge) +EndFunc ;==>Main + +; ============================================================================== +; ; Function to update a text element inside the WebView UI +; ============================================================================== +Func UpdateWebUI($oWebV2M, $sElementId, $sNewText) + If Not IsObj($oWebV2M) Then Return '' + + ; Escape backslashes, single quotes and handle new lines for JavaScript safety + Local $sCleanText = StringReplace($sNewText, "\", "\\") + $sCleanText = StringReplace($sCleanText, "'", "\'") + $sCleanText = StringReplace($sCleanText, @CRLF, "\n") + $sCleanText = StringReplace($sCleanText, @LF, "\n") + + Local $sJavaScript = "document.getElementById('" & $sElementId & "').innerText = '" & $sCleanText & "';" + _NetWebView2_ExecuteScript($oWebV2M, $sJavaScript) +EndFunc ;==>UpdateWebUI + +; ============================================================================== +; MY EVENT HANDLER: Bridge (JavaScript Messages) +; ============================================================================== +Func _BridgeMyEventsHandler_OnMessageReceived($oWebV2M, $hGUI, $sMessage) + Local Static $iMsgCnt = 0 + + If $sMessage = "CLOSE_APP" Then + If MsgBox(36, "Confirm", "Exit Application?", 0, $hGUI) = 6 Then Exit + Else + MsgBox(64, "JS Notification", "Message from Browser: " & $sMessage) + $iMsgCnt += 1 + UpdateWebUI($oWebV2M, "mainTitle", $iMsgCnt & " Hello from AutoIt!") + EndIf +EndFunc ;==>_BridgeMyEventsHandler_OnMessageReceived + +; ============================================================================== +; MyHook_ Events +; ============================================================================== +Func MyHook_OnMessageReceived($oWebV2M, $hGUI, $sMsg) + #forceref $oWebV2M, $hGUI, $sMsg + ConsoleWrite("> [MyHook] OnMessageReceived: GUI:" & $hGUI & " Msg: " & (StringLen($sMsg) > 30 ? StringLeft($sMsg, 30) & "..." : $sMsg) & @CRLF) + Local $iSplitPos = StringInStr($sMsg, "|") + Local $sCommand = $iSplitPos ? StringStripWS(StringLeft($sMsg, $iSplitPos - 1), 3) : $sMsg + Local $sData = $iSplitPos ? StringTrimLeft($sMsg, $iSplitPos) : "" +;~ Local $aParts + + Switch $sCommand + Case "INIT_READY" + + Case "FRAME_HTML_SOURCE" + $iSplitPos = StringInStr($sData, "|") + Local $sIDX = StringLeft($sData, $iSplitPos - 1) + ConsoleWrite(" >> $sIDX=" & $sIDX & @CRLF) + Local $sHtmlSource = StringTrimLeft($sData, $iSplitPos) + If $sHtmlSource = "null" Then $sHtmlSource = "!! " + Fire_And_Wait($sHtmlSource) + EndSwitch +EndFunc ;==>MyHook_OnMessageReceived + +; ============================================================================== +; HELPER: Demo HTML Content +; ============================================================================== +Func __GetDemoHTML() + Local $sH = _ + '' & _ + '
' & _ + '

WebView2 + AutoIt .NET Manager

' & _ ; Fixed ID attribute + '

The communication is now 100% Event-Driven (No Sleep needed).

' & _ + ' ' & _ + ' ' & _ + '
' & _ + '' + Return $sH +EndFunc ;==>__GetDemoHTML + +Func __Example_Log($s_ScriptLineNumber, $sString, $iError = @error, $iExtended = @extended) + ConsoleWrite(@ScriptName & ' SLN=' & $s_ScriptLineNumber & ' [' & $iError & '/' & $iExtended & '] ::: ' & $sString & @CRLF) + Return SetError($iError, $iExtended, '') +EndFunc ;==>__Example_Log + +; #FUNCTION# ==================================================================================================================== +; Name...........: Fire_And_Wait +; Description....: Synchronizes asynchronous events by waiting for a response or a timeout. +; Syntax.........: Fire_And_Wait([$sData = "" [, $iTimeout = 5000]]) +; Parameters.....: $sData - [Optional] Data string. +; If provided: Acts as a "Signal" (setter) from the Event Handler. +; If empty: Acts as a "Listener" (getter) from the Main Script. +; $iTimeout - [Optional] Maximum wait time in milliseconds (Default is 5000ms). +; Return values..: Success - Returns the stored data string. +; Sets @extended to the duration of the wait in ms. +; Failure - Returns an empty string and sets @error: +; |1 - Timeout reached. +; Author.........: YourName +; Modified.......: 2026-02-23 +; Remarks........: This function uses static variables to bridge the gap between async COM events and sync script execution. +; It effectively pauses the script execution until the WebView2 event fires back with data. +; =============================================================================================================================== +Func Fire_And_Wait($sData = "", $iTimeout = 5000) + Local Static $vStoredData = "" + Local Static $hJobTimer = 0 + + ; === Part A: Response (From Event Handler) === + If $sData <> "" Then + $vStoredData = $sData + Return True + EndIf + + ; === Part B: Fire and Wait (From Main Script) === + $vStoredData = "" + $hJobTimer = TimerInit() + + While $vStoredData = "" + If TimerDiff($hJobTimer) > $iTimeout Then + ConsoleWrite("! Fire_And_Wait | TIMEOUT after " & Round(TimerDiff($hJobTimer), 2) & " ms" & @CRLF) + Return SetError(1, 0, "") + EndIf + Sleep(10) + WEnd + + Local $fDuration = TimerDiff($hJobTimer) + Local $vResult = $vStoredData + $vStoredData = "" ; Reset for next use + + ConsoleWrite("> Fire_And_Wait | Duration: " & Round($fDuration, 0) & " ms | Status: SUCCESS" & @CRLF) + + Return SetError(0, Int($fDuration), $vResult) +EndFunc ;==>Fire_And_Wait diff --git a/src/Core/HostResourceAccessKind.cs b/src/Core/HostResourceAccessKind.cs new file mode 100644 index 0000000..4d1e26c --- /dev/null +++ b/src/Core/HostResourceAccessKind.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace NetWebView2Lib +{ + /// + /// Resource access kind for Virtual Host Mapping. + /// + [ComVisible(true)] + public enum HostResourceAccessKind + { + Allow = 0, + Deny = 1, + DenyCors = 2 + } +} diff --git a/src/WebView2Bridge.cs b/src/Core/WebView2Bridge.cs similarity index 100% rename from src/WebView2Bridge.cs rename to src/Core/WebView2Bridge.cs diff --git a/src/Core/WebView2Frame.cs b/src/Core/WebView2Frame.cs new file mode 100644 index 0000000..a497455 --- /dev/null +++ b/src/Core/WebView2Frame.cs @@ -0,0 +1,140 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace NetWebView2Lib +{ + [Guid("12345678-ABCD-1234-EF00-1234567890AB")] // New GUID + [InterfaceType(ComInterfaceType.InterfaceIsDual)] + [ComVisible(true)] + public interface IWebView2Frame + { + [DispId(1)] string Name { get; } + [DispId(2)] void ExecuteScript(string script); + [DispId(3)] void PostWebMessageAsJson(string webMessageAsJson); + [DispId(4)] void PostWebMessageAsString(string webMessageAsString); + [DispId(5)] bool IsDestroyed { get; } + [DispId(6)] string ExecuteScriptWithResult(string script); + [DispId(7)] void AddHostObjectToScript(string name, object rawObject); + [DispId(8)] void RemoveHostObjectFromScript(string name); + [DispId(9)] uint FrameId { get; } + [DispId(10)] string Source { get; } + } + + [ClassInterface(ClassInterfaceType.None)] + [ComVisible(true)] + [Guid("87654321-DCBA-4321-00FE-BA0987654321")] // New GUID + [ProgId("NetWebView2Lib.WebView2Frame")] + public class WebView2Frame : IWebView2Frame + { + private readonly Microsoft.Web.WebView2.Core.CoreWebView2Frame _frame; + private readonly WebView2Manager _manager; + + public WebView2Frame(Microsoft.Web.WebView2.Core.CoreWebView2Frame frame, WebView2Manager manager = null) + { + _frame = frame; + _manager = manager; + } + + public string Name + { + get + { + try { return _frame.Name; } + catch { return ""; } + } + } + + public uint FrameId + { + get + { + try { return _frame.FrameId; } + catch { return 0; } + } + } + + public string Source + { + get + { + if (_manager != null) return _manager.GetFrameUrlByObject(_frame); + try { return _frame.GetType().GetProperty("Source")?.GetValue(_frame) as string ?? "unknown"; } + catch { return "unknown"; } + } + } + + public void ExecuteScript(string script) + { + if (IsDestroyed) return; + try { _frame.ExecuteScriptAsync(script); } catch { } + } + + public string ExecuteScriptWithResult(string script) + { + if (IsDestroyed) return "ERROR: Frame Destroyed"; + try + { + var task = _frame.ExecuteScriptAsync(script); + return WaitAndGetResult(task); + } + catch (Exception ex) { return "ERROR: " + ex.Message; } + } + + public void AddHostObjectToScript(string name, object rawObject) + { + if (IsDestroyed) return; + try { _frame.AddHostObjectToScript(name, rawObject, new string[] { "*" }); } catch { } + } + + public void RemoveHostObjectFromScript(string name) + { + if (IsDestroyed) return; + try { _frame.RemoveHostObjectFromScript(name); } catch { } + } + + private T WaitAndGetResult(Task task, int timeoutSeconds = 20) + { + var start = DateTime.Now; + while (!task.IsCompleted) + { + Application.DoEvents(); + if ((DateTime.Now - start).TotalSeconds > timeoutSeconds) throw new TimeoutException("Operation timed out."); + System.Threading.Thread.Sleep(1); + } + if (task.IsFaulted && task.Exception != null) throw task.Exception.InnerException; + return task.Result; + } + + public void PostWebMessageAsJson(string webMessageAsJson) + { + if (IsDestroyed) return; + try { _frame.PostWebMessageAsJson(webMessageAsJson); } catch { } + } + + public void PostWebMessageAsString(string webMessageAsString) + { + if (IsDestroyed) return; + try { _frame.PostWebMessageAsString(webMessageAsString); } catch { } + } + + public bool IsDestroyed + { + get + { + try + { + var x = _frame.Name; + return false; + } + catch + { + return true; + } + } + } + + public bool IsDestroyedMethod() => IsDestroyed; + } +} diff --git a/src/Core/WebView2Manager.Actions.cs b/src/Core/WebView2Manager.Actions.cs new file mode 100644 index 0000000..357cb8a --- /dev/null +++ b/src/Core/WebView2Manager.Actions.cs @@ -0,0 +1,509 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.WinForms; + +namespace NetWebView2Lib +{ + public partial class WebView2Manager + { + #region 9. PUBLIC API: NAVIGATION & CONTROL + public void Navigate(string url) => InvokeOnUiThread(() => _webView.CoreWebView2.Navigate(url)); + + public void NavigateToString(string htmlContent) + { + _webView.Invoke(new Action(async () => { + int attempts = 0; + while (_webView.CoreWebView2 == null && attempts < 20) { await Task.Delay(50); attempts++; } + _webView.CoreWebView2?.NavigateToString(htmlContent); + })); + } + + public void Reload() => InvokeOnUiThread(() => _webView.CoreWebView2?.Reload()); + public void Stop() => InvokeOnUiThread(() => _webView.CoreWebView2?.Stop()); + public void GoBack() => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null && _webView.CoreWebView2.CanGoBack) _webView.CoreWebView2.GoBack(); }); + public void GoForward() => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null && _webView.CoreWebView2.CanGoForward) _webView.CoreWebView2.GoForward(); }); + public bool GetCanGoBack() => _webView?.CoreWebView2?.CanGoBack ?? false; + public bool GetCanGoForward() => _webView?.CoreWebView2?.CanGoForward ?? false; + + public string AddInitializationScript(string script) + { + if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; + return RunOnUiThread(() => + { + try + { + var task = _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(script); + string scriptId = WaitAndGetResult(task); + _webView.CoreWebView2.ExecuteScriptAsync(script); + return scriptId; + } + catch (Exception ex) { return "ERROR: " + ex.Message; } + }); + } + + public void RemoveInitializationScript(string scriptId) + { + InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null && !string.IsNullOrEmpty(scriptId)) _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(scriptId); }); + } + + public void SetContextMenuEnabled(bool enabled) + { + _contextMenuEnabled = enabled; + InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null) _webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true; }); + } + #endregion + + #region 10. PUBLIC API: DATA EXTRACTION + public async void GetHtmlSource() + { + if (_webView?.CoreWebView2 == null) return; + string html = await _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML"); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "HTML_SOURCE|" + CleanJsString(html)); + } + + public async void GetSelectedText() + { + if (_webView?.CoreWebView2 == null) return; + string selectedText = await _webView.CoreWebView2.ExecuteScriptAsync("window.getSelection().toString()"); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "SELECTED_TEXT|" + CleanJsString(selectedText)); + } + + public bool ParseJsonToInternal(string json) => _internalParser.Parse(json?.Trim()); + public string GetInternalJsonValue(string path) => _internalParser.GetTokenValue(path); + + public bool BindJsonToBrowser(string variableName) + { + try + { + if (_webView?.CoreWebView2 == null) return false; + string jsonData = _internalParser.GetMinifiedJson() ?? "{}"; + string safeJson = jsonData.Replace("\\", "\\\\").Replace("'", "\\'"); + string script = $@"try {{ window.{variableName} = JSON.parse('{safeJson}'); true; }} catch(e) {{ false; }}"; + _webView.CoreWebView2.ExecuteScriptAsync(script); + return true; + } + catch { return false; } + } + + public void SyncInternalData(string json, string bindToVariableName = "") + { + if (ParseJsonToInternal(json) && !string.IsNullOrEmpty(bindToVariableName)) BindJsonToBrowser(bindToVariableName); + } + + public async void GetInnerText() + { + if (_webView?.CoreWebView2 == null) return; + try { + string text = await _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.innerText"); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "INNER_TEXT|" + CleanJsString(text)); + } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|INNER_TEXT_FAILED: " + ex.Message); } + } + #endregion + + #region 11. PUBLIC API: UI & INTERACTION + public void ExecuteScript(string script) => InvokeOnUiThread(() => _webView.CoreWebView2.ExecuteScriptAsync(script)); + + public async void ExecuteScriptOnPage(string script) + { + if (_webView?.CoreWebView2 != null) await _webView.CoreWebView2.ExecuteScriptAsync(script); + } + + public void PostWebMessage(string message) => InvokeOnUiThread(() => _webView.CoreWebView2.PostWebMessageAsString(message)); + + public string ExecuteScriptWithResult(string script) + { + if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; + try + { + var task = _webView.CoreWebView2.ExecuteScriptAsync(script); + return CleanJsString(WaitAndGetResult(task)); + } + catch (Exception ex) { return "ERROR: " + ex.Message; } + } + + public async void InjectCss(string cssCode) + { + string js = $"(function() {{ let style = document.getElementById('{StyleId}'); if (!style) {{ style = document.createElement('style'); style.id = '{StyleId}'; document.head.appendChild(style); }} style.innerHTML = `{cssCode.Replace("`", "\\` text-decoration")}`; }})();"; + ExecuteScript(js); + if (!string.IsNullOrEmpty(_lastCssRegistrationId)) _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(_lastCssRegistrationId); + _lastCssRegistrationId = await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(js); + } + + public void ClearInjectedCss() + { + if (!string.IsNullOrEmpty(_lastCssRegistrationId)) { _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(_lastCssRegistrationId); _lastCssRegistrationId = ""; } + ExecuteScript($"(function() {{ let style = document.getElementById('{StyleId}'); if (style) style.remove(); }})();"); + } + + public void ToggleAuditHighlights(bool enable) + { + if (enable) InjectCss("img, h1, h2, h3, table, a { outline: 3px solid #FF6A00 !important; outline-offset: -3px !important; }"); + else ClearInjectedCss(); + } + + public async void CapturePreview(string filePath, string format) + { + if (_webView?.CoreWebView2 == null) return; + var imgFormat = format.ToLower().Contains("jpg") ? CoreWebView2CapturePreviewImageFormat.Jpeg : CoreWebView2CapturePreviewImageFormat.Png; + try { + using (var fs = File.Create(filePath)) await _webView.CoreWebView2.CapturePreviewAsync(imgFormat, fs); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "CAPTURE_SUCCESS|" + filePath); + } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "CAPTURE_ERROR|" + ex.Message); } + } + + public void ShowPrintUI() => _webView?.CoreWebView2.ShowPrintUI(); + + public void Print() + { + InvokeOnUiThread(async () => { + if (_webView?.CoreWebView2 != null) { + try { await _webView.CoreWebView2.ExecuteScriptAsync("window.print();"); } + catch(Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PRINT_ERROR|" + ex.Message); } + } + }); + } + + public void SetLockState(bool lockState) + { + InvokeOnUiThread(() => { + if (_webView?.CoreWebView2 != null) { + var s = _webView.CoreWebView2.Settings; + s.AreDefaultContextMenusEnabled = !lockState; + s.AreDevToolsEnabled = !lockState; + s.IsZoomControlEnabled = !lockState; + s.IsBuiltInErrorPageEnabled = !lockState; + s.AreDefaultScriptDialogsEnabled = !lockState; + s.AreBrowserAcceleratorKeysEnabled = !lockState; + s.IsStatusBarEnabled = !lockState; + } + _areBrowserPopupsAllowed = !lockState; + _contextMenuEnabled = !lockState; + }); + } + + public void LockWebView() => SetLockState(true); + public void UnLockWebView() => SetLockState(false); + public void DisableBrowserFeatures() => LockWebView(); + public void EnableBrowserFeatures() => UnLockWebView(); + + public async void ExportToPdf(string filePath) + { + if (_webView?.CoreWebView2 == null) return; + try { + await _webView.CoreWebView2.PrintToPdfAsync(filePath); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PDF_EXPORT_SUCCESS|" + filePath); + } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PDF_EXPORT_ERROR|" + ex.Message); } + } + + public string CaptureSnapshot(string cdpParameters = "{\"format\": \"mhtml\"}") + { + if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; + return RunOnUiThread(() => { + try { + var task = _webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Page.captureSnapshot", cdpParameters); + string json = WaitAndGetResult(task); + var dict = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); + return dict != null && dict.ContainsKey("data") ? dict["data"] : "ERROR: Page capture failed"; + } catch (Exception ex) { return "ERROR: " + ex.Message; } + }); + } + + public string ExportPageData(int format, string filePath) + { + if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; + return RunOnUiThread(() => { + try { + string result = ""; + if (format == 0) result = CleanJsString(WaitAndGetResult(_webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML"))); + else if (format == 1) result = CaptureSnapshot(); + if (!string.IsNullOrEmpty(filePath) && !result.StartsWith("ERROR:")) File.WriteAllText(filePath, result); + return result; + } catch (Exception ex) { return "ERROR: " + ex.Message; } + }); + } + + public string PrintToPdfStream() + { + if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; + return RunOnUiThread(() => { + try { + var task = _webView.CoreWebView2.PrintToPdfStreamAsync(null); + Stream stream = WaitAndGetResult(task); + using (MemoryStream ms = new MemoryStream()) { stream.CopyTo(ms); return Convert.ToBase64String(ms.ToArray()); } + } catch (Exception ex) { return "ERROR: " + ex.Message; } + }); + } + + public async void CallDevToolsProtocolMethod(string methodName, string parametersJson) + { + if (_webView?.CoreWebView2 == null) return; + try { + string result = await _webView.CoreWebView2.CallDevToolsProtocolMethodAsync(methodName, parametersJson); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"CDP_RESULT|{methodName}|{result}"); + } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"CDP_ERROR|{methodName}|{ex.Message}"); } + } + + public void SetZoom(double factor) => InvokeOnUiThread(() => _webView.ZoomFactor = factor); + public void ResetZoom() => SetZoom(1.0); + public void SetMuted(bool muted) => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null) _webView.CoreWebView2.IsMuted = muted; }); + public bool IsMuted() => _webView?.CoreWebView2?.IsMuted ?? false; + public void Resize(int w, int h) => InvokeOnUiThread(() => _webView.Size = new Size(w, h)); + + public async void ClearBrowserData() + { + await _webView.EnsureCoreWebView2Async(); + await _webView.CoreWebView2.Profile.ClearBrowsingDataAsync(); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "DATA_CLEARED"); + } + + public async void ClearCache() + { + if (_webView?.CoreWebView2 != null) + await _webView.CoreWebView2.Profile.ClearBrowsingDataAsync(CoreWebView2BrowsingDataKinds.DiskCache | CoreWebView2BrowsingDataKinds.LocalStorage); + } + + public async void GetCookies(string channelId) + { + if (_webView?.CoreWebView2?.CookieManager == null) return; + try { + var cookieList = await _webView.CoreWebView2.CookieManager.GetCookiesAsync(null); + var sb = new System.Text.StringBuilder("["); + for(int i=0; i _webView?.CoreWebView2?.CookieManager?.DeleteAllCookies(); + + public void SetAdBlock(bool active) => _isAdBlockActive = active; + public void AddBlockRule(string domain) { if (!string.IsNullOrEmpty(domain)) _blockList.Add(domain.ToLower()); } + public void ClearBlockRules() => _blockList.Clear(); + + public void SetAutoResize(bool enabled) + { + if (_parentHandle == IntPtr.Zero || _parentSubclass == null) return; + _autoResizeEnabled = enabled; + if (_autoResizeEnabled) { _parentSubclass.AssignHandle(_parentHandle); PerformSmartResize(); } + else _parentSubclass.ReleaseHandle(); + } + + public void SetZoomFactor(double factor) { if (factor >= 0.1 && factor <= 5.0) InvokeOnUiThread(() => _webView.ZoomFactor = factor); } + public void OpenDevToolsWindow() => InvokeOnUiThread(() => _webView?.CoreWebView2?.OpenDevToolsWindow()); + public void WebViewSetFocus() => InvokeOnUiThread(() => _webView?.Focus()); + + public void SetUserAgent(string userAgent) { InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.UserAgent = userAgent; }); } + public string GetDocumentTitle() => _webView?.CoreWebView2?.DocumentTitle ?? ""; + public string GetSource() => _webView?.Source?.ToString() ?? ""; + public uint GetBrowserProcessId() { try { return _webView?.CoreWebView2?.BrowserProcessId ?? 0; } catch { return 0; } } + + public void SetScriptEnabled(bool enabled) { InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsScriptEnabled = enabled; }); } + public void SetWebMessageEnabled(bool enabled) { InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsWebMessageEnabled = enabled; }); } + public void SetStatusBarEnabled(bool enabled) { InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsStatusBarEnabled = enabled; }); } + + public void SetDownloadPath(string path) { _customDownloadPath = path; if (!Directory.Exists(path)) Directory.CreateDirectory(path); } + public string ActiveDownloadsList => string.Join("|", _activeDownloads.Keys); + + public void CancelDownloads(string uri = "") + { + if (string.IsNullOrEmpty(uri)) { foreach (var d in _activeDownloads.Values) d.Cancel(); _activeDownloads.Clear(); } + else if (_activeDownloads.ContainsKey(uri)) { _activeDownloads[uri].Cancel(); _activeDownloads.Remove(uri); } + } + + public void SetVirtualHostNameToFolderMapping(string hostName, string folderPath, int accessKind) + { + InvokeOnUiThread(() => _webView?.CoreWebView2?.SetVirtualHostNameToFolderMapping(hostName, folderPath, (Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind)accessKind)); + } + + public string CapturePreviewAsBase64(string format) + { + try { + var imgFormat = format.ToLower() == "jpeg" ? CoreWebView2CapturePreviewImageFormat.Jpeg : CoreWebView2CapturePreviewImageFormat.Png; + using (var ms = new MemoryStream()) { + WaitTask(_webView.CoreWebView2.CapturePreviewAsync(imgFormat, ms)); + return $"data:image/{format.ToLower()};base64,{Convert.ToBase64String(ms.ToArray())}"; + } + } catch (Exception ex) { return "Error: " + ex.Message; } + } + + public void AddExtension(string extensionPath) + { + InvokeOnUiThread(async () => { + if (_webView?.CoreWebView2?.Profile == null || !Directory.Exists(extensionPath)) return; + try { + var ext = await _webView.CoreWebView2.Profile.AddBrowserExtensionAsync(extensionPath); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "EXTENSION_LOADED|" + ext.Id); + } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION_FAILED|" + ex.Message); } + }); + } + + public void RemoveExtension(string extensionId) + { + InvokeOnUiThread(async () => { + if (_webView?.CoreWebView2?.Profile == null) return; + try { + var extensions = await _webView.CoreWebView2.Profile.GetBrowserExtensionsAsync(); + foreach (var ext in extensions) { + if (ext.Id == extensionId) { await ext.RemoveAsync(); OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "EXTENSION_REMOVED|" + extensionId); return; } + } + } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|REMOVE_EXTENSION_FAILED|" + ex.Message); } + }); + } + #endregion + + #region 16. UNIFIED SETTINGS (PROPERTIES) + public bool AreDevToolsEnabled + { + get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreDevToolsEnabled ?? false); + set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreDevToolsEnabled = value; }); + } + + public bool AreBrowserPopupsAllowed + { + get => _areBrowserPopupsAllowed; + set => InvokeOnUiThread(() => _areBrowserPopupsAllowed = value); + } + + public bool AreDefaultContextMenusEnabled + { + get => _contextMenuEnabled; + set => SetContextMenuEnabled(value); + } + + public bool Verbose + { + get => _verbose; + set { _verbose = value; Log("Verbose mode: " + (_verbose ? "ENABLED" : "DISABLED")); } + } + + public bool AreDefaultScriptDialogsEnabled + { + get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreDefaultScriptDialogsEnabled ?? true); + set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = value; }); + } + + public bool AreBrowserAcceleratorKeysEnabled + { + get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreBrowserAcceleratorKeysEnabled ?? true); + set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = value; }); + } + + public bool IsStatusBarEnabled + { + get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.IsStatusBarEnabled ?? true); + set => SetStatusBarEnabled(value); + } + + public double ZoomFactor + { + get => RunOnUiThread(() => _webView?.ZoomFactor ?? 1.0); + set => SetZoomFactor(value); + } + + public string BackColor + { + get => RunOnUiThread(() => ColorTranslator.ToHtml(_webView.DefaultBackgroundColor)); + set => InvokeOnUiThread(() => { + try { _webView.DefaultBackgroundColor = ColorTranslator.FromHtml(value.Replace("0x", "#")); } + catch { _webView.DefaultBackgroundColor = Color.White; } + }); + } + + public bool AreHostObjectsAllowed + { + get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreHostObjectsAllowed ?? true); + set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreHostObjectsAllowed = value; }); + } + + public int Anchor + { + get => RunOnUiThread(() => (int)_webView.Anchor); + set => InvokeOnUiThread(() => _webView.Anchor = (AnchorStyles)value); + } + + public int BorderStyle { get => 0; set { } } + public bool CustomMenuEnabled { get => _customMenuEnabled; set => _customMenuEnabled = value; } + public string AdditionalBrowserArguments { get => _additionalBrowserArguments; set => _additionalBrowserArguments = value; } + + public int HiddenPdfToolbarItems + { + get => RunOnUiThread(() => (int)(_webView?.CoreWebView2?.Settings?.HiddenPdfToolbarItems ?? CoreWebView2PdfToolbarItems.None)); + set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.HiddenPdfToolbarItems = (CoreWebView2PdfToolbarItems)value; }); + } + + public bool IsDownloadUIEnabled { get => _isDownloadUIEnabled; set => _isDownloadUIEnabled = value; } + public bool HttpStatusCodeEventsEnabled { get => _httpStatusCodeEventsEnabled; set => _httpStatusCodeEventsEnabled = value; } + public bool HttpStatusCodeDocumentOnly { get => _httpStatusCodeDocumentOnly; set => _httpStatusCodeDocumentOnly = value; } + public bool IsDownloadHandled { get => _isDownloadHandledOverride; set => _isDownloadHandledOverride = value; } + + public bool IsZoomControlEnabled + { + get => _webView?.CoreWebView2?.Settings?.IsZoomControlEnabled ?? _isZoomControlEnabled; + set { _isZoomControlEnabled = value; InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsZoomControlEnabled = value; }); } + } + + public bool IsBuiltInErrorPageEnabled + { + get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.IsBuiltInErrorPageEnabled ?? true); + set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsBuiltInErrorPageEnabled = value; }); + } + #endregion + + private void PerformSmartResize() + { + if (_webView == null || _parentHandle == IntPtr.Zero) return; + if (_webView.InvokeRequired) { _webView.Invoke(new Action(PerformSmartResize)); return; } + if (GetClientRect(_parentHandle, out Rect rect)) + { + int newWidth = (rect.Right - rect.Left) - _offsetX - _marginRight; + int newHeight = (rect.Bottom - rect.Top) - _offsetY - _marginBottom; + Log($"SmartResize: ParentSize={rect.Right - rect.Left}x{rect.Bottom - rect.Top}, NewWebViewSize={newWidth}x{newHeight}"); + _webView.Left = _offsetX; _webView.Top = _offsetY; + _webView.Width = Math.Max(10, newWidth); _webView.Height = Math.Max(10, newHeight); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "WINDOW_RESIZED|" + _webView.Width + "|" + _webView.Height); + } + } + + private class ParentWindowSubclass : NativeWindow + { + private const int WM_SIZE = 0x0005; + private readonly Action _onResize; + + public ParentWindowSubclass(Action onResize) + { + _onResize = onResize; + } + + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + if (m.Msg == WM_SIZE) _onResize?.Invoke(); + } + } + } +} diff --git a/src/Core/WebView2Manager.Events.cs b/src/Core/WebView2Manager.Events.cs new file mode 100644 index 0000000..db3f6f8 --- /dev/null +++ b/src/Core/WebView2Manager.Events.cs @@ -0,0 +1,340 @@ +using System; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.WinForms; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace NetWebView2Lib +{ + public partial class WebView2Manager + { + #region 2. DELEGATES & EVENTS + // v2.0.0: All delegates now include sender and parentHandle parameters + + /// Delegate for message events. + public delegate void OnMessageReceivedDelegate(object sender, string parentHandle, string message); + public delegate void OnNavigationStartingDelegate(object sender, string parentHandle, string url); + public delegate void OnNavigationCompletedDelegate(object sender, string parentHandle, bool isSuccess, int webErrorStatus); + public delegate void OnTitleChangedDelegate(object sender, string parentHandle, string newTitle); + public delegate void OnURLChangedDelegate(object sender, string parentHandle, string newUrl); + public delegate void OnBrowserGotFocusDelegate(object sender, string parentHandle, int reason); + public delegate void OnBrowserLostFocusDelegate(object sender, string parentHandle, int reason); + public delegate void OnWebResourceResponseReceivedDelegate(object sender, string parentHandle, int statusCode, string reasonPhrase, string requestUrl); + public delegate void OnContextMenuDelegate(object sender, string parentHandle, string menuData); + public delegate void OnZoomChangedDelegate(object sender, string parentHandle, double factor); + public delegate void OnContextMenuRequestedDelegate(object sender, string parentHandle, string linkUrl, int x, int y, string selectionText); + public delegate void OnDownloadStartingDelegate(object sender, string parentHandle, string uri, string defaultPath); + public delegate void OnDownloadStateChangedDelegate(object sender, string parentHandle, string state, string uri, long totalBytes, long receivedBytes); + public delegate void OnAcceleratorKeyPressedDelegate(object sender, string parentHandle, object args); + public delegate void OnProcessFailedDelegate(object sender, string parentHandle, object args); + public delegate void OnBasicAuthenticationRequestedDelegate(object sender, string parentHandle, object args); + public delegate void OnPermissionRequestedDelegate(object sender, string parentHandle, object args); + + public delegate void OnFrameNavigationStartingDelegate(object sender, string parentHandle, object frame, string uri); + public delegate void OnFrameNavigationCompletedDelegate(object sender, string parentHandle, object frame, bool isSuccess, int webErrorStatus); + public delegate void OnFrameContentLoadingDelegate(object sender, string parentHandle, object frame, long navigationId); + public delegate void OnFrameDOMContentLoadedDelegate(object sender, string parentHandle, object frame, long navigationId); + public delegate void OnFrameWebMessageReceivedDelegate(object sender, string parentHandle, object frame, string message); + public delegate void OnFrameCreatedDelegate(object sender, string parentHandle, object frame); + public delegate void OnFrameDestroyedDelegate(object sender, string parentHandle, object frame); + public delegate void OnFrameNameChangedDelegate(object sender, string parentHandle, object frame); + public delegate void OnFramePermissionRequestedDelegate(object sender, string parentHandle, object frame, object args); + + // Event declarations + public event OnMessageReceivedDelegate OnMessageReceived; + public event OnNavigationStartingDelegate OnNavigationStarting; + public event OnNavigationCompletedDelegate OnNavigationCompleted; + public event OnTitleChangedDelegate OnTitleChanged; + public event OnURLChangedDelegate OnURLChanged; + public event OnWebResourceResponseReceivedDelegate OnWebResourceResponseReceived; + public event OnDownloadStartingDelegate OnDownloadStarting; + public event OnDownloadStateChangedDelegate OnDownloadStateChanged; + public event OnContextMenuDelegate OnContextMenu; + public event OnContextMenuRequestedDelegate OnContextMenuRequested; + public event OnZoomChangedDelegate OnZoomChanged; + public event OnBrowserGotFocusDelegate OnBrowserGotFocus; + public event OnBrowserLostFocusDelegate OnBrowserLostFocus; + public event OnAcceleratorKeyPressedDelegate OnAcceleratorKeyPressed; + public event OnProcessFailedDelegate OnProcessFailed; + public event OnBasicAuthenticationRequestedDelegate OnBasicAuthenticationRequested; + public event OnPermissionRequestedDelegate OnPermissionRequested; + + public event OnFrameNavigationStartingDelegate OnFrameNavigationStarting; + public event OnFrameNavigationCompletedDelegate OnFrameNavigationCompleted; + public event OnFrameCreatedDelegate OnFrameCreated; + public event OnFrameDestroyedDelegate OnFrameDestroyed; + public event OnFrameNameChangedDelegate OnFrameNameChanged; + public event OnFramePermissionRequestedDelegate OnFramePermissionRequested; + public event OnFrameContentLoadingDelegate OnFrameContentLoading; + public event OnFrameDOMContentLoadedDelegate OnFrameDOMContentLoaded; + public event OnFrameWebMessageReceivedDelegate OnFrameWebMessageReceived; + #endregion + + /// + /// Normalizes an IntPtr handle as an AutoIt Advanced Window Description string. + /// Format: [HANDLE:0x00160678] + /// + private string FormatHandle(IntPtr handle) + { + if (handle == IntPtr.Zero) return "[HANDLE:0x" + IntPtr.Zero.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; + return "[HANDLE:0x" + handle.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; + } + + #region 8. EVENT REGISTRATION + private void RegisterEvents() + { + if (_webView?.CoreWebView2 == null) return; + + // Frame Tracking + _webView.CoreWebView2.FrameCreated += (s, e) => { + RegisterFrame(e.Frame); + InvokeOnUiThread(() => OnFrameCreated?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(e.Frame))); + }; + + // Context Menu Event + _webView.CoreWebView2.ContextMenuRequested += async (sender, args) => + { + Log($"ContextMenuRequested: Location={args.Location.X}x{args.Location.Y}, Target={args.ContextMenuTarget.Kind}"); + args.Handled = !_contextMenuEnabled; + + try + { + string script = "document.elementFromPoint(" + args.Location.X + "," + args.Location.Y + ").closest('table') ? 'TABLE' : document.elementFromPoint(" + args.Location.X + "," + args.Location.Y + ").tagName"; + string tagNameResult = await _webView.CoreWebView2.ExecuteScriptAsync(script); + + InvokeOnUiThread(() => { + string tagName = tagNameResult?.Trim('\"') ?? "UNKNOWN"; + string k = args.ContextMenuTarget.Kind.ToString(); + string src = args.ContextMenuTarget.HasSourceUri ? args.ContextMenuTarget.SourceUri : ""; + string lnk = args.ContextMenuTarget.HasLinkUri ? args.ContextMenuTarget.LinkUri : ""; + string sel = args.ContextMenuTarget.HasSelection ? args.ContextMenuTarget.SelectionText : ""; + + OnContextMenuRequested?.Invoke(this, FormatHandle(_parentHandle), lnk, args.Location.X, args.Location.Y, sel); + + string cleanSrc = src.Replace("\"", "\\\""); + string cleanLnk = lnk.Replace("\"", "\\\""); + string cleanSel = sel.Replace("\"", "\\\"").Replace("\r", "").Replace("\n", "\\n"); + + string json = "{" + + "\"x\":" + args.Location.X + "," + + "\"y\":" + args.Location.Y + "," + + "\"kind\":\"" + k + "\"," + + "\"tagName\":\"" + tagName + "\"," + + "\"src\":\"" + cleanSrc + "\"," + + "\"link\":\"" + cleanLnk + "\"," + + "\"selection\":\"" + cleanSel + "\"" + + "}"; + + OnContextMenu?.Invoke(this, FormatHandle(_parentHandle), "JSON:" + json); + }); + } + catch (Exception ex) { Debug.WriteLine("ContextMenu Error: " + ex.Message); } + }; + + // Ad Blocking + _webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); + _webView.CoreWebView2.WebResourceRequested += (s, e) => + { + string uri = e.Request.Uri.ToLower(); + if (!_isAdBlockActive) return; + foreach (var domain in _blockList) + { + if (uri.Contains(domain)) + { + e.Response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(null, 403, "Forbidden", ""); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"BLOCKED_AD|{uri}"); + return; + } + } + }; + + _webView.CoreWebView2.NewWindowRequested += (s, e) => + { + if (!_areBrowserPopupsAllowed) { + e.Handled = true; + if (!string.IsNullOrEmpty(e.Uri)) + { + string targetUri = e.Uri; + _webView.BeginInvoke(new Action(() => { + if (_webView?.CoreWebView2 != null) _webView.CoreWebView2.Navigate(targetUri); + })); + } + } + }; + + // Navigation & Content Events + _webView.CoreWebView2.NavigationStarting += (s, e) => { + OnNavigationStarting?.Invoke(this, FormatHandle(_parentHandle), e.Uri); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_STARTING|" + e.Uri); + }; + + _webView.CoreWebView2.NavigationCompleted += (s, e) => { + OnNavigationCompleted?.Invoke(this, FormatHandle(_parentHandle), e.IsSuccess, (int)e.WebErrorStatus); + if (e.IsSuccess) + { + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_COMPLETED"); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "TITLE_CHANGED|" + _webView.CoreWebView2.DocumentTitle); + } + else OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_ERROR|" + e.WebErrorStatus); + }; + + _webView.CoreWebView2.SourceChanged += (s, e) => { + OnURLChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.CoreWebView2.Source); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "URL_CHANGED|" + _webView.Source); + }; + + _webView.CoreWebView2.DocumentTitleChanged += (s, e) => { + OnTitleChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.CoreWebView2.DocumentTitle); + }; + + _webView.ZoomFactorChanged += (s, e) => { + OnZoomChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.ZoomFactor); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ZOOM_CHANGED|" + _webView.ZoomFactor); + }; + + _webView.CoreWebView2.ProcessFailed += (s, e) => { + OnProcessFailed?.Invoke(this, FormatHandle(_parentHandle), new ProcessFailedEventArgsWrapper(e, _parentHandle)); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PROCESS_FAILED|" + e.ProcessFailedKind + "|" + e.Reason); + }; + + _webView.CoreWebView2.BasicAuthenticationRequested += (s, e) => { + OnBasicAuthenticationRequested?.Invoke(this, FormatHandle(_parentHandle), new BasicAuthenticationRequestedEventArgsWrapper(e, _parentHandle)); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BASIC_AUTH_REQUESTED|" + e.Uri); + }; + + _webView.CoreWebView2.PermissionRequested += (s, e) => { + OnPermissionRequested?.Invoke(this, FormatHandle(_parentHandle), new WebView2PermissionRequestedEventArgsWrapper(e, _parentHandle)); + }; + + _webView.CoreWebView2.WebMessageReceived += (s, e) => + { + string message = e.TryGetWebMessageAsString(); + if (message != null && message.StartsWith("CONTEXT_MENU_REQUEST|")) + { + var parts = message.Split('|'); + if (parts.Length >= 5) + { + string lnk = parts[1]; + int x = int.TryParse(parts[2], out int px) ? px : 0; + int y = int.TryParse(parts[3], out int py) ? py : 0; + string sel = parts[4]; + _webView.BeginInvoke(new Action(() => OnContextMenuRequested?.Invoke(this, FormatHandle(_parentHandle), lnk, x, y, sel))); + return; + } + } + _bridge.RaiseMessage(message); + }; + + _webView.GotFocus += (s, e) => { + OnBrowserGotFocus?.Invoke(this, FormatHandle(_parentHandle), 0); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BROWSER_GOT_FOCUS|"); + }; + + _webView.LostFocus += (s, e) => { + _webView.BeginInvoke(new Action(() => { + IntPtr focusedHandle = GetFocus(); + if (focusedHandle != _webView.Handle && !IsChild(_webView.Handle, focusedHandle)) { + OnBrowserLostFocus?.Invoke(this, FormatHandle(_parentHandle), 0); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BROWSER_LOST_FOCUS|"); + } + })); + }; + + _webView.CoreWebView2.AddHostObjectToScript("autoit", _bridge); + + _webView.CoreWebView2.WebResourceResponseReceived += (s, e) => + { + if (!_httpStatusCodeEventsEnabled || e.Response == null) return; + int statusCode = e.Response.StatusCode; + string reasonPhrase = GetReasonPhrase(statusCode, e.Response.ReasonPhrase); + string requestUrl = e.Request.Uri; + if (_httpStatusCodeDocumentOnly && !e.Request.Headers.Contains("X-NetWebView2-IsDoc")) return; + OnWebResourceResponseReceived?.Invoke(this, FormatHandle(_parentHandle), statusCode, reasonPhrase, requestUrl); + }; + + _webView.CoreWebView2.DownloadStarting += async (s, e) => + { + var deferral = e.GetDeferral(); + string currentUri = e.DownloadOperation.Uri; + if (!string.IsNullOrEmpty(_customDownloadPath)) + { + string fileName = System.IO.Path.GetFileName(e.ResultFilePath); + e.ResultFilePath = System.IO.Path.Combine(_customDownloadPath, fileName); + } + _activeDownloads[currentUri] = e.DownloadOperation; + _isDownloadHandledOverride = false; + OnDownloadStarting?.Invoke(this, FormatHandle(_parentHandle), e.DownloadOperation.Uri, e.ResultFilePath); + + var sw = Stopwatch.StartNew(); + while (sw.ElapsedMilliseconds < 600 && !_isDownloadHandledOverride) await Task.Delay(10); + + InvokeOnUiThread(() => { + if (_isDownloadHandledOverride) + { + e.Cancel = true; + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_CANCELLED|{e.DownloadOperation.Uri}"); + deferral.Complete(); + return; + } + e.Handled = !_isDownloadUIEnabled; + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_STARTING|{e.DownloadOperation.Uri}|{e.ResultFilePath}"); + + e.DownloadOperation.StateChanged += (sender, args) => + { + long totalBytes = (long)(e.DownloadOperation.TotalBytesToReceive ?? 0); + long receivedBytes = (long)e.DownloadOperation.BytesReceived; + OnDownloadStateChanged?.Invoke(this, FormatHandle(_parentHandle), e.DownloadOperation.State.ToString(), e.DownloadOperation.Uri, totalBytes, receivedBytes); + if (e.DownloadOperation.State == Microsoft.Web.WebView2.Core.CoreWebView2DownloadState.Interrupted) + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_CANCELLED|{currentUri}|{e.DownloadOperation.InterruptReason}"); + if (e.DownloadOperation.State != Microsoft.Web.WebView2.Core.CoreWebView2DownloadState.InProgress) + _activeDownloads.Remove(currentUri); + }; + + int lastPercent = -1; + e.DownloadOperation.BytesReceivedChanged += (sender, args) => + { + long total = (long)(e.DownloadOperation.TotalBytesToReceive ?? 0); + long received = (long)e.DownloadOperation.BytesReceived; + if (total > 0) + { + int currentPercent = (int)((received * 100) / total); + if (currentPercent > lastPercent) + { + lastPercent = currentPercent; + OnDownloadStateChanged?.Invoke(this, FormatHandle(_parentHandle), "InProgress", e.DownloadOperation.Uri, total, received); + } + } + }; + deferral.Complete(); + }); + }; + + // Accelerator Key Pressed Event (Reflection) + try + { + var controllerField = typeof(WebView2).GetField("_coreWebView2Controller", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (controllerField?.GetValue(_webView) is CoreWebView2Controller controller) + { + controller.AcceleratorKeyPressed += (s, e) => + { + var eventArgs = new WebView2AcceleratorKeyPressedEventArgs(e, this); + if (!string.IsNullOrEmpty(_blockedVirtualKeys)) + { + string vKeyStr = e.VirtualKey.ToString(); + if (_blockedVirtualKeys.Split(',').Any(k => k.Trim() == vKeyStr)) + { + e.Handled = true; + eventArgs.Block(); + } + } + OnAcceleratorKeyPressed?.Invoke(this, FormatHandle(_parentHandle), eventArgs); + if (eventArgs.IsCurrentlyHandled) e.Handled = true; + }; + } + } + catch (Exception ex) { Debug.WriteLine("AcceleratorKey Reflection Error: " + ex.Message); } + } + #endregion + } +} diff --git a/src/Core/WebView2Manager.FrameMgmt.cs b/src/Core/WebView2Manager.FrameMgmt.cs new file mode 100644 index 0000000..f7714af --- /dev/null +++ b/src/Core/WebView2Manager.FrameMgmt.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using Microsoft.Web.WebView2.Core; + +namespace NetWebView2Lib +{ + public partial class WebView2Manager + { + #region 11. PUBLIC API: IFRAME MANAGEMENT + private void RegisterFrame(CoreWebView2Frame frame) + { + lock (_frames) + { + _frames.Add(frame); + lock (_frameUrls) { _frameUrls[frame] = "about:blank"; } + frame.Destroyed += (s, e) => + { + InvokeOnUiThread(() => OnFrameDestroyed?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this))); + lock (_frames) { _frames.Remove(frame); } + lock (_frameUrls) { _frameUrls.Remove(frame); } + }; + + frame.NameChanged += (s, e) => InvokeOnUiThread(() => OnFrameNameChanged?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this))); + + frame.NavigationStarting += (s, e) => + { + lock (_frameUrls) { _frameUrls[frame] = e.Uri; } + InvokeOnUiThread(() => OnFrameNavigationStarting?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this), e.Uri)); + }; + frame.NavigationCompleted += (s, e) => + { + if (e.IsSuccess) { lock (_frameUrls) { _frameUrls[frame] = frame.GetType().GetProperty("Source")?.GetValue(frame) as string ?? _frameUrls[frame]; } } + InvokeOnUiThread(() => OnFrameNavigationCompleted?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this), e.IsSuccess, (int)e.WebErrorStatus)); + }; + frame.ContentLoading += (s, e) => InvokeOnUiThread(() => OnFrameContentLoading?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this), (long)e.NavigationId)); + frame.DOMContentLoaded += (s, e) => InvokeOnUiThread(() => OnFrameDOMContentLoaded?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this), (long)e.NavigationId)); + frame.WebMessageReceived += (s, e) => + { + string msg = ""; + try { msg = e.TryGetWebMessageAsString(); } catch { msg = e.WebMessageAsJson; } + InvokeOnUiThread(() => OnFrameWebMessageReceived?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this), msg)); + }; + frame.PermissionRequested += (s, e) => InvokeOnUiThread(() => OnFramePermissionRequested?.Invoke(this, FormatHandle(_parentHandle), new WebView2Frame(frame, this), new WebView2PermissionRequestedEventArgsWrapper(e, _parentHandle))); + } + } + + internal string GetFrameUrlByObject(CoreWebView2Frame frame) + { + lock (_frameUrls) { return _frameUrls.TryGetValue(frame, out var url) ? url : "about:blank"; } + } + + public int GetFrameCount() { lock (_frames) return _frames.Count; } + + public string GetFrameUrl(int index) + { + lock (_frames) + { + if (index < 0 || index >= _frames.Count) return "ERROR: Invalid frame index"; + lock (_frameUrls) { return _frameUrls.TryGetValue(_frames[index], out var url) ? url : "about:blank"; } + } + } + + public string GetFrameName(int index) + { + lock (_frames) { if (index < 0 || index >= _frames.Count) return "ERROR: Invalid frame index"; try { return _frames[index].Name; } catch { return "ERROR: Access failed"; } } + } + + public object GetFrame(int index) + { + lock (_frames) { if (index < 0 || index >= _frames.Count) return null; try { return new WebView2Frame(_frames[index], this); } catch { return null; } } + } + + public string GetFrameUrls() + { + lock (_frames) { lock (_frameUrls) { var urls = _frames.Select(f => _frameUrls.TryGetValue(f, out var url) ? url : "about:blank"); return string.Join("|", urls); } } + } + + public string GetFrameNames() + { + lock (_frames) { var names = _frames.Select(f => { try { return f.Name; } catch { return "ERROR"; } }); return string.Join("|", names); } + } + + public async void GetFrameHtmlSource(int index) + { + CoreWebView2Frame frame = null; + lock (_frames) { if (index >= 0 && index < _frames.Count) frame = _frames[index]; } + if (frame == null) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"ERROR|FRAME_NOT_FOUND:{index}"); return; } + try { + string html = await frame.ExecuteScriptAsync("document.documentElement.outerHTML"); + InvokeOnUiThread(() => OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"FRAME_HTML_SOURCE|{index}|" + CleanJsString(html))); + } + catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"ERROR|FRAME_HTML_FAILED|{index}|" + ex.Message); } + } + + public object GetFrameById(uint frameId) + { + lock (_frames) + { + var frame = _frames.FirstOrDefault(f => { + try { return f.FrameId == frameId; } + catch { return false; } + }); + return frame != null ? new WebView2Frame(frame, this) : null; + } + } + #endregion + } +} diff --git a/src/Core/WebView2Manager.Helpers.cs b/src/Core/WebView2Manager.Helpers.cs new file mode 100644 index 0000000..177947b --- /dev/null +++ b/src/Core/WebView2Manager.Helpers.cs @@ -0,0 +1,143 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace NetWebView2Lib +{ + public partial class WebView2Manager + { + #region 3. NATIVE METHODS & HELPERS + [DllImport("user32.dll")] + private static extern bool GetClientRect(IntPtr hWnd, out Rect lpRect); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); + + [DllImport("user32.dll")] + private static extern IntPtr GetFocus(); + + [DllImport("user32.dll")] + private static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd); + + /// + /// A simple Rectangle struct. + /// + [StructLayout(LayoutKind.Sequential)] + public struct Rect { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + #endregion + + #region 17. HELPER METHODS + private void Log(string message) + { + if (_verbose) + { + string h = FormatHandle(_parentHandle); + Console.WriteLine($"+++[NetWebView2Lib]{h}[{DateTime.Now:HH:mm:ss.fff}] {message}"); + } + } + + private T RunOnUiThread(Func func) + { + if (_webView == null || _webView.IsDisposed) return default; + if (_webView.InvokeRequired) return (T)_webView.Invoke(func); + return func(); + } + + private void InvokeOnUiThread(Action action) + { + if (_webView == null || _webView.IsDisposed) return; + if (_webView.InvokeRequired) _webView.Invoke(action); + else action(); + } + + private T WaitAndGetResult(Task task, int timeoutSeconds = 20) + { + var start = DateTime.Now; + while (!task.IsCompleted) + { + System.Windows.Forms.Application.DoEvents(); + if ((DateTime.Now - start).TotalSeconds > timeoutSeconds) throw new TimeoutException("Operation timed out."); + System.Threading.Thread.Sleep(1); + } + return task.Result; + } + + private void WaitTask(Task task, int timeoutSeconds = 20) + { + var start = DateTime.Now; + while (!task.IsCompleted) + { + System.Windows.Forms.Application.DoEvents(); + if ((DateTime.Now - start).TotalSeconds > timeoutSeconds) throw new TimeoutException("Operation timed out."); + System.Threading.Thread.Sleep(1); + } + if (task.IsFaulted && task.Exception != null) throw task.Exception.InnerException; + } + + public string EncodeURI(string value) => string.IsNullOrEmpty(value) ? "" : System.Net.WebUtility.UrlEncode(value); + public string DecodeURI(string value) => string.IsNullOrEmpty(value) ? "" : System.Net.WebUtility.UrlDecode(value); + public string EncodeB64(string value) => string.IsNullOrEmpty(value) ? "" : Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(value)); + + /// Decodes a Base64-encoded string into a byte array. + public byte[] DecodeB64ToBinary(string base64Text) + { + if (string.IsNullOrEmpty(base64Text)) return new byte[0]; + try { return Convert.FromBase64String(base64Text); } + catch { return new byte[0]; } + } + + /// Encodes raw binary data (byte array) to a Base64 string. + public string EncodeBinaryToB64(object binaryData) + { + if (binaryData is byte[] bytes) return Convert.ToBase64String(bytes); + return ""; + } + + public string DecodeB64(string value) + { + if (string.IsNullOrEmpty(value)) return ""; + try { return System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(value)); } + catch { return ""; } + } + + private string CleanJsString(string input) + { + if (string.IsNullOrEmpty(input)) return ""; + string decoded = System.Text.RegularExpressions.Regex.Unescape(input); + if (decoded.StartsWith("\"") && decoded.EndsWith("\"") && decoded.Length >= 2) decoded = decoded.Substring(1, decoded.Length - 2); + return decoded; + } + + private string GetReasonPhrase(int statusCode, string originalReason) + { + if (!string.IsNullOrEmpty(originalReason)) return originalReason; + switch (statusCode) + { + case 200: return "OK"; + case 201: return "Created"; + case 204: return "No Content"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 304: return "Not Modified"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 429: return "Too Many Requests"; + case 500: return "Internal Server Error"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + default: + try { return ((System.Net.HttpStatusCode)statusCode).ToString(); } + catch { return "Unknown"; } + } + } + #endregion + } +} diff --git a/src/Core/WebView2Manager.Main.cs b/src/Core/WebView2Manager.Main.cs new file mode 100644 index 0000000..a0a0df8 --- /dev/null +++ b/src/Core/WebView2Manager.Main.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.WinForms; + +namespace NetWebView2Lib +{ + // --- 3. THE MANAGER CLASS --- + /// + /// The Main Manager Class for WebView2 Interaction. + /// + [Guid("E3F4A5B6-C7D8-4E9F-0A1B-2C3D4E5F6A7B")] + [ComSourceInterfaces(typeof(IWebViewEvents))] + [ClassInterface(ClassInterfaceType.None)] + [ComVisible(true)] + [ProgId("NetWebView2Lib.WebView2Manager")] + public partial class WebView2Manager : IWebViewActions + { + #region 1. PRIVATE FIELDS + private readonly WebView2 _webView; + private readonly WebView2Bridge _bridge; + private readonly WebView2Parser _internalParser = new WebView2Parser(); + private readonly List _frames = new List(); + private readonly Dictionary _frameUrls = new Dictionary(); + private bool _verbose = false; + + private bool _isAdBlockActive = false; + private readonly List _blockList = new List(); + private const string StyleId = "autoit-injected-style"; + private bool _areBrowserPopupsAllowed = false; + private bool _contextMenuEnabled = true; + private bool _autoResizeEnabled = false; + private bool _customMenuEnabled = false; + private string _additionalBrowserArguments = ""; + private string _customDownloadPath = ""; + private bool _isDownloadUIEnabled = true; + private bool _httpStatusCodeEventsEnabled = true; + private bool _httpStatusCodeDocumentOnly = true; + private bool _isDownloadHandledOverride = false; + private bool _isZoomControlEnabled = true; + private string _failureReportFolderPath = ""; + + private int _offsetX = 0; + private int _offsetY = 0; + private int _marginRight = 0; + private int _marginBottom = 0; + private IntPtr _parentHandle = IntPtr.Zero; + private ParentWindowSubclass _parentSubclass; + + private string _lastCssRegistrationId = ""; + private System.Threading.SynchronizationContext _uiContext; + + // Keeps active downloads keyed by their URI + private readonly Dictionary _activeDownloads = new Dictionary(); + #endregion + + #region 4. CONSTRUCTOR + /// + /// Initializes a new instance of the WebViewManager class. + /// + public WebView2Manager() + { + _webView = new WebView2(); + _bridge = new WebView2Bridge(); + } + + /// + /// Performs cleanup of the WebView2 control and associated resources. + /// + public void Cleanup() + { + Log("Cleanup requested."); + _webView?.Dispose(); + Log("Cleanup completed."); + } + #endregion + + #region 5. BRIDGE & STATUS + /// + /// Get the Bridge object for AutoIt interaction. + /// + public IBridgeActions GetBridge() + { + return _bridge; + } + + /// + /// Check if WebView2 is initialized and ready. + /// + public bool IsReady() => _webView?.CoreWebView2 != null; + + /// Allows internal classes to push debug/info messages to AutoIt. + internal void InternalPushMessage(string message) + { + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), message); + } + + /// + /// Gets the internal window handle of the WebView2 control. + /// + public string BrowserWindowHandle => FormatHandle(_webView?.Handle ?? IntPtr.Zero); + + /// + /// Gets the parent window handle provided during initialization. + /// + public string ParentWindowHandle => FormatHandle(_parentHandle); + + /// Internal access to parent handle for event wrappers. + internal IntPtr ParentHandleIntPtr => _parentHandle; + + private string _blockedVirtualKeys = ""; + /// + /// A comma-separated list of Virtual Key codes to be blocked synchronously (e.g., "116,123"). + /// This ensures blocking (Handled = true) works without COM timing issues. + /// + public string BlockedVirtualKeys + { + get => _blockedVirtualKeys; + set => _blockedVirtualKeys = value ?? ""; + } + + /// Gets the version of the DLL. + public string Version => AssemblyUtils.GetVersion(); + + /// + /// Gets or sets the folder path where WebView2 failure reports (crash dumps) are stored. + /// If not set before Initialize, defaults to 'Crashes' subfolder in userDataFolder. + /// + public string FailureReportFolderPath + { + get => _failureReportFolderPath; + set => _failureReportFolderPath = value ?? ""; + } + #endregion + + #region 6. CORE INITIALIZATION + /// + /// Initializes the WebView2 control within the specified parent window handle. + /// Supports browser extensions and custom user data folders. + /// + public async void Initialize(object parentHandle, string userDataFolder, int x = 0, int y = 0, int width = 0, int height = 0) + { + Log($"Initialize request: parent={parentHandle}, x={x}, y={y}, w={width}, h={height}"); + try + { + // Capture the UI thread context for Law #1 compliance + _uiContext = System.Threading.SynchronizationContext.Current; + if (_uiContext != null) _bridge.SetSyncContext(_uiContext); + + // Convert the incoming handle from AutoIt (passed as object/pointer) + long rawHandleValue = Convert.ToInt64(parentHandle); + _parentHandle = new IntPtr(rawHandleValue); + + // v2.0.0: "Seal" the bridge context immediately after handle conversion + _bridge.SetParentContext(this, _parentHandle); + + // Store offsets for Smart Resize + _offsetX = x; + _offsetY = y; + + // Calculate Margins based on Parent's size at initialization + int calcWidth = width; + int calcHeight = height; + + if (GetClientRect(_parentHandle, out Rect parentRect)) + { + int pWidth = parentRect.Right - parentRect.Left; + int pHeight = parentRect.Bottom - parentRect.Top; + + // If user provides 0 (or less), we assume they want to fill the parent + if (width <= 0) + { + calcWidth = Math.Max(10, pWidth - x); + _marginRight = 0; + } + else + { + _marginRight = Math.Max(0, (pWidth - x) - width); + } + + if (height <= 0) + { + calcHeight = Math.Max(10, pHeight - y); + _marginBottom = 0; + } + else + { + _marginBottom = Math.Max(0, (pHeight - y) - height); + } + } + else + { + _marginRight = 0; + _marginBottom = 0; + } + + // Initialize the Subclass helper for Smart Resize + _parentSubclass = new ParentWindowSubclass(() => PerformSmartResize()); + + // Manage User Data Folder (User Profile) + // If no path is provided, create a default one in the application directory + if (string.IsNullOrEmpty(userDataFolder)) + { + userDataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebView2_Default_Profile"); + } + Log($"UserDataFolder: {userDataFolder}"); + + // Create the directory if it doesn't exist + if (!Directory.Exists(userDataFolder)) Directory.CreateDirectory(userDataFolder); + + // UI Setup on the main UI thread + // Finalize setup + _webView.Location = new Point(x, y); + _webView.Size = new Size(calcWidth, calcHeight); + Log($"Final initial size: {_webView.Size.Width}x{_webView.Size.Height}"); + + // Attach to parent + InvokeOnUiThread(() => { + // Attach the WebView to the AutoIt window/container + SetParent(_webView.Handle, _parentHandle); + _webView.Visible = false; + }); + + // --- NEW: EXTENSION & FAILURE HANDLING SETUP --- + // We must enable extensions and configure crash dumps in the Environment Options BEFORE creation + var options = new CoreWebView2EnvironmentOptions { AreBrowserExtensionsEnabled = true }; + + // Handle FailureReportFolderPath logic (Law #5 - Practicality) + if (string.IsNullOrEmpty(_failureReportFolderPath)) + { + _failureReportFolderPath = Path.Combine(userDataFolder, "FailureReportFolder"); + } + + if (!Directory.Exists(_failureReportFolderPath)) + { + Directory.CreateDirectory(_failureReportFolderPath); + } + + // Fallback: Use environment variable because FailureReportFolderPath is missing from Options in this SDK version + Environment.SetEnvironmentVariable("WEBVIEW2_FAILURE_REPORT_FOLDER_PATH", _failureReportFolderPath); + if (_failureReportFolderPath != "") Log($"FailureReportFolderPath: {_failureReportFolderPath}"); + + if (!string.IsNullOrEmpty(_additionalBrowserArguments)) options.AdditionalBrowserArguments = _additionalBrowserArguments; + + // Initialize the Environment with the Custom Data Folder and our Options + // Note: The second parameter is the userDataFolder, the third is the options + var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder, options); + + // Wait for the CoreWebView2 engine to be ready + await _webView.EnsureCoreWebView2Async(env); + + // Apply settings and register events + ConfigureSettings(); + RegisterEvents(); + + // Add default context menu bridge helper + await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(@" + window.dispatchEventToAutoIt = function(lnk, x, y, sel) { + window.chrome.webview.postMessage('CONTEXT_MENU_REQUEST|' + (lnk||'') + '|' + (x||0) + '|' + (y||0) + '|' + (sel||'')); + }; + "); + + // Make the browser visible once everything is loaded + InvokeOnUiThread(() => _webView.Visible = true); + + // Signal ready + Log("WebView2 Initialized successfully."); + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "INIT_READY"); + } + catch (Exception ex) + { + Log("Initialization error: " + ex.Message); + // Send error details back to AutoIt if initialization fails + OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|INIT_FAILED|" + ex.Message); + } + } + /// + /// Configure WebView2 settings. + /// + private void ConfigureSettings() + { + var settings = _webView.CoreWebView2.Settings; + settings.IsWebMessageEnabled = true; // Enable Web Messages + settings.AreDevToolsEnabled = true; // Enable DevTools by default + settings.AreDefaultContextMenusEnabled = true; // Keep TRUE to ensure the event fires + settings.IsZoomControlEnabled = _isZoomControlEnabled; // Apply custom zoom setting + _webView.DefaultBackgroundColor = Color.Transparent; + } + #endregion + } + + /// + /// Compatibility Layer for WebViewManager (Legacy name). + /// Inherits all functionality from WebView2Manager. + /// + [Guid("A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D")] // OLD GUID + [ComSourceInterfaces(typeof(IWebViewEvents))] + [ClassInterface(ClassInterfaceType.None)] + [ComVisible(true)] + [ProgId("NetWebView2.Manager")] // OLD ProgID + public partial class WebViewManager : WebView2Manager, IWebViewActions + { + // Inherits everything + } +} diff --git a/src/WebView2Parser.cs b/src/Core/WebView2Parser.cs similarity index 100% rename from src/WebView2Parser.cs rename to src/Core/WebView2Parser.cs diff --git a/src/Events/BaseWebViewEventArgs.cs b/src/Events/BaseWebViewEventArgs.cs new file mode 100644 index 0000000..8145a03 --- /dev/null +++ b/src/Events/BaseWebViewEventArgs.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; + +namespace NetWebView2Lib +{ + /// + /// Base interface for all WebView2 event arguments. + /// Provides consistent sender information for multi-instance support. + /// + [Guid("D1D2D3D4-E5F6-4A5B-9C8D-7E6F5A4B3C2D")] + [InterfaceType(ComInterfaceType.InterfaceIsDual)] + [ComVisible(true)] + public interface IBaseWebViewEventArgs + { + /// Raw pointer to the parent window handle. + [DispId(100)] int SenderPtr { get; } + + /// Formatted AutoIt handle string: [HANDLE:0x...] + [DispId(101)] string SenderHandle { get; } + } + + /// + /// Abstract base class for WebView2 event argument wrappers. + /// Includes common properties for identifying the sender instance. + /// + [ComVisible(true)] + public abstract class BaseWebViewEventArgs : IBaseWebViewEventArgs + { + public int SenderPtr { get; protected set; } + public string SenderHandle { get; protected set; } + + protected BaseWebViewEventArgs() { } + + /// + /// Initializes the sender information from the manager's handle. + /// + protected void InitializeSender(IntPtr handle) + { + SenderPtr = (int)handle; + SenderHandle = $"[HANDLE:0x{handle.ToInt64():X}]"; + } + } +} diff --git a/src/BasicAuthenticationRequestedEventArgsWrapper.cs b/src/Events/BasicAuthenticationRequestedEventArgsWrapper.cs similarity index 87% rename from src/BasicAuthenticationRequestedEventArgsWrapper.cs rename to src/Events/BasicAuthenticationRequestedEventArgsWrapper.cs index 696c273..e2d8bfa 100644 --- a/src/BasicAuthenticationRequestedEventArgsWrapper.cs +++ b/src/Events/BasicAuthenticationRequestedEventArgsWrapper.cs @@ -1,6 +1,6 @@ -using Microsoft.Web.WebView2.Core; using System.Runtime.InteropServices; using System; +using Microsoft.Web.WebView2.Core; namespace NetWebView2Lib { @@ -10,7 +10,7 @@ namespace NetWebView2Lib [Guid("A1B2C3D4-E5F6-4A5B-9C8D-7E6F5A4B3C2D")] [InterfaceType(ComInterfaceType.InterfaceIsDual)] [ComVisible(true)] - public interface IBasicAuthenticationRequestedEventArgs + public interface IBasicAuthenticationRequestedEventArgs : IBaseWebViewEventArgs { [DispId(1)] string Uri { get; } [DispId(2)] string Challenge { get; } @@ -27,7 +27,7 @@ public interface IBasicAuthenticationRequestedEventArgs [Guid("B1C2D3E4-F5A6-4B7C-8D9E-0F1A2B3C4D5E")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] - public class BasicAuthenticationRequestedEventArgsWrapper : IBasicAuthenticationRequestedEventArgs + public class BasicAuthenticationRequestedEventArgsWrapper : BaseWebViewEventArgs, IBasicAuthenticationRequestedEventArgs { private readonly CoreWebView2BasicAuthenticationRequestedEventArgs _args; private readonly CoreWebView2Deferral _deferral; @@ -58,8 +58,9 @@ public string Password /// public BasicAuthenticationRequestedEventArgsWrapper() { } - public BasicAuthenticationRequestedEventArgsWrapper(CoreWebView2BasicAuthenticationRequestedEventArgs args) + public BasicAuthenticationRequestedEventArgsWrapper(CoreWebView2BasicAuthenticationRequestedEventArgs args, IntPtr senderHandle) { + InitializeSender(senderHandle); _args = args; _deferral = args.GetDeferral(); } diff --git a/src/ProcessFailedEventArgsWrapper.cs b/src/Events/ProcessFailedEventArgsWrapper.cs similarity index 82% rename from src/ProcessFailedEventArgsWrapper.cs rename to src/Events/ProcessFailedEventArgsWrapper.cs index 5d6da11..4342216 100644 --- a/src/ProcessFailedEventArgsWrapper.cs +++ b/src/Events/ProcessFailedEventArgsWrapper.cs @@ -1,6 +1,6 @@ -using Microsoft.Web.WebView2.Core; using System; using System.Runtime.InteropServices; +using Microsoft.Web.WebView2.Core; namespace NetWebView2Lib { @@ -11,7 +11,7 @@ namespace NetWebView2Lib [Guid("F3A4B5C6-D7E8-4F9A-0B1C-2D3E4F5A6B7C")] [InterfaceType(ComInterfaceType.InterfaceIsDual)] [ComVisible(true)] - public interface IProcessFailedEventArgs + public interface IProcessFailedEventArgs : IBaseWebViewEventArgs { [DispId(1)] int ProcessFailedKind { get; } [DispId(2)] int Reason { get; } @@ -25,7 +25,7 @@ public interface IProcessFailedEventArgs [Guid("E2F3A4B5-C6D7-4E8F-9A0B-1C2D3E4F5A6B")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] - public class ProcessFailedEventArgsWrapper : IProcessFailedEventArgs + public class ProcessFailedEventArgsWrapper : BaseWebViewEventArgs, IProcessFailedEventArgs { public int ProcessFailedKind { get; } public int Reason { get; } @@ -37,8 +37,9 @@ public class ProcessFailedEventArgsWrapper : IProcessFailedEventArgs /// public ProcessFailedEventArgsWrapper() { } - public ProcessFailedEventArgsWrapper(CoreWebView2ProcessFailedEventArgs args) + public ProcessFailedEventArgsWrapper(CoreWebView2ProcessFailedEventArgs args, IntPtr senderHandle) { + InitializeSender(senderHandle); ProcessFailedKind = (int)args.ProcessFailedKind; Reason = (int)args.Reason; ExitCode = args.ExitCode; diff --git a/src/WebView2AcceleratorKeyPressedEventArgs.cs b/src/Events/WebView2AcceleratorKeyPressedEventArgs.cs similarity index 87% rename from src/WebView2AcceleratorKeyPressedEventArgs.cs rename to src/Events/WebView2AcceleratorKeyPressedEventArgs.cs index bc6b1bd..f0670cc 100644 --- a/src/WebView2AcceleratorKeyPressedEventArgs.cs +++ b/src/Events/WebView2AcceleratorKeyPressedEventArgs.cs @@ -1,6 +1,6 @@ -using Microsoft.Web.WebView2.Core; using System; using System.Runtime.InteropServices; +using Microsoft.Web.WebView2.Core; namespace NetWebView2Lib { @@ -11,7 +11,7 @@ namespace NetWebView2Lib [Guid("9A8B7C6D-5E4F-3A2B-1C0D-9E8F7A6B5C4D")] [InterfaceType(ComInterfaceType.InterfaceIsDual)] [ComVisible(true)] - public interface IWebView2AcceleratorKeyPressedEventArgs + public interface IWebView2AcceleratorKeyPressedEventArgs : IBaseWebViewEventArgs { [DispId(1)] uint VirtualKey { get; } [DispId(2)] int KeyEventLParam { get; } @@ -34,7 +34,7 @@ public interface IWebView2AcceleratorKeyPressedEventArgs [Guid("E1F2A3B4-C5D6-4E7F-8A9B-0C1D2E3F4A5B")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] - public class WebView2AcceleratorKeyPressedEventArgs : IWebView2AcceleratorKeyPressedEventArgs + public class WebView2AcceleratorKeyPressedEventArgs : BaseWebViewEventArgs, IWebView2AcceleratorKeyPressedEventArgs { private readonly CoreWebView2AcceleratorKeyPressedEventArgs _args; private readonly WebView2Manager _manager; @@ -71,10 +71,14 @@ public void Block() try { _args.Handled = true; } catch { } } + public WebView2AcceleratorKeyPressedEventArgs() { } + public WebView2AcceleratorKeyPressedEventArgs(CoreWebView2AcceleratorKeyPressedEventArgs args, WebView2Manager manager) { _args = args; _manager = manager; + InitializeSender(manager.ParentHandleIntPtr); + VirtualKey = args.VirtualKey; KeyEventLParam = args.KeyEventLParam; KeyEventKind = (int)args.KeyEventKind; diff --git a/src/Events/WebView2PermissionRequestedEventArgsWrapper.cs b/src/Events/WebView2PermissionRequestedEventArgsWrapper.cs new file mode 100644 index 0000000..9a0c996 --- /dev/null +++ b/src/Events/WebView2PermissionRequestedEventArgsWrapper.cs @@ -0,0 +1,69 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.Web.WebView2.Core; + +namespace NetWebView2Lib +{ + [Guid("E2A3B4C5-D6E7-4F8A-9B0C-1D2E3F4A5B6C")] + [InterfaceType(ComInterfaceType.InterfaceIsDual)] + [ComVisible(true)] + public interface IWebView2PermissionRequestedEventArgs : IBaseWebViewEventArgs + { + [DispId(1)] bool Handled { get; set; } + [DispId(2)] bool IsUserInitiated { get; } + [DispId(3)] int PermissionKind { get; } + [DispId(4)] int State { get; set; } + [DispId(5)] string Uri { get; } + [DispId(20)] void GetDeferral(); + [DispId(21)] void Complete(); + } + + [ClassInterface(ClassInterfaceType.None)] + [ComVisible(true)] + [Guid("9C727559-5496-44B0-AB1B-1977CF83ADB0")] // Updated Guid + public class WebView2PermissionRequestedEventArgsWrapper : BaseWebViewEventArgs, IWebView2PermissionRequestedEventArgs + { + private readonly CoreWebView2PermissionRequestedEventArgs _args; + private CoreWebView2Deferral _deferral; + + public WebView2PermissionRequestedEventArgsWrapper(CoreWebView2PermissionRequestedEventArgs args, IntPtr parentHandle) + { + _args = args; + InitializeSender(parentHandle); + } + + public bool Handled + { + get => _args.Handled; + set => _args.Handled = value; + } + + public bool IsUserInitiated => _args.IsUserInitiated; + + public int PermissionKind => (int)_args.PermissionKind; + + public int State + { + get => (int)_args.State; + set => _args.State = (CoreWebView2PermissionState)value; + } + + public string Uri => _args.Uri; + + public void GetDeferral() + { + if (_deferral == null) + { + _deferral = _args.GetDeferral(); + } + } + + public void Complete() + { + if (_deferral != null) + { + _deferral.Complete(); + } + } + } +} diff --git a/src/Interfaces/IWebViewActions.cs b/src/Interfaces/IWebViewActions.cs new file mode 100644 index 0000000..26ecedc --- /dev/null +++ b/src/Interfaces/IWebViewActions.cs @@ -0,0 +1,260 @@ +using System.Runtime.InteropServices; + +namespace NetWebView2Lib +{ + /// + /// Actions available to call from AutoIt. + /// + [Guid("CCB12345-6789-4ABC-DEF0-1234567890AB")] + [InterfaceType(ComInterfaceType.InterfaceIsDual)] + [ComVisible(true)] + public interface IWebViewActions + { + /// Set additional browser arguments (switches) before initialization. + [DispId(100)] string AdditionalBrowserArguments { get; set; } + /// Initialize the WebView. + [DispId(101)] void Initialize(object parentHandle, string userDataFolder, int x = 0, int y = 0, int width = 0, int height = 0); + /// Navigate to a URL. + [DispId(102)] void Navigate(string url); + /// Navigate to HTML content. + [DispId(103)] void NavigateToString(string htmlContent); + /// Execute JavaScript. + [DispId(104)] void ExecuteScript(string script); + /// Resize the WebView. + [DispId(105)] void Resize(int width, int height); + /// Clean up resources. + [DispId(106)] void Cleanup(); + /// Get the Bridge object. + [DispId(107)] IBridgeActions GetBridge(); + /// Export to PDF. + [DispId(108)] void ExportToPdf(string filePath); + /// Check if ready. + [DispId(109)] bool IsReady(); + /// Enable/Disable Context Menu. + [DispId(110)] void SetContextMenuEnabled(bool enabled); + /// Lock the WebView. + [DispId(111)] void LockWebView(); + /// Unlock the WebView by re-enabling restricted features. + [DispId(215)] void UnLockWebView(); + /// Enable major browser features. + [DispId(227)] void EnableBrowserFeatures(); + /// Disable browser features. + [DispId(112)] void DisableBrowserFeatures(); + /// Go Back. + [DispId(113)] void GoBack(); + /// Reset Zoom. + [DispId(114)] void ResetZoom(); + /// Inject CSS. + [DispId(115)] void InjectCss(string cssCode); + /// Clear Injected CSS. + [DispId(116)] void ClearInjectedCss(); + /// Toggle Audit Highlights. + [DispId(117)] void ToggleAuditHighlights(bool enable); + /// Set AdBlock active state. + [DispId(118)] void SetAdBlock(bool active); + /// Add a block rule. + [DispId(119)] void AddBlockRule(string domain); + /// Clear all block rules. + [DispId(120)] void ClearBlockRules(); + /// Set the lockdown state of the WebView. + [DispId(220)] void SetLockState(bool lockState); + + /// Go Forward. + + [DispId(121)] void GoForward(); + /// Get HTML Source. + [DispId(122)] void GetHtmlSource(); + /// Get Selected Text. + [DispId(123)] void GetSelectedText(); + /// Set Zoom factor. + [DispId(124)] void SetZoom(double factor); + /// Parse JSON to internal storage. + [DispId(125)] bool ParseJsonToInternal(string json); + /// Get value from internal JSON. + [DispId(126)] string GetInternalJsonValue(string path); + /// Clear browsing data. + [DispId(127)] void ClearBrowserData(); + /// Reload. + [DispId(128)] void Reload(); + /// Stop loading. + [DispId(129)] void Stop(); + /// Show Print UI. + [DispId(130)] void ShowPrintUI(); + /// Set Muted state. + [DispId(131)] void SetMuted(bool muted); + /// Check if Muted. + [DispId(132)] bool IsMuted(); + /// Set User Agent. + [DispId(133)] void SetUserAgent(string userAgent); + /// Get Document Title. + [DispId(134)] string GetDocumentTitle(); + /// Get Source URL. + [DispId(135)] string GetSource(); + /// Enable/Disable Script. + [DispId(136)] void SetScriptEnabled(bool enabled); + /// Enable/Disable Web Message. + [DispId(137)] void SetWebMessageEnabled(bool enabled); + /// Enable/Disable Status Bar. + [DispId(138)] void SetStatusBarEnabled(bool enabled); + /// Capture Preview. + [DispId(139)] void CapturePreview(string filePath, string format); + /// Call CDP Method. + [DispId(140)] void CallDevToolsProtocolMethod(string methodName, string parametersJson); + /// Get Cookies. + [DispId(141)] void GetCookies(string channelId); + /// Add a Cookie. + [DispId(142)] void AddCookie(string name, string value, string domain, string path); + /// Delete a Cookie. + [DispId(143)] void DeleteCookie(string name, string domain, string path); + /// Delete All Cookies. + [DispId(144)] void DeleteAllCookies(); + /// Get current frame count. + [DispId(146)] int GetFrameCount(); + /// Get HTML source of a specific frame. + [DispId(147)] void GetFrameHtmlSource(int index); + /// Get URL of a specific frame. + [DispId(148)] string GetFrameUrl(int index); + /// Get Name of a specific frame. + [DispId(149)] string GetFrameName(int index); + /// Get all frame URLs pipe-separated. + [DispId(152)] string GetFrameUrls(); + /// Get all frame names pipe-separated. + [DispId(153)] string GetFrameNames(); + /// Get a Frame Object (IWebView2Frame). + [DispId(154)] object GetFrame(int index); + /// Get a Frame Object by its FrameId. + [DispId(230)] object GetFrameById(uint frameId); + + /// Print. + [DispId(145)] void Print(); + /// Add Extension. + [DispId(150)] void AddExtension(string extensionPath); + /// Remove Extension. + [DispId(151)] void RemoveExtension(string extensionId); + + /// Check if can go back. + [DispId(162)] bool GetCanGoBack(); + /// Check if can go forward. + [DispId(163)] bool GetCanGoForward(); + /// Get Browser Process ID. + [DispId(164)] uint GetBrowserProcessId(); + /// Encode a string for URL. + [DispId(165)] string EncodeURI(string value); + /// Decode a URL string. + [DispId(166)] string DecodeURI(string value); + /// Encode a string for Base64. + [DispId(167)] string EncodeB64(string value); + /// Decode a Base64 string. + [DispId(168)] string DecodeB64(string value); + + // --- NEW UNIFIED SETTINGS (PROPERTIES) --- + /// Are DevTools enabled. + [DispId(170)] bool AreDevToolsEnabled { get; set; } + /// Are default context menus enabled. + [DispId(171)] bool AreDefaultContextMenusEnabled { get; set; } + /// Verbose Log state. + [DispId(228)] bool Verbose { get; set; } + /// Are default script dialogs enabled. + [DispId(172)] bool AreDefaultScriptDialogsEnabled { get; set; } + /// Are browser accelerator keys enabled. + [DispId(173)] bool AreBrowserAcceleratorKeysEnabled { get; set; } + /// Is status bar enabled. + [DispId(174)] bool IsStatusBarEnabled { get; set; } + /// Zoom Factor. + [DispId(175)] double ZoomFactor { get; set; } + /// Background Color (Hex string). + [DispId(176)] string BackColor { get; set; } + /// Are host objects allowed. + [DispId(177)] bool AreHostObjectsAllowed { get; set; } + /// Anchor (Resizing). + [DispId(178)] int Anchor { get; set; } + /// Border Style. + [DispId(179)] int BorderStyle { get; set; } + + // --- NEW UNIFIED METHODS --- + /// Set Zoom Factor (Wrapper). + [DispId(180)] void SetZoomFactor(double factor); + /// Open DevTools Window. + [DispId(181)] void OpenDevToolsWindow(); + /// Focus the WebView. + [DispId(182)] void WebViewSetFocus(); + /// Are browser popups allowed or redirected to the same window. + [DispId(183)] bool AreBrowserPopupsAllowed { get; set; } + /// Add a script that executes on every page load (Permanent Injection). Returns the ScriptId. + [DispId(184)] string AddInitializationScript(string script); + /// Removes a script previously added via AddInitializationScript. + [DispId(217)] void RemoveInitializationScript(string scriptId); + /// Binds the internal JSON data to a browser variable. + [DispId(185)] bool BindJsonToBrowser(string variableName); + /// Syncs JSON data to internal parser and optionally binds it to a browser variable. + [DispId(186)] void SyncInternalData(string json, string bindToVariableName = ""); + + /// Execute JavaScript and return result synchronously (Blocking wait). + [DispId(188)] string ExecuteScriptWithResult(string script); + /// Enables or disables automatic resizing of the WebView to fill its parent. + [DispId(189)] void SetAutoResize(bool enabled); + + /// Execute JavaScript on the current page immediately. + [DispId(191)] void ExecuteScriptOnPage(string script); + + /// Clears the browser cache (DiskCache and LocalStorage). + [DispId(193)] void ClearCache(); + /// Enables or disables custom context menu handling. + [DispId(194)] bool CustomMenuEnabled { get; set; } + /// Enable/Disable OnWebResourceResponseReceived event. + [DispId(195)] bool HttpStatusCodeEventsEnabled { get; set; } + /// Filter HttpStatusCode events to only include the main document. + [DispId(196)] bool HttpStatusCodeDocumentOnly { get; set; } + + + /// Get inner text. + [DispId(200)] void GetInnerText(); + + /// Capture page data as MHTML or other CDP snapshot formats. + [DispId(201)] string CaptureSnapshot(string cdpParameters = "{\"format\": \"mhtml\"}"); + /// Export page data as HTML or MHTML (Legacy Support). + [DispId(207)] string ExportPageData(int format, string filePath); + /// Capture page as PDF and return as Base64 string. + [DispId(202)] string PrintToPdfStream(); + /// Control PDF toolbar items visibility. + [DispId(203)] int HiddenPdfToolbarItems { get; set; } + /// Custom Download Path. + [DispId(204)] void SetDownloadPath(string path); + /// Enable/Disable default Download UI. + [DispId(205)] bool IsDownloadUIEnabled { get; set; } + /// Decode a Base64 string to raw binary data (byte array). + [DispId(206)] byte[] DecodeB64ToBinary(string base64Text); + + /// Capture preview as Base64 string. + [DispId(216)] string CapturePreviewAsBase64(string format); + + /// Cancels downloads. If uri is null or empty, cancels all active downloads. + [DispId(210)] void CancelDownloads(string uri = ""); + /// Returns a pipe-separated string of all active download URIs. + [DispId(214)] string ActiveDownloadsList { get; } + /// Set to true to suppress the default download UI, typically set during the OnDownloadStarting event. + [DispId(211)] bool IsDownloadHandled { get; set; } + /// Enable/Disable Zoom control (Ctrl+Wheel, shortcuts). + [DispId(212)] bool IsZoomControlEnabled { get; set; } + /// Enable/Disable the built-in browser error page. + [DispId(213)] bool IsBuiltInErrorPageEnabled { get; set; } + /// Encodes raw binary data (byte array) to a Base64 string. + [DispId(219)] string EncodeBinaryToB64(object binaryData); + /// Maps a virtual host name to a local folder path. + [DispId(218)] void SetVirtualHostNameToFolderMapping(string hostName, string folderPath, int accessKind); + + /// Gets the internal window handle of the WebView2 control. + [DispId(222)] string BrowserWindowHandle { get; } + + /// Gets the parent window handle provided during initialization. + [DispId(229)] string ParentWindowHandle { get; } + + /// A comma-separated list of Virtual Key codes to block (e.g., "116,123"). + [DispId(223)] string BlockedVirtualKeys { get; set; } + /// Gets the version of the DLL. + [DispId(224)] string Version { get; } + + /// Gets or sets the folder path where WebView2 failure reports (crash dumps) are stored. + [DispId(226)] string FailureReportFolderPath { get; set; } + } +} diff --git a/src/Interfaces/IWebViewEvents.cs b/src/Interfaces/IWebViewEvents.cs new file mode 100644 index 0000000..ba23157 --- /dev/null +++ b/src/Interfaces/IWebViewEvents.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +namespace NetWebView2Lib +{ + // --- 1. EVENTS INTERFACE (What C# sends to AutoIt) --- + /// + /// Events sent from C# to AutoIt. + /// v2.0.0: All events now include sender and parentHandle for multi-instance support. + /// + [Guid("B2C3D4E5-F6A7-4B6C-9D0E-1F2A3B4C5D6E")] + [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] + [ComVisible(true)] + public interface IWebViewEvents + { + /// Triggered when a message is sent from the WebView. + /// The WebViewManager instance. + /// The parent window handle. + /// The message content. + [DispId(1)] void OnMessageReceived(object sender, string parentHandle, string message); + [DispId(2)] void OnNavigationStarting(object sender, string parentHandle, string url); + [DispId(3)] void OnNavigationCompleted(object sender, string parentHandle, bool isSuccess, int webErrorStatus); + [DispId(4)] void OnTitleChanged(object sender, string parentHandle, string newTitle); + [DispId(5)] void OnWebResourceResponseReceived(object sender, string parentHandle, int statusCode, string reasonPhrase, string requestUrl); + [DispId(6)] void OnContextMenu(object sender, string parentHandle, string menuData); + [DispId(10)] void OnZoomChanged(object sender, string parentHandle, double factor); + [DispId(11)] void OnBrowserGotFocus(object sender, string parentHandle, int reason); + [DispId(12)] void OnBrowserLostFocus(object sender, string parentHandle, int reason); + [DispId(13)] void OnURLChanged(object sender, string parentHandle, string newUrl); + [DispId(190)] void OnContextMenuRequested(object sender, string parentHandle, string linkUrl, int x, int y, string selectionText); + [DispId(208)] void OnDownloadStarting(object sender, string parentHandle, string uri, string defaultPath); + [DispId(209)] void OnDownloadStateChanged(object sender, string parentHandle, string state, string uri, long totalBytes, long receivedBytes); + [DispId(221)] void OnAcceleratorKeyPressed(object sender, string parentHandle, object args); + [DispId(225)] void OnProcessFailed(object sender, string parentHandle, object args); + [DispId(228)] void OnBasicAuthenticationRequested(object sender, string parentHandle, object args); + [DispId(239)] void OnPermissionRequested(object sender, string parentHandle, object args); + + // --- FRAME EVENTS --- + [DispId(229)] void OnFrameNavigationStarting(object sender, string parentHandle, object frame, string uri); + [DispId(230)] void OnFrameNavigationCompleted(object sender, string parentHandle, object frame, bool isSuccess, int webErrorStatus); + [DispId(231)] void OnFrameContentLoading(object sender, string parentHandle, object frame, long navigationId); + [DispId(232)] void OnFrameDOMContentLoaded(object sender, string parentHandle, object frame, long navigationId); + [DispId(233)] void OnFrameWebMessageReceived(object sender, string parentHandle, object frame, string message); + [DispId(234)] void OnFrameCreated(object sender, string parentHandle, object frame); + [DispId(235)] void OnFrameDestroyed(object sender, string parentHandle, object frame); + [DispId(236)] void OnFrameNameChanged(object sender, string parentHandle, object frame); + [DispId(238)] void OnFramePermissionRequested(object sender, string parentHandle, object frame, object args); + } +} diff --git a/src/JsonParser.cs b/src/JsonParser.cs deleted file mode 100644 index 231c61c..0000000 --- a/src/JsonParser.cs +++ /dev/null @@ -1,725 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; - -// --- Version 2.0.0-beta.3 --- - -namespace NetWebView2Lib -{ - /// - /// COM interface for JsonParser class. - /// - [Guid("D1E2F3A4-B5C6-4D7E-8F9A-0B1C2D3E4F5A")] - [InterfaceType(ComInterfaceType.InterfaceIsDual)] - [ComVisible(true)] - public interface IJsonParser - { - /// - /// Parses a JSON string. Automatically detects if it's an Object or an Array. - /// - [DispId(201)] bool Parse(string json); - - /// - /// Retrieves a value by JSON path (e.g., "items[0].name"). - /// - [DispId(202)] string GetTokenValue(string path); - - /// - /// Returns the count of elements if the JSON is an array. - /// - [DispId(203)] int GetArrayLength(string path); - - /// - /// Updates or adds a value at the specified path (only for JObject). - /// - [DispId(204)] void SetTokenValue(string path, string value); - - /// - /// Loads JSON content directly from a file. - /// - [DispId(205)] bool LoadFromFile(string filePath); - - /// - /// Saves the current JSON state back to a file. - /// - [DispId(206)] bool SaveToFile(string filePath); - - /// - /// Checks if a path exists in the current JSON structure. - /// - [DispId(207)] bool Exists(string path); - - /// - /// Clears the internal data. - /// - [DispId(208)] void Clear(); - - /// - /// Returns the full JSON string. - /// - [DispId(209)] string GetJson(); - - /// - /// Escapes a string to be safe for use in JSON. - /// - [DispId(210)] string EscapeString(string plainText); - - /// - /// Unescapes a JSON string back to plain text. - /// - [DispId(211)] string UnescapeString(string escapedText); - - /// - /// Returns the JSON string with nice formatting (Indented). - /// - [DispId(212)] string GetPrettyJson(); - - /// - /// Minifies a JSON string (removes spaces and new lines). - /// - [DispId(213)] string GetMinifiedJson(); - - /// - /// Merges another JSON string into the current JSON structure. - /// - [DispId(214)] bool Merge(string jsonContent); - - /// - /// Merges JSON content from a file into the current JSON structure. - /// - [DispId(215)] bool MergeFromFile(string filePath); - - /// - /// Returns the type of the token at the specified path (e.g., Object, Array, String, Integer). - /// - [DispId(216)] string GetTokenType(string path); - - /// - /// Removes the token at the specified path. - /// - [DispId(217)] bool RemoveToken(string path); - - /// - /// Searches the JSON structure using a JSONPath query and returns a JSON array of results. - /// - [DispId(218)] string Search(string query); - - /// - /// Flattens the JSON structure into a single-level object with dot-notated paths. - /// - [DispId(219)] string Flatten(); - - /// - /// Clones the current JSON data to another named parser instance. - /// - [DispId(220)] bool CloneTo(string parserName); - - /// - /// Flattens the JSON structure into a table-like string with specified delimiters. - /// - [DispId(221)] string FlattenToTable(string colDelim, string rowDelim); - /// Encodes a string for Base64. - [DispId(222)] string EncodeB64(string plainText); - /// Decodes a Base64 string. - [DispId(223)] string DecodeB64(string base64Text); - /// Decodes a Base64 string and saves it directly to a file. - [DispId(224)] bool DecodeB64ToFile(string base64Text, string filePath); - - /// Returns the count of elements (array items or object properties) at the specified path. - [DispId(225)] int GetTokenCount(string path); - - /// Returns a delimited string of keys for the object at the specified path. - [DispId(226)] string GetKeys(string path, string delimiter); - - /// Sorts a JSON array by a specific key. - [DispId(227)] bool SortArray(string arrayPath, string key, bool descending); - - /// Removes duplicate objects from a JSON array based on a key's value. - [DispId(228)] bool SelectUnique(string arrayPath, string key); - - /// Decodes a Base64 string to raw binary data (byte array). - [DispId(229)] byte[] DecodeB64ToBinary(string base64Text); - - /// Gets the version of the DLL. - [DispId(230)] string Version { get; } - } - - /// - /// Provides methods for parsing, manipulating, and serializing JSON data using Newtonsoft.Json. Supports both JSON - /// objects and arrays, and enables reading from and writing to files, querying values by path, and formatting JSON - /// output. - /// - /// The JsonParser class is designed for simple JSON operations and is compatible with COM - /// interop. It automatically detects whether the input JSON is an object or an array and exposes methods for common - /// tasks such as value retrieval, modification, and file I/O. Thread safety is not guaranteed; if used from - /// multiple threads, external synchronization is required. - [Guid("C5D6E7F8-A9B0-4C1D-8E2F-3A4B5C6D7E8F")] - [ComVisible(true)] - [ProgId("NetJson.Parser")] - [ClassInterface(ClassInterfaceType.None)] - - - public class JsonParser : IJsonParser - { - private JObject _jsonObj; - private JArray _jsonArray; - - /// - /// Parses a JSON string. Automatically detects if it's an Object or an Array. - /// - public bool Parse(string json) - { - if (string.IsNullOrWhiteSpace(json)) return false; - - try - { - string trimmed = json.Trim(); - // Check if it's an array or object - if (trimmed.StartsWith("[")) - { - _jsonArray = JArray.Parse(trimmed); - _jsonObj = null; // Clear object if we have an array - } - else - { - _jsonObj = JObject.Parse(trimmed); - _jsonArray = null; // Clear array if we have an object - } - return true; - } - catch { return false; } - } - - /// - /// Loads JSON content directly from a file. - /// - public bool LoadFromFile(string filePath) - { - try - { - if (!File.Exists(filePath)) return false; - string content = File.ReadAllText(filePath); - return Parse(content); - } - catch { return false; } - } - - /// - /// Saves the current JSON state back to a file. - /// - public bool SaveToFile(string filePath) - { - try - { - string content = ""; - if (_jsonObj != null) content = _jsonObj.ToString(); - else if (_jsonArray != null) content = _jsonArray.ToString(); - - if (string.IsNullOrEmpty(content)) return false; - - File.WriteAllText(filePath, content); - return true; - } - catch { return false; } - } - - /// - /// Updates or adds a value at the specified path, creating missing parent objects if necessary. - /// Supports smart typing for numbers, booleans, and nulls. - /// - public void SetTokenValue(string path, string value) - { - try - { - if (string.IsNullOrEmpty(path)) return; - - JObject root = _jsonObj; - if (root == null) - { - if (_jsonArray != null) return; // Cannot set named paths on a root array easily without index - _jsonObj = new JObject(); - root = _jsonObj; - } - - string[] parts = path.Split('.'); - JContainer current = root; - - for (int i = 0; i < parts.Length - 1; i++) - { - string part = parts[i]; - var next = current.SelectToken(part); - - if (next == null || next.Type != JTokenType.Object) - { - var newObj = new JObject(); - if (current is JObject obj) obj[part] = newObj; - else if (current is JArray arr) { /* Array indexing not handled in deep creation for now */ } - current = newObj; - } - else - { - current = (JContainer)next; - } - } - - string lastPart = parts[parts.Length - 1]; - if (current is JObject finalObj) - { - finalObj[lastPart] = ParseValueSmart(value); - } - } - catch { /* Handle or log error if needed */ } - } - - private JToken ParseValueSmart(string value) - { - if (string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) return true; - if (string.Equals(value, "false", StringComparison.OrdinalIgnoreCase)) return false; - if (string.Equals(value, "null", StringComparison.OrdinalIgnoreCase)) return JValue.CreateNull(); - - // Leading zero check: if starts with 0 and not just "0" or "0.", treat as string - if (value.Length > 1 && value.StartsWith("0") && !value.StartsWith("0.")) return value; - - if (long.TryParse(value, out long l)) return l; - if (double.TryParse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double d)) return d; - - return value; - } - - /// - /// Returns a delimited string of keys for the object at the specified path. - /// - public string GetKeys(string path, string delimiter = "|") - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null) return ""; - - var token = string.IsNullOrEmpty(path) ? root : root.SelectToken(path); - if (token is JObject obj) - { - var keys = obj.Properties().Select(p => p.Name); - return string.Join(delimiter, keys); - } - return ""; - } - catch { return ""; } - } - - /// - /// Returns the count of elements if the JSON is an array. - /// - public int GetArrayLength(string path) => GetTokenCount(path); - - /// - /// Returns the count of elements (array items or object properties) at the specified path. - /// - public int GetTokenCount(string path) - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null) return (string.IsNullOrEmpty(path)) ? 0 : -1; - - var token = (string.IsNullOrEmpty(path) || path == "$") ? root : root.SelectToken(path); - if (token == null) return 0; - - if (token is JArray arr) return arr.Count; - if (token is JObject obj) return obj.Properties().Count(); - - return 0; - } - catch { return -1; } - } - - - /// - /// Retrieves a value by JSON path (e.g., "items[0].name"). - /// - public string GetTokenValue(string path) - { - try - { - // Use root (object or array) to select the token - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - - if (root == null) return ""; - - var token = root.SelectToken(path); - return token?.ToString() ?? ""; - } - catch { return ""; } - } - - /// - /// Checks if a path exists in the current JSON structure. - /// - public bool Exists(string path) - { - try - { - JToken token = null; - if (_jsonObj != null) token = _jsonObj.SelectToken(path); - else if (_jsonArray != null) token = _jsonArray.SelectToken(path); - return token != null; - } - catch { return false; } - } - - /// - /// Clears the internal data. - /// - public void Clear() - { - _jsonObj = null; - _jsonArray = null; - } - - /// - /// Returns the full JSON string. - /// - public string GetJson() - { - if (_jsonObj != null) return _jsonObj.ToString(); - if (_jsonArray != null) return _jsonArray.ToString(); - return ""; - } - - /// - /// Escapes a string to be safe for use in JSON. - /// - public string EscapeString(string plainText) - { - if (string.IsNullOrEmpty(plainText)) return ""; - return JsonConvert.ToString(plainText).Trim('"'); - } - - /// - /// Unescapes a JSON string back to plain text. - /// - public string UnescapeString(string escapedText) - { - try - { - if (string.IsNullOrEmpty(escapedText)) return ""; - // Wrap in quotes to form a valid JSON string - return JsonConvert.DeserializeObject("\"" + escapedText + "\""); - } - catch { return escapedText; } - } - - /// - /// Returns the JSON string with nice formatting (Indented). - /// - public string GetPrettyJson() - { - if (_jsonObj != null) return _jsonObj.ToString(Formatting.Indented); - if (_jsonArray != null) return _jsonArray.ToString(Formatting.Indented); - return ""; - } - - /// - /// Minifies a JSON string (removes spaces and new lines). - /// - public string GetMinifiedJson() - { - if (_jsonObj != null) return _jsonObj.ToString(Formatting.None); - if (_jsonArray != null) return _jsonArray.ToString(Formatting.None); - return ""; - } - - /// - /// Merges another JSON string into the current JSON structure. - /// - public bool Merge(string jsonContent) - { - try - { - if (string.IsNullOrWhiteSpace(jsonContent)) return false; - - if (_jsonObj != null) - { - JObject newObj = JObject.Parse(jsonContent); - _jsonObj.Merge(newObj, new JsonMergeSettings - { - // Ορίζει πώς θα ενωθούν οι πίνακες (π.χ. προσθήκη στο τέλος) - MergeArrayHandling = MergeArrayHandling.Union - }); - return true; - } - return false; - } - catch { return false; } - } - - /// - /// Merges JSON content from a file into the current JSON structure. - /// - public bool MergeFromFile(string filePath) - { - try - { - if (!File.Exists(filePath)) return false; - string content = File.ReadAllText(filePath); - return Merge(content); // Καλούμε την Merge που ήδη έφτιαξες! - } - catch { return false; } - } - - /// - /// Returns the type of the token at the specified path (e.g., Object, Array, String, Integer). - /// - public string GetTokenType(string path) - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null) return "None"; - - var token = string.IsNullOrEmpty(path) ? root : root.SelectToken(path); - return token?.Type.ToString() ?? "None"; - } - catch { return "Error"; } - } - - /// - /// Removes the token at the specified path. - /// - public bool RemoveToken(string path) - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null || string.IsNullOrEmpty(path)) return false; - - var token = root.SelectToken(path); - if (token == null) return false; - - if (token.Parent is JProperty property) - { - // If the token is a property, remove the entire property - property.Remove(); - return true; - } - else if (token.Parent is JArray || token is JProperty) - { - // If the token is an item in an array or a property value, remove it directly - token.Remove(); - return true; - } - else - { - // For other types, just remove the token - token.Remove(); - return true; - } - } - catch { return false; } - } - - /// - /// Searches the JSON structure using a JSONPath query and returns a JSON array of results. - /// - public string Search(string query) - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null || string.IsNullOrWhiteSpace(query)) return "[]"; - - // SelectTokens returns a list of all matching elements - var results = root.SelectTokens(query); - - JArray resultArray = new JArray(); - foreach (var token in results) - { - resultArray.Add(token); - } - - return resultArray.ToString(Formatting.None); - } - catch { return "[]"; } - } - - /// - /// Sorts a JSON array by a specific key. - /// - public bool SortArray(string arrayPath, string key, bool descending = false) - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null) return false; - - JToken token = string.IsNullOrEmpty(arrayPath) ? root : root.SelectToken(arrayPath); - if (token is JArray arr) - { - var sorted = descending - ? arr.OrderByDescending(x => x[key]?.ToString()).ToList() - : arr.OrderBy(x => x[key]?.ToString()).ToList(); - - arr.Clear(); - foreach (var item in sorted) arr.Add(item); - return true; - } - return false; - } - catch { return false; } - } - - /// - /// Removes duplicate objects from a JSON array based on a key's value. - /// - public bool SelectUnique(string arrayPath, string key) - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null) return false; - - JToken token = string.IsNullOrEmpty(arrayPath) ? root : root.SelectToken(arrayPath); - if (token is JArray arr) - { - var unique = arr.Where(x => x is JObject) - .Cast() - .GroupBy(x => x[key]?.ToString()) - .Select(g => g.First()) - .ToList(); - - arr.Clear(); - foreach (var item in unique) arr.Add(item); - return true; - } - return false; - } - catch { return false; } - } - - /// - /// Flattens the JSON structure into a single-level object with dot-notated paths. - /// - public string Flatten() - { - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null) return "{}"; - var dict = new Dictionary(); - FillFlattenDictionary(root, "", dict); - return JsonConvert.SerializeObject(dict, Formatting.None); - } - catch { return "{}"; } - } - - /// Helper method to recursively flatten the JSON structure - private void FillFlattenDictionary(JToken token, string prefix, Dictionary dict) - { - switch (token.Type) - { - case JTokenType.Object: - foreach (var prop in token.Children()) - FillFlattenDictionary(prop.Value, string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}.{prop.Name}", dict); - break; - case JTokenType.Array: - int index = 0; - foreach (var item in token.Children()) - { - FillFlattenDictionary(item, $"{prefix}[{index}]", dict); - index++; - } - break; - default: - dict.Add(prefix, token.ToString()); - break; - } - } - - /// - /// Clones the current JSON data to another named parser instance. - /// - public bool CloneTo(string parserName) - { - // Here the logic depends on whether you have a manager for multiple instances. - // A simple and safe approach is not returning JSON - // so AutoIt does: $oNewParser.Parse($oOldParser.GetJson()) - // But for Logbook, we define it as a Clone function. - try - { - string currentJson = GetJson(); - return !string.IsNullOrEmpty(currentJson); - } - catch { return false; } - } - - /// - /// Flattens the JSON structure into a table-like string with specified delimiters. - /// - public string FlattenToTable(string colDelim, string rowDelim) - { - // If the user sends blanks, set the AutoIt defaults. - if (string.IsNullOrEmpty(colDelim)) colDelim = "|"; - if (string.IsNullOrEmpty(rowDelim)) rowDelim = "\r\n"; - - try - { - JToken root = (_jsonArray != null) ? (JToken)_jsonArray : (JToken)_jsonObj; - if (root == null) return ""; - - var dict = new Dictionary(); - FillFlattenDictionary(root, "", dict); - - var lines = dict.Select(kvp => $"{kvp.Key}{colDelim}{kvp.Value}"); - return string.Join(rowDelim, lines); - } - catch { return ""; } - } - - public string EncodeB64(string plainText) - { - if (string.IsNullOrEmpty(plainText)) return ""; - byte[] bytes = System.Text.Encoding.UTF8.GetBytes(plainText); - return Convert.ToBase64String(bytes); - } - - public string DecodeB64(string base64Text) - { - if (string.IsNullOrEmpty(base64Text)) return ""; - try - { - byte[] bytes = Convert.FromBase64String(base64Text); - return System.Text.Encoding.UTF8.GetString(bytes); - } - catch { return ""; } - } - - public bool DecodeB64ToFile(string base64Text, string filePath) - { - if (string.IsNullOrEmpty(base64Text) || string.IsNullOrEmpty(filePath)) return false; - try - { - byte[] bytes = Convert.FromBase64String(base64Text); - File.WriteAllBytes(filePath, bytes); - return true; - } - catch { return false; } - } - - public byte[] DecodeB64ToBinary(string base64Text) - { - if (string.IsNullOrEmpty(base64Text)) return new byte[0]; - try - { - return Convert.FromBase64String(base64Text); - } - catch { return new byte[0]; } - } - - /// Gets the version of the DLL. - public string Version => AssemblyUtils.GetVersion(); - } -} \ No newline at end of file diff --git a/src/NetWebView2Lib.csproj b/src/NetWebView2Lib.csproj index 7325226..e89d6a4 100644 --- a/src/NetWebView2Lib.csproj +++ b/src/NetWebView2Lib.csproj @@ -104,25 +104,33 @@ - - - - - - + + + + + + + + + + + - + + + + + + - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index b8398f1..2089772 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -29,6 +28,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] -[assembly: AssemblyInformationalVersion("2.0.0-stable")] +[assembly: AssemblyVersion("2.1.0.0")] +[assembly: AssemblyFileVersion("2.1.0.0")] +[assembly: AssemblyInformationalVersion("2.1.0-alpha")] diff --git a/src/AssemblyUtils.cs b/src/Utils/AssemblyUtils.cs similarity index 93% rename from src/AssemblyUtils.cs rename to src/Utils/AssemblyUtils.cs index 73e5c49..db225ba 100644 --- a/src/AssemblyUtils.cs +++ b/src/Utils/AssemblyUtils.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.InteropServices; namespace NetWebView2Lib { diff --git a/src/WebView2Manager.cs b/src/WebView2Manager.cs deleted file mode 100644 index 870f2e8..0000000 --- a/src/WebView2Manager.cs +++ /dev/null @@ -1,2381 +0,0 @@ -using Microsoft.Web.WebView2.Core; -using Microsoft.Web.WebView2.WinForms; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using System.Windows.Forms; - -// --- Version 2.0.0-beta.3 --- -// Breaking Change: Sender-Aware Events (Issue #52) - -namespace NetWebView2Lib -{ - /// - /// Resource access kind for Virtual Host Mapping. - /// - [ComVisible(true)] - public enum HostResourceAccessKind - { - Allow = 0, - Deny = 1, - DenyCors = 2 - } - - // --- 1. EVENTS INTERFACE (What C# sends to AutoIt) --- - /// - /// Events sent from C# to AutoIt. - /// v2.0.0: All events now include sender and parentHandle for multi-instance support. - /// - [Guid("B2C3D4E5-F6A7-4B6C-9D0E-1F2A3B4C5D6E")] - [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] - [ComVisible(true)] - public interface IWebViewEvents - { - /// Triggered when a message is sent from the WebView. - /// The WebViewManager instance. - /// The parent window handle. - /// The message content. - [DispId(1)] void OnMessageReceived(object sender, string parentHandle, string message); - [DispId(2)] void OnNavigationStarting(object sender, string parentHandle, string url); - [DispId(3)] void OnNavigationCompleted(object sender, string parentHandle, bool isSuccess, int webErrorStatus); - [DispId(4)] void OnTitleChanged(object sender, string parentHandle, string newTitle); - [DispId(5)] void OnWebResourceResponseReceived(object sender, string parentHandle, int statusCode, string reasonPhrase, string requestUrl); - [DispId(6)] void OnContextMenu(object sender, string parentHandle, string menuData); - [DispId(10)] void OnZoomChanged(object sender, string parentHandle, double factor); - [DispId(11)] void OnBrowserGotFocus(object sender, string parentHandle, int reason); - [DispId(12)] void OnBrowserLostFocus(object sender, string parentHandle, int reason); - [DispId(13)] void OnURLChanged(object sender, string parentHandle, string newUrl); - [DispId(190)] void OnContextMenuRequested(object sender, string parentHandle, string linkUrl, int x, int y, string selectionText); - [DispId(208)] void OnDownloadStarting(object sender, string parentHandle, string uri, string defaultPath); - [DispId(209)] void OnDownloadStateChanged(object sender, string parentHandle, string state, string uri, long totalBytes, long receivedBytes); - [DispId(221)] void OnAcceleratorKeyPressed(object sender, string parentHandle, object args); - [DispId(225)] void OnProcessFailed(object sender, string parentHandle, object args); - [DispId(228)] void OnBasicAuthenticationRequested(object sender, string parentHandle, object args); - } - - - /// - /// Actions available to call from AutoIt. - /// - [Guid("CCB12345-6789-4ABC-DEF0-1234567890AB")] - [InterfaceType(ComInterfaceType.InterfaceIsDual)] - [ComVisible(true)] - public interface IWebViewActions - { - /// Set additional browser arguments (switches) before initialization. - [DispId(100)] string AdditionalBrowserArguments { get; set; } - /// Initialize the WebView. - [DispId(101)] void Initialize(object parentHandle, string userDataFolder, int x = 0, int y = 0, int width = 0, int height = 0); - /// Navigate to a URL. - [DispId(102)] void Navigate(string url); - /// Navigate to HTML content. - [DispId(103)] void NavigateToString(string htmlContent); - /// Execute JavaScript. - [DispId(104)] void ExecuteScript(string script); - /// Resize the WebView. - [DispId(105)] void Resize(int width, int height); - /// Clean up resources. - [DispId(106)] void Cleanup(); - /// Get the Bridge object. - [DispId(107)] IBridgeActions GetBridge(); - /// Export to PDF. - [DispId(108)] void ExportToPdf(string filePath); - /// Check if ready. - [DispId(109)] bool IsReady(); - /// Enable/Disable Context Menu. - [DispId(110)] void SetContextMenuEnabled(bool enabled); - /// Lock the WebView. - [DispId(111)] void LockWebView(); - /// Unlock the WebView by re-enabling restricted features. - [DispId(215)] void UnLockWebView(); - /// Enable major browser features. - [DispId(227)] void EnableBrowserFeatures(); - /// Disable browser features. - [DispId(112)] void DisableBrowserFeatures(); - /// Go Back. - [DispId(113)] void GoBack(); - /// Reset Zoom. - [DispId(114)] void ResetZoom(); - /// Inject CSS. - [DispId(115)] void InjectCss(string cssCode); - /// Clear Injected CSS. - [DispId(116)] void ClearInjectedCss(); - /// Toggle Audit Highlights. - [DispId(117)] void ToggleAuditHighlights(bool enable); - /// Set AdBlock active state. - [DispId(118)] void SetAdBlock(bool active); - /// Add a block rule. - [DispId(119)] void AddBlockRule(string domain); - /// Clear all block rules. - [DispId(120)] void ClearBlockRules(); - /// Set the lockdown state of the WebView. - [DispId(220)] void SetLockState(bool lockState); - - /// Go Forward. - - [DispId(121)] void GoForward(); - /// Get HTML Source. - [DispId(122)] void GetHtmlSource(); - /// Get Selected Text. - [DispId(123)] void GetSelectedText(); - /// Set Zoom factor. - [DispId(124)] void SetZoom(double factor); - /// Parse JSON to internal storage. - [DispId(125)] bool ParseJsonToInternal(string json); - /// Get value from internal JSON. - [DispId(126)] string GetInternalJsonValue(string path); - /// Clear browsing data. - [DispId(127)] void ClearBrowserData(); - /// Reload. - [DispId(128)] void Reload(); - /// Stop loading. - [DispId(129)] void Stop(); - /// Show Print UI. - [DispId(130)] void ShowPrintUI(); - /// Set Muted state. - [DispId(131)] void SetMuted(bool muted); - /// Check if Muted. - [DispId(132)] bool IsMuted(); - /// Set User Agent. - [DispId(133)] void SetUserAgent(string userAgent); - /// Get Document Title. - [DispId(134)] string GetDocumentTitle(); - /// Get Source URL. - [DispId(135)] string GetSource(); - /// Enable/Disable Script. - [DispId(136)] void SetScriptEnabled(bool enabled); - /// Enable/Disable Web Message. - [DispId(137)] void SetWebMessageEnabled(bool enabled); - /// Enable/Disable Status Bar. - [DispId(138)] void SetStatusBarEnabled(bool enabled); - /// Capture Preview. - [DispId(139)] void CapturePreview(string filePath, string format); - /// Call CDP Method. - [DispId(140)] void CallDevToolsProtocolMethod(string methodName, string parametersJson); - /// Get Cookies. - [DispId(141)] void GetCookies(string channelId); - /// Add a Cookie. - [DispId(142)] void AddCookie(string name, string value, string domain, string path); - /// Delete a Cookie. - [DispId(143)] void DeleteCookie(string name, string domain, string path); - /// Delete All Cookies. - [DispId(144)] void DeleteAllCookies(); - /// Get current frame count. - [DispId(146)] int GetFrameCount(); - /// Get HTML source of a specific frame. - [DispId(147)] void GetFrameHtmlSource(int index); - /// Get URL of a specific frame. - [DispId(148)] string GetFrameUrl(int index); - /// Get Name of a specific frame. - [DispId(149)] string GetFrameName(int index); - /// Get all frame URLs pipe-separated. - [DispId(152)] string GetFrameUrls(); - /// Get all frame names pipe-separated. - [DispId(153)] string GetFrameNames(); - - /// Print. - [DispId(145)] void Print(); - /// Add Extension. - [DispId(150)] void AddExtension(string extensionPath); - /// Remove Extension. - [DispId(151)] void RemoveExtension(string extensionId); - - /// Check if can go back. - [DispId(162)] bool GetCanGoBack(); - /// Check if can go forward. - [DispId(163)] bool GetCanGoForward(); - /// Get Browser Process ID. - [DispId(164)] uint GetBrowserProcessId(); - /// Encode a string for URL. - [DispId(165)] string EncodeURI(string value); - /// Decode a URL string. - [DispId(166)] string DecodeURI(string value); - /// Encode a string for Base64. - [DispId(167)] string EncodeB64(string value); - /// Decode a Base64 string. - [DispId(168)] string DecodeB64(string value); - - // --- NEW UNIFIED SETTINGS (PROPERTIES) --- - /// Check if DevTools are enabled. - [DispId(170)] bool AreDevToolsEnabled { get; set; } - /// Check if default context menus are enabled. - [DispId(171)] bool AreDefaultContextMenusEnabled { get; set; } - /// Verbose Log state. - [DispId(228)] bool Verbose { get; set; } - /// Check if default script dialogs are enabled. - [DispId(172)] bool AreDefaultScriptDialogsEnabled { get; set; } - /// Check if browser accelerator keys are enabled. - [DispId(173)] bool AreBrowserAcceleratorKeysEnabled { get; set; } - /// Check if status bar is enabled. - [DispId(174)] bool IsStatusBarEnabled { get; set; } - /// Get/Set Zoom Factor. - [DispId(175)] double ZoomFactor { get; set; } - /// Set Background Color (Hex string). - [DispId(176)] string BackColor { get; set; } - /// Check if host objects are allowed. - [DispId(177)] bool AreHostObjectsAllowed { get; set; } - /// Get/Set Anchor (Resizing). - [DispId(178)] int Anchor { get; set; } - /// Get/Set Border Style. - [DispId(179)] int BorderStyle { get; set; } - - // --- NEW UNIFIED METHODS --- - /// Set Zoom Factor (Wrapper). - [DispId(180)] void SetZoomFactor(double factor); - /// Open DevTools Window. - [DispId(181)] void OpenDevToolsWindow(); - /// Focus the WebView. - [DispId(182)] void WebViewSetFocus(); - /// Check if browser popups are allowed or redirected to the same window. - [DispId(183)] bool AreBrowserPopupsAllowed { get; set; } - /// Add a script that executes on every page load (Permanent Injection). Returns the ScriptId. - [DispId(184)] string AddInitializationScript(string script); - /// Removes a script previously added via AddInitializationScript. - [DispId(217)] void RemoveInitializationScript(string scriptId); - /// Binds the internal JSON data to a browser variable. - [DispId(185)] bool BindJsonToBrowser(string variableName); - /// Syncs JSON data to internal parser and optionally binds it to a browser variable. - [DispId(186)] void SyncInternalData(string json, string bindToVariableName = ""); - - /// Execute JavaScript and return result synchronously (Blocking wait). - [DispId(188)] string ExecuteScriptWithResult(string script); - /// Enables or disables automatic resizing of the WebView to fill its parent. - [DispId(189)] void SetAutoResize(bool enabled); - - /// Execute JavaScript on the current page immediately. - [DispId(191)] void ExecuteScriptOnPage(string script); - - /// Clears the browser cache (DiskCache and LocalStorage). - [DispId(193)] void ClearCache(); - /// Enables or disables custom context menu handling. - [DispId(194)] bool CustomMenuEnabled { get; set; } - /// Enable/Disable OnWebResourceResponseReceived event. - [DispId(195)] bool HttpStatusCodeEventsEnabled { get; set; } - /// Filter HttpStatusCode events to only include the main document. - [DispId(196)] bool HttpStatusCodeDocumentOnly { get; set; } - - - /// Get inner text. - [DispId(200)] void GetInnerText(); - - /// Capture page data as MHTML or other CDP snapshot formats. - [DispId(201)] string CaptureSnapshot(string cdpParameters = "{\"format\": \"mhtml\"}"); - /// Export page data as HTML or MHTML (Legacy Support). - [DispId(207)] string ExportPageData(int format, string filePath); - /// Capture page as PDF and return as Base64 string. - [DispId(202)] string PrintToPdfStream(); - /// Control PDF toolbar items visibility. - [DispId(203)] int HiddenPdfToolbarItems { get; set; } - /// Custom Download Path. - [DispId(204)] void SetDownloadPath(string path); - /// Enable/Disable default Download UI. - [DispId(205)] bool IsDownloadUIEnabled { get; set; } - /// Decode a Base64 string to raw binary data (byte array). - [DispId(206)] byte[] DecodeB64ToBinary(string base64Text); - - /// Capture preview as Base64 string. - [DispId(216)] string CapturePreviewAsBase64(string format); - - /// Cancels downloads. If uri is null or empty, cancels all active downloads. - [DispId(210)] void CancelDownloads(string uri = ""); - /// Returns a pipe-separated string of all active download URIs. - [DispId(214)] string ActiveDownloadsList { get; } - /// Set to true to suppress the default download UI, typically set during the OnDownloadStarting event. - [DispId(211)] bool IsDownloadHandled { get; set; } - /// Enable/Disable Zoom control (Ctrl+Wheel, shortcuts). - [DispId(212)] bool IsZoomControlEnabled { get; set; } - /// Enable/Disable the built-in browser error page. - [DispId(213)] bool IsBuiltInErrorPageEnabled { get; set; } - /// Encodes raw binary data (byte array) to a Base64 string. - [DispId(219)] string EncodeBinaryToB64(object binaryData); - /// Maps a virtual host name to a local folder path. - [DispId(218)] void SetVirtualHostNameToFolderMapping(string hostName, string folderPath, int accessKind); - - /// Gets the internal window handle of the WebView2 control. - [DispId(222)] string BrowserWindowHandle { get; } - - /// Gets the parent window handle provided during initialization. - [DispId(229)] string ParentWindowHandle { get; } - - /// A comma-separated list of Virtual Key codes to block (e.g., "116,123"). - [DispId(223)] string BlockedVirtualKeys { get; set; } - /// Gets the version of the DLL. - [DispId(224)] string Version { get; } - - /// Gets or sets the folder path where WebView2 failure reports (crash dumps) are stored. - [DispId(226)] string FailureReportFolderPath { get; set; } - } - - // --- 3. THE MANAGER CLASS --- - /// - /// The Main Manager Class for WebView2 Interaction. - /// - [Guid("E3F4A5B6-C7D8-4E9F-0A1B-2C3D4E5F6A7B")] - [ComSourceInterfaces(typeof(IWebViewEvents))] - [ClassInterface(ClassInterfaceType.None)] - [ComVisible(true)] - [ProgId("NetWebView2Lib.WebView2Manager")] - public class WebView2Manager : IWebViewActions - { - #region 1. PRIVATE FIELDS - private readonly WebView2 _webView; - private readonly WebView2Bridge _bridge; - private readonly WebView2Parser _internalParser = new WebView2Parser(); - private readonly List _frames = new List(); - private bool _verbose = false; - - private bool _isAdBlockActive = false; - private readonly List _blockList = new List(); - private const string StyleId = "autoit-injected-style"; - private bool _areBrowserPopupsAllowed = false; - private bool _contextMenuEnabled = true; - private bool _autoResizeEnabled = false; - private bool _customMenuEnabled = false; - private string _additionalBrowserArguments = ""; - private string _customDownloadPath = ""; - private bool _isDownloadUIEnabled = true; - private bool _httpStatusCodeEventsEnabled = true; - private bool _httpStatusCodeDocumentOnly = true; - private bool _isDownloadHandledOverride = false; - private bool _isZoomControlEnabled = true; - private string _failureReportFolderPath = ""; - - private int _offsetX = 0; - private int _offsetY = 0; - private int _marginRight = 0; - private int _marginBottom = 0; - private IntPtr _parentHandle = IntPtr.Zero; - private ParentWindowSubclass _parentSubclass; - - private string _lastCssRegistrationId = ""; - private System.Threading.SynchronizationContext _uiContext; - - // Keeps active downloads keyed by their URI - private readonly Dictionary _activeDownloads = new Dictionary(); - - #endregion - - #region 2. DELEGATES & EVENTS - // v2.0.0: All delegates now include sender and parentHandle parameters - - /// Delegate for message events. - public delegate void OnMessageReceivedDelegate(object sender, string parentHandle, string message); - public delegate void OnNavigationStartingDelegate(object sender, string parentHandle, string url); - public delegate void OnNavigationCompletedDelegate(object sender, string parentHandle, bool isSuccess, int webErrorStatus); - public delegate void OnTitleChangedDelegate(object sender, string parentHandle, string newTitle); - public delegate void OnURLChangedDelegate(object sender, string parentHandle, string newUrl); - public delegate void OnBrowserGotFocusDelegate(object sender, string parentHandle, int reason); - public delegate void OnBrowserLostFocusDelegate(object sender, string parentHandle, int reason); - public delegate void OnWebResourceResponseReceivedDelegate(object sender, string parentHandle, int statusCode, string reasonPhrase, string requestUrl); - public delegate void OnContextMenuDelegate(object sender, string parentHandle, string menuData); - public delegate void OnZoomChangedDelegate(object sender, string parentHandle, double factor); - public delegate void OnContextMenuRequestedDelegate(object sender, string parentHandle, string linkUrl, int x, int y, string selectionText); - public delegate void OnDownloadStartingDelegate(object sender, string parentHandle, string uri, string defaultPath); - public delegate void OnDownloadStateChangedDelegate(object sender, string parentHandle, string state, string uri, long totalBytes, long receivedBytes); - public delegate void OnAcceleratorKeyPressedDelegate(object sender, string parentHandle, object args); - public delegate void OnProcessFailedDelegate(object sender, string parentHandle, object args); - public delegate void OnBasicAuthenticationRequestedDelegate(object sender, string parentHandle, object args); - - /// - /// Normalizes an IntPtr handle as an AutoIt Advanced Window Description string. - /// Format: [HANDLE:0x00160678] - /// This ensures AutoIt recognizes it natively in all window functions. - /// - private string FormatHandle(IntPtr handle) - { - if (handle == IntPtr.Zero) return "[HANDLE:0x" + IntPtr.Zero.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; - return "[HANDLE:0x" + handle.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; - } - - // Event declarations - /// Event fired when a message is received. - public event OnMessageReceivedDelegate OnMessageReceived; - /// Event fired when navigation starts. - public event OnNavigationStartingDelegate OnNavigationStarting; - /// Event fired when navigation is completed. - public event OnNavigationCompletedDelegate OnNavigationCompleted; - /// Event fired when the document title changes. - public event OnTitleChangedDelegate OnTitleChanged; - /// Event fired when the URL changes. - public event OnURLChangedDelegate OnURLChanged; - /// Event fired when a web resource response is received. - public event OnWebResourceResponseReceivedDelegate OnWebResourceResponseReceived; - /// Event fired when a download is starting. - public event OnDownloadStartingDelegate OnDownloadStarting; - /// Event fired when a download state changed. - public event OnDownloadStateChangedDelegate OnDownloadStateChanged; - /// Event fired when a custom context menu is requested. - public event OnContextMenuDelegate OnContextMenu; - /// Event fired when a simplified context menu is requested. - public event OnContextMenuRequestedDelegate OnContextMenuRequested; - /// Event fired when zoom factor changes. - public event OnZoomChangedDelegate OnZoomChanged; - /// Event fired when browser gets focus. - public event OnBrowserGotFocusDelegate OnBrowserGotFocus; - /// Event fired when browser loses focus. - public event OnBrowserLostFocusDelegate OnBrowserLostFocus; - - /// Event fired when an accelerator key is pressed. - public event OnAcceleratorKeyPressedDelegate OnAcceleratorKeyPressed; - - /// Event fired when a WebView2 process fails. - public event OnProcessFailedDelegate OnProcessFailed; - - /// Event fired when basic authentication is requested. - public event OnBasicAuthenticationRequestedDelegate OnBasicAuthenticationRequested; - #endregion - - #region 3. NATIVE METHODS & HELPERS - [DllImport("user32.dll")] - private static extern bool GetClientRect(IntPtr hWnd, out Rect lpRect); - - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); - - [DllImport("user32.dll")] - private static extern IntPtr GetFocus(); - - [DllImport("user32.dll")] - private static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd); - - /// - /// A simple Rectangle struct. - /// - [StructLayout(LayoutKind.Sequential)] - public struct Rect { - /// Left position - public int Left; - /// Top position - public int Top; - /// Right position - public int Right; - /// Bottom position - public int Bottom; - } - #endregion - - #region 4. CONSTRUCTOR - /// - /// Initializes a new instance of the WebViewManager class. - /// - public WebView2Manager() - { - _webView = new WebView2(); - _bridge = new WebView2Bridge(); - } - - /// - /// Performs cleanup of the WebView2 control and associated resources. - /// - public void Cleanup() - { - Log("Cleanup requested."); - _webView?.Dispose(); - Log("Cleanup completed."); - } - #endregion - - #region 5. BRIDGE & STATUS - /// - /// Get the Bridge object for AutoIt interaction. - /// - public IBridgeActions GetBridge() - { - return _bridge; - } - - /// - /// Check if WebView2 is initialized and ready. - /// - public bool IsReady() => _webView?.CoreWebView2 != null; - - /// Allows internal classes to push debug/info messages to AutoIt. - internal void InternalPushMessage(string message) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), message); - } - - /// - /// Gets the internal window handle of the WebView2 control. - /// - public string BrowserWindowHandle => FormatHandle(_webView?.Handle ?? IntPtr.Zero); - - /// - /// Gets the parent window handle provided during initialization. - /// - public string ParentWindowHandle => FormatHandle(_parentHandle); - - private string _blockedVirtualKeys = ""; - /// - /// A comma-separated list of Virtual Key codes to be blocked synchronously (e.g., "116,123"). - /// This ensures blocking (Handled = true) works without COM timing issues. - /// - public string BlockedVirtualKeys - { - get => _blockedVirtualKeys; - set => _blockedVirtualKeys = value ?? ""; - } - - /// Gets the version of the DLL. - public string Version => AssemblyUtils.GetVersion(); - - /// - /// Gets or sets the folder path where WebView2 failure reports (crash dumps) are stored. - /// If not set before Initialize, defaults to 'Crashes' subfolder in userDataFolder. - /// - public string FailureReportFolderPath - { - get => _failureReportFolderPath; - set => _failureReportFolderPath = value ?? ""; - } - #endregion - - #region 6. CORE INITIALIZATION - /// - /// Initializes the WebView2 control within the specified parent window handle. - /// Supports browser extensions and custom user data folders. - /// - public async void Initialize(object parentHandle, string userDataFolder, int x = 0, int y = 0, int width = 0, int height = 0) - { - Log($"Initialize request: parent={parentHandle}, x={x}, y={y}, w={width}, h={height}"); - try - { - // Capture the UI thread context for Law #1 compliance - _uiContext = System.Threading.SynchronizationContext.Current; - if (_uiContext != null) _bridge.SetSyncContext(_uiContext); - - // Convert the incoming handle from AutoIt (passed as object/pointer) - long rawHandleValue = Convert.ToInt64(parentHandle); - _parentHandle = new IntPtr(rawHandleValue); - - // v2.0.0: "Seal" the bridge context immediately after handle conversion - _bridge.SetParentContext(this, _parentHandle); - - // Store offsets for Smart Resize - _offsetX = x; - _offsetY = y; - - // Calculate Margins based on Parent's size at initialization - int calcWidth = width; - int calcHeight = height; - - if (GetClientRect(_parentHandle, out Rect parentRect)) - { - int pWidth = parentRect.Right - parentRect.Left; - int pHeight = parentRect.Bottom - parentRect.Top; - - // If user provides 0 (or less), we assume they want to fill the parent - if (width <= 0) - { - calcWidth = Math.Max(10, pWidth - x); - _marginRight = 0; - } - else - { - _marginRight = Math.Max(0, (pWidth - x) - width); - } - - if (height <= 0) - { - calcHeight = Math.Max(10, pHeight - y); - _marginBottom = 0; - } - else - { - _marginBottom = Math.Max(0, (pHeight - y) - height); - } - } - else - { - _marginRight = 0; - _marginBottom = 0; - } - - // Initialize the Subclass helper for Smart Resize - _parentSubclass = new ParentWindowSubclass(() => PerformSmartResize()); - - // Manage User Data Folder (User Profile) - // If no path is provided, create a default one in the application directory - if (string.IsNullOrEmpty(userDataFolder)) - { - userDataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebView2_Default_Profile"); - } - Log($"UserDataFolder: {userDataFolder}"); - - // Create the directory if it doesn't exist - if (!Directory.Exists(userDataFolder)) Directory.CreateDirectory(userDataFolder); - - // UI Setup on the main UI thread - // Finalize setup - _webView.Location = new Point(x, y); - _webView.Size = new Size(calcWidth, calcHeight); - Log($"Final initial size: {_webView.Size.Width}x{_webView.Size.Height}"); - - // Attach to parent - InvokeOnUiThread(() => { - // Attach the WebView to the AutoIt window/container - SetParent(_webView.Handle, _parentHandle); - _webView.Visible = false; - }); - - // --- NEW: EXTENSION & FAILURE HANDLING SETUP --- - // We must enable extensions and configure crash dumps in the Environment Options BEFORE creation - var options = new CoreWebView2EnvironmentOptions { AreBrowserExtensionsEnabled = true }; - - // Handle FailureReportFolderPath logic (Law #5 - Practicality) - if (string.IsNullOrEmpty(_failureReportFolderPath)) - { - _failureReportFolderPath = Path.Combine(userDataFolder, "FailureReportFolder"); - } - - if (!Directory.Exists(_failureReportFolderPath)) - { - Directory.CreateDirectory(_failureReportFolderPath); - } - - // Fallback: Use environment variable because FailureReportFolderPath is missing from Options in this SDK version - Environment.SetEnvironmentVariable("WEBVIEW2_FAILURE_REPORT_FOLDER_PATH", _failureReportFolderPath); - if (_failureReportFolderPath != "") Log($"FailureReportFolderPath: {_failureReportFolderPath}"); - - if (!string.IsNullOrEmpty(_additionalBrowserArguments)) options.AdditionalBrowserArguments = _additionalBrowserArguments; - - // Initialize the Environment with the Custom Data Folder and our Options - // Note: The second parameter is the userDataFolder, the third is the options - var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder, options); - - // Wait for the CoreWebView2 engine to be ready - await _webView.EnsureCoreWebView2Async(env); - - // Apply settings and register events - ConfigureSettings(); - RegisterEvents(); - - // Add default context menu bridge helper - await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(@" - window.dispatchEventToAutoIt = function(lnk, x, y, sel) { - window.chrome.webview.postMessage('CONTEXT_MENU_REQUEST|' + (lnk||'') + '|' + (x||0) + '|' + (y||0) + '|' + (sel||'')); - }; - "); - - // Make the browser visible once everything is loaded - InvokeOnUiThread(() => _webView.Visible = true); - - // Signal ready - Log("WebView2 Initialized successfully."); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "INIT_READY"); - } - catch (Exception ex) - { - Log("Initialization error: " + ex.Message); - // Send error details back to AutoIt if initialization fails - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|INIT_FAILED|" + ex.Message); - } - } - - /// - /// Adds a browser extension from an unpacked folder (containing manifest.json). - /// This method should be called after receiving the "INIT_READY" message. - /// - /// The full path to the unpacked extension folder. - public void AddExtension(string extensionPath) - { - Log($"AddExtension requested: {extensionPath}"); - InvokeOnUiThread(async () => - { - // Ensure the WebView and Profile are ready - if (_webView?.CoreWebView2?.Profile == null) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION|WebView2 Profile not ready."); - return; - } - - // Validate the extension path - if (!System.IO.Directory.Exists(extensionPath)) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION|Path not found: " + extensionPath); - return; - } - - try - { - // Add the browser extension - var ext = await _webView.CoreWebView2.Profile.AddBrowserExtensionAsync(extensionPath); - - // Notify AutoIt that the extension has been loaded successfully - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "EXTENSION_LOADED|" + ext.Id); - } - catch (Exception ex) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION_FAILED|" + ex.Message); - } - }); - } - - /// - /// Removes a browser extension by its ID. - /// - /// The ID of the extension to remove. - public void RemoveExtension(string extensionId) - { - Log($"RemoveExtension requested: {extensionId}"); - InvokeOnUiThread(async () => - { - if (_webView?.CoreWebView2?.Profile == null) return; - - try - { - // Retrieve all installed extensions - var extensions = await _webView.CoreWebView2.Profile.GetBrowserExtensionsAsync(); - - foreach (var ext in extensions) - { - if (ext.Id == extensionId) - { - await ext.RemoveAsync(); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "EXTENSION_REMOVED|" + extensionId); - return; - } - } - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION_NOT_FOUND|" + extensionId); - } - catch (Exception ex) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|REMOVE_EXTENSION_FAILED|" + ex.Message); - } - }); - } - #endregion - - #region 7. SETTINGS & CONFIGURATION - /// - /// Configure WebView2 settings. - /// - private void ConfigureSettings() - { - var settings = _webView.CoreWebView2.Settings; - settings.IsWebMessageEnabled = true; // Enable Web Messages - settings.AreDevToolsEnabled = true; // Enable DevTools by default - settings.AreDefaultContextMenusEnabled = true; // Keep TRUE to ensure the event fires - settings.IsZoomControlEnabled = _isZoomControlEnabled; // Apply custom zoom setting - _webView.DefaultBackgroundColor = Color.Transparent; - } - - /// - /// Disable certain browser features for a controlled environment. - /// - public void DisableBrowserFeatures() - { - LockWebView(); - } - #endregion - - #region 8. EVENT REGISTRATION - /// - /// Register event handlers for WebView2 events. - /// - private void RegisterEvents() - { - if (_webView?.CoreWebView2 == null) return; - - // Frame Tracking - _webView.CoreWebView2.FrameCreated += (s, e) => RegisterFrame(e.Frame); - - // --- RESTORED LOGIC --- - - // Context Menu Event - _webView.CoreWebView2.ContextMenuRequested += async (sender, args) => - { - Log($"ContextMenuRequested: Location={args.Location.X}x{args.Location.Y}, Target={args.ContextMenuTarget.Kind}"); - args.Handled = !_contextMenuEnabled; - - try - { - // 2. Data Retrieval (Async parts first) - // Check if the element or any of its parents is TABLE - string script = "document.elementFromPoint(" + args.Location.X + "," + args.Location.Y + ").closest('table') ? 'TABLE' : document.elementFromPoint(" + args.Location.X + "," + args.Location.Y + ").tagName"; - string tagNameResult = await _webView.CoreWebView2.ExecuteScriptAsync(script); - - InvokeOnUiThread(() => { - string tagName = tagNameResult?.Trim('\"') ?? "UNKNOWN"; - - // Extraction of Context info - string k = args.ContextMenuTarget.Kind.ToString(); - string src = args.ContextMenuTarget.HasSourceUri ? args.ContextMenuTarget.SourceUri : ""; - string lnk = args.ContextMenuTarget.HasLinkUri ? args.ContextMenuTarget.LinkUri : ""; - string sel = args.ContextMenuTarget.HasSelection ? args.ContextMenuTarget.SelectionText : ""; - - // --- CASE A: Parameter-based Event (v1.4.2 Priority) --- - // This is DispId 190 for AutoIt compatibility - _webView.BeginInvoke(new Action(() => { - OnContextMenuRequested?.Invoke(this, FormatHandle(_parentHandle), lnk, args.Location.X, args.Location.Y, sel); - })); - - // --- CASE B: Legacy JSON-based Event (v1.4.1 compatibility) --- - // Build JSON - Escaping for safety - string cleanSrc = src.Replace("\"", "\\\""); - string cleanLnk = lnk.Replace("\"", "\\\""); - string cleanSel = sel.Replace("\"", "\\\"").Replace("\r", "").Replace("\n", "\\n"); - - string json = "{" + - "\"x\":" + args.Location.X + "," + - "\"y\":" + args.Location.Y + "," + - "\"kind\":\"" + k + "\"," + - "\"tagName\":\"" + tagName + "\"," + - "\"src\":\"" + cleanSrc + "\"," + - "\"link\":\"" + cleanLnk + "\"," + - "\"selection\":\"" + cleanSel + "\"" + - "}"; - - _webView.BeginInvoke(new Action(() => { - OnContextMenu?.Invoke(this, FormatHandle(_parentHandle), "JSON:" + json); - })); - }); - } - catch (Exception ex) - { - Debug.WriteLine("ContextMenu Error: " + ex.Message); - } - }; - - // Ad Blocking - _webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); - _webView.CoreWebView2.WebResourceRequested += (s, e) => - { - // Track if this is a Document request for HttpStatusCode filtering - string uri = e.Request.Uri.ToLower(); - Log($"WebResourceRequested: {uri} (Context={e.ResourceContext})"); - - if (!_isAdBlockActive) return; - foreach (var domain in _blockList) - { - if (uri.Contains(domain)) - { - e.Response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(null, 403, "Forbidden", ""); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"BLOCKED_AD|{uri}"); - return; - } - } - }; - - _webView.CoreWebView2.NewWindowRequested += (s, e) => - { - Log($"NewWindowRequested: {e.Uri}"); - if (!_areBrowserPopupsAllowed) { - e.Handled = true; - if (!string.IsNullOrEmpty(e.Uri)) - { - string targetUri = e.Uri; - _webView.BeginInvoke(new Action(() => { - if (_webView?.CoreWebView2 != null) - { - _webView.CoreWebView2.Navigate(targetUri); - } - })); - } - } - }; - - // --- END RESTORED LOGIC --- - - // Navigation & Content Events - _webView.CoreWebView2.NavigationStarting += (s, e) => { - Log($"NavigationStarting: {e.Uri}"); - OnNavigationStarting?.Invoke(this, FormatHandle(_parentHandle), e.Uri); - - string url = e.Uri; - - // Notify AutoIt about navigation start - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_STARTING|" + url); - }; - - _webView.CoreWebView2.NavigationCompleted += (s, e) => { - Log($"NavigationCompleted: Success={e.IsSuccess}, ErrorStatus={e.WebErrorStatus}"); - OnNavigationCompleted?.Invoke(this, FormatHandle(_parentHandle), e.IsSuccess, (int)e.WebErrorStatus); - - // Keep the old OnMessageReceived for compatibility (optional) - if (e.IsSuccess) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_COMPLETED"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "TITLE_CHANGED|" + _webView.CoreWebView2.DocumentTitle); - } - else - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_ERROR|" + e.WebErrorStatus); - } - }; - - _webView.CoreWebView2.SourceChanged += (s, e) => { - Log($"SourceChanged: {_webView.CoreWebView2.Source}"); - OnURLChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.CoreWebView2.Source); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "URL_CHANGED|" + _webView.Source); - }; - - _webView.CoreWebView2.DocumentTitleChanged += (s, e) => { - OnTitleChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.CoreWebView2.DocumentTitle); - }; - - // Zoom Factor Changed Event - _webView.ZoomFactorChanged += (s, e) => { - OnZoomChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.ZoomFactor); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ZOOM_CHANGED|" + _webView.ZoomFactor); - }; - - // Process Failed Event - _webView.CoreWebView2.ProcessFailed += (s, e) => { - Log($"ProcessFailed: Kind={e.ProcessFailedKind}, Reason={e.Reason}"); - OnProcessFailed?.Invoke(this, FormatHandle(_parentHandle), new ProcessFailedEventArgsWrapper(e)); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PROCESS_FAILED|" + e.ProcessFailedKind + "|" + e.Reason); - }; - - // Basic Authentication Requested Event - _webView.CoreWebView2.BasicAuthenticationRequested += (s, e) => { - Log($"BasicAuthenticationRequested: Uri={e.Uri}"); - OnBasicAuthenticationRequested?.Invoke(this, FormatHandle(_parentHandle), new BasicAuthenticationRequestedEventArgsWrapper(e)); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BASIC_AUTH_REQUESTED|" + e.Uri); - }; - - // Communication Event <---> AutoIt <---> JavaScript - //_webView.CoreWebView2.WebMessageReceived += (s, e) => { - // OnMessageReceived?.Invoke(e.TryGetWebMessageAsString()); - //}; - - _webView.CoreWebView2.WebMessageReceived += (s, e) => - { - string message = e.TryGetWebMessageAsString(); - Log($"WebMessageReceived: {message}"); - - // Routing: Check if this is a JS-triggered context menu request - if (message != null && message.StartsWith("CONTEXT_MENU_REQUEST|")) - { - var parts = message.Split('|'); - if (parts.Length >= 5) - { - string lnk = parts[1]; - int x = int.TryParse(parts[2], out int px) ? px : 0; - int y = int.TryParse(parts[3], out int py) ? py : 0; - string sel = parts[4]; - - _webView.BeginInvoke(new Action(() => { - OnContextMenuRequested?.Invoke(this, FormatHandle(_parentHandle), lnk, x, y, sel); - })); - return; // Handled - } - } - - _bridge.RaiseMessage(message); // Send message to the correct channel - }; - - // Focus Events (Native Bridge) - _webView.GotFocus += (s, e) => { - Log("GotFocus"); - OnBrowserGotFocus?.Invoke(this, FormatHandle(_parentHandle), 0); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BROWSER_GOT_FOCUS|"); - }; - - _webView.LostFocus += (s, e) => { - Log("LostFocus (Check)"); - _webView.BeginInvoke(new Action(() => { - // Use Native API to see which HWND REALLY has the focus - IntPtr focusedHandle = GetFocus(); - - // If focusedHandle is NOT _webView.Handle - // AND is NOT a child (IsChild) of _webView.Handle, then we truly lost focus. - if (focusedHandle != _webView.Handle && !IsChild(_webView.Handle, focusedHandle)) { - Log("LostFocus (True)"); - OnBrowserLostFocus?.Invoke(this, FormatHandle(_parentHandle), 0); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BROWSER_LOST_FOCUS|"); - } - })); - }; - - // Communication Event <---> AutoIt <---> JavaScript - _webView.CoreWebView2.AddHostObjectToScript("autoit", _bridge); - - _webView.CoreWebView2.WebResourceResponseReceived += (s, e) => - { - if (!_httpStatusCodeEventsEnabled || e.Response == null) return; - - int statusCode = e.Response.StatusCode; - string reasonPhrase = GetReasonPhrase(statusCode, e.Response.ReasonPhrase); - string requestUrl = e.Request.Uri; - - Log($"WebResourceResponseReceived: {statusCode} {reasonPhrase} - {requestUrl}"); - - if (_httpStatusCodeDocumentOnly) - { - bool isDoc = e.Request.Headers.Contains("X-NetWebView2-IsDoc"); - if (!isDoc) return; - } - - _webView.BeginInvoke(new Action(() => { - OnWebResourceResponseReceived?.Invoke(this, FormatHandle(_parentHandle), statusCode, reasonPhrase, requestUrl); - })); - }; - - _webView.CoreWebView2.DownloadStarting += async (s, e) => - { - // Law #2: Use deferral for non-blocking wait - var deferral = e.GetDeferral(); - string currentUri = e.DownloadOperation.Uri; - - // 1. Apply Custom Download Path if it exists - if (!string.IsNullOrEmpty(_customDownloadPath)) - { - string fileName = System.IO.Path.GetFileName(e.ResultFilePath); - string fullPath = System.IO.Path.Combine(_customDownloadPath, fileName); - e.ResultFilePath = fullPath; - } - - // Add to list so it can be canceled later - _activeDownloads[currentUri] = e.DownloadOperation; - - // Reset per-download overrides - _isDownloadHandledOverride = false; // Default to FALSE (Let Edge handle it) - - // Fire the event to AutoIt - OnDownloadStarting?.Invoke(this, FormatHandle(_parentHandle), e.DownloadOperation.Uri, e.ResultFilePath); // Using the updated path - - // --- ROBUST ASYNC WAIT FOR AUTOIT (Law #2) --- - var sw = Stopwatch.StartNew(); - while (sw.ElapsedMilliseconds < 600 && !_isDownloadHandledOverride) - { - // Non-blocking wait that allows UI to stay responsive without DoEvents() - await Task.Delay(10); - } - - // Law #1: Ensure we are back on the UI thread after await before Touching CoreWebView2 members - InvokeOnUiThread(() => { - if (_isDownloadHandledOverride) - { - e.Cancel = true; - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_CANCELLED|{e.DownloadOperation.Uri}"); - deferral.Complete(); - return; - } - - // --- STANDARD MODE: EDGE --- - e.Handled = !_isDownloadUIEnabled; - - // Using e.ResultFilePath which now contains the correct path - // Note: accessing e.DownloadOperation.Uri is safe here as it was captured into currentUri earlier, - // but we can use the original object as we are back on the UI thread. - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_STARTING|{e.DownloadOperation.Uri}|{e.ResultFilePath}"); - - e.DownloadOperation.StateChanged += (sender, args) => - { - long totalBytes = (long)(e.DownloadOperation.TotalBytesToReceive ?? 0); - long receivedBytes = (long)e.DownloadOperation.BytesReceived; - - // Update status (InProgress, Completed, etc.) - OnDownloadStateChanged?.Invoke(this, FormatHandle(_parentHandle), e.DownloadOperation.State.ToString(), e.DownloadOperation.Uri, totalBytes, receivedBytes); - - // DOWNLOAD_CANCELLED - if (e.DownloadOperation.State == Microsoft.Web.WebView2.Core.CoreWebView2DownloadState.Interrupted) - { - // Send DOWNLOAD_CANCELLED|URL|Reason (e.g. UserCanceled, NetworkFailed, etc.) - var reason = e.DownloadOperation.InterruptReason; - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_CANCELLED|{currentUri}|{reason}"); - } - - // Clear list when finished (for any reason) - if (e.DownloadOperation.State != Microsoft.Web.WebView2.Core.CoreWebView2DownloadState.InProgress) - { - _activeDownloads.Remove(currentUri); - } - }; - - // --- Progress Tracking --- - int lastPercent = -1; - e.DownloadOperation.BytesReceivedChanged += (sender, args) => - { - long total = (long)(e.DownloadOperation.TotalBytesToReceive ?? 0); - long received = (long)e.DownloadOperation.BytesReceived; - - if (total > 0) - { - int currentPercent = (int)((received * 100) / total); - - // We only send an update to AutoIt if the percentage has changed - // so as not to "clog" the script with thousands of messages. - if (currentPercent > lastPercent) - { - lastPercent = currentPercent; - OnDownloadStateChanged?.Invoke(this, FormatHandle(_parentHandle), "InProgress", e.DownloadOperation.Uri, total, received); - } - } - }; - - deferral.Complete(); - }); - }; - - // Accelerator Key Pressed Event - // Restoration: Using reflection to access internal CoreWebView2Controller (WinForms SDK limitation) - try - { - var controllerField = typeof(WebView2).GetField("_coreWebView2Controller", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - if (controllerField != null) - { - if (controllerField.GetValue(_webView) is CoreWebView2Controller controller) - { - controller.AcceleratorKeyPressed += (s, e) => - { - var eventArgs = new WebView2AcceleratorKeyPressedEventArgs(e, this); - - // --- PRE-EMPTIVE BLOCKING --- - // Check if the key is in the blocked list - if (!string.IsNullOrEmpty(_blockedVirtualKeys)) - { - string vKeyStr = e.VirtualKey.ToString(); - if (_blockedVirtualKeys.Split(',').Any(k => k.Trim() == vKeyStr)) - { - e.Handled = true; - eventArgs.Block(); // Sync internal state - } - } - - // Dispatch to AutoIt - OnAcceleratorKeyPressed?.Invoke(this, FormatHandle(_parentHandle), eventArgs); - - // Apply state back (support for any synchronous COM result if lucky) - if (eventArgs.IsCurrentlyHandled) e.Handled = true; - }; - } - } - } - catch (Exception ex) - { - Debug.WriteLine("AcceleratorKey Reflection Error: " + ex.Message); - } - } - #endregion - - #region 9. PUBLIC API: NAVIGATION & CONTROL - /// Navigate to a specified URL. - public void Navigate(string url) - { - if (_webView == null) return; - Log($"Navigate: {url}"); - InvokeOnUiThread(() => _webView.CoreWebView2.Navigate(url)); - } - - /// Navigate to a string containing HTML content. - public void NavigateToString(string htmlContent) - { - _webView.Invoke(new Action(async () => { - int attempts = 0; - while (_webView.CoreWebView2 == null && attempts < 20) { await Task.Delay(50); attempts++; } - _webView.CoreWebView2?.NavigateToString(htmlContent); - })); - } - - /// Reload the current page. - public void Reload() => InvokeOnUiThread(() => _webView.CoreWebView2?.Reload()); - - /// Stops any ongoing navigation or loading. - public void Stop() => InvokeOnUiThread(() => _webView.CoreWebView2?.Stop()); - - /// Navigate back in history. - public void GoBack() => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null && _webView.CoreWebView2.CanGoBack) _webView.CoreWebView2.GoBack(); }); - - /// Navigate forward in history. - public void GoForward() => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null && _webView.CoreWebView2.CanGoForward) _webView.CoreWebView2.GoForward(); }); - - /// Check if it's possible to navigate back. - public bool GetCanGoBack() => _webView?.CoreWebView2?.CanGoBack ?? false; - - /// Check if it's possible to navigate forward. - public bool GetCanGoForward() => _webView?.CoreWebView2?.CanGoForward ?? false; - - /// Adds a script that executes on every page load (Permanent Injection). Returns the ScriptId. - public string AddInitializationScript(string script) - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - return RunOnUiThread(() => - { - try - { - var task = _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(script); - string scriptId = WaitAndGetResult(task); - - // Execute immediately on current page for full lifecycle effect - _webView.CoreWebView2.ExecuteScriptAsync(script); - - return scriptId; - } - catch (Exception ex) - { - return "ERROR: " + ex.Message; - } - }); - } - - /// Removes a script previously added via AddInitializationScript. - public void RemoveInitializationScript(string scriptId) - { - InvokeOnUiThread(() => - { - if (_webView?.CoreWebView2 != null && !string.IsNullOrEmpty(scriptId)) - { - _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(scriptId); - } - }); - } - - /// Controls the context menu behavior. - public void SetContextMenuEnabled(bool enabled) - { - _contextMenuEnabled = enabled; - InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null) _webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true; }); - } - #endregion - - #region 10. PUBLIC API: DATA EXTRACTION - /// - /// Retrieve the full HTML source of the current page. - /// - public async void GetHtmlSource() - { - if (_webView?.CoreWebView2 == null) return; - string html = await _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "HTML_SOURCE|" + CleanJsString(html)); - } - - /// - /// Retrieve the currently selected text on the page. - /// - public async void GetSelectedText() - { - if (_webView?.CoreWebView2 == null) return; - string selectedText = await _webView.CoreWebView2.ExecuteScriptAsync("window.getSelection().toString()"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "SELECTED_TEXT|" + CleanJsString(selectedText)); - } - - - /// - /// Parse JSON into the internal parser. - /// - public bool ParseJsonToInternal(string json) => _internalParser.Parse(json?.Trim()); - - /// - /// Get a value from the internal JSON parser. - /// - public string GetInternalJsonValue(string path) => _internalParser.GetTokenValue(path); - - /// - /// Binds the internal JSON data to a browser variable. - /// - public bool BindJsonToBrowser(string variableName) - { - try - { - if (_webView?.CoreWebView2 == null) return false; - - // 2. Get minified JSON from internal parser - string jsonData = _internalParser.GetMinifiedJson(); - if (string.IsNullOrEmpty(jsonData)) - { - jsonData = "{}"; // Fallback to empty object - } - - // 3. Escape for JS safety - string safeJson = jsonData.Replace("\\", "\\\\").Replace("'", "\\'"); - - // 3. Build script with JS try-catch and console logging - string script = $@" - try {{ - window.{variableName} = JSON.parse('{safeJson}'); - console.log('NetWebView2Lib: Data bound to window.{variableName}'); - true; - }} catch (e) {{ - console.error('NetWebView2Lib Bind Error:', e); - false; - }}"; - - // 4. Execute script - _webView.CoreWebView2.ExecuteScriptAsync(script); - return true; - } - catch (Exception ex) - { - Debug.WriteLine("BindJsonToBrowser Error: " + ex.Message); - return false; - } - } - - /// - /// Syncs JSON data to internal parser and optionally binds it to a browser variable. - /// - public void SyncInternalData(string json, string bindToVariableName = "") - { - if (ParseJsonToInternal(json)) - { - if (!string.IsNullOrEmpty(bindToVariableName)) - { - BindJsonToBrowser(bindToVariableName); - } - } - } - - /// - /// Retrieves the entire text content (innerText) of the document and sends it back to AutoIt. - /// - public async void GetInnerText() - { - if (_webView?.CoreWebView2 == null) return; - try { - string html = await _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.innerText"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "INNER_TEXT|" + CleanJsString(html)); - } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|INNER_TEXT_FAILED: " + ex.Message); } - } - #endregion - - #region 11. PUBLIC API: UI & INTERACTION - /// - /// Execute arbitrary JavaScript code. - /// - public void ExecuteScript(string script) - { - if (_webView == null) return; - Log($"ExecuteScript: {script.Substring(0, Math.Min(script.Length, 100))}{(script.Length > 100 ? "..." : "")}"); - InvokeOnUiThread(() => _webView.CoreWebView2.ExecuteScriptAsync(script)); - } - - /// - /// Execute JavaScript on the current page immediately. - /// - /// The JavaScript code to be executed. - public async void ExecuteScriptOnPage(string script) - { - if (_webView?.CoreWebView2 == null) return; - await _webView.CoreWebView2.ExecuteScriptAsync(script); - } - - /// - /// Posts a web message to the WebView2 control. - /// - /// The message string to post. - public void PostWebMessage(string message) - { - if (_webView == null) return; - Log($"PostWebMessage: {message}"); - InvokeOnUiThread(() => _webView.CoreWebView2.PostWebMessageAsString(message)); - } - - /// - /// Executes JavaScript and returns the result synchronously using a Message Pump. - /// - public string ExecuteScriptWithResult(string script) - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - try - { - var task = _webView.CoreWebView2.ExecuteScriptAsync(script); - var start = DateTime.Now; - while (!task.IsCompleted) - { - System.Windows.Forms.Application.DoEvents(); - if ((DateTime.Now - start).TotalSeconds > 5) return "ERROR: Script Timeout"; - System.Threading.Thread.Sleep(1); - } - string result = task.Result; - if (result == "null" || result == null) return string.Empty; - if (result.StartsWith("\"") && result.EndsWith("\"")) - { - result = result.Substring(1, result.Length - 2); - result = System.Text.RegularExpressions.Regex.Unescape(result); - } - return result; - } - catch (Exception ex) { return "ERROR: " + ex.Message; } - } - - /// - /// Inject CSS code into the current page. - /// - public async void InjectCss(string cssCode) - { - string js = $"(function() {{ let style = document.getElementById('{StyleId}'); if (!style) {{ style = document.createElement('style'); style.id = '{StyleId}'; document.head.appendChild(style); }} style.innerHTML = `{cssCode.Replace("`", "\\` text-decoration")}`; }})();"; - ExecuteScript(js); - if (!string.IsNullOrEmpty(_lastCssRegistrationId)) _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(_lastCssRegistrationId); - _lastCssRegistrationId = await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(js); - } - - /// - /// Remove previously injected CSS. - /// - public void ClearInjectedCss() - { - if (!string.IsNullOrEmpty(_lastCssRegistrationId)) - { - _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(_lastCssRegistrationId); - _lastCssRegistrationId = ""; - } - ExecuteScript($"(function() {{ let style = document.getElementById('{StyleId}'); if (style) style.remove(); }})();"); - } - - /// - /// Toggle audit highlights on/off. - /// - public void ToggleAuditHighlights(bool enable) - { - if (enable) InjectCss("img, h1, h2, h3, table, a { outline: 3px solid #FF6A00 !important; outline-offset: -3px !important; }"); - else ClearInjectedCss(); - } - - /// - /// Capture a screenshot (preview) of the current view. - /// - public async void CapturePreview(string filePath, string format) - { - if (_webView?.CoreWebView2 == null) return; - CoreWebView2CapturePreviewImageFormat imageFormat = format.ToLower().Contains("jpg") ? CoreWebView2CapturePreviewImageFormat.Jpeg : CoreWebView2CapturePreviewImageFormat.Png; - try - { - using (var fileStream = File.Create(filePath)) await _webView.CoreWebView2.CapturePreviewAsync(imageFormat, fileStream); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "CAPTURE_SUCCESS|" + filePath); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "CAPTURE_ERROR|" + ex.Message); } - } - - /// - /// Shows the print UI dialog. - /// - public void ShowPrintUI() => _webView?.CoreWebView2.ShowPrintUI(); - - /// - /// Initiate the native Print dialog via JS. - /// - public void Print() - { - InvokeOnUiThread(async () => { - if (_webView?.CoreWebView2 != null) - { - try { await _webView.CoreWebView2.ExecuteScriptAsync("window.print();"); } - catch(Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PRINT_ERROR|" + ex.Message); } - } - }); - } - - /// - /// Unified master switch to lock or unlock the WebView features. - /// - /// True to lock down, False to unlock. - public void SetLockState(bool lockState) - { - InvokeOnUiThread(() => { - if (_webView?.CoreWebView2 != null) - { - var s = _webView.CoreWebView2.Settings; - s.AreDefaultContextMenusEnabled = !lockState; - s.AreDevToolsEnabled = !lockState; - s.IsZoomControlEnabled = !lockState; - s.IsBuiltInErrorPageEnabled = !lockState; - s.AreDefaultScriptDialogsEnabled = !lockState; - s.AreBrowserAcceleratorKeysEnabled = !lockState; - s.IsStatusBarEnabled = !lockState; - } - _areBrowserPopupsAllowed = !lockState; - _contextMenuEnabled = !lockState; // Sync internal field - }); - } - - /// - /// Lock down the WebView by disabling certain features (Legacy). - /// - public void LockWebView() => SetLockState(true); - - /// - /// Unlock the WebView by re-enabling restricted features (Legacy). - /// - public void UnLockWebView() => SetLockState(false); - - /// - /// Enable major browser features for a controlled environment. - /// - public void EnableBrowserFeatures() => UnLockWebView(); - - - /// - /// Saves the current page as a PDF file. - /// - public async void ExportToPdf(string filePath) - { - if (_webView?.CoreWebView2 == null) return; - try - { - await _webView.CoreWebView2.PrintToPdfAsync(filePath); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PDF_EXPORT_SUCCESS|" + filePath); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PDF_EXPORT_ERROR|" + ex.Message); } - } - - /// - /// Capture page data as MHTML or other CDP snapshot formats. - /// - public string CaptureSnapshot(string cdpParameters = "{\"format\": \"mhtml\"}") - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - - return RunOnUiThread(() => - { - try - { - // Use a more robust wait that pumps messages - var task = _webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Page.captureSnapshot", cdpParameters); - string json = WaitAndGetResult(task); - - if (string.IsNullOrEmpty(json) || json == "null") - { - return "ERROR: CDP Page.captureSnapshot returned empty result."; - } - - // CDP returns a JSON object like {"data": "..."} - var dict = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); - if (dict != null && dict.ContainsKey("data")) - { - return dict["data"]; - } - return "ERROR: Page capture failed - 'data' field missing in CDP response."; - } - catch (Exception ex) - { - return "ERROR: " + ex.Message; - } - }); - } - - /// - /// Export page data as HTML or MHTML. - /// - public string ExportPageData(int format, string filePath) - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - - return RunOnUiThread(() => - { - try - { - string result = string.Empty; - if (format == 0) // HTML Only - { - var task = _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML"); - result = WaitAndGetResult(task); - result = CleanJsString(result); - } - else if (format == 1) // MHTML Snapshot - { - result = CaptureSnapshot(); - } - - // Check if the result itself is an error from CaptureSnapshot - if (string.IsNullOrEmpty(result) || result.StartsWith("ERROR:")) - { - return string.IsNullOrEmpty(result) ? "ERROR: Export failed - empty result." : result; - } - - if (!string.IsNullOrEmpty(filePath)) - { - try - { - File.WriteAllText(filePath, result); - return "SUCCESS: File saved to " + filePath; - } - catch (Exception ex) - { - return "ERROR: File write failed: " + ex.Message; - } - } - return result; - } - catch (Exception ex) { return "ERROR: " + ex.Message; } - }); - } - - /// - /// Capture the current page as a PDF and return it as a Base64 string. - /// - public string PrintToPdfStream() - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - - return RunOnUiThread(() => - { - try - { - var task = _webView.CoreWebView2.PrintToPdfStreamAsync(null); - Stream stream = WaitAndGetResult(task); - if (stream == null) return "ERROR: PDF stream is null"; - - using (MemoryStream ms = new MemoryStream()) - { - stream.CopyTo(ms); - return Convert.ToBase64String(ms.ToArray()); - } - } - catch (Exception ex) { return "ERROR: " + ex.Message; } - }); - } - - /// - /// Call a DevTools Protocol (CDP) method directly. - /// - public async void CallDevToolsProtocolMethod(string methodName, string parametersJson) - { - if (_webView?.CoreWebView2 == null) return; - try - { - string result = await _webView.CoreWebView2.CallDevToolsProtocolMethodAsync(methodName, parametersJson); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"CDP_RESULT|{methodName}|{result}"); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"CDP_ERROR|{methodName}|{ex.Message}"); } - } - - /// - /// Set the zoom factor. - /// - public void SetZoom(double factor) => InvokeOnUiThread(() => _webView.ZoomFactor = factor); - - /// - /// Reset zoom to default (100%). - /// - public void ResetZoom() => SetZoom(1.0); - - /// - /// Sets the mute status for audio. - /// - public void SetMuted(bool muted) => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null) _webView.CoreWebView2.IsMuted = muted; }); - - /// - /// Gets the current mute status. - /// - public bool IsMuted() => _webView?.CoreWebView2?.IsMuted ?? false; - - /// - /// Resize the WebView control. - /// - public void Resize(int w, int h) => InvokeOnUiThread(() => _webView.Size = new Size(w, h)); - - /// - /// Clean up resources. - /// - - #endregion - - #region 12. COOKIES & CACHE - /// - /// Clear browser data (cookies, cache, history, etc.). - /// - public async void ClearBrowserData() - { - await _webView.EnsureCoreWebView2Async(); - await _webView.CoreWebView2.Profile.ClearBrowsingDataAsync(); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "DATA_CLEARED"); - } - - /// - /// Clears the browser cache (DiskCache and LocalStorage). - /// - public async void ClearCache() - { - if (_webView?.CoreWebView2 == null) return; - await _webView.CoreWebView2.Profile.ClearBrowsingDataAsync(CoreWebView2BrowsingDataKinds.DiskCache | CoreWebView2BrowsingDataKinds.LocalStorage); - } - - /// - /// Get Cookies asynchronously. - /// - public async void GetCookies(string channelId) - { - if (_webView?.CoreWebView2?.CookieManager == null) return; - try - { - var cookieList = await _webView.CoreWebView2.CookieManager.GetCookiesAsync(null); - var sb = new System.Text.StringBuilder("["); - for(int i=0; i - /// Add or Update a Cookie. - /// - public void AddCookie(string name, string value, string domain, string path) - { - if (_webView?.CoreWebView2?.CookieManager == null) return; - try - { - var cookie = _webView.CoreWebView2.CookieManager.CreateCookie(name, value, domain, path); - _webView.CoreWebView2.CookieManager.AddOrUpdateCookie(cookie); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"COOKIE_ADD_ERROR|{ex.Message}"); } - } - - /// - /// Delete a specific Cookie. - /// - public void DeleteCookie(string name, string domain, string path) - { - if (_webView?.CoreWebView2?.CookieManager == null) return; - var cookie = _webView.CoreWebView2.CookieManager.CreateCookie(name, "", domain, path); - _webView.CoreWebView2.CookieManager.DeleteCookie(cookie); - } - - /// - /// Delete All Cookies. - /// - public void DeleteAllCookies() => _webView?.CoreWebView2?.CookieManager?.DeleteAllCookies(); - #endregion - - #region 13. AD BLOCKING - /// - /// Set AdBlock active state. - /// - public void SetAdBlock(bool active) => _isAdBlockActive = active; - - /// - /// Add a domain to the block list. - /// - public void AddBlockRule(string domain) { if (!string.IsNullOrEmpty(domain)) _blockList.Add(domain.ToLower()); } - - /// - /// Clear all ad block rules. - /// - public void ClearBlockRules() => _blockList.Clear(); - #endregion - - #region 14. SMART RESIZE - /// - /// Enables or disables automatic resizing. - /// - public void SetAutoResize(bool enabled) - { - if (_parentHandle == IntPtr.Zero || _parentSubclass == null) return; - _autoResizeEnabled = enabled; - if (_autoResizeEnabled) { _parentSubclass.AssignHandle(_parentHandle); PerformSmartResize(); } - else _parentSubclass.ReleaseHandle(); - } - - private void PerformSmartResize() - { - if (_webView == null || _parentHandle == IntPtr.Zero) return; - if (_webView.InvokeRequired) { _webView.Invoke(new Action(PerformSmartResize)); return; } - if (GetClientRect(_parentHandle, out Rect rect)) - { - int newWidth = (rect.Right - rect.Left) - _offsetX - _marginRight; - int newHeight = (rect.Bottom - rect.Top) - _offsetY - _marginBottom; - Log($"SmartResize: ParentSize={rect.Right - rect.Left}x{rect.Bottom - rect.Top}, NewWebViewSize={newWidth}x{newHeight}"); - _webView.Left = _offsetX; _webView.Top = _offsetY; - _webView.Width = Math.Max(10, newWidth); _webView.Height = Math.Max(10, newHeight); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "WINDOW_RESIZED|" + _webView.Width + "|" + _webView.Height); - } - } - - private class ParentWindowSubclass : NativeWindow - { - private const int WM_SIZE = 0x0005; - private readonly Action _onResize; - - public ParentWindowSubclass(Action onResize) - { - _onResize = onResize; - } - - protected override void WndProc(ref Message m) - { - base.WndProc(ref m); - if (m.Msg == WM_SIZE) _onResize?.Invoke(); - } - } - #endregion - - #region 15. MISC PUBLIC API - public void SetZoomFactor(double factor) - { - if (factor < 0.1 || factor > 5.0) return; - InvokeOnUiThread(() => _webView.ZoomFactor = factor); - } - - public void OpenDevToolsWindow() => InvokeOnUiThread(() => _webView?.CoreWebView2?.OpenDevToolsWindow()); - - public void WebViewSetFocus() => InvokeOnUiThread(() => _webView?.Focus()); - - public void SetUserAgent(string userAgent) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.UserAgent = userAgent; }); - } - - public string GetDocumentTitle() => _webView?.CoreWebView2?.DocumentTitle ?? ""; - - public string GetSource() => _webView?.Source?.ToString() ?? ""; - - public uint GetBrowserProcessId() - { - try { return _webView?.CoreWebView2?.BrowserProcessId ?? 0; } - catch { return 0; } - } - - public void SetScriptEnabled(bool enabled) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsScriptEnabled = enabled; }); - } - - public void SetWebMessageEnabled(bool enabled) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsWebMessageEnabled = enabled; }); - } - - public void SetStatusBarEnabled(bool enabled) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsStatusBarEnabled = enabled; }); - } - #endregion - - #region 16. UNIFIED SETTINGS (PROPERTIES) - public bool AreDevToolsEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreDevToolsEnabled ?? false); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreDevToolsEnabled = value; }); - } - - /// - /// Check if browser popups are allowed or redirected to the same window. - /// - public bool AreBrowserPopupsAllowed - { - get => _areBrowserPopupsAllowed; - set => InvokeOnUiThread(() => _areBrowserPopupsAllowed = value); - } - - public bool AreDefaultContextMenusEnabled - { - get => _contextMenuEnabled; - set => SetContextMenuEnabled(value); // Reuse existing logic - } - - /// Verbose Log state. - public bool Verbose - { - get => _verbose; - set - { - _verbose = value; - Log("Verbose mode: " + (_verbose ? "ENABLED" : "DISABLED")); - } - } - - public bool AreDefaultScriptDialogsEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreDefaultScriptDialogsEnabled ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = value; }); - } - - public bool AreBrowserAcceleratorKeysEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreBrowserAcceleratorKeysEnabled ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = value; }); - } - - public bool IsStatusBarEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.IsStatusBarEnabled ?? true); - set => SetStatusBarEnabled(value); // Reuse existing logic - } - - public double ZoomFactor - { - get => RunOnUiThread(() => _webView?.ZoomFactor ?? 1.0); - set => SetZoomFactor(value); - } - - public string BackColor - { - get => RunOnUiThread(() => ColorTranslator.ToHtml(_webView.DefaultBackgroundColor)); - set => InvokeOnUiThread(() => { - try { - // Fix 0x prefix for AutoIt - string hex = value.Replace("0x", "#"); - _webView.DefaultBackgroundColor = ColorTranslator.FromHtml(hex); - } catch { _webView.DefaultBackgroundColor = Color.White; } - }); - } - - public bool AreHostObjectsAllowed - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreHostObjectsAllowed ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreHostObjectsAllowed = value; }); - } - - public int Anchor - { - get => RunOnUiThread(() => (int)_webView.Anchor); - set => InvokeOnUiThread(() => _webView.Anchor = (AnchorStyles)value); - } - - public int BorderStyle - { - get => 0; // WebView2 control does not support BorderStyle property natively. - set { /* No-op: WebView2 does not support BorderStyle directly */ } - } - - /// Enable/Disable Custom Context Menu. - public bool CustomMenuEnabled - { - get => _customMenuEnabled; - set => _customMenuEnabled = value; - } - - /// - /// Gets or sets additional browser arguments (switches) to be passed to the Chromium engine. - /// These must be set BEFORE calling Initialize(). - /// - public string AdditionalBrowserArguments - { - get => _additionalBrowserArguments; - set => _additionalBrowserArguments = value; - } - - /// - /// Control PDF toolbar items visibility (bitwise combination of CoreWebView2PdfToolbarItems). - /// - public int HiddenPdfToolbarItems - { - get => RunOnUiThread(() => (int)(_webView?.CoreWebView2?.Settings?.HiddenPdfToolbarItems ?? CoreWebView2PdfToolbarItems.None)); - set => InvokeOnUiThread(() => { - if (_webView?.CoreWebView2?.Settings != null) - _webView.CoreWebView2.Settings.HiddenPdfToolbarItems = (CoreWebView2PdfToolbarItems)value; - }); - } - - /// Sets a custom download path for file downloads. - public void SetDownloadPath(string path) - { - _customDownloadPath = path; - if (!System.IO.Directory.Exists(path)) - { - System.IO.Directory.CreateDirectory(path); // Create the folder if it doesn't exist - } - } - - /// Returns a pipe-separated string of all active download URIs. - public string ActiveDownloadsList => string.Join("|", _activeDownloads.Keys); - - /// Cancels downloads. If uri is null or empty, cancels all active downloads. - /// Optional: The specific URI to cancel. - public void CancelDownloads(string uri = "") - { - if (string.IsNullOrEmpty(uri)) - { - // Cancel all - foreach (var download in _activeDownloads.Values) - { - download.Cancel(); - } - _activeDownloads.Clear(); - } - else if (_activeDownloads.ContainsKey(uri)) - { - // Cancel specific - _activeDownloads[uri].Cancel(); - _activeDownloads.Remove(uri); - } - } - - public bool IsDownloadUIEnabled - { - get => _isDownloadUIEnabled; - set => _isDownloadUIEnabled = value; - } - - /// Decodes a Base64-encoded string into a byte array. - /// The Base64-encoded string to decode. - /// A byte array containing the decoded binary data, or an empty array if the input is null, empty, or invalid. - public byte[] DecodeB64ToBinary(string base64Text) - { - if (string.IsNullOrEmpty(base64Text)) return new byte[0]; - try - { - return Convert.FromBase64String(base64Text); - } - catch { return new byte[0]; } - } - - /// Encodes raw binary data (byte array) to a Base64 string. - public string EncodeBinaryToB64(object binaryData) - { - if (binaryData is byte[] bytes) - { - return Convert.ToBase64String(bytes); - } - return ""; - } - - /// Enable/Disable OnWebResourceResponseReceived event. - public bool HttpStatusCodeEventsEnabled - { - get => _httpStatusCodeEventsEnabled; - set => _httpStatusCodeEventsEnabled = value; - } - - /// Filter HttpStatusCode events to only include the main document. - public bool HttpStatusCodeDocumentOnly - { - get => _httpStatusCodeDocumentOnly; - set => _httpStatusCodeDocumentOnly = value; - } - - - public bool IsDownloadHandled - { - get => _isDownloadHandledOverride; - set => _isDownloadHandledOverride = value; - } - - public bool IsZoomControlEnabled - { - get => _webView?.CoreWebView2?.Settings?.IsZoomControlEnabled ?? _isZoomControlEnabled; - set - { - _isZoomControlEnabled = value; - InvokeOnUiThread(() => { - if (_webView?.CoreWebView2?.Settings != null) - _webView.CoreWebView2.Settings.IsZoomControlEnabled = value; - }); - } - } - - public bool IsBuiltInErrorPageEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.IsBuiltInErrorPageEnabled ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsBuiltInErrorPageEnabled = value; }); - } - - /// - /// Maps a virtual host name to a local folder path. - /// - public void SetVirtualHostNameToFolderMapping(string hostName, string folderPath, int accessKind) - { - InvokeOnUiThread(() => { - if (_webView?.CoreWebView2 != null) - { - _webView.CoreWebView2.SetVirtualHostNameToFolderMapping(hostName, folderPath, (Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind)accessKind); - } - }); - } - - /// - /// Captures a screenshot of the current WebView2 content and returns it as a Base64-encoded data URL. - /// - /// The image format for the screenshot, either 'png' or 'jpeg'. - /// A Base64-encoded data URL representing the captured image. - public string CapturePreviewAsBase64(string format) - { - try - { - var imgFormat = format.ToLower() == "jpeg" ? - CoreWebView2CapturePreviewImageFormat.Jpeg : - CoreWebView2CapturePreviewImageFormat.Png; - - using (var ms = new System.IO.MemoryStream()) - { - var task = _webView.CoreWebView2.CapturePreviewAsync(imgFormat, ms); - WaitTask(task); - return $"data:image/{format.ToLower()};base64,{Convert.ToBase64String(ms.ToArray())}"; - } - } - catch (Exception ex) - { - return "Error: " + ex.Message; - } - } - - #endregion - - /// - /// Provides a standard HTTP reason phrase if the server response is empty (common in HTTP/2+). - /// - private string GetReasonPhrase(int statusCode, string originalReason) - { - if (!string.IsNullOrEmpty(originalReason)) return originalReason; - - switch (statusCode) - { - case 200: return "OK"; - case 201: return "Created"; - case 204: return "No Content"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 304: return "Not Modified"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 429: return "Too Many Requests"; - case 500: return "Internal Server Error"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - default: - try { return ((System.Net.HttpStatusCode)statusCode).ToString(); } - catch { return "Unknown"; } - } - } - - #region 11. PUBLIC API: IFRAME MANAGEMENT - /// - /// Registers a new frame for tracking. - /// - private void RegisterFrame(CoreWebView2Frame frame) - { - lock (_frames) - { - _frames.Add(frame); - frame.Destroyed += (s, e) => - { - lock (_frames) { _frames.Remove(frame); } - }; - } - } - - /// - /// Returns the number of currently tracked iframes. - /// - public int GetFrameCount() - { - lock (_frames) return _frames.Count; - } - - /// - /// Returns the URL of the specified frame index. - /// - public string GetFrameUrl(int index) - { - lock (_frames) - { - if (index < 0 || index >= _frames.Count) return "ERROR: Invalid frame index"; - try { - var prop = _frames[index].GetType().GetProperty("Uri"); - return prop != null ? (string)prop.GetValue(_frames[index]) : "Unknown"; - } catch { return "ERROR: Access failed"; } - } - } - - /// - /// Returns the Name of the specified frame index. - /// - public string GetFrameName(int index) - { - lock (_frames) - { - if (index < 0 || index >= _frames.Count) return "ERROR: Invalid frame index"; - try { return _frames[index].Name; } catch { return "ERROR: Access failed"; } - } - } - - /// - /// Returns a pipe-separated string of all tracked iframe URLs. - /// - public string GetFrameUrls() - { - lock (_frames) - { - var urls = _frames.Select(f => { - try { - var prop = f.GetType().GetProperty("Uri"); - return prop != null ? (string)prop.GetValue(f) : "Unknown"; - } catch { return "ERROR"; } - }); - return string.Join("|", urls); - } - } - - /// - /// Returns a pipe-separated string of all tracked iframe names. - /// - public string GetFrameNames() - { - lock (_frames) - { - var names = _frames.Select(f => { - try { return f.Name; } catch { return "ERROR"; } - }); - return string.Join("|", names); - } - } - - /// - /// Asynchronously retrieves the HTML of the frame at the specified index. - /// Sends result via OnMessageReceived with 'FRAME_HTML_SOURCE|{index}|' prefix. - /// - public async void GetFrameHtmlSource(int index) - { - CoreWebView2Frame frame = null; - lock (_frames) - { - if (index >= 0 && index < _frames.Count) frame = _frames[index]; - } - - if (frame == null) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"ERROR|FRAME_NOT_FOUND:{index}"); - return; - } - - try - { - string htmlResult = await frame.ExecuteScriptAsync("document.documentElement.outerHTML"); - InvokeOnUiThread(() => { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"FRAME_HTML_SOURCE|{index}|" + CleanJsString(htmlResult)); - }); - } - catch (Exception ex) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"ERROR|FRAME_HTML_FAILED|{index}|" + ex.Message); - } - } - #endregion - - #region 17. HELPER METHODS - private void Log(string message) - { - if (_verbose) - { - string h = FormatHandle(_parentHandle); - Console.WriteLine($"+++[NetWebView2Lib]{h}[{DateTime.Now:HH:mm:ss.fff}] {message}"); - } - } - - private T RunOnUiThread(Func func) - { - if (_webView == null || _webView.IsDisposed) return default; - if (_webView.InvokeRequired) return (T)_webView.Invoke(func); - else return func(); - } - - /// Invoke actions on the UI thread - private void InvokeOnUiThread(Action action) - { - if (_webView == null || _webView.IsDisposed) return; - if (_webView.InvokeRequired) _webView.Invoke(action); - else action(); - } - - /// - /// Wait for an async task to complete and return the result (Synchronous wrapper for COM). - /// - private T WaitAndGetResult(Task task, int timeoutSeconds = 20) - { - var start = DateTime.Now; - while (!task.IsCompleted) - { - System.Windows.Forms.Application.DoEvents(); - if ((DateTime.Now - start).TotalSeconds > timeoutSeconds) throw new TimeoutException("Operation timed out."); - System.Threading.Thread.Sleep(1); - } - return task.Result; - } - - /// - /// Waits for the specified Task to complete, processing Windows Forms events during the wait and enforcing a - /// timeout. - /// - /// The Task to wait for completion. - /// The maximum number of seconds to wait before timing out. Defaults to 20 seconds. - /// Thrown if the Task does not complete within the specified timeout. - private void WaitTask(Task task, int timeoutSeconds = 20) - { - var start = DateTime.Now; - while (!task.IsCompleted) - { - System.Windows.Forms.Application.DoEvents(); - if ((DateTime.Now - start).TotalSeconds > timeoutSeconds) - throw new TimeoutException("Operation timed out."); - System.Threading.Thread.Sleep(1); - } - if (task.IsFaulted && task.Exception != null) - throw task.Exception.InnerException; - } - - - /// - /// Encodes a string for safe use in a URL. - /// - public string EncodeURI(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - return System.Net.WebUtility.UrlEncode(value); - } - - /// - /// Decodes a URL-encoded string. - /// - public string DecodeURI(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - return System.Net.WebUtility.UrlDecode(value); - } - - /// - /// Encodes a string to Base64 (UTF-8). - /// - public string EncodeB64(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - var bytes = System.Text.Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); - } - - /// - /// Decodes a Base64 string to plain text (UTF-8). - /// - public string DecodeB64(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - try { - var bytes = Convert.FromBase64String(value); - return System.Text.Encoding.UTF8.GetString(bytes); - } catch { return ""; } // Fail safe - } - - /// - /// Clean up JavaScript string results. - /// - private string CleanJsString(string input) - { - if (string.IsNullOrEmpty(input)) return ""; - string decoded = System.Text.RegularExpressions.Regex.Unescape(input); - if (decoded.StartsWith("\"") && decoded.EndsWith("\"") && decoded.Length >= 2) - decoded = decoded.Substring(1, decoded.Length - 2); - return decoded; - } - #endregion - } - - /// - /// Compatibility Layer for WebViewManager (Legacy name). - /// Inherits all functionality from WebView2Manager. - /// - [Guid("A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D")] // OLD GUID - [ComSourceInterfaces(typeof(IWebViewEvents))] - [ClassInterface(ClassInterfaceType.None)] - [ComVisible(true)] - [ProgId("NetWebView2.Manager")] // OLD ProgID - public class WebViewManager : WebView2Manager, IWebViewActions - { - // Inherits everything - } -} diff --git a/src/WebViewBridge.cs b/src/WebViewBridge.cs deleted file mode 100644 index 36e6583..0000000 --- a/src/WebViewBridge.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading; - -// --- Version 2.0.0-beta.3 --- -// Breaking Change: Sender-Aware Events (Issue #52) - -namespace NetWebView2Lib -{ - /// - /// Delegate for detecting when messages are received from JavaScript. - /// v2.0.0: Now includes sender object and parent window handle. - /// - /// The WebViewManager instance that owns this bridge. - /// The parent window handle (Advanced Window Description). - /// The message content. - [ComVisible(true)] - public delegate void OnMessageReceivedDelegate(object sender, string parentHandle, string message); - - /// - /// Event interface for receiving messages from JavaScript via AutoIt. - /// v2.0.0: Sender-Aware pattern. - /// - [Guid("3E4F5A6B-7C8D-9E0F-1A2B-3C4D5E6F7A8B")] - [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] - [ComVisible(true)] - public interface IBridgeEvents - { - /// - /// Triggered when a message is received from JavaScript. - /// - /// The WebViewManager instance. - /// The parent window handle. - /// The message content. - [DispId(1)] - void OnMessageReceived(object sender, string parentHandle, string message); - } - - /// - /// Action interface for sending messages from JavaScript to AutoIt. - /// - [Guid("2D3E4F5A-6A7A-4A9B-8C7D-2E3F4A5B6C7D")] - [InterfaceType(ComInterfaceType.InterfaceIsDual)] - [ComVisible(true)] - public interface IBridgeActions - { - /// - /// Send a message to AutoIt. - /// - /// The message to send. - [DispId(1)] - void RaiseMessage(string message); - - /// Gets the version of the DLL. - [DispId(2)] string Version { get; } - } - - /// - /// Implementation of the bridge between WebView2 JavaScript and AutoIt. - /// v2.0.0: Supports Sender-Aware event pattern. - /// - [Guid("1A2B3C4D-5E6F-4A8B-9C0D-1E2F3A4B5C6D")] - [ClassInterface(ClassInterfaceType.None)] - [ComSourceInterfaces(typeof(IBridgeEvents))] - [ComVisible(true)] - public class WebViewBridge : IBridgeActions - { - /// - /// Event fired when a message is received from JavaScript. - /// - public event OnMessageReceivedDelegate OnMessageReceived; - - private SynchronizationContext _syncContext; - private readonly System.Diagnostics.Stopwatch _throttleStopwatch = System.Diagnostics.Stopwatch.StartNew(); - private long _lastMessageTicks = 0; - private const long ThrottlingIntervalTicks = TimeSpan.TicksPerSecond / 50; // max 50 calls/sec - - // v2.0.0: Parent context for Sender-Aware events - private object _sender; - private string _parentHandle; - - /// - /// Initializes a new instance of the WebViewBridge class. - /// - public WebViewBridge() - { - _syncContext = SynchronizationContext.Current ?? new SynchronizationContext(); - } - - /// - /// Sets the parent context for Sender-Aware events. - /// Must be called immediately after construction. - /// - public void SetParentContext(object sender, object hwnd) - { - _sender = sender; - - if (hwnd is IntPtr ptr) - { - _parentHandle = "[HANDLE:0x" + ptr.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; - } - else - { - _parentHandle = hwnd?.ToString() ?? "[HANDLE:0x" + IntPtr.Zero.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; - } - } - - /// - /// Updates the SynchronizationContext used for raising events. - /// - /// The new context. - public void SetSyncContext(SynchronizationContext context) - { - _syncContext = context; - } - - /// - /// Send a message from JavaScript to AutoIt. - /// v2.0.0: Now passes sender and parentHandle to the event. - /// - /// The message content. - public void RaiseMessage(string message) - { - if (OnMessageReceived != null) - { - // Throttling: Max 50 messages per second (Law #2: Performance) - long currentTicks = _throttleStopwatch.ElapsedTicks; - if (currentTicks - _lastMessageTicks < ThrottlingIntervalTicks) return; - _lastMessageTicks = currentTicks; - - // v2.0.0: Pass sender and parentHandle for multi-instance support - _syncContext?.Post(_ => OnMessageReceived?.Invoke(_sender, _parentHandle, message), null); - } - } - - /// Gets the version of the DLL. - public string Version => AssemblyUtils.GetVersion(); - } -} diff --git a/src/WebViewManager.cs b/src/WebViewManager.cs deleted file mode 100644 index 1895077..0000000 --- a/src/WebViewManager.cs +++ /dev/null @@ -1,2169 +0,0 @@ -using Microsoft.Web.WebView2.Core; -using Microsoft.Web.WebView2.WinForms; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using System.Windows.Forms; - -// --- Version 2.0.0-beta.3 --- -// Breaking Change: Sender-Aware Events (Issue #52) - -namespace NetWebView2Lib -{ - /// - /// Resource access kind for Virtual Host Mapping. - /// - [ComVisible(true)] - public enum HostResourceAccessKind - { - Allow = 0, - Deny = 1, - DenyCors = 2 - } - - // --- 1. EVENTS INTERFACE (What C# sends to AutoIt) --- - /// - /// Events sent from C# to AutoIt. - /// v2.0.0: All events now include sender and parentHandle for multi-instance support. - /// - [Guid("B2C3D4E5-F6A7-4B6C-9D0E-1F2A3B4C5D6E")] - [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] - [ComVisible(true)] - public interface IWebViewEvents - { - /// Triggered when a message is sent from the WebView. - /// The WebViewManager instance. - /// The parent window handle. - /// The message content. - [DispId(1)] void OnMessageReceived(object sender, string parentHandle, string message); - [DispId(2)] void OnNavigationStarting(object sender, string parentHandle, string url); - [DispId(3)] void OnNavigationCompleted(object sender, string parentHandle, bool isSuccess, int webErrorStatus); - [DispId(4)] void OnTitleChanged(object sender, string parentHandle, string newTitle); - [DispId(5)] void OnWebResourceResponseReceived(object sender, string parentHandle, int statusCode, string reasonPhrase, string requestUrl); - [DispId(6)] void OnContextMenu(object sender, string parentHandle, string menuData); - [DispId(10)] void OnZoomChanged(object sender, string parentHandle, double factor); - [DispId(11)] void OnBrowserGotFocus(object sender, string parentHandle, int reason); - [DispId(12)] void OnBrowserLostFocus(object sender, string parentHandle, int reason); - [DispId(13)] void OnURLChanged(object sender, string parentHandle, string newUrl); - [DispId(190)] void OnContextMenuRequested(object sender, string parentHandle, string linkUrl, int x, int y, string selectionText); - [DispId(208)] void OnDownloadStarting(object sender, string parentHandle, string uri, string defaultPath); - [DispId(209)] void OnDownloadStateChanged(object sender, string parentHandle, string state, string uri, long totalBytes, long receivedBytes); - [DispId(221)] void OnAcceleratorKeyPressed(object sender, string parentHandle, object args); - [DispId(225)] void OnProcessFailed(object sender, string parentHandle, object args); - [DispId(228)] void OnBasicAuthenticationRequested(object sender, string parentHandle, object args); - } - - - /// - /// Actions available to call from AutoIt. - /// - [Guid("CCB12345-6789-4ABC-DEF0-1234567890AB")] - [InterfaceType(ComInterfaceType.InterfaceIsDual)] - [ComVisible(true)] - public interface IWebViewActions - { - /// Set additional browser arguments (switches) before initialization. - [DispId(100)] string AdditionalBrowserArguments { get; set; } - /// Initialize the WebView. - [DispId(101)] void Initialize(object parentHandle, string userDataFolder, int x = 0, int y = 0, int width = 0, int height = 0); - /// Navigate to a URL. - [DispId(102)] void Navigate(string url); - /// Navigate to HTML content. - [DispId(103)] void NavigateToString(string htmlContent); - /// Execute JavaScript. - [DispId(104)] void ExecuteScript(string script); - /// Resize the WebView. - [DispId(105)] void Resize(int width, int height); - /// Clean up resources. - [DispId(106)] void Cleanup(); - /// Get the Bridge object. - [DispId(107)] IBridgeActions GetBridge(); - /// Export to PDF. - [DispId(108)] void ExportToPdf(string filePath); - /// Check if ready. - [DispId(109)] bool IsReady(); - /// Enable/Disable Context Menu. - [DispId(110)] void SetContextMenuEnabled(bool enabled); - /// Lock the WebView. - [DispId(111)] void LockWebView(); - /// Unlock the WebView by re-enabling restricted features. - [DispId(215)] void UnLockWebView(); - /// Enable major browser features. - [DispId(227)] void EnableBrowserFeatures(); - /// Disable browser features. - [DispId(112)] void DisableBrowserFeatures(); - /// Go Back. - [DispId(113)] void GoBack(); - /// Reset Zoom. - [DispId(114)] void ResetZoom(); - /// Inject CSS. - [DispId(115)] void InjectCss(string cssCode); - /// Clear Injected CSS. - [DispId(116)] void ClearInjectedCss(); - /// Toggle Audit Highlights. - [DispId(117)] void ToggleAuditHighlights(bool enable); - /// Set AdBlock active state. - [DispId(118)] void SetAdBlock(bool active); - /// Add a block rule. - [DispId(119)] void AddBlockRule(string domain); - /// Clear all block rules. - [DispId(120)] void ClearBlockRules(); - /// Set the lockdown state of the WebView. - [DispId(220)] void SetLockState(bool lockState); - - /// Go Forward. - - [DispId(121)] void GoForward(); - /// Get HTML Source. - [DispId(122)] void GetHtmlSource(); - /// Get Selected Text. - [DispId(123)] void GetSelectedText(); - /// Set Zoom factor. - [DispId(124)] void SetZoom(double factor); - /// Parse JSON to internal storage. - [DispId(125)] bool ParseJsonToInternal(string json); - /// Get value from internal JSON. - [DispId(126)] string GetInternalJsonValue(string path); - /// Clear browsing data. - [DispId(127)] void ClearBrowserData(); - /// Reload. - [DispId(128)] void Reload(); - /// Stop loading. - [DispId(129)] void Stop(); - /// Show Print UI. - [DispId(130)] void ShowPrintUI(); - /// Set Muted state. - [DispId(131)] void SetMuted(bool muted); - /// Check if Muted. - [DispId(132)] bool IsMuted(); - /// Set User Agent. - [DispId(133)] void SetUserAgent(string userAgent); - /// Get Document Title. - [DispId(134)] string GetDocumentTitle(); - /// Get Source URL. - [DispId(135)] string GetSource(); - /// Enable/Disable Script. - [DispId(136)] void SetScriptEnabled(bool enabled); - /// Enable/Disable Web Message. - [DispId(137)] void SetWebMessageEnabled(bool enabled); - /// Enable/Disable Status Bar. - [DispId(138)] void SetStatusBarEnabled(bool enabled); - /// Capture Preview. - [DispId(139)] void CapturePreview(string filePath, string format); - /// Call CDP Method. - [DispId(140)] void CallDevToolsProtocolMethod(string methodName, string parametersJson); - /// Get Cookies. - [DispId(141)] void GetCookies(string channelId); - /// Add a Cookie. - [DispId(142)] void AddCookie(string name, string value, string domain, string path); - /// Delete a Cookie. - [DispId(143)] void DeleteCookie(string name, string domain, string path); - /// Delete All Cookies. - [DispId(144)] void DeleteAllCookies(); - - /// Print. - [DispId(145)] void Print(); - /// Add Extension. - [DispId(150)] void AddExtension(string extensionPath); - /// Remove Extension. - [DispId(151)] void RemoveExtension(string extensionId); - - /// Check if can go back. - [DispId(162)] bool GetCanGoBack(); - /// Check if can go forward. - [DispId(163)] bool GetCanGoForward(); - /// Get Browser Process ID. - [DispId(164)] uint GetBrowserProcessId(); - /// Encode a string for URL. - [DispId(165)] string EncodeURI(string value); - /// Decode a URL string. - [DispId(166)] string DecodeURI(string value); - /// Encode a string for Base64. - [DispId(167)] string EncodeB64(string value); - /// Decode a Base64 string. - [DispId(168)] string DecodeB64(string value); - - // --- NEW UNIFIED SETTINGS (PROPERTIES) --- - /// Check if DevTools are enabled. - [DispId(170)] bool AreDevToolsEnabled { get; set; } - /// Check if default context menus are enabled. - [DispId(171)] bool AreDefaultContextMenusEnabled { get; set; } - /// Check if default script dialogs are enabled. - [DispId(172)] bool AreDefaultScriptDialogsEnabled { get; set; } - /// Check if browser accelerator keys are enabled. - [DispId(173)] bool AreBrowserAcceleratorKeysEnabled { get; set; } - /// Check if status bar is enabled. - [DispId(174)] bool IsStatusBarEnabled { get; set; } - /// Get/Set Zoom Factor. - [DispId(175)] double ZoomFactor { get; set; } - /// Set Background Color (Hex string). - [DispId(176)] string BackColor { get; set; } - /// Check if host objects are allowed. - [DispId(177)] bool AreHostObjectsAllowed { get; set; } - /// Get/Set Anchor (Resizing). - [DispId(178)] int Anchor { get; set; } - /// Get/Set Border Style. - [DispId(179)] int BorderStyle { get; set; } - - // --- NEW UNIFIED METHODS --- - /// Set Zoom Factor (Wrapper). - [DispId(180)] void SetZoomFactor(double factor); - /// Open DevTools Window. - [DispId(181)] void OpenDevToolsWindow(); - /// Focus the WebView. - [DispId(182)] void WebViewSetFocus(); - /// Check if browser popups are allowed or redirected to the same window. - [DispId(183)] bool AreBrowserPopupsAllowed { get; set; } - /// Add a script that executes on every page load (Permanent Injection). Returns the ScriptId. - [DispId(184)] string AddInitializationScript(string script); - /// Removes a script previously added via AddInitializationScript. - [DispId(217)] void RemoveInitializationScript(string scriptId); - /// Binds the internal JSON data to a browser variable. - [DispId(185)] bool BindJsonToBrowser(string variableName); - /// Syncs JSON data to internal parser and optionally binds it to a browser variable. - [DispId(186)] void SyncInternalData(string json, string bindToVariableName = ""); - - /// Execute JavaScript and return result synchronously (Blocking wait). - [DispId(188)] string ExecuteScriptWithResult(string script); - /// Enables or disables automatic resizing of the WebView to fill its parent. - [DispId(189)] void SetAutoResize(bool enabled); - - /// Execute JavaScript on the current page immediately. - [DispId(191)] void ExecuteScriptOnPage(string script); - - /// Clears the browser cache (DiskCache and LocalStorage). - [DispId(193)] void ClearCache(); - /// Enables or disables custom context menu handling. - [DispId(194)] bool CustomMenuEnabled { get; set; } - /// Enable/Disable OnWebResourceResponseReceived event. - [DispId(195)] bool HttpStatusCodeEventsEnabled { get; set; } - /// Filter HttpStatusCode events to only include the main document. - [DispId(196)] bool HttpStatusCodeDocumentOnly { get; set; } - - - /// Get inner text. - [DispId(200)] void GetInnerText(); - - /// Capture page data as MHTML or other CDP snapshot formats. - [DispId(201)] string CaptureSnapshot(string cdpParameters = "{\"format\": \"mhtml\"}"); - /// Export page data as HTML or MHTML (Legacy Support). - [DispId(207)] string ExportPageData(int format, string filePath); - /// Capture page as PDF and return as Base64 string. - [DispId(202)] string PrintToPdfStream(); - /// Control PDF toolbar items visibility. - [DispId(203)] int HiddenPdfToolbarItems { get; set; } - /// Custom Download Path. - [DispId(204)] void SetDownloadPath(string path); - /// Enable/Disable default Download UI. - [DispId(205)] bool IsDownloadUIEnabled { get; set; } - /// Decode a Base64 string to raw binary data (byte array). - [DispId(206)] byte[] DecodeB64ToBinary(string base64Text); - - /// Capture preview as Base64 string. - [DispId(216)] string CapturePreviewAsBase64(string format); - - /// Cancels downloads. If uri is null or empty, cancels all active downloads. - [DispId(210)] void CancelDownloads(string uri = ""); - /// Returns a pipe-separated string of all active download URIs. - [DispId(214)] string ActiveDownloadsList { get; } - /// Set to true to suppress the default download UI, typically set during the OnDownloadStarting event. - [DispId(211)] bool IsDownloadHandled { get; set; } - /// Enable/Disable Zoom control (Ctrl+Wheel, shortcuts). - [DispId(212)] bool IsZoomControlEnabled { get; set; } - /// Enable/Disable the built-in browser error page. - [DispId(213)] bool IsBuiltInErrorPageEnabled { get; set; } - /// Encodes raw binary data (byte array) to a Base64 string. - [DispId(219)] string EncodeBinaryToB64(object binaryData); - /// Maps a virtual host name to a local folder path. - [DispId(218)] void SetVirtualHostNameToFolderMapping(string hostName, string folderPath, int accessKind); - - /// Gets the internal window handle of the WebView2 control. - [DispId(222)] string BrowserWindowHandle { get; } - - /// Gets the parent window handle provided during initialization. - [DispId(229)] string ParentWindowHandle { get; } - - /// A comma-separated list of Virtual Key codes to block (e.g., "116,123"). - [DispId(223)] string BlockedVirtualKeys { get; set; } - /// Gets the version of the DLL. - [DispId(224)] string Version { get; } - - /// Gets or sets the folder path where WebView2 failure reports (crash dumps) are stored. - [DispId(226)] string FailureReportFolderPath { get; set; } - } - - // --- 3. THE MANAGER CLASS --- - /// - /// The Main Manager Class for WebView2 Interaction. - /// - [Guid("A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D")] - [ComSourceInterfaces(typeof(IWebViewEvents))] - [ClassInterface(ClassInterfaceType.None)] - [ComVisible(true)] - [ProgId("NetWebView2.Manager")] - public class WebViewManager : IWebViewActions - { - #region 1. PRIVATE FIELDS - private readonly WebView2 _webView; - private readonly WebViewBridge _bridge; - private readonly JsonParser _internalParser = new JsonParser(); - - private bool _isAdBlockActive = false; - private readonly List _blockList = new List(); - private const string StyleId = "autoit-injected-style"; - private bool _areBrowserPopupsAllowed = false; - private bool _contextMenuEnabled = true; - private bool _autoResizeEnabled = false; - private bool _customMenuEnabled = false; - private string _additionalBrowserArguments = ""; - private string _customDownloadPath = ""; - private bool _isDownloadUIEnabled = true; - private bool _httpStatusCodeEventsEnabled = true; - private bool _httpStatusCodeDocumentOnly = true; - private bool _isDownloadHandledOverride = false; - private bool _isZoomControlEnabled = true; - private string _failureReportFolderPath = ""; - - private int _offsetX = 0; - private int _offsetY = 0; - private int _marginRight = 0; - private int _marginBottom = 0; - private IntPtr _parentHandle = IntPtr.Zero; - private ParentWindowSubclass _parentSubclass; - - private string _lastCssRegistrationId = ""; - private System.Threading.SynchronizationContext _uiContext; - - // Keeps active downloads keyed by their URI - private readonly Dictionary _activeDownloads = new Dictionary(); - - #endregion - - #region 2. DELEGATES & EVENTS - // v2.0.0: All delegates now include sender and parentHandle parameters - - /// Delegate for message events. - public delegate void OnMessageReceivedDelegate(object sender, string parentHandle, string message); - public delegate void OnNavigationStartingDelegate(object sender, string parentHandle, string url); - public delegate void OnNavigationCompletedDelegate(object sender, string parentHandle, bool isSuccess, int webErrorStatus); - public delegate void OnTitleChangedDelegate(object sender, string parentHandle, string newTitle); - public delegate void OnURLChangedDelegate(object sender, string parentHandle, string newUrl); - public delegate void OnBrowserGotFocusDelegate(object sender, string parentHandle, int reason); - public delegate void OnBrowserLostFocusDelegate(object sender, string parentHandle, int reason); - public delegate void OnWebResourceResponseReceivedDelegate(object sender, string parentHandle, int statusCode, string reasonPhrase, string requestUrl); - public delegate void OnContextMenuDelegate(object sender, string parentHandle, string menuData); - public delegate void OnZoomChangedDelegate(object sender, string parentHandle, double factor); - public delegate void OnContextMenuRequestedDelegate(object sender, string parentHandle, string linkUrl, int x, int y, string selectionText); - public delegate void OnDownloadStartingDelegate(object sender, string parentHandle, string uri, string defaultPath); - public delegate void OnDownloadStateChangedDelegate(object sender, string parentHandle, string state, string uri, long totalBytes, long receivedBytes); - public delegate void OnAcceleratorKeyPressedDelegate(object sender, string parentHandle, object args); - public delegate void OnProcessFailedDelegate(object sender, string parentHandle, object args); - public delegate void OnBasicAuthenticationRequestedDelegate(object sender, string parentHandle, object args); - - /// - /// Normalizes an IntPtr handle as an AutoIt Advanced Window Description string. - /// Format: [HANDLE:0x00160678] - /// This ensures AutoIt recognizes it natively in all window functions. - /// - private string FormatHandle(IntPtr handle) - { - if (handle == IntPtr.Zero) return "[HANDLE:0x" + IntPtr.Zero.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; - return "[HANDLE:0x" + handle.ToString("X").PadLeft(IntPtr.Size * 2, '0') + "]"; - } - - // Event declarations - /// Event fired when a message is received. - public event OnMessageReceivedDelegate OnMessageReceived; - /// Event fired when navigation starts. - public event OnNavigationStartingDelegate OnNavigationStarting; - /// Event fired when navigation is completed. - public event OnNavigationCompletedDelegate OnNavigationCompleted; - /// Event fired when the document title changes. - public event OnTitleChangedDelegate OnTitleChanged; - /// Event fired when the URL changes. - public event OnURLChangedDelegate OnURLChanged; - /// Event fired when a web resource response is received. - public event OnWebResourceResponseReceivedDelegate OnWebResourceResponseReceived; - /// Event fired when a download is starting. - public event OnDownloadStartingDelegate OnDownloadStarting; - /// Event fired when a download state changed. - public event OnDownloadStateChangedDelegate OnDownloadStateChanged; - /// Event fired when a custom context menu is requested. - public event OnContextMenuDelegate OnContextMenu; - /// Event fired when a simplified context menu is requested. - public event OnContextMenuRequestedDelegate OnContextMenuRequested; - /// Event fired when zoom factor changes. - public event OnZoomChangedDelegate OnZoomChanged; - /// Event fired when browser gets focus. - public event OnBrowserGotFocusDelegate OnBrowserGotFocus; - /// Event fired when browser loses focus. - public event OnBrowserLostFocusDelegate OnBrowserLostFocus; - - /// Event fired when an accelerator key is pressed. - public event OnAcceleratorKeyPressedDelegate OnAcceleratorKeyPressed; - - /// Event fired when a WebView2 process fails. - public event OnProcessFailedDelegate OnProcessFailed; - - /// Event fired when basic authentication is requested. - public event OnBasicAuthenticationRequestedDelegate OnBasicAuthenticationRequested; - #endregion - - #region 3. NATIVE METHODS & HELPERS - [DllImport("user32.dll")] - private static extern bool GetClientRect(IntPtr hWnd, out Rect lpRect); - - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); - - [DllImport("user32.dll")] - private static extern IntPtr GetFocus(); - - [DllImport("user32.dll")] - private static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd); - - /// - /// A simple Rectangle struct. - /// - [StructLayout(LayoutKind.Sequential)] - public struct Rect { - /// Left position - public int Left; - /// Top position - public int Top; - /// Right position - public int Right; - /// Bottom position - public int Bottom; - } - #endregion - - #region 4. CONSTRUCTOR - /// - /// Initializes a new instance of the WebViewManager class. - /// - public WebViewManager() - { - _webView = new WebView2(); - _bridge = new WebViewBridge(); - } - #endregion - - #region 5. BRIDGE & STATUS - /// - /// Get the Bridge object for AutoIt interaction. - /// - public IBridgeActions GetBridge() - { - return _bridge; - } - - /// - /// Check if WebView2 is initialized and ready. - /// - public bool IsReady() => _webView?.CoreWebView2 != null; - - /// Allows internal classes to push debug/info messages to AutoIt. - internal void InternalPushMessage(string message) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), message); - } - - /// - /// Gets the internal window handle of the WebView2 control. - /// - public string BrowserWindowHandle => FormatHandle(_webView?.Handle ?? IntPtr.Zero); - - /// - /// Gets the parent window handle provided during initialization. - /// - public string ParentWindowHandle => FormatHandle(_parentHandle); - - private string _blockedVirtualKeys = ""; - /// - /// A comma-separated list of Virtual Key codes to be blocked synchronously (e.g., "116,123"). - /// This ensures blocking (Handled = true) works without COM timing issues. - /// - public string BlockedVirtualKeys - { - get => _blockedVirtualKeys; - set => _blockedVirtualKeys = value ?? ""; - } - - /// Gets the version of the DLL. - public string Version => AssemblyUtils.GetVersion(); - - /// - /// Gets or sets the folder path where WebView2 failure reports (crash dumps) are stored. - /// If not set before Initialize, defaults to 'Crashes' subfolder in userDataFolder. - /// - public string FailureReportFolderPath - { - get => _failureReportFolderPath; - set => _failureReportFolderPath = value ?? ""; - } - #endregion - - #region 6. CORE INITIALIZATION - /// - /// Initializes the WebView2 control within the specified parent window handle. - /// Supports browser extensions and custom user data folders. - /// - public async void Initialize(object parentHandle, string userDataFolder, int x = 0, int y = 0, int width = 0, int height = 0) - { - try - { - // Capture the UI thread context for Law #1 compliance - _uiContext = System.Threading.SynchronizationContext.Current; - if (_uiContext != null) _bridge.SetSyncContext(_uiContext); - - // Convert the incoming handle from AutoIt (passed as object/pointer) - long rawHandleValue = Convert.ToInt64(parentHandle); - _parentHandle = new IntPtr(rawHandleValue); - - // v2.0.0: "Seal" the bridge context immediately after handle conversion - _bridge.SetParentContext(this, _parentHandle); - - // Store offsets for Smart Resize - _offsetX = x; - _offsetY = y; - - // Calculate Margins based on Parent's size at initialization - int calcWidth = width; - int calcHeight = height; - - if (GetClientRect(_parentHandle, out Rect parentRect)) - { - int pWidth = parentRect.Right - parentRect.Left; - int pHeight = parentRect.Bottom - parentRect.Top; - - // If user provides 0 (or less), we assume they want to fill the parent - if (width <= 0) - { - calcWidth = Math.Max(10, pWidth - x); - _marginRight = 0; - } - else - { - _marginRight = Math.Max(0, (pWidth - x) - width); - } - - if (height <= 0) - { - calcHeight = Math.Max(10, pHeight - y); - _marginBottom = 0; - } - else - { - _marginBottom = Math.Max(0, (pHeight - y) - height); - } - } - else - { - _marginRight = 0; - _marginBottom = 0; - } - - // Initialize the Subclass helper for Smart Resize - _parentSubclass = new ParentWindowSubclass(() => PerformSmartResize()); - - // Manage User Data Folder (User Profile) - // If no path is provided, create a default one in the application directory - if (string.IsNullOrEmpty(userDataFolder)) - { - userDataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebView2_Default_Profile"); - } - - // Create the directory if it doesn't exist - if (!Directory.Exists(userDataFolder)) Directory.CreateDirectory(userDataFolder); - - // UI Setup on the main UI thread - InvokeOnUiThread(() => { - _webView.Location = new Point(x, y); - _webView.Size = new Size(calcWidth, calcHeight); - // Attach the WebView to the AutoIt window/container - SetParent(_webView.Handle, _parentHandle); - _webView.Visible = false; - }); - - // --- NEW: EXTENSION & FAILURE HANDLING SETUP --- - // We must enable extensions and configure crash dumps in the Environment Options BEFORE creation - var options = new CoreWebView2EnvironmentOptions { AreBrowserExtensionsEnabled = true }; - - // Handle FailureReportFolderPath logic (Law #5 - Practicality) - if (string.IsNullOrEmpty(_failureReportFolderPath)) - { - _failureReportFolderPath = Path.Combine(userDataFolder, "Crashes"); - } - - if (!Directory.Exists(_failureReportFolderPath)) - { - Directory.CreateDirectory(_failureReportFolderPath); - } - - // Fallback: Use environment variable because FailureReportFolderPath is missing from Options in this SDK version - Environment.SetEnvironmentVariable("WEBVIEW2_FAILURE_REPORT_FOLDER_PATH", _failureReportFolderPath); - - if (!string.IsNullOrEmpty(_additionalBrowserArguments)) options.AdditionalBrowserArguments = _additionalBrowserArguments; - - // Initialize the Environment with the Custom Data Folder and our Options - // Note: The second parameter is the userDataFolder, the third is the options - var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder, options); - - // Wait for the CoreWebView2 engine to be ready - await _webView.EnsureCoreWebView2Async(env); - - // Apply settings and register events - ConfigureSettings(); - RegisterEvents(); - - // Add default context menu bridge helper - await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(@" - window.dispatchEventToAutoIt = function(lnk, x, y, sel) { - window.chrome.webview.postMessage('CONTEXT_MENU_REQUEST|' + (lnk||'') + '|' + (x||0) + '|' + (y||0) + '|' + (sel||'')); - }; - "); - - // Make the browser visible once everything is loaded - InvokeOnUiThread(() => _webView.Visible = true); - - // Notify AutoIt that the browser is ready (Standard style without ID) - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "INIT_READY"); - } - catch (Exception ex) - { - // Send error details back to AutoIt if initialization fails - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|INIT_FAILED|" + ex.Message); - } - } - - /// - /// Adds a browser extension from an unpacked folder (containing manifest.json). - /// This method should be called after receiving the "INIT_READY" message. - /// - /// The full path to the unpacked extension folder. - public void AddExtension(string extensionPath) - { - InvokeOnUiThread(async () => - { - // Ensure the WebView and Profile are ready - if (_webView?.CoreWebView2?.Profile == null) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION|WebView2 Profile not ready."); - return; - } - - // Validate the extension path - if (!System.IO.Directory.Exists(extensionPath)) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION|Path not found: " + extensionPath); - return; - } - - try - { - // Add the browser extension - var ext = await _webView.CoreWebView2.Profile.AddBrowserExtensionAsync(extensionPath); - - // Notify AutoIt that the extension has been loaded successfully - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "EXTENSION_LOADED|" + ext.Id); - } - catch (Exception ex) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION_FAILED|" + ex.Message); - } - }); - } - - /// - /// Removes a browser extension by its ID. - /// - /// The ID of the extension to remove. - public void RemoveExtension(string extensionId) - { - InvokeOnUiThread(async () => - { - if (_webView?.CoreWebView2?.Profile == null) return; - - try - { - // Retrieve all installed extensions - var extensions = await _webView.CoreWebView2.Profile.GetBrowserExtensionsAsync(); - - foreach (var ext in extensions) - { - if (ext.Id == extensionId) - { - await ext.RemoveAsync(); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "EXTENSION_REMOVED|" + extensionId); - return; - } - } - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|EXTENSION_NOT_FOUND|" + extensionId); - } - catch (Exception ex) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|REMOVE_EXTENSION_FAILED|" + ex.Message); - } - }); - } - #endregion - - #region 7. SETTINGS & CONFIGURATION - /// - /// Configure WebView2 settings. - /// - private void ConfigureSettings() - { - var settings = _webView.CoreWebView2.Settings; - settings.IsWebMessageEnabled = true; // Enable Web Messages - settings.AreDevToolsEnabled = true; // Enable DevTools by default - settings.AreDefaultContextMenusEnabled = true; // Keep TRUE to ensure the event fires - settings.IsZoomControlEnabled = _isZoomControlEnabled; // Apply custom zoom setting - _webView.DefaultBackgroundColor = Color.Transparent; - } - - /// - /// Disable certain browser features for a controlled environment. - /// - public void DisableBrowserFeatures() - { - LockWebView(); - } - #endregion - - #region 8. EVENT REGISTRATION - /// - /// Register event handlers for WebView2 events. - /// - private void RegisterEvents() - { - if (_webView?.CoreWebView2 == null) return; - - // --- RESTORED LOGIC --- - - // Context Menu Event - _webView.CoreWebView2.ContextMenuRequested += async (sender, args) => - { - // 1. Browser Default Menu Strategy - // Handled=true means we block the browser's menu. - // Handled=false means we let the browser show its own menu. - args.Handled = !_contextMenuEnabled; - - try - { - // 2. Data Retrieval (Async parts first) - // Check if the element or any of its parents is TABLE - string script = "document.elementFromPoint(" + args.Location.X + "," + args.Location.Y + ").closest('table') ? 'TABLE' : document.elementFromPoint(" + args.Location.X + "," + args.Location.Y + ").tagName"; - string tagName = await _webView.CoreWebView2.ExecuteScriptAsync(script); - tagName = tagName?.Trim('\"') ?? "UNKNOWN"; - - // Extraction of Context info - string k = args.ContextMenuTarget.Kind.ToString(); - string src = args.ContextMenuTarget.HasSourceUri ? args.ContextMenuTarget.SourceUri : ""; - string lnk = args.ContextMenuTarget.HasLinkUri ? args.ContextMenuTarget.LinkUri : ""; - string sel = args.ContextMenuTarget.HasSelection ? args.ContextMenuTarget.SelectionText : ""; - - // --- CASE A: Parameter-based Event (v1.4.2 Priority) --- - // This is DispId 190 for AutoIt compatibility - _webView.BeginInvoke(new Action(() => { - OnContextMenuRequested?.Invoke(this, FormatHandle(_parentHandle), lnk, args.Location.X, args.Location.Y, sel); - })); - - // --- CASE B: Legacy JSON-based Event (v1.4.1 compatibility) --- - // Build JSON - Escaping for safety - string cleanSrc = src.Replace("\"", "\\\""); - string cleanLnk = lnk.Replace("\"", "\\\""); - string cleanSel = sel.Replace("\"", "\\\"").Replace("\r", "").Replace("\n", "\\n"); - - string json = "{" + - "\"x\":" + args.Location.X + "," + - "\"y\":" + args.Location.Y + "," + - "\"kind\":\"" + k + "\"," + - "\"tagName\":\"" + tagName + "\"," + - "\"src\":\"" + cleanSrc + "\"," + - "\"link\":\"" + cleanLnk + "\"," + - "\"selection\":\"" + cleanSel + "\"" + - "}"; - - _webView.BeginInvoke(new Action(() => { - OnContextMenu?.Invoke(this, FormatHandle(_parentHandle), "JSON:" + json); - })); - } - catch (Exception ex) - { - Debug.WriteLine("ContextMenu Error: " + ex.Message); - } - }; - - // Ad Blocking - _webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); - _webView.CoreWebView2.WebResourceRequested += (s, e) => - { - // Track if this is a Document request for HttpStatusCode filtering - if (_httpStatusCodeEventsEnabled && _httpStatusCodeDocumentOnly && e.ResourceContext == CoreWebView2WebResourceContext.Document) - { - e.Request.Headers.SetHeader("X-NetWebView2-IsDoc", "1"); - } - - if (!_isAdBlockActive) return; - string uri = e.Request.Uri.ToLower(); - foreach (var domain in _blockList) - { - if (uri.Contains(domain)) - { - e.Response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(null, 403, "Forbidden", ""); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"BLOCKED_AD|{uri}"); - return; - } - } - }; - - _webView.CoreWebView2.NewWindowRequested += (s, e) => - { - if (!_areBrowserPopupsAllowed) { - e.Handled = true; - if (!string.IsNullOrEmpty(e.Uri)) - { - string targetUri = e.Uri; - _webView.BeginInvoke(new Action(() => { - if (_webView?.CoreWebView2 != null) - { - _webView.CoreWebView2.Navigate(targetUri); - } - })); - } - } - }; - - // --- END RESTORED LOGIC --- - - // Navigation & Content Events - _webView.CoreWebView2.NavigationStarting += (s, e) => { - OnNavigationStarting?.Invoke(this, FormatHandle(_parentHandle), e.Uri); - - string url = e.Uri; - - // Notify AutoIt about navigation start - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_STARTING|" + url); - }; - - _webView.CoreWebView2.NavigationCompleted += (s, e) => { - OnNavigationCompleted?.Invoke(this, FormatHandle(_parentHandle), e.IsSuccess, (int)e.WebErrorStatus); - - // Keep the old OnMessageReceived for compatibility (optional) - if (e.IsSuccess) - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_COMPLETED"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "TITLE_CHANGED|" + _webView.CoreWebView2.DocumentTitle); - } - else - { - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "NAV_ERROR|" + e.WebErrorStatus); - } - }; - - _webView.CoreWebView2.SourceChanged += (s, e) => { - OnURLChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.CoreWebView2.Source); - }; - - // Source Changed Event - _webView.CoreWebView2.SourceChanged += (s, e) => OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "URL_CHANGED|" + _webView.Source); - - _webView.CoreWebView2.DocumentTitleChanged += (s, e) => { - OnTitleChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.CoreWebView2.DocumentTitle); - }; - - // Zoom Factor Changed Event - _webView.ZoomFactorChanged += (s, e) => { - OnZoomChanged?.Invoke(this, FormatHandle(_parentHandle), _webView.ZoomFactor); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ZOOM_CHANGED|" + _webView.ZoomFactor); - }; - - // Process Failed Event - _webView.CoreWebView2.ProcessFailed += (s, e) => { - OnProcessFailed?.Invoke(this, FormatHandle(_parentHandle), new ProcessFailedEventArgsWrapper(e)); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PROCESS_FAILED|" + e.ProcessFailedKind + "|" + e.Reason); - }; - - // Basic Authentication Requested Event - _webView.CoreWebView2.BasicAuthenticationRequested += (s, e) => { - OnBasicAuthenticationRequested?.Invoke(this, FormatHandle(_parentHandle), new BasicAuthenticationRequestedEventArgsWrapper(e)); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BASIC_AUTH_REQUESTED|" + e.Uri); - }; - - // Communication Event <---> AutoIt <---> JavaScript - //_webView.CoreWebView2.WebMessageReceived += (s, e) => { - // OnMessageReceived?.Invoke(e.TryGetWebMessageAsString()); - //}; - - // --- THE FIX IS HERE --- - // Instead of sending the WebMessage to OnMessageReceived (Manager), - // we send it to Bridge so that Bridge_OnMessageReceived in AutoIt can catch it. - _webView.CoreWebView2.WebMessageReceived += (s, e) => - { - string message = e.TryGetWebMessageAsString(); - - // Routing: Check if this is a JS-triggered context menu request - if (message != null && message.StartsWith("CONTEXT_MENU_REQUEST|")) - { - var parts = message.Split('|'); - if (parts.Length >= 5) - { - string lnk = parts[1]; - int x = int.TryParse(parts[2], out int px) ? px : 0; - int y = int.TryParse(parts[3], out int py) ? py : 0; - string sel = parts[4]; - - _webView.BeginInvoke(new Action(() => { - OnContextMenuRequested?.Invoke(this, FormatHandle(_parentHandle), lnk, x, y, sel); - })); - return; // Handled - } - } - - _bridge.RaiseMessage(message); // Send message to the correct channel - }; - - // Focus Events (Native Bridge) - _webView.GotFocus += (s, e) => { - OnBrowserGotFocus?.Invoke(this, FormatHandle(_parentHandle), 0); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BROWSER_GOT_FOCUS|"); - }; - - _webView.LostFocus += (s, e) => { - _webView.BeginInvoke(new Action(() => { - // Use Native API to see which HWND REALLY has the focus - IntPtr focusedHandle = GetFocus(); - - // If focusedHandle is NOT _webView.Handle - // AND is NOT a child (IsChild) of _webView.Handle, then we truly lost focus. - if (focusedHandle != _webView.Handle && !IsChild(_webView.Handle, focusedHandle)) { - OnBrowserLostFocus?.Invoke(this, FormatHandle(_parentHandle), 0); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "BROWSER_LOST_FOCUS|"); - } - })); - }; - - // Communication Event <---> AutoIt <---> JavaScript - _webView.CoreWebView2.AddHostObjectToScript("autoit", _bridge); - - // HttpStatusCode Tracking - _webView.CoreWebView2.WebResourceResponseReceived += (s, e) => - { - if (!_httpStatusCodeEventsEnabled) return; - - if (_httpStatusCodeDocumentOnly) - { - bool isDoc = e.Request.Headers.Contains("X-NetWebView2-IsDoc"); - if (!isDoc) return; - } - - if (e.Response != null) - { - int statusCode = e.Response.StatusCode; - string reasonPhrase = GetReasonPhrase(statusCode, e.Response.ReasonPhrase); - string requestUrl = e.Request.Uri; - _webView.BeginInvoke(new Action(() => { - OnWebResourceResponseReceived?.Invoke(this, FormatHandle(_parentHandle), statusCode, reasonPhrase, requestUrl); - })); - } - }; - - _webView.CoreWebView2.DownloadStarting += async (s, e) => - { - // Law #2: Use deferral for non-blocking wait - var deferral = e.GetDeferral(); - string currentUri = e.DownloadOperation.Uri; - - // 1. Apply Custom Download Path if it exists - if (!string.IsNullOrEmpty(_customDownloadPath)) - { - string fileName = System.IO.Path.GetFileName(e.ResultFilePath); - string fullPath = System.IO.Path.Combine(_customDownloadPath, fileName); - e.ResultFilePath = fullPath; - } - - // Add to list so it can be canceled later - _activeDownloads[currentUri] = e.DownloadOperation; - - // Reset per-download overrides - _isDownloadHandledOverride = false; // Default to FALSE (Let Edge handle it) - - // Fire the event to AutoIt - OnDownloadStarting?.Invoke(this, FormatHandle(_parentHandle), e.DownloadOperation.Uri, e.ResultFilePath); // Using the updated path - - // --- ROBUST ASYNC WAIT FOR AUTOIT (Law #2) --- - var sw = Stopwatch.StartNew(); - while (sw.ElapsedMilliseconds < 600 && !_isDownloadHandledOverride) - { - // Non-blocking wait that allows UI to stay responsive without DoEvents() - await Task.Delay(10); - } - - if (_isDownloadHandledOverride) - { - e.Cancel = true; - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_CANCELLED|{e.DownloadOperation.Uri}"); - deferral.Complete(); - return; - } - - // --- STANDARD MODE: EDGE --- - e.Handled = !_isDownloadUIEnabled; - - // Using e.ResultFilePath which now contains the correct path - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_STARTING|{e.DownloadOperation.Uri}|{e.ResultFilePath}"); - - e.DownloadOperation.StateChanged += (sender, args) => - { - long totalBytes = (long)(e.DownloadOperation.TotalBytesToReceive ?? 0); - long receivedBytes = (long)e.DownloadOperation.BytesReceived; - - // Update status (InProgress, Completed, etc.) - OnDownloadStateChanged?.Invoke(this, FormatHandle(_parentHandle), e.DownloadOperation.State.ToString(), e.DownloadOperation.Uri, totalBytes, receivedBytes); - - // DOWNLOAD_CANCELLED - if (e.DownloadOperation.State == Microsoft.Web.WebView2.Core.CoreWebView2DownloadState.Interrupted) - { - // Send DOWNLOAD_CANCELLED|URL|Reason (e.g. UserCanceled, NetworkFailed, etc.) - var reason = e.DownloadOperation.InterruptReason; - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"DOWNLOAD_CANCELLED|{currentUri}|{reason}"); - } - - // Clear list when finished (for any reason) - if (e.DownloadOperation.State != Microsoft.Web.WebView2.Core.CoreWebView2DownloadState.InProgress) - { - _activeDownloads.Remove(currentUri); - } - }; - - // --- Progress Tracking --- - int lastPercent = -1; - e.DownloadOperation.BytesReceivedChanged += (sender, args) => - { - long total = (long)(e.DownloadOperation.TotalBytesToReceive ?? 0); - long received = (long)e.DownloadOperation.BytesReceived; - - if (total > 0) - { - int currentPercent = (int)((received * 100) / total); - - // We only send an update to AutoIt if the percentage has changed - // so as not to "clog" the script with thousands of messages. - if (currentPercent > lastPercent) - { - lastPercent = currentPercent; - OnDownloadStateChanged?.Invoke(this, FormatHandle(_parentHandle), "InProgress", e.DownloadOperation.Uri, total, received); - } - } - }; - - deferral.Complete(); - }; - - // Accelerator Key Pressed Event - // Restoration: Using reflection to access internal CoreWebView2Controller (WinForms SDK limitation) - try - { - var controllerField = typeof(WebView2).GetField("_coreWebView2Controller", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - if (controllerField != null) - { - if (controllerField.GetValue(_webView) is CoreWebView2Controller controller) - { - controller.AcceleratorKeyPressed += (s, e) => - { - var eventArgs = new WebView2AcceleratorKeyPressedEventArgs(e, this); - - // --- PRE-EMPTIVE BLOCKING --- - // Check if the key is in the blocked list - if (!string.IsNullOrEmpty(_blockedVirtualKeys)) - { - string vKeyStr = e.VirtualKey.ToString(); - if (_blockedVirtualKeys.Split(',').Any(k => k.Trim() == vKeyStr)) - { - e.Handled = true; - eventArgs.Block(); // Sync internal state - } - } - - // Dispatch to AutoIt - OnAcceleratorKeyPressed?.Invoke(this, FormatHandle(_parentHandle), eventArgs); - - // Apply state back (support for any synchronous COM result if lucky) - if (eventArgs.IsCurrentlyHandled) e.Handled = true; - }; - } - } - } - catch (Exception ex) - { - Debug.WriteLine("AcceleratorKey Reflection Error: " + ex.Message); - } - } - #endregion - - #region 9. PUBLIC API: NAVIGATION & CONTROL - /// Navigate to a specified URL. - public void Navigate(string url) => InvokeOnUiThread(() => _webView.CoreWebView2?.Navigate(url)); - - /// Navigate to a string containing HTML content. - public void NavigateToString(string htmlContent) - { - _webView.Invoke(new Action(async () => { - int attempts = 0; - while (_webView.CoreWebView2 == null && attempts < 20) { await Task.Delay(50); attempts++; } - _webView.CoreWebView2?.NavigateToString(htmlContent); - })); - } - - /// Reload the current page. - public void Reload() => InvokeOnUiThread(() => _webView.CoreWebView2?.Reload()); - - /// Stops any ongoing navigation or loading. - public void Stop() => InvokeOnUiThread(() => _webView.CoreWebView2?.Stop()); - - /// Navigate back in history. - public void GoBack() => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null && _webView.CoreWebView2.CanGoBack) _webView.CoreWebView2.GoBack(); }); - - /// Navigate forward in history. - public void GoForward() => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null && _webView.CoreWebView2.CanGoForward) _webView.CoreWebView2.GoForward(); }); - - /// Check if it's possible to navigate back. - public bool GetCanGoBack() => _webView?.CoreWebView2?.CanGoBack ?? false; - - /// Check if it's possible to navigate forward. - public bool GetCanGoForward() => _webView?.CoreWebView2?.CanGoForward ?? false; - - /// Adds a script that executes on every page load (Permanent Injection). Returns the ScriptId. - public string AddInitializationScript(string script) - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - return RunOnUiThread(() => - { - try - { - var task = _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(script); - string scriptId = WaitAndGetResult(task); - - // Execute immediately on current page for full lifecycle effect - _webView.CoreWebView2.ExecuteScriptAsync(script); - - return scriptId; - } - catch (Exception ex) - { - return "ERROR: " + ex.Message; - } - }); - } - - /// Removes a script previously added via AddInitializationScript. - public void RemoveInitializationScript(string scriptId) - { - InvokeOnUiThread(() => - { - if (_webView?.CoreWebView2 != null && !string.IsNullOrEmpty(scriptId)) - { - _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(scriptId); - } - }); - } - - /// Controls the context menu behavior. - public void SetContextMenuEnabled(bool enabled) - { - _contextMenuEnabled = enabled; - InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null) _webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true; }); - } - #endregion - - #region 10. PUBLIC API: DATA EXTRACTION - /// - /// Retrieve the full HTML source of the current page. - /// - public async void GetHtmlSource() - { - if (_webView?.CoreWebView2 == null) return; - string html = await _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "HTML_SOURCE|" + CleanJsString(html)); - } - - /// - /// Retrieve the currently selected text on the page. - /// - public async void GetSelectedText() - { - if (_webView?.CoreWebView2 == null) return; - string selectedText = await _webView.CoreWebView2.ExecuteScriptAsync("window.getSelection().toString()"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "SELECTED_TEXT|" + CleanJsString(selectedText)); - } - - - /// - /// Parse JSON into the internal parser. - /// - public bool ParseJsonToInternal(string json) => _internalParser.Parse(json?.Trim()); - - /// - /// Get a value from the internal JSON parser. - /// - public string GetInternalJsonValue(string path) => _internalParser.GetTokenValue(path); - - /// - /// Binds the internal JSON data to a browser variable. - /// - public bool BindJsonToBrowser(string variableName) - { - try - { - if (_webView?.CoreWebView2 == null) return false; - - // 2. Get minified JSON from internal parser - string jsonData = _internalParser.GetMinifiedJson(); - if (string.IsNullOrEmpty(jsonData)) - { - jsonData = "{}"; // Fallback to empty object - } - - // 3. Escape for JS safety - string safeJson = jsonData.Replace("\\", "\\\\").Replace("'", "\\'"); - - // 3. Build script with JS try-catch and console logging - string script = $@" - try {{ - window.{variableName} = JSON.parse('{safeJson}'); - console.log('NetWebView2Lib: Data bound to window.{variableName}'); - true; - }} catch (e) {{ - console.error('NetWebView2Lib Bind Error:', e); - false; - }}"; - - // 4. Execute script - _webView.CoreWebView2.ExecuteScriptAsync(script); - return true; - } - catch (Exception ex) - { - Debug.WriteLine("BindJsonToBrowser Error: " + ex.Message); - return false; - } - } - - /// - /// Syncs JSON data to internal parser and optionally binds it to a browser variable. - /// - public void SyncInternalData(string json, string bindToVariableName = "") - { - if (ParseJsonToInternal(json)) - { - if (!string.IsNullOrEmpty(bindToVariableName)) - { - BindJsonToBrowser(bindToVariableName); - } - } - } - - /// - /// Retrieves the entire text content (innerText) of the document and sends it back to AutoIt. - /// - public async void GetInnerText() - { - if (_webView?.CoreWebView2 == null) return; - try { - string html = await _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.innerText"); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "INNER_TEXT|" + CleanJsString(html)); - } catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "ERROR|INNER_TEXT_FAILED: " + ex.Message); } - } - #endregion - - #region 11. PUBLIC API: UI & INTERACTION - /// - /// Execute arbitrary JavaScript code. - /// - public void ExecuteScript(string script) - { - if (_webView?.CoreWebView2 != null) - _webView.Invoke(new Action(() => _webView.CoreWebView2.ExecuteScriptAsync(script))); - } - - /// - /// Execute JavaScript on the current page immediately. - /// - /// The JavaScript code to be executed. - public async void ExecuteScriptOnPage(string script) - { - if (_webView?.CoreWebView2 == null) return; - await _webView.CoreWebView2.ExecuteScriptAsync(script); - } - - /// - /// Executes JavaScript and returns the result synchronously using a Message Pump. - /// - public string ExecuteScriptWithResult(string script) - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - try - { - var task = _webView.CoreWebView2.ExecuteScriptAsync(script); - var start = DateTime.Now; - while (!task.IsCompleted) - { - System.Windows.Forms.Application.DoEvents(); - if ((DateTime.Now - start).TotalSeconds > 5) return "ERROR: Script Timeout"; - System.Threading.Thread.Sleep(1); - } - string result = task.Result; - if (result == "null" || result == null) return string.Empty; - if (result.StartsWith("\"") && result.EndsWith("\"")) - { - result = result.Substring(1, result.Length - 2); - result = System.Text.RegularExpressions.Regex.Unescape(result); - } - return result; - } - catch (Exception ex) { return "ERROR: " + ex.Message; } - } - - /// - /// Inject CSS code into the current page. - /// - public async void InjectCss(string cssCode) - { - string js = $"(function() {{ let style = document.getElementById('{StyleId}'); if (!style) {{ style = document.createElement('style'); style.id = '{StyleId}'; document.head.appendChild(style); }} style.innerHTML = `{cssCode.Replace("`", "\\` text-decoration")}`; }})();"; - ExecuteScript(js); - if (!string.IsNullOrEmpty(_lastCssRegistrationId)) _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(_lastCssRegistrationId); - _lastCssRegistrationId = await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(js); - } - - /// - /// Remove previously injected CSS. - /// - public void ClearInjectedCss() - { - if (!string.IsNullOrEmpty(_lastCssRegistrationId)) - { - _webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(_lastCssRegistrationId); - _lastCssRegistrationId = ""; - } - ExecuteScript($"(function() {{ let style = document.getElementById('{StyleId}'); if (style) style.remove(); }})();"); - } - - /// - /// Toggle audit highlights on/off. - /// - public void ToggleAuditHighlights(bool enable) - { - if (enable) InjectCss("img, h1, h2, h3, table, a { outline: 3px solid #FF6A00 !important; outline-offset: -3px !important; }"); - else ClearInjectedCss(); - } - - /// - /// Capture a screenshot (preview) of the current view. - /// - public async void CapturePreview(string filePath, string format) - { - if (_webView?.CoreWebView2 == null) return; - CoreWebView2CapturePreviewImageFormat imageFormat = format.ToLower().Contains("jpg") ? CoreWebView2CapturePreviewImageFormat.Jpeg : CoreWebView2CapturePreviewImageFormat.Png; - try - { - using (var fileStream = File.Create(filePath)) await _webView.CoreWebView2.CapturePreviewAsync(imageFormat, fileStream); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "CAPTURE_SUCCESS|" + filePath); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "CAPTURE_ERROR|" + ex.Message); } - } - - /// - /// Shows the print UI dialog. - /// - public void ShowPrintUI() => _webView?.CoreWebView2.ShowPrintUI(); - - /// - /// Initiate the native Print dialog via JS. - /// - public void Print() - { - InvokeOnUiThread(async () => { - if (_webView?.CoreWebView2 != null) - { - try { await _webView.CoreWebView2.ExecuteScriptAsync("window.print();"); } - catch(Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PRINT_ERROR|" + ex.Message); } - } - }); - } - - /// - /// Unified master switch to lock or unlock the WebView features. - /// - /// True to lock down, False to unlock. - public void SetLockState(bool lockState) - { - InvokeOnUiThread(() => { - if (_webView?.CoreWebView2 != null) - { - var s = _webView.CoreWebView2.Settings; - s.AreDefaultContextMenusEnabled = !lockState; - s.AreDevToolsEnabled = !lockState; - s.IsZoomControlEnabled = !lockState; - s.IsBuiltInErrorPageEnabled = !lockState; - s.AreDefaultScriptDialogsEnabled = !lockState; - s.AreBrowserAcceleratorKeysEnabled = !lockState; - s.IsStatusBarEnabled = !lockState; - } - _areBrowserPopupsAllowed = !lockState; - _contextMenuEnabled = !lockState; // Sync internal field - }); - } - - /// - /// Lock down the WebView by disabling certain features (Legacy). - /// - public void LockWebView() => SetLockState(true); - - /// - /// Unlock the WebView by re-enabling restricted features (Legacy). - /// - public void UnLockWebView() => SetLockState(false); - - /// - /// Enable major browser features for a controlled environment. - /// - public void EnableBrowserFeatures() => UnLockWebView(); - - - /// - /// Saves the current page as a PDF file. - /// - public async void ExportToPdf(string filePath) - { - if (_webView?.CoreWebView2 == null) return; - try - { - await _webView.CoreWebView2.PrintToPdfAsync(filePath); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PDF_EXPORT_SUCCESS|" + filePath); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "PDF_EXPORT_ERROR|" + ex.Message); } - } - - /// - /// Capture page data as MHTML or other CDP snapshot formats. - /// - public string CaptureSnapshot(string cdpParameters = "{\"format\": \"mhtml\"}") - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - - return RunOnUiThread(() => - { - try - { - // Use a more robust wait that pumps messages - var task = _webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Page.captureSnapshot", cdpParameters); - string json = WaitAndGetResult(task); - - if (string.IsNullOrEmpty(json) || json == "null") - { - return "ERROR: CDP Page.captureSnapshot returned empty result."; - } - - // CDP returns a JSON object like {"data": "..."} - var dict = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); - if (dict != null && dict.ContainsKey("data")) - { - return dict["data"]; - } - return "ERROR: Page capture failed - 'data' field missing in CDP response."; - } - catch (Exception ex) - { - return "ERROR: " + ex.Message; - } - }); - } - - /// - /// Export page data as HTML or MHTML. - /// - public string ExportPageData(int format, string filePath) - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - - return RunOnUiThread(() => - { - try - { - string result = string.Empty; - if (format == 0) // HTML Only - { - var task = _webView.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML"); - result = WaitAndGetResult(task); - result = CleanJsString(result); - } - else if (format == 1) // MHTML Snapshot - { - result = CaptureSnapshot(); - } - - // Check if the result itself is an error from CaptureSnapshot - if (string.IsNullOrEmpty(result) || result.StartsWith("ERROR:")) - { - return string.IsNullOrEmpty(result) ? "ERROR: Export failed - empty result." : result; - } - - if (!string.IsNullOrEmpty(filePath)) - { - try - { - File.WriteAllText(filePath, result); - return "SUCCESS: File saved to " + filePath; - } - catch (Exception ex) - { - return "ERROR: File write failed: " + ex.Message; - } - } - return result; - } - catch (Exception ex) { return "ERROR: " + ex.Message; } - }); - } - - /// - /// Capture the current page as a PDF and return it as a Base64 string. - /// - public string PrintToPdfStream() - { - if (_webView?.CoreWebView2 == null) return "ERROR: WebView not initialized"; - - return RunOnUiThread(() => - { - try - { - var task = _webView.CoreWebView2.PrintToPdfStreamAsync(null); - Stream stream = WaitAndGetResult(task); - if (stream == null) return "ERROR: PDF stream is null"; - - using (MemoryStream ms = new MemoryStream()) - { - stream.CopyTo(ms); - return Convert.ToBase64String(ms.ToArray()); - } - } - catch (Exception ex) { return "ERROR: " + ex.Message; } - }); - } - - /// - /// Call a DevTools Protocol (CDP) method directly. - /// - public async void CallDevToolsProtocolMethod(string methodName, string parametersJson) - { - if (_webView?.CoreWebView2 == null) return; - try - { - string result = await _webView.CoreWebView2.CallDevToolsProtocolMethodAsync(methodName, parametersJson); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"CDP_RESULT|{methodName}|{result}"); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"CDP_ERROR|{methodName}|{ex.Message}"); } - } - - /// - /// Set the zoom factor. - /// - public void SetZoom(double factor) => InvokeOnUiThread(() => _webView.ZoomFactor = factor); - - /// - /// Reset zoom to default (100%). - /// - public void ResetZoom() => SetZoom(1.0); - - /// - /// Sets the mute status for audio. - /// - public void SetMuted(bool muted) => InvokeOnUiThread(() => { if (_webView?.CoreWebView2 != null) _webView.CoreWebView2.IsMuted = muted; }); - - /// - /// Gets the current mute status. - /// - public bool IsMuted() => _webView?.CoreWebView2?.IsMuted ?? false; - - /// - /// Resize the WebView control. - /// - public void Resize(int w, int h) => InvokeOnUiThread(() => _webView.Size = new Size(w, h)); - - /// - /// Clean up resources. - /// - public void Cleanup() => _webView?.Dispose(); - - #endregion - - #region 12. COOKIES & CACHE - /// - /// Clear browser data (cookies, cache, history, etc.). - /// - public async void ClearBrowserData() - { - await _webView.EnsureCoreWebView2Async(); - await _webView.CoreWebView2.Profile.ClearBrowsingDataAsync(); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "DATA_CLEARED"); - } - - /// - /// Clears the browser cache (DiskCache and LocalStorage). - /// - public async void ClearCache() - { - if (_webView?.CoreWebView2 == null) return; - await _webView.CoreWebView2.Profile.ClearBrowsingDataAsync(CoreWebView2BrowsingDataKinds.DiskCache | CoreWebView2BrowsingDataKinds.LocalStorage); - } - - /// - /// Get Cookies asynchronously. - /// - public async void GetCookies(string channelId) - { - if (_webView?.CoreWebView2?.CookieManager == null) return; - try - { - var cookieList = await _webView.CoreWebView2.CookieManager.GetCookiesAsync(null); - var sb = new System.Text.StringBuilder("["); - for(int i=0; i - /// Add or Update a Cookie. - /// - public void AddCookie(string name, string value, string domain, string path) - { - if (_webView?.CoreWebView2?.CookieManager == null) return; - try - { - var cookie = _webView.CoreWebView2.CookieManager.CreateCookie(name, value, domain, path); - _webView.CoreWebView2.CookieManager.AddOrUpdateCookie(cookie); - } - catch (Exception ex) { OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), $"COOKIE_ADD_ERROR|{ex.Message}"); } - } - - /// - /// Delete a specific Cookie. - /// - public void DeleteCookie(string name, string domain, string path) - { - if (_webView?.CoreWebView2?.CookieManager == null) return; - var cookie = _webView.CoreWebView2.CookieManager.CreateCookie(name, "", domain, path); - _webView.CoreWebView2.CookieManager.DeleteCookie(cookie); - } - - /// - /// Delete All Cookies. - /// - public void DeleteAllCookies() => _webView?.CoreWebView2?.CookieManager?.DeleteAllCookies(); - #endregion - - #region 13. AD BLOCKING - /// - /// Set AdBlock active state. - /// - public void SetAdBlock(bool active) => _isAdBlockActive = active; - - /// - /// Add a domain to the block list. - /// - public void AddBlockRule(string domain) { if (!string.IsNullOrEmpty(domain)) _blockList.Add(domain.ToLower()); } - - /// - /// Clear all ad block rules. - /// - public void ClearBlockRules() => _blockList.Clear(); - #endregion - - #region 14. SMART RESIZE - /// - /// Enables or disables automatic resizing. - /// - public void SetAutoResize(bool enabled) - { - if (_parentHandle == IntPtr.Zero || _parentSubclass == null) return; - _autoResizeEnabled = enabled; - if (_autoResizeEnabled) { _parentSubclass.AssignHandle(_parentHandle); PerformSmartResize(); } - else _parentSubclass.ReleaseHandle(); - } - - private void PerformSmartResize() - { - if (_webView == null || _parentHandle == IntPtr.Zero) return; - if (_webView.InvokeRequired) { _webView.Invoke(new Action(PerformSmartResize)); return; } - if (GetClientRect(_parentHandle, out Rect rect)) - { - int newWidth = (rect.Right - rect.Left) - _offsetX - _marginRight; - int newHeight = (rect.Bottom - rect.Top) - _offsetY - _marginBottom; - _webView.Left = _offsetX; _webView.Top = _offsetY; - _webView.Width = Math.Max(10, newWidth); _webView.Height = Math.Max(10, newHeight); - OnMessageReceived?.Invoke(this, FormatHandle(_parentHandle), "WINDOW_RESIZED|" + _webView.Width + "|" + _webView.Height); - } - } - - private class ParentWindowSubclass : NativeWindow - { - private const int WM_SIZE = 0x0005; - private readonly Action _onResize; - - public ParentWindowSubclass(Action onResize) - { - _onResize = onResize; - } - - protected override void WndProc(ref Message m) - { - base.WndProc(ref m); - if (m.Msg == WM_SIZE) _onResize?.Invoke(); - } - } - #endregion - - #region 15. MISC PUBLIC API - public void SetZoomFactor(double factor) - { - if (factor < 0.1 || factor > 5.0) return; - InvokeOnUiThread(() => _webView.ZoomFactor = factor); - } - - public void OpenDevToolsWindow() => InvokeOnUiThread(() => _webView?.CoreWebView2?.OpenDevToolsWindow()); - - public void WebViewSetFocus() => InvokeOnUiThread(() => _webView?.Focus()); - - public void SetUserAgent(string userAgent) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.UserAgent = userAgent; }); - } - - public string GetDocumentTitle() => _webView?.CoreWebView2?.DocumentTitle ?? ""; - - public string GetSource() => _webView?.Source?.ToString() ?? ""; - - public uint GetBrowserProcessId() - { - try { return _webView?.CoreWebView2?.BrowserProcessId ?? 0; } - catch { return 0; } - } - - public void SetScriptEnabled(bool enabled) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsScriptEnabled = enabled; }); - } - - public void SetWebMessageEnabled(bool enabled) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsWebMessageEnabled = enabled; }); - } - - public void SetStatusBarEnabled(bool enabled) - { - InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsStatusBarEnabled = enabled; }); - } - #endregion - - #region 16. UNIFIED SETTINGS (PROPERTIES) - public bool AreDevToolsEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreDevToolsEnabled ?? false); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreDevToolsEnabled = value; }); - } - - /// - /// Check if browser popups are allowed or redirected to the same window. - /// - public bool AreBrowserPopupsAllowed - { - get => _areBrowserPopupsAllowed; - set => InvokeOnUiThread(() => _areBrowserPopupsAllowed = value); - } - - public bool AreDefaultContextMenusEnabled - { - get => _contextMenuEnabled; - set => SetContextMenuEnabled(value); // Reuse existing logic - } - - public bool AreDefaultScriptDialogsEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreDefaultScriptDialogsEnabled ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = value; }); - } - - public bool AreBrowserAcceleratorKeysEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreBrowserAcceleratorKeysEnabled ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = value; }); - } - - public bool IsStatusBarEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.IsStatusBarEnabled ?? true); - set => SetStatusBarEnabled(value); // Reuse existing logic - } - - public double ZoomFactor - { - get => RunOnUiThread(() => _webView?.ZoomFactor ?? 1.0); - set => SetZoomFactor(value); - } - - public string BackColor - { - get => RunOnUiThread(() => ColorTranslator.ToHtml(_webView.DefaultBackgroundColor)); - set => InvokeOnUiThread(() => { - try { - // Fix 0x prefix for AutoIt - string hex = value.Replace("0x", "#"); - _webView.DefaultBackgroundColor = ColorTranslator.FromHtml(hex); - } catch { _webView.DefaultBackgroundColor = Color.White; } - }); - } - - public bool AreHostObjectsAllowed - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.AreHostObjectsAllowed ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.AreHostObjectsAllowed = value; }); - } - - public int Anchor - { - get => RunOnUiThread(() => (int)_webView.Anchor); - set => InvokeOnUiThread(() => _webView.Anchor = (AnchorStyles)value); - } - - public int BorderStyle - { - get => 0; // WebView2 control does not support BorderStyle property natively. - set { /* No-op: WebView2 does not support BorderStyle directly */ } - } - - /// Enable/Disable Custom Context Menu. - public bool CustomMenuEnabled - { - get => _customMenuEnabled; - set => _customMenuEnabled = value; - } - - /// - /// Gets or sets additional browser arguments (switches) to be passed to the Chromium engine. - /// These must be set BEFORE calling Initialize(). - /// - public string AdditionalBrowserArguments - { - get => _additionalBrowserArguments; - set => _additionalBrowserArguments = value; - } - - /// - /// Control PDF toolbar items visibility (bitwise combination of CoreWebView2PdfToolbarItems). - /// - public int HiddenPdfToolbarItems - { - get => RunOnUiThread(() => (int)(_webView?.CoreWebView2?.Settings?.HiddenPdfToolbarItems ?? CoreWebView2PdfToolbarItems.None)); - set => InvokeOnUiThread(() => { - if (_webView?.CoreWebView2?.Settings != null) - _webView.CoreWebView2.Settings.HiddenPdfToolbarItems = (CoreWebView2PdfToolbarItems)value; - }); - } - - /// Sets a custom download path for file downloads. - public void SetDownloadPath(string path) - { - _customDownloadPath = path; - if (!System.IO.Directory.Exists(path)) - { - System.IO.Directory.CreateDirectory(path); // Create the folder if it doesn't exist - } - } - - /// Returns a pipe-separated string of all active download URIs. - public string ActiveDownloadsList => string.Join("|", _activeDownloads.Keys); - - /// Cancels downloads. If uri is null or empty, cancels all active downloads. - /// Optional: The specific URI to cancel. - public void CancelDownloads(string uri = "") - { - if (string.IsNullOrEmpty(uri)) - { - // Cancel all - foreach (var download in _activeDownloads.Values) - { - download.Cancel(); - } - _activeDownloads.Clear(); - } - else if (_activeDownloads.ContainsKey(uri)) - { - // Cancel specific - _activeDownloads[uri].Cancel(); - _activeDownloads.Remove(uri); - } - } - - public bool IsDownloadUIEnabled - { - get => _isDownloadUIEnabled; - set => _isDownloadUIEnabled = value; - } - - /// Decodes a Base64-encoded string into a byte array. - /// The Base64-encoded string to decode. - /// A byte array containing the decoded binary data, or an empty array if the input is null, empty, or invalid. - public byte[] DecodeB64ToBinary(string base64Text) - { - if (string.IsNullOrEmpty(base64Text)) return new byte[0]; - try - { - return Convert.FromBase64String(base64Text); - } - catch { return new byte[0]; } - } - - /// Encodes raw binary data (byte array) to a Base64 string. - public string EncodeBinaryToB64(object binaryData) - { - if (binaryData is byte[] bytes) - { - return Convert.ToBase64String(bytes); - } - return ""; - } - - /// Enable/Disable OnWebResourceResponseReceived event. - public bool HttpStatusCodeEventsEnabled - { - get => _httpStatusCodeEventsEnabled; - set => _httpStatusCodeEventsEnabled = value; - } - - /// Filter HttpStatusCode events to only include the main document. - public bool HttpStatusCodeDocumentOnly - { - get => _httpStatusCodeDocumentOnly; - set => _httpStatusCodeDocumentOnly = value; - } - - - public bool IsDownloadHandled - { - get => _isDownloadHandledOverride; - set => _isDownloadHandledOverride = value; - } - - public bool IsZoomControlEnabled - { - get => _webView?.CoreWebView2?.Settings?.IsZoomControlEnabled ?? _isZoomControlEnabled; - set - { - _isZoomControlEnabled = value; - InvokeOnUiThread(() => { - if (_webView?.CoreWebView2?.Settings != null) - _webView.CoreWebView2.Settings.IsZoomControlEnabled = value; - }); - } - } - - public bool IsBuiltInErrorPageEnabled - { - get => RunOnUiThread(() => _webView?.CoreWebView2?.Settings?.IsBuiltInErrorPageEnabled ?? true); - set => InvokeOnUiThread(() => { if (_webView?.CoreWebView2?.Settings != null) _webView.CoreWebView2.Settings.IsBuiltInErrorPageEnabled = value; }); - } - - /// - /// Maps a virtual host name to a local folder path. - /// - public void SetVirtualHostNameToFolderMapping(string hostName, string folderPath, int accessKind) - { - InvokeOnUiThread(() => { - if (_webView?.CoreWebView2 != null) - { - _webView.CoreWebView2.SetVirtualHostNameToFolderMapping(hostName, folderPath, (Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind)accessKind); - } - }); - } - - /// - /// Captures a screenshot of the current WebView2 content and returns it as a Base64-encoded data URL. - /// - /// The image format for the screenshot, either 'png' or 'jpeg'. - /// A Base64-encoded data URL representing the captured image. - public string CapturePreviewAsBase64(string format) - { - try - { - var imgFormat = format.ToLower() == "jpeg" ? - CoreWebView2CapturePreviewImageFormat.Jpeg : - CoreWebView2CapturePreviewImageFormat.Png; - - using (var ms = new System.IO.MemoryStream()) - { - var task = _webView.CoreWebView2.CapturePreviewAsync(imgFormat, ms); - WaitTask(task); - return $"data:image/{format.ToLower()};base64,{Convert.ToBase64String(ms.ToArray())}"; - } - } - catch (Exception ex) - { - return "Error: " + ex.Message; - } - } - - #endregion - - /// - /// Provides a standard HTTP reason phrase if the server response is empty (common in HTTP/2+). - /// - private string GetReasonPhrase(int statusCode, string originalReason) - { - if (!string.IsNullOrEmpty(originalReason)) return originalReason; - - switch (statusCode) - { - case 200: return "OK"; - case 201: return "Created"; - case 204: return "No Content"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 304: return "Not Modified"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 429: return "Too Many Requests"; - case 500: return "Internal Server Error"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - default: - try { return ((System.Net.HttpStatusCode)statusCode).ToString(); } - catch { return "Unknown"; } - } - } - - #region 17. HELPER METHODS - private T RunOnUiThread(Func func) - { - if (_webView == null || _webView.IsDisposed) return default; - if (_webView.InvokeRequired) return (T)_webView.Invoke(func); - else return func(); - } - - /// Invoke actions on the UI thread - private void InvokeOnUiThread(Action action) - { - if (_webView == null || _webView.IsDisposed) return; - if (_webView.InvokeRequired) _webView.Invoke(action); - else action(); - } - - /// - /// Wait for an async task to complete and return the result (Synchronous wrapper for COM). - /// - private T WaitAndGetResult(Task task, int timeoutSeconds = 20) - { - var start = DateTime.Now; - while (!task.IsCompleted) - { - System.Windows.Forms.Application.DoEvents(); - if ((DateTime.Now - start).TotalSeconds > timeoutSeconds) throw new TimeoutException("Operation timed out."); - System.Threading.Thread.Sleep(1); - } - return task.Result; - } - - /// - /// Waits for the specified Task to complete, processing Windows Forms events during the wait and enforcing a - /// timeout. - /// - /// The Task to wait for completion. - /// The maximum number of seconds to wait before timing out. Defaults to 20 seconds. - /// Thrown if the Task does not complete within the specified timeout. - private void WaitTask(Task task, int timeoutSeconds = 20) - { - var start = DateTime.Now; - while (!task.IsCompleted) - { - System.Windows.Forms.Application.DoEvents(); - if ((DateTime.Now - start).TotalSeconds > timeoutSeconds) - throw new TimeoutException("Operation timed out."); - System.Threading.Thread.Sleep(1); - } - if (task.IsFaulted && task.Exception != null) - throw task.Exception.InnerException; - } - - - /// - /// Encodes a string for safe use in a URL. - /// - public string EncodeURI(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - return System.Net.WebUtility.UrlEncode(value); - } - - /// - /// Decodes a URL-encoded string. - /// - public string DecodeURI(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - return System.Net.WebUtility.UrlDecode(value); - } - - /// - /// Encodes a string to Base64 (UTF-8). - /// - public string EncodeB64(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - var bytes = System.Text.Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); - } - - /// - /// Decodes a Base64 string to plain text (UTF-8). - /// - public string DecodeB64(string value) - { - if (string.IsNullOrEmpty(value)) return ""; - try { - var bytes = Convert.FromBase64String(value); - return System.Text.Encoding.UTF8.GetString(bytes); - } catch { return ""; } // Fail safe - } - - /// - /// Clean up JavaScript string results. - /// - private string CleanJsString(string input) - { - if (string.IsNullOrEmpty(input)) return ""; - string decoded = System.Text.RegularExpressions.Regex.Unescape(input); - if (decoded.StartsWith("\"") && decoded.EndsWith("\"") && decoded.Length >= 2) - decoded = decoded.Substring(1, decoded.Length - 2); - return decoded; - } - #endregion - } -} diff --git a/src/packages.config b/src/packages.config index 0e766c4..d7e3f38 100644 --- a/src/packages.config +++ b/src/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file