文档层级

  • Docs
    • Microsoft Edge documentation
      • Microsoft Edge Developer documentation
        • WebView2
          • Fundamentals
            • Web and native interop

Interop of native-side and web-side code

介绍了一些用例

  • 当前进到不同的网页时,更新宿主窗口
  • 发送一个原生的camera object给web app使用
  • 为当前的应用在web侧执行一个特定的js文件

Before you begin

https://github.com/MicrosoftEdge/WebView2Samples存储了一些示例。

Scenario: Simple messaging

Send Messages from the host app to WebView2

通过PostWebMessageAsJson发送Json文本给WebView2. WebView2中通过侦听message事件来获取Json文本。 也可以通过PostWebMessageAsString发送字符串。

Receive message strings via postMessage

WebView2通过window.chrome.webview.postMessage向宿主发送消息。 宿主需要通过注册WebMessageReceived来接受消息。 消息内容为纯文本,格式需要应用自己定义。

Round-trip messages

略。

Scenario: Send JavaScript code

宿主通过`ExecuteScriptAsync异步调用某JS代码。 具体查看Use JavaScript in WebView2 (Run JavaScript from native code)

Scenario: Send native objects

将一个原生对象传给WebView2,让其能够调用原生对象的方法。 这种情况下,消息被封装成了对象方法的调用。 具体的设置是通过AddHostObjectToScript达成的。 在WebView2中使用window.chrome.webview.hostObjects.{name}来访问这些原生对象。

Call web-side code from native-side code

Basic WebView2 functions

除了ExecuteScriptAsync,还有OnDocumentCreatedAsync。后者用于注册在DOM加载完成时执行的代码。

Scenario: ExecuteScript JSON-encoded results

略。

Scenario: Running a dedicated script file

略。

Scenario: Removing drag-and-drop functionality

略。

Scenario: Removing the context menu

略。

Call native-side code from web-side code

介绍AddHostObjectToScript用编口(API)。

目的是让运行于沙箱中的web代码能够原生对象的功能。比如调用网摄头(webcam)。

本文中使用的例子是WebView2 Win32 sample app。更多参考Embed web content into native applications

Step 1: Install Visual Studio, install git, clone the WebView2Samples repo, and open the solution

过。

Step 2: Define the host object and implement IDispatch

Part 2A: Create the COM interface

此处使用HostObjectSample.idl定义COM类的接口。

该COM类,也就是HostObjectSample,需要实现两个接口, 一个是IDispatch,用于AddHostObjectToScript; 另一个是自定义的,用于WebView2交互的接口。

IDispatch意味着调用是JS中动态创建的,更多参考IDispatch interface (oaidl.h)

实现IDispatch的话则参考Type Libraries and the Object Description Language

编译IDL文件会产生translation lookaside buffer (TLB)文件。需要把这个TLB文件加入到项目中。

实际的代码在SampleApps/WebView2APISample/HostObjectSample.idl

Part 2B: Create the C++ object

创建COM使用的是Windows Runtime C++ Template Library (WRL),已经被C++/WinRT替代了。

实际的代码在

  • SampleApps/WebView2APISample/HostObjectSampleImpl.cpp
  • SampleApps/WebView2APISample/HostObjectSampleImpl.h

Step 3: Call the AddHostObjectToScript API

m_hostObject.query_to<IDispatch>(&remoteObjectAsVariant.pdispVal);将COM例现转成IDispatch接口的例现,并盛放在VARIANT类型的remoteObjectAsVariant标的的属域pdispVal中。

需要显示设置VARIANT所含的数据类型:remoteObjectAsVariant.vt = VT_DISPATCH

关于VARIANT是一种动态数据类型,其支持的数据类型可以在https://docs.microsoft.com/en-us/windows/win32/api/oaidl/ns-oaidl-variant查的。AddHostObjectToScript只支持VARIANT中的部分数据类型,查看WebView2 Win32 C++ ICoreWebView2 | Microsoft Docs

Step 4: Use AddHostObjectToScript to pass a method to the web

ScenarioAddHostObject.html中可以看到前面注册的宿主对象的使用。

同步调用和异步调用的方式不太一样:

        document.getElementById("invokeMethodAsyncButton").addEventListener("click", async () => {
            const paramValue1 = document.getElementById("invokeMethodAsyncParam1").value;
            const paramValue2 = parseInt(document.getElementById("invokeMethodAsyncParam2").value);
            const resultValue = await chrome.webview.hostObjects.sample.MethodWithParametersAndReturnValue(paramValue1, paramValue2);
            document.getElementById("invokeMethodAsyncOutput").textContent = resultValue;
        });

        document.getElementById("invokeMethodSyncButton").addEventListener("click", () => {
            const paramValue1 = document.getElementById("invokeMethodSyncParam1").value;
            const paramValue2 = parseInt(document.getElementById("invokeMethodSyncParam2").value);
            const resultValue = chrome.webview.hostObjects.sync.sample.MethodWithParametersAndReturnValue(paramValue1, paramValue2);
            document.getElementById("invokeMethodSyncOutput").textContent = resultValue;
        });

另外chrome.webview.hostObjects.options.shouldSerializeDates = true;是啥意思?

更多参考interface ICoreWebView2

其他

WRL Reference

(完)

2022-03-27更新

Win32 C++ WebView2 API conventions

Async methods

异步方法使用待理工(delegate)接口来联系告知以下信息:

  • 异步执行的结束
  • 成功还是失败
  • 执行获得的结果

待理工接口有一个Invoke方法,首参是HRESULT,用作成败码;可选的二参用作执行结果。

例如,ICoreWebView2::CapturePreview接受一个ICoreWebView2CapturePreviewCompletedHandler指针,作为最终的参数。对于ICoreWebView2CapturePreviewCompletedHandler,可以手动实现,也可以使用WRL Callback实现。

下面是针对ICoreWebView2::ExecuteScript的示例:

void ScriptComponent::InjectScript()
{
    TextInputDialog dialog(
        m_appWindow->GetMainWindow(),
        L"Inject Script",
        L"Enter script code:",
        L"Enter the JavaScript code to run in the webview.",
        L"window.getComputedStyle(document.body).backgroundColor");
    if (dialog.confirmed)
    {
        m_webView->ExecuteScript(dialog.input.c_str(),
            Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
                [](HRESULT error, PCWSTR result) -> HRESULT
        {
            if (error != S_OK) {
                ShowFailure(error, L"ExecuteScript failed");
            }
            MessageBox(nullptr, result, L"ExecuteScript Result", MB_OK);
            return S_OK;
        }).Get());
    }
}

Events

时间通过add_EventName、remove_EventName来添加和移除。添加的时候,需要传入一个代理工,返回的是一个EventRegistrationToken号牌。此号牌可用于在remove_EventName中移除事件处理。

事件代理工接口也只有单个Invoke方法,首参为事件发起者,二参为事件参量。

下面是针对NavigationCompleted的示例;

// Register a handler for the NavigationCompleted event.
// Check whether the navigation succeeded, and if not, do something.
// Also update the Cancel buttons.
CHECK_FAILURE(m_webView->add_NavigationCompleted(
    Callback<ICoreWebView2NavigationCompletedEventHandler>(
        [this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args)
            -> HRESULT {
            BOOL success;
            CHECK_FAILURE(args->get_IsSuccess(&success));
            if (!success)
            {
                COREWEBVIEW2_WEB_ERROR_STATUS webErrorStatus;
                CHECK_FAILURE(args->get_WebErrorStatus(&webErrorStatus));
                if (webErrorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_DISCONNECTED)
                {
                    // Do something here if you want to handle a specific error case.
                    // In most cases it is not necessary, because the WebView
                    // displays an error page automatically.
                }
            }
            m_toolbar->SetItemEnabled(Toolbar::Item_CancelButton, false);
            m_toolbar->SetItemEnabled(Toolbar::Item_ReloadButton, true);
            return S_OK;
        })
        .Get(),
    &m_navigationCompletedToken));

Strings

字符串输出参数是LPWSTR的以null结尾的,并且是通过CoTaskMemAlloc分配的。所有权会被传递给请求方,用完需要使用CoTaskMemFree来释放。

字符串输入参数也是LPWSTR的以null结尾的。对于同步的函数请求,请求方保证在调用期间字符串不会失效。如果接收方需要在函数请求结束时必须使用,必须存一个副本。

URI and JSON parsing

许多方法的参数类型时URI和JSON字符串。

你可以使用所偏好的料库来处理URI和JSON。

比如WinRT的

  • RuntimeClass_Windows_Data_Json_JsonObject、IJsonObjectStatics
  • RuntimeClass_Windows_Foundation_Uri、IUriRuntimeClassFactory

如果使用IUri以及CreateUri,你可能要使用下列的URI创建旗标,以匹配WebView的URI解析:

Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME | Uri_CREATE_NO_DECODE_EXTRA_INFO

WebView2 Win32 AddHostObjectToScript

宿主标的通过代理window.chrome.webview.hostObjects.{name}体现出来。宿主标的是promise,且可以化解成一个表征宿主标的代理。此promise会被拒绝,如果应用没有添加{name}的宿主标的。代理上对宿主标的方法的访问也是通过promise进行的。

支持的是简单类型、IDispatch和数组。实现了IDispatch的IUnknown也被支持。一般化的IUnkown、VT_DECIAL或VT_RECORD不受支持。远端的JS标的,比如说回调函数,被表示为VT_DISPATCH类别的VARIANT。JS回调方法可以使用DISPID的DISPID_VALUE来调用。嵌套的数组最多支持三层嵌套。不支持引用类型的数组。VT_EMPTY和VT_NULL映射成JS的null。JS中的null和undefined则映射成VT_EMPTY。

宿主标的也呈现在window.chrome.webview.hostObjects.sync.{name}。此处的代理执行的时候采用的是同步的方式,会阻塞正在运行的脚本。JS阻塞的时候,无法在宿主代码中执行JS回调,并且会返回失败码HRESULT_FROM_WIN32(ERROR_POSSIBLE_DEADLOCK)

异步代理和同步代理可以指向同一个宿主标的。在一个代理上做的改动,在另一个代理上可见。

宿主标的代理,本质上是JS代理标的,可以拦截对所有辖属的访问、方法的调用。作为Function或者标的原型一部分的辖属或方法是本地(JS环境)运行的。任何在chrome.webview.hostObjects.options.forceLocalProperties也是本地运行的,默认包含toJSON以及Symbol.toPrimitive

chrome.webview.hostObjects.cleanupSome可以为宿主标的代理执行尽力而为的垃圾回收。

chrome.webview.hostObjects.options提供了一些调整宿主标的的能力:

  • forceLocalProperties
  • log,默认为null,一个使用示例是console.log.bind(console),暂时不明所以
  • shouldSerializeDates,
    • 为false的时候JS的Date标的会使用JSON。stringify之后以字符串形式发送给宿主
    • 为true的时候可以序列化成VT_DATE发给宿主

下列方法在代理上也是本地化运行的:

  • applyHostFunction、getHostProperty、setHostProperty
  • getLocalProperty、setLocalProperty
  • sync,示例const syncProxy = await chrome.webview.hostObjects.sample.methodCall().sync()
  • async,示例const asyncProxy = chrome.webview.hostObjects.sync.sample.methodCall().async()
  • then

在异步宿主标的代理上设置一个辖属会立马返回。这是JS代理标的的要求。如果需要异步等待辖属设置完成,可以使用setHostProperty方法。

参考

(更新完)