Skip to content

[Problem/Bug]: unstable execution order of init scripts in iframe #5315

@Legend-Master

Description

@Legend-Master

What happened?

When using AddScriptToExecuteOnDocumentCreated to run initialization scripts, the order of execution could be wrong in iframes when running the script in that origin for the first time, please see the reproduction below

From testing, it seems like if you exit the app (exiting normally, not with terminate or anything) and then run it again, the order's right again (the order of calling AddScriptToExecuteOnDocumentCreated)

Importance

Moderate. My app's user experience is affected, but still usable.

Runtime Channel

Stable release (WebView2 Runtime)

Runtime Version

138.0.3351.95

SDK Version

1.0.3351.48

Framework

Win32

Operating System

Windows 10

OS Version

10.0.19045

Repro steps

Editing from https://github.com/MicrosoftEdge/WebView2Samples/tree/main/GettingStartedGuides/Win32_GettingStarted

webview->AddScriptToExecuteOnDocumentCreated(L"console.log(1)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(2)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(3)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(4)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(5)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(6)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(7)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(8)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(9)", nullptr);
Sleep(100);
webview->AddScriptToExecuteOnDocumentCreated(L"console.log(10)", nullptr);
Sleep(100);

webview->AddScriptToExecuteOnDocumentCreated(L"if (location.href === 'http://localhost:8000/') {window.addEventListener('DOMContentLoaded', () => {const iframe = document.createElement('iframe');iframe.id = '__tauri_isolation__';iframe.sandbox.add('allow-scripts');iframe.src = 'http://localhost:8000/iframe.html';document.body.append(iframe)})}", nullptr);

webview->Navigate(L"http://localhost:8000/");
Entire file

// compile with: /D_UNICODE /DUNICODE /DWIN32 /D_WINDOWS /c

#include <windows.h>
#include <stdlib.h>
#include <string>
#include <tchar.h>
#include <wrl.h>
#include <wil/com.h>
// <IncludeHeader>
// include WebView2 header
#include "WebView2.h"
// </IncludeHeader>

using namespace Microsoft::WRL;

// Global variables

// The main window class name.
static TCHAR szWindowClass[] = _T("DesktopApp");

// The string that appears in the application's title bar.
static TCHAR szTitle[] = _T("WebView sample");

HINSTANCE hInst;

// Forward declarations of functions included in this code module:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// Pointer to WebViewController
static wil::com_ptr<ICoreWebView2Controller> webviewController;

// Pointer to WebView window
static wil::com_ptr<ICoreWebView2> webview;

int CALLBACK WinMain(
	_In_ HINSTANCE hInstance,
	_In_ HINSTANCE hPrevInstance,
	_In_ LPSTR     lpCmdLine,
	_In_ int       nCmdShow
)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

	if (!RegisterClassEx(&wcex))
	{
		MessageBox(NULL,
			_T("Call to RegisterClassEx failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	// Store instance handle in our global variable
	hInst = hInstance;

	// The parameters to CreateWindow explained:
	// szWindowClass: the name of the application
	// szTitle: the text that appears in the title bar
	// WS_OVERLAPPEDWINDOW: the type of window to create
	// CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y)
	// 500, 100: initial size (width, length)
	// NULL: the parent of this window
	// NULL: this application does not have a menu bar
	// hInstance: the first parameter from WinMain
	// NULL: not used in this application
	HWND hWnd = CreateWindow(
		szWindowClass,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		800, 600,
		NULL,
		NULL,
		hInstance,
		NULL
	);

	if (!hWnd)
	{
		MessageBox(NULL,
			_T("Call to CreateWindow failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	// The parameters to ShowWindow explained:
	// hWnd: the value returned from CreateWindow
	// nCmdShow: the fourth parameter from WinMain
	ShowWindow(hWnd,
		nCmdShow);
	UpdateWindow(hWnd);

	// <-- WebView2 sample code starts here -->
	// Step 3 - Create a single WebView within the parent window
	// Locate the browser and set up the environment for WebView
	CreateCoreWebView2EnvironmentWithOptions(nullptr, nullptr, nullptr,
		Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
			[hWnd](HRESULT result, ICoreWebView2Environment* env) -> HRESULT {

				// Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd
				env->CreateCoreWebView2Controller(hWnd, Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
					[hWnd](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
						if (controller != nullptr) {
							webviewController = controller;
							webviewController->get_CoreWebView2(&webview);
						}

						// Add a few settings for the webview
						// The demo step is redundant since the values are the default settings
						wil::com_ptr<ICoreWebView2Settings> settings;
						// webview->get_Settings(&settings);
						// settings->put_IsScriptEnabled(TRUE);
						// settings->put_AreDefaultScriptDialogsEnabled(TRUE);
						// settings->put_IsWebMessageEnabled(TRUE);

						// Resize WebView to fit the bounds of the parent window
						RECT bounds;
						GetClientRect(hWnd, &bounds);
						webviewController->put_Bounds(bounds);

						// Schedule an async task to navigate to Bing
						//webview->Navigate(L"http://localhost:8000/");

						// <NavigationEvents>
						// Step 4 - Navigation events
						// register an ICoreWebView2NavigationStartingEventHandler to cancel any non-https navigation
						// EventRegistrationToken token;
						// webview->add_NavigationStarting(Callback<ICoreWebView2NavigationStartingEventHandler>(
						// 	[](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT {
						// 		wil::unique_cotaskmem_string uri;
						// 		args->get_Uri(&uri);
						// 		std::wstring source(uri.get());
						// 		if (source.substr(0, 5) != L"https") {
						// 			args->put_Cancel(true);
						// 		}
						// 		return S_OK;
						// 	}).Get(), &token);
						// </NavigationEvents>

						// <Scripting>
						// Step 5 - Scripting
						// Schedule an async task to add initialization script that freezes the Object object
						// webview->AddScriptToExecuteOnDocumentCreated(L"Object.freeze(Object);", nullptr);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(1)", nullptr);
						Sleep(100);
						//webview->AddScriptToExecuteOnDocumentCreated(L"if (!window.frameElement) {window.addEventListener('DOMContentLoaded', () => {;const iframe = document.createElement('iframe');iframe.id = '__tauri_isolation__';iframe.sandbox.add('allow-scripts');iframe.src = 'http://localhost:8000';document.body.append(iframe)})}", nullptr);
						//Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(2)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(3)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(4)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(5)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(6)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(7)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(8)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(9)", nullptr);
						Sleep(100);
						webview->AddScriptToExecuteOnDocumentCreated(L"console.log(10)", nullptr);
						Sleep(100);

						webview->AddScriptToExecuteOnDocumentCreated(L"if (location.href === 'http://localhost:8000/') {window.addEventListener('DOMContentLoaded', () => {const iframe = document.createElement('iframe');iframe.id = '__tauri_isolation__';iframe.sandbox.add('allow-scripts');iframe.src = 'http://localhost:8000/iframe.html';document.body.append(iframe)})}", nullptr);

						webview->Navigate(L"http://localhost:8000/");

						// Schedule an async task to get the document URL
						// webview->ExecuteScript(L"window.document.URL;", Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
						// 	[](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT {
						// 		LPCWSTR URL = resultObjectAsJson;
						// 		//doSomethingWithURL(URL);
						// 		return S_OK;
						// 	}).Get());
						// </Scripting>

						// <CommunicationHostWeb>
						// Step 6 - Communication between host and web content
						// Set an event handler for the host to return received message back to the web content
						// webview->add_WebMessageReceived(Callback<ICoreWebView2WebMessageReceivedEventHandler>(
						// 	[](ICoreWebView2* webview, ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT {
						// 		wil::unique_cotaskmem_string message;
						// 		args->TryGetWebMessageAsString(&message);
						// 		// processMessage(&message);
						// 		webview->PostWebMessageAsString(message.get());
						// 		return S_OK;
						// 	}).Get(), &token);

						// Schedule an async task to add initialization script that
						// 1) Add an listener to print message from the host
						// 2) Post document URL to the host
						// webview->AddScriptToExecuteOnDocumentCreated(
						// 	L"window.chrome.webview.addEventListener(\'message\', event => alert(event.data));" \
						// 	L"window.chrome.webview.postMessage(window.document.URL);",
						// 	nullptr);
						// </CommunicationHostWeb>

						return S_OK;
					}).Get());
				return S_OK;
			}).Get());



	// <-- WebView2 sample code ends here -->

	// Main message loop:
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_DESTROY  - post a quit message and return
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	TCHAR greeting[] = _T("Hello, Windows desktop!");

	switch (message)
	{
	case WM_SIZE:
		if (webviewController != nullptr) {
			RECT bounds;
			GetClientRect(hWnd, &bounds);
			webviewController->put_Bounds(bounds);
		};
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}

	return 0;
}

Both http://localhost:8000/ and http://localhost:8000/iframe.html point to an basic HTML file

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body></body>
</html>

Run it and look at the console, make sure you delete the webview data folder next to the compiled executable after first run

The result that I'm currently getting:

Image

Repros in Edge Browser

No, issue does not reproduce in the corresponding Edge version

Regression

Don't know

Last working version (if regression)

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions