Skip to content

_NetWebView2_ExecuteScript() added iMode#49

Merged
ioa747 merged 2 commits intoioa747:mainfrom
mlipok:_NetWebView2_ExecuteScript()
Feb 2, 2026
Merged

_NetWebView2_ExecuteScript() added iMode#49
ioa747 merged 2 commits intoioa747:mainfrom
mlipok:_NetWebView2_ExecuteScript()

Conversation

@mlipok
Copy link
Copy Markdown
Contributor

@mlipok mlipok commented Feb 1, 2026

Important

Thanks for your effort and interest 💛 in improving the project. It's very appreciated.

Description

Adding $iMode to have possibility to use different JS executions modes

🔗 Linked GitHub Issues

#45 (comment)

Closes #\45

📋 What is the current behavior?

you can only use ExecuteScript (Fire-and-Forget)

🚀 What is the new behavior?

all 3 modes are supported
ExecuteScript (Fire-and-Forget)
ExecuteScriptOnPage (Async-Void Trap)
ExecuteScriptWithResult


Type of changes

  • 🪲 Bugfix (change which fixes an issue)
  • ⭐ New Feature (change which adds functionality)
  • 🔒 Security fix (change which improves security)
  • 🔮 Code style update (formatting, renaming)
  • 🔨 Refactoring (code optimization without functional change)
  • 📚 Documentation (updates to README or docs)
  • ⚙️ Build or CI related changes
  • 🧿 Other type

Breaking changes 🔥

  • Yes
  • No

How and where was this tested?

🖥️ Describe where you tested your changes

Not Related

🔬 Describe how you tested your changes

  • Manually tested in system and context shown above
  • Ran automatic tests (unit tests, integration tests, etc.)
  • Other ways

Checklist

  • I have read and understood the available contributing guidelines.
  • I have ensured my code follows the available code conventions.
  • I have reviewed my changes.
  • I have made corresponding changes to the documentation (if applicable).
  • I have added/updated necessary tests to cover the changes (if applicable).
  • I have checked for potential security implications.

Additional context

#45

Screenshots

none

Note to reviewers

none

@mlipok
Copy link
Copy Markdown
Contributor Author

mlipok commented Feb 1, 2026

Please check this cases:

; example 1
ConsoleWrite($_g_oWeb.ExecuteScriptWithResult('console.log("Hello world!");') & @CRLF)
MsgBox($MB_TOPMOST, "TEST #" & @ScriptLineNumber, $_g_oWeb.ExecuteScriptWithResult('console.log("Hello world v1");'))

; example 2
ConsoleWrite("Log: " & $_g_oWeb.ExecuteScriptWithResult('var x = "Hello world"; x;') & @CRLF)
MsgBox($MB_TOPMOST, "TEST #" & @ScriptLineNumber, $_g_oWeb.ExecuteScriptWithResult('var x = "Hello world v2"; x;'))

; example 3
Local $sText = $_g_oWeb.ExecuteScriptWithResult("var x = extractPDFText(); x;")
MsgBox($MB_TOPMOST, "Text from PDF v1", $sText)

; example 4
$sText = $_g_oWeb.ExecuteScriptWithResult("extractPDFText();")
MsgBox($MB_TOPMOST, "Text from PDF v2", $sText)

I wonder why not all of them work as I'd expect. I mean, they just example 2 give some kind of result.

@mlipok mlipok marked this pull request as draft February 1, 2026 21:01
@mlipok
Copy link
Copy Markdown
Contributor Author

mlipok commented Feb 1, 2026

Converted to DRAFT as it needs to talk and eventually fix - before merge.

@ioa747
Copy link
Copy Markdown
Owner

ioa747 commented Feb 2, 2026

Hi @mlipok,

the reason some examples return empty strings lies in how JavaScript evaluation works in WebView2 and how the results are serialized.

Example 1: console.log() returns undefined

$_g_oWeb.ExecuteScriptWithResult('console.log("Hello world!");')

Why it 'fails': In JavaScript, the console.log() function returns undefined. ExecuteScriptAsync (and our wrapper) captures the return value of the script. Since console.log only sends data to the DevTools console and returns nothing to the engine, you get an empty result in AutoIt.

Example 2: The "Last Expression" Rule (Works!)

$_g_oWeb.ExecuteScriptWithResult('var x = "Hello world"; x;')

Why it works: JavaScript returns the value of the last expression evaluated. Since x; is the last line, its value is passed back to C# and then to AutoIt.


Why you're getting empty results. In the Static_PDF_Viewer.au3 example?

The function extractPDFText() is designed as an Asynchronous Event-based function, not a synchronous return function.

How it works now: When you call extractPDFText(), it processes the PDF in the background and then triggers the OnMessageReceived event in AutoIt with the prefix PDF_TEXT|. It does not return the string directly to the script caller.

Why Example 3 & 4 fail: Since the JS function doesn't have a return statement (it uses postMessage instead), ExecuteScriptWithResult correctly receives 'null/undefined'.

If you want to use ExecuteScriptWithResult to get the text directly, we would need to refactor the JS helper to be a synchronous return, but since PDF parsing is inherently asynchronous in pdf.js, the Event-based approach (which is already working in the example) is the stable way to go.

Check your __EVENTS_Bridge_OnMessageReceived function in the example, that is where your PDF text actually arrives!

; ❌ This returns nothing because the JS function uses postMessage internally
Local $sVoid = $_g_oWeb.ExecuteScriptWithResult("extractPDFText();") 

; ✅ This is where the data actually goes (check your console/logs)
Func __EVENTS_Bridge_OnMessageReceived($sMsg)
    If StringInStr($sMsg, "PDF_TEXT|") Then
        ConsoleWrite("Data received via Event: " & $sMsg & @CRLF)
    EndIf
EndFunc

@mlipok
Copy link
Copy Markdown
Contributor Author

mlipok commented Feb 2, 2026

Example 1: console.log() returns undefined

$_g_oWeb.ExecuteScriptWithResult('console.log("Hello world!");')

Why it 'fails': In JavaScript, the console.log() function returns undefined. ExecuteScriptAsync (and our wrapper) captures the return value of the script. Since console.log only sends data to the DevTools console and returns nothing to the engine, you get an empty result in AutoIt.

Found some answer here:

MicrosoftEdge/WebView2Feedback#2982

@mlipok
Copy link
Copy Markdown
Contributor Author

mlipok commented Feb 2, 2026

The explanations for Examples 2, 3, and 4 are clear enough.
Is there anything else we can do for Example 1? I mean, in accordance with the link posted above.

@mlipok mlipok marked this pull request as ready for review February 2, 2026 01:17
@ioa747
Copy link
Copy Markdown
Owner

ioa747 commented Feb 2, 2026

Hi @mlipok, regarding Example 1 and the WebView2 feedback link:

The 'problem' is that ExecuteScriptAsync only returns the result of the expression evaluation, while the DevTools console is a separate environment that hooks into the browser's stdout.

Can we make it work? Yes, but it requires 'Monkey Patching' the console object. If you really need to capture console.log output directly into an AutoIt variable via ExecuteScriptWithResult, you could inject this script during initialization:

// Monkey patch console.log to buffer messages
window.console.logBuffer = [];
const originalLog = console.log;
console.log = function() {
    window.console.logBuffer.push(Array.from(arguments).join(' '));
    originalLog.apply(console, arguments);
};

// Then in AutoIt, to get the logs:
// $_g_oWeb.ExecuteScriptWithResult("var logs = console.logBuffer.join('\\n'); console.logBuffer = []; logs;")

My Recommendation: Unless you are building a custom Debugger, it’s usually better to:
Use direct returns for data (Example 2 style).
Use window.chrome.webview.postMessage for logging/events (so they arrive asynchronously in AutoIt without blocking).

@ioa747 ioa747 merged commit 434203f into ioa747:main Feb 2, 2026
@mlipok
Copy link
Copy Markdown
Contributor Author

mlipok commented Feb 2, 2026

My Recommendation: Unless you are building a custom Debugger, it’s usually better to:
Use direct returns for data (Example 2 style).
Use window.chrome.webview.postMessage for logging/events (so they arrive asynchronously in AutoIt without blocking).

All is clear
No more questions here.
Thanks.

@mlipok mlipok deleted the _NetWebView2_ExecuteScript() branch February 2, 2026 01:42
@ioa747
Copy link
Copy Markdown
Owner

ioa747 commented Feb 2, 2026

I found it
https://github.com/ioa747/NetWebView2Lib/blob/main/examples/v1.4.2_jsonTree/JS_Lib/Bak/00_Core.js

Semiotically in v1.4.2 I had made a js Debuger

#include "NetWebView2Lib.au3"

Global $oWebV2M, $oBridge

_Example_Console_Redirect()

Func _Example_Console_Redirect()
    Local $hGUI = GUICreate("Console Redirect Test", 400, 300)

    ; 1. Initialize WebView2
    $oWebV2M = _NetWebView2_CreateManager()
    _NetWebView2_Initialize($oWebV2M, $hGUI, @TempDir & "\NVW2_Test", 0, 0, 0, 0)

    ; 2. Get Bridge and Register Events
    $oBridge = $oWebV2M.GetBridge()
	ObjEvent($oBridge, "BridgeEvents_", "IBridgeEvents")

    ; 3. Prepare the Core Bridge JS (The snippet we cleaned up)
    Local $sCoreJS = _Get_Core_Bridge_JS()

    ; 4. Inject the bridge
    $oWebV2M.AddInitializationScript($sCoreJS)

    GUISetState(@SW_SHOW)

	; navigate to the page
	_NetWebView2_Navigate($oWebV2M, "about:blank")

    ; 6. TEST: Execute a console.log
    ; Note: We don't use ExecuteScriptWithResult here because
    ; the data will come back through the Bridge Event!
    $oWebV2M.ExecuteScript("console.log('Hello from JavaScript to AutoIt Console!');")
    $oWebV2M.ExecuteScript("console.error('This is a test error message');")

    While GUIGetMsg() <> -3
    WEnd

    _NetWebView2_CleanUp($oWebV2M, $oBridge)
EndFunc

; This function handles the incoming messages from the JS Bridge
Func BridgeEvents_OnMessageReceived($sMsg)
    ; Check if it's a JSON message from our Bridge
    If StringLeft($sMsg, 1) = "{" Then
        ; For simplicity in this example, we just print the raw message
        ; In a real app, you would parse the JSON to get .message and .level
        ConsoleWrite(">>> BRIDGE MESSAGE: " & $sMsg & @CRLF)
    EndIf
EndFunc

Func _Get_Core_Bridge_JS()
    Local $sJS = ""
    $sJS &= "/**" & @CRLF
    $sJS &= " * NetWebView2Lib Core Bridge" & @CRLF
    $sJS &= " * Handles Console Hijacking and Global Error Reporting" & @CRLF
    $sJS &= " */" & @CRLF
    $sJS &= "" & @CRLF
    $sJS &= "(function() {" & @CRLF
    $sJS &= "    // 1. Configuration & State" & @CRLF
    $sJS &= "    window.NET_BRIDGE_ENABLED = true;" & @CRLF
    $sJS &= "" & @CRLF
    $sJS &= "    /**" & @CRLF
    $sJS &= "     * Centralized message dispatcher to AutoIt" & @CRLF
    $sJS &= "     */" & @CRLF
    $sJS &= "    const dispatchToAutoIt = (data) => {" & @CRLF
    $sJS &= "        try {" & @CRLF
    $sJS &= "            if (window.chrome && window.chrome.webview) {" & @CRLF
    $sJS &= "                window.chrome.webview.postMessage(JSON.stringify(data));" & @CRLF
    $sJS &= "            }" & @CRLF
    $sJS &= "        } catch (e) {" & @CRLF
    $sJS &= "            // Silent fail if bridge is not fully ready" & @CRLF
    $sJS &= "        }" & @CRLF
    $sJS &= "    };" & @CRLF
    $sJS &= "" & @CRLF
    $sJS &= "    /**" & @CRLF
    $sJS &= "     * Console Hijacking Logic" & @CRLF
    $sJS &= "     */" & @CRLF
    $sJS &= "    const originalConsole = {" & @CRLF
    $sJS &= "        log: console.log," & @CRLF
    $sJS &= "        error: console.error," & @CRLF
    $sJS &= "        warn: console.warn," & @CRLF
    $sJS &= "        info: console.info" & @CRLF
    $sJS &= "    };" & @CRLF
    $sJS &= "" & @CRLF
    $sJS &= "    const createWrappedConsole = (type) => {" & @CRLF
    $sJS &= "        return function() {" & @CRLF
    $sJS &= "            // Send to AutoIt" & @CRLF
    $sJS &= "            dispatchToAutoIt({" & @CRLF
    $sJS &= "                type: ""CONSOLE_LOG""," & @CRLF
    $sJS &= "                level: type.toUpperCase()," & @CRLF
    $sJS &= "                message: Array.from(arguments).map(arg => " & @CRLF
    $sJS &= "                    (typeof arg === 'object') ? JSON.stringify(arg) : String(arg)" & @CRLF
    $sJS &= "                ).join(' ')," & @CRLF
    $sJS &= "                timestamp: new Date().toISOString()" & @CRLF
    $sJS &= "            });" & @CRLF
    $sJS &= "            // Keep original browser behavior" & @CRLF
    $sJS &= "            originalConsole[type].apply(console, arguments);" & @CRLF
    $sJS &= "        };" & @CRLF
    $sJS &= "    };" & @CRLF
    $sJS &= "" & @CRLF
    $sJS &= "    // Replace standard console methods" & @CRLF
    $sJS &= "    console.log = createWrappedConsole('log');" & @CRLF
    $sJS &= "    console.error = createWrappedConsole('error');" & @CRLF
    $sJS &= "    console.warn = createWrappedConsole('warn');" & @CRLF
    $sJS &= "    console.info = createWrappedConsole('info');" & @CRLF
    $sJS &= "" & @CRLF
    $sJS &= "    /**" & @CRLF
    $sJS &= "     * 2. Global Runtime Error Handler" & @CRLF
    $sJS &= "     */" & @CRLF
    $sJS &= "    window.onerror = function(message, source, lineno, colno, error) {" & @CRLF
    $sJS &= "        dispatchToAutoIt({" & @CRLF
    $sJS &= "            type: ""JS_ERROR""," & @CRLF
    $sJS &= "            message: message," & @CRLF
    $sJS &= "            source: source," & @CRLF
    $sJS &= "            line: lineno," & @CRLF
    $sJS &= "            column: colno," & @CRLF
    $sJS &= "            stack: error ? error.stack : """"" & @CRLF
    $sJS &= "        });" & @CRLF
    $sJS &= "        return false; // Let browser handle it as well" & @CRLF
    $sJS &= "    };" & @CRLF
    $sJS &= "" & @CRLF
    $sJS &= "    // Signal that bridge is active" & @CRLF
    $sJS &= "    dispatchToAutoIt({ type: ""SYSTEM"", message: ""Core Bridge Injected"" });" & @CRLF
    $sJS &= "})();"
	Return $sJS
EndFunc

@mlipok mlipok mentioned this pull request Feb 2, 2026
30 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants