{"id":19665963,"url":"https://github.com/microsoftedge/webview2browser","last_synced_at":"2025-04-05T20:05:37.899Z","repository":{"id":37396960,"uuid":"200690883","full_name":"MicrosoftEdge/WebView2Browser","owner":"MicrosoftEdge","description":"A web browser built with the Microsoft Edge WebView2 control.","archived":false,"fork":false,"pushed_at":"2023-07-31T17:21:25.000Z","size":3580,"stargazers_count":426,"open_issues_count":16,"forks_count":85,"subscribers_count":46,"default_branch":"main","last_synced_at":"2025-03-29T19:04:27.253Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MicrosoftEdge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-05T16:25:01.000Z","updated_at":"2025-03-28T03:09:52.000Z","dependencies_parsed_at":"2024-11-11T16:37:11.676Z","dependency_job_id":null,"html_url":"https://github.com/MicrosoftEdge/WebView2Browser","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MicrosoftEdge%2FWebView2Browser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MicrosoftEdge%2FWebView2Browser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MicrosoftEdge%2FWebView2Browser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MicrosoftEdge%2FWebView2Browser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MicrosoftEdge","download_url":"https://codeload.github.com/MicrosoftEdge/WebView2Browser/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247393568,"owners_count":20931812,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T16:25:40.655Z","updated_at":"2025-04-05T20:05:37.880Z","avatar_url":"https://github.com/MicrosoftEdge.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\ndescription: \"A web browser built with the Microsoft Edge WebView2 control.\"\nextendedZipContent: \n  - \n    path: LICENSE\n    target: LICENSE\nlanguages: \n  - cpp\n  - javascript\npage_type: sample\nproducts: \n  - microsoft-edge\nurlFragment: webview2browser\n---\n# WebView2Browser\n\nA web browser built with the [Microsoft Edge WebView2](https://aka.ms/webview2) control.\n\n![WebView2Browser](https://raw.githubusercontent.com/MicrosoftEdge/WebView2Browser/master/screenshots/WebView2Browser.png)\n\nWebView2Browser is a sample Windows desktop application demonstrating the WebView2 control capabilities. It is built as a Win32 [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) project and makes use of both C++ and JavaScript in the WebView2 environment to power its features.\n\nWebView2Browser shows some of the simplest uses of WebView2 -such as creating and navigating a WebView, but also some more complex workflows like using the [PostWebMessageAsJson API](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2#postwebmessageasjson) to communicate WebViews in separate environments. It is intended as a rich code sample to look at how you can use WebView2 APIs to build your own app.\n\nFor more information, see the article in the Microsoft Edge Developer documentation: [WebView2Browser](https://learn.microsoft.com/microsoft-edge/webview2/samples/webview2browser).\n\n## Requisites\n\n* [Microsoft Edge (Chromium)](https://www.microsoftedgeinsider.com/download/) installed on a supported OS.\n* [Visual Studio](https://visualstudio.microsoft.com/vs/) with C++ support installed.\n\nThe [WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/) is recommended for the installation and the minimum version is 86.0.622.38.\n\n## Build the browser\n\nClone the repository and open the solution in Visual Studio. WebView2 is already included as a NuGet package* in the project!\n\n* Clone this repository\n* Open the solution in Visual Studio 2019**\n* Make the changes listed below if you're using a Windows version below Windows 10.\n* Set the target you want to build (Debug/Release, x86/x64)\n* Build the solution\n\nThat's it. Everything should be ready to just launch the app.\n\n*You can get the WebView2 NuGet Package through the Visual Studio NuGet Package Manager.  \n**You can also use Visual Studio 2017 by changing the project's Platform Toolset in Project Properties/Configuration properties/General/Platform Toolset. You might also need to change the Windows SDK to the latest version available to you.\n\n## Using versions below Windows 10\n\nThere's a couple of changes you need to make if you want to build and run the browser in other versions of Windows. This is because of how DPI is handled in Windows 10 vs previous versions of Windows.\n\nIn `WebViewBrowserApp.cpp`, you will need to replace the call to `SetProcessDpiAwarenessContext` and call `SetProcessDPIAware` instead.\n\n```cpp\nint APIENTRY wWinMain(_In_ HINSTANCE hInstance,\n                      _In_opt_ HINSTANCE hPrevInstance,\n                      _In_ LPWSTR    lpCmdLine,\n                      _In_ int       nCmdShow)\n{\n    UNREFERENCED_PARAMETER(hPrevInstance);\n    UNREFERENCED_PARAMETER(lpCmdLine);\n\n    // Call SetProcessDPIAware() instead when using Windows 7 or any version\n    // below 1703 (Windows 10).\n    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);\n\n    BrowserWindow::RegisterClass(hInstance);\n\n    // ...\n```\n\nIn `BrowserWindow.cpp`, you will need to remove the call to `GetDpiForWindow`.\n\n```cpp\nint BrowserWindow::GetDPIAwareBound(int bound)\n{\n    // Remove the GetDpiForWindow call when using Windows 7 or any version\n    // below 1607 (Windows 10). You will also have to make sure the build\n    // directory is clean before building again.\n    return (bound * GetDpiForWindow(m_hWnd) / DEFAULT_DPI);\n}\n```\n\n## Browser layout\n\nWebView2Browser has a multi-WebView approach to integrate web content and application UI into a Windows Desktop application. This allows the browser to use standard web technologies (HTML, CSS, JavaScript) to light up the interface but also enables the app to fetch favicons from the web and use IndexedDB for storing favorites and history.\n\nThe multi-WebView approach involves using two separate WebView environments (each with its own user data directory): one for the UI WebViews and the other for all content WebViews. UI WebViews (controls and options dropdown) use the UI environment while web content WebViews (one per tab) use the content environment.\n\n![Browser layout](https://raw.githubusercontent.com/MicrosoftEdge/WebView2Browser/master/screenshots/layout.png)\n\n## Features\n\nWebView2Browser provides all the functionalities to make a basic web browser, but there's plenty of room for you to play around.\n\n* Go back/forward\n* Reload page\n* Cancel navigation\n* Multiple tabs\n* History\n* Favorites\n* Search from the address bar\n* Page security status\n* Clearing cache and cookies\n\n## WebView2 APIs\n\nWebView2Browser makes use of a handful of the APIs available in WebView2. For the APIs not used here, you can find more about them in the [Microsoft Edge WebView2 Reference](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32). The following is a list of the most interesting APIs WebView2Browser uses and the feature(s) they enable.\n\nAPI | Feature(s)\n:--- | :---\nCreateCoreWebView2EnvironmentWithOptions | Used to create the environments for UI and content WebViews. Different user data directories are passed to isolate UI from web content. |\nICoreWebView2 | There are several WebViews in WebView2Browser and most features make use of members in this interface, the table below shows how they're used.\nICoreWebView2DevToolsProtocolEventReceivedEventHandler | Used along with add_DevToolsProtocolEventReceived to listen for CDP security events to update the lock icon in the browser UI. |\nICoreWebView2DevToolsProtocolEventReceiver | Used along with add_DevToolsProtocolEventReceived to listen for CDP security events to update the lock icon in the browser UI. |\nICoreWebView2ExecuteScriptCompletedHandler | Used along with ExecuteScript to get the title and favicon from the visited page. |\nICoreWebView2FocusChangedEventHandler | Used along with add_LostFocus to hide the browser options dropdown when it loses focus.\nICoreWebView2HistoryChangedEventHandler | Used along with add_HistoryChanged to update the navigation buttons in the browser UI. |\nICoreWebView2Controller | There are several WebViewControllers in WebView2Browser and we fetch the associated WebViews from them.\nICoreWebView2NavigationCompletedEventHandler | Used along with add_NavigationCompleted to update the reload button in the browser UI.\nICoreWebView2Settings | Used to disable DevTools in the browser UI.\nICoreWebView2SourceChangedEventHandler | Used along with add_SourceChanged to update the address bar in the browser UI. |\nICoreWebView2WebMessageReceivedEventHandler | This is one of the most important APIs to WebView2Browser. Most functionalities involving communication across WebViews use this.\n\nICoreWebView2 API | Feature(s)\n:--- | :---\nadd_NavigationStarting | Used to display the cancel navigation button in the controls WebView.\nadd_SourceChanged | Used to update the address bar.\nadd_HistoryChanged | Used to update go back/forward buttons.\nadd_NavigationCompleted | Used to display the reload button once a navigation completes.\nExecuteScript | Used to get the title and favicon of a visited page.\nPostWebMessageAsJson | Used to communicate WebViews. All messages use JSON to pass parameters needed.\nadd_WebMessageReceived | Used to handle web messages posted to the WebView.\nCallDevToolsProtocolMethod | Used to enable listening for security events, which will notify of security status changes in a document.\n\nICoreWebView2Controller API | Feature(s)\n:--- | :---\nget_CoreWebView2 | Used to get the CoreWebView2 associated with this CoreWebView2Controller.\nadd_LostFocus | Used to hide the options dropdown when the user clicks away of it.\n\n## Implementing the features\n\nThe sections below describe how some of the features in WebView2Browser were implemented. You can look at the source code for more details about how everything works here.\n\n* [The basics](#the-basics)\n  * [Set up the environment, create a WebView](#set-up-the-environment-create-a-webview)\n  * [Navigate to web page](#navigate-to-web-page)\n  * [Updating the address bar](#updating-the-address-bar)\n  * [Going back, going forward](#going-back-going-forward)\n* [Some interesting features](#some-interesting-features)\n  * [Communicating the WebViews](#communicating-the-webviews)\n  * [Tab handling](#tab-handling)\n  * [Updating the security icon](#updating-the-security-icon)\n  * [Populating the history](#populating-the-history)\n* [Handling JSON and URIs](#handling-json-and-uris)\n\n## The basics\n\n### Set up the environment, create a WebView\n\nWebView2 allows you to host web content in your Windows app. It exposes the globals [CreateCoreWebView2Environment](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environment) and [CreateCoreWebView2EnvironmentWithOptions](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions) from which we can create the two separate environments for the browser's UI and content.\n\n```cpp\n    // Get directory for user data. This will be kept separated from the\n    // directory for the browser UI data.\n    std::wstring userDataDirectory = GetAppDataDirectory();\n    userDataDirectory.append(L\"\\\\User Data\");\n\n    // Create WebView environment for web content requested by the user. All\n    // tabs will be created from this environment and kept isolated from the\n    // browser UI. This environment is created first so the UI can request new\n    // tabs when it's ready.\n    HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(nullptr, userDataDirectory.c_str(),\n        L\"\", Callback\u003cICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler\u003e(\n            [this](HRESULT result, ICoreWebView2Environment* env) -\u003e HRESULT\n    {\n        RETURN_IF_FAILED(result);\n\n        m_contentEnv = env;\n        HRESULT hr = InitUIWebViews();\n\n        if (!SUCCEEDED(hr))\n        {\n            OutputDebugString(L\"UI WebViews environment creation failed\\n\");\n        }\n\n        return hr;\n    }).Get());\n```\n\n```cpp\nHRESULT BrowserWindow::InitUIWebViews()\n{\n    // Get data directory for browser UI data\n    std::wstring browserDataDirectory = GetAppDataDirectory();\n    browserDataDirectory.append(L\"\\\\Browser Data\");\n\n    // Create WebView environment for browser UI. A separate data directory is\n    // used to isolate the browser UI from web content requested by the user.\n    return CreateCoreWebView2EnvironmentWithOptions(nullptr, browserDataDirectory.c_str(),\n        L\"\", Callback\u003cICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler\u003e(\n            [this](HRESULT result, ICoreWebView2Environment* env) -\u003e HRESULT\n    {\n        // Environment is ready, create the WebView\n        m_uiEnv = env;\n\n        RETURN_IF_FAILED(CreateBrowserControlsWebView());\n        RETURN_IF_FAILED(CreateBrowserOptionsWebView());\n\n        return S_OK;\n    }).Get());\n}\n```\n\nWe use the [ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2createcorewebview2environmentcompletedhandler) to create the UI WebViews once the environment is ready.\n\n```cpp\nHRESULT BrowserWindow::CreateBrowserControlsWebView()\n{\n    return m_uiEnv-\u003eCreateCoreWebView2Controller(m_hWnd, Callback\u003cICoreWebView2CreateCoreWebView2ControllerCompletedHandler\u003e(\n        [this](HRESULT result, ICoreWebView2Controller* controller) -\u003e HRESULT\n    {\n        if (!SUCCEEDED(result))\n        {\n            OutputDebugString(L\"Controls WebView creation failed\\n\");\n            return result;\n        }\n        // WebView created\n        m_controlsController = controller;\n        CheckFailure(m_controlsController-\u003eget_CoreWebView2(\u0026m_controlsWebView), L\"\");\n\n        wil::com_ptr\u003cICoreWebView2Settings\u003e settings;\n        RETURN_IF_FAILED(m_controlsWebView-\u003eget_Settings(\u0026settings));\n        RETURN_IF_FAILED(settings-\u003eput_AreDevToolsEnabled(FALSE));\n\n        RETURN_IF_FAILED(m_controlsController-\u003eadd_ZoomFactorChanged(Callback\u003cICoreWebView2ZoomFactorChangedEventHandler\u003e(\n            [](ICoreWebView2Controller* controller, IUnknown* args) -\u003e HRESULT\n        {\n            controller-\u003eput_ZoomFactor(1.0);\n            return S_OK;\n        }\n        ).Get(), \u0026m_controlsZoomToken));\n\n        RETURN_IF_FAILED(m_controlsWebView-\u003eadd_WebMessageReceived(m_uiMessageBroker.Get(), \u0026m_controlsUIMessageBrokerToken));\n        RETURN_IF_FAILED(ResizeUIWebViews());\n\n        std::wstring controlsPath = GetFullPathFor(L\"wvbrowser_ui\\\\controls_ui\\\\default.html\");\n        RETURN_IF_FAILED(m_controlsWebView-\u003eNavigate(controlsPath.c_str()));\n\n        return S_OK;\n    }).Get());\n}\n```\n\nWe're setting up a few things here. The [ICoreWebView2Settings](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2settings) interface is used to disable DevTools in the WebView powering the browser controls. We're also adding a handler for received web messages. This handler will enable us to do something when the user interacts with the controls in this WebView.\n\n### Navigate to web page\n\nYou can navigate to a web page by entering its URI in the address bar. When pressing Enter, the controls WebView will post a web message to the host app so it can navigate the active tab to the specified location. Code below shows how the host Win32 application will handle that message.\n\n```cpp\n        case MG_NAVIGATE:\n        {\n            std::wstring uri(args.at(L\"uri\").as_string());\n            std::wstring browserScheme(L\"browser://\");\n\n            if (uri.substr(0, browserScheme.size()).compare(browserScheme) == 0)\n            {\n                // No encoded search URI\n                std::wstring path = uri.substr(browserScheme.size());\n                if (path.compare(L\"favorites\") == 0 ||\n                    path.compare(L\"settings\") == 0 ||\n                    path.compare(L\"history\") == 0)\n                {\n                    std::wstring filePath(L\"wvbrowser_ui\\\\content_ui\\\\\");\n                    filePath.append(path);\n                    filePath.append(L\".html\");\n                    std::wstring fullPath = GetFullPathFor(filePath.c_str());\n                    CheckFailure(m_tabs.at(m_activeTabId)-\u003em_contentWebView-\u003eNavigate(fullPath.c_str()), L\"Can't navigate to browser page.\");\n                }\n                else\n                {\n                    OutputDebugString(L\"Requested unknown browser page\\n\");\n                }\n            }\n            else if (!SUCCEEDED(m_tabs.at(m_activeTabId)-\u003em_contentWebView-\u003eNavigate(uri.c_str())))\n            {\n                CheckFailure(m_tabs.at(m_activeTabId)-\u003em_contentWebView-\u003eNavigate(args.at(L\"encodedSearchURI\").as_string().c_str()), L\"Can't navigate to requested page.\");\n            }\n        }\n        break;\n```\n\nWebView2Browser will check the URI against browser pages (i.e. favorites, settings, history) and navigate to the requested location or use the provided URI to search Bing as a fallback.\n\n### Updating the address bar\n\nThe address bar is updated every time there is a change in the active tab's document source and along with other controls when switching tabs. Each WebView will fire an event when the state of the document changes, we can use this event to get the new source on updates and forward the change to the controls WebView (we'll also update the go back and go forward buttons).\n\n```cpp\n        // Register event handler for doc state change\n        RETURN_IF_FAILED(m_contentWebView-\u003eadd_SourceChanged(Callback\u003cICoreWebView2SourceChangedEventHandler\u003e(\n            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2SourceChangedEventArgs* args) -\u003e HRESULT\n        {\n            BrowserWindow::CheckFailure(browserWindow-\u003eHandleTabURIUpdate(m_tabId, webview), L\"Can't update address bar\");\n\n            return S_OK;\n        }).Get(), \u0026m_uriUpdateForwarderToken));\n```\n\n```cpp\nHRESULT BrowserWindow::HandleTabURIUpdate(size_t tabId, ICoreWebView2* webview)\n{\n    wil::unique_cotaskmem_string source;\n    RETURN_IF_FAILED(webview-\u003eget_Source(\u0026source));\n\n    web::json::value jsonObj = web::json::value::parse(L\"{}\");\n    jsonObj[L\"message\"] = web::json::value(MG_UPDATE_URI);\n    jsonObj[L\"args\"] = web::json::value::parse(L\"{}\");\n    jsonObj[L\"args\"][L\"tabId\"] = web::json::value::number(tabId);\n    jsonObj[L\"args\"][L\"uri\"] = web::json::value(source.get());\n\n    // ...\n\n    RETURN_IF_FAILED(PostJsonToWebView(jsonObj, m_controlsWebView.Get()));\n\n    return S_OK;\n}\n\nHRESULT BrowserWindow::HandleTabHistoryUpdate(size_t tabId, ICoreWebView2* webview)\n{\n    // ...\n\n    BOOL canGoForward = FALSE;\n    RETURN_IF_FAILED(webview-\u003eget_CanGoForward(\u0026canGoForward));\n    jsonObj[L\"args\"][L\"canGoForward\"] = web::json::value::boolean(canGoForward);\n\n    BOOL canGoBack = FALSE;\n    RETURN_IF_FAILED(webview-\u003eget_CanGoBack(\u0026canGoBack));\n    jsonObj[L\"args\"][L\"canGoBack\"] = web::json::value::boolean(canGoBack);\n\n    RETURN_IF_FAILED(PostJsonToWebView(jsonObj, m_controlsWebView.Get()));\n\n    return S_OK;\n}\n```\n\nWe have sent the `MG_UPDATE_URI` message along with the URI to the controls WebView. Now we want to reflect those changes on the tab state and update the UI if necessary.\n\n```javascript\n        case commands.MG_UPDATE_URI:\n            if (isValidTabId(args.tabId)) {\n                const tab = tabs.get(args.tabId);\n                let previousURI = tab.uri;\n\n                // Update the tab state\n                tab.uri = args.uri;\n                tab.uriToShow = args.uriToShow;\n                tab.canGoBack = args.canGoBack;\n                tab.canGoForward = args.canGoForward;\n\n                // If the tab is active, update the controls UI\n                if (args.tabId == activeTabId) {\n                    updateNavigationUI(message);\n                }\n\n                // ...\n            }\n            break;\n```\n\n### Going back, going forward\n\nEach WebView will keep a history for the navigations it has performed so we only need to connect the browser UI with the corresponding methods. If the active tab's WebView can be navigated back/forward, the buttons will post a web message to the host application when clicked.\n\nThe JavaScript side:\n\n```javascript\n    document.querySelector('#btn-forward').addEventListener('click', function(e) {\n        if (document.getElementById('btn-forward').className === 'btn') {\n            var message = {\n                message: commands.MG_GO_FORWARD,\n                args: {}\n            };\n            window.chrome.webview.postMessage(message);\n        }\n    });\n\n    document.querySelector('#btn-back').addEventListener('click', function(e) {\n        if (document.getElementById('btn-back').className === 'btn') {\n            var message = {\n                message: commands.MG_GO_BACK,\n                args: {}\n            };\n            window.chrome.webview.postMessage(message);\n        }\n    });\n```\n\nThe host application side:\n\n```cpp\n        case MG_GO_FORWARD:\n        {\n            CheckFailure(m_tabs.at(m_activeTabId)-\u003em_contentWebView-\u003eGoForward(), L\"\");\n        }\n        break;\n        case MG_GO_BACK:\n        {\n            CheckFailure(m_tabs.at(m_activeTabId)-\u003em_contentWebView-\u003eGoBack(), L\"\");\n        }\n        break;\n```\n\n### Reloading, stop navigation\n\nWe use the `NavigationStarting` event fired by a content WebView to update its associated tab loading state in the controls WebView. Similarly, when a WebView fires the `NavigationCompleted` event, we use that event to instruct the controls WebView to update the tab state. The active tab state in the controls WebView will determine whether to show the reload or the cancel button. Each of those will post a message back to the host application when clicked, so that the WebView for that tab can be reloaded or have its navigation canceled, accordingly.\n\n```javascript\nfunction reloadActiveTabContent() {\n    var message = {\n        message: commands.MG_RELOAD,\n        args: {}\n    };\n    window.chrome.webview.postMessage(message);\n}\n\n // ...\n\n    document.querySelector('#btn-reload').addEventListener('click', function(e) {\n        var btnReload = document.getElementById('btn-reload');\n        if (btnReload.className === 'btn-cancel') {\n            var message = {\n                message: commands.MG_CANCEL,\n                args: {}\n            };\n            window.chrome.webview.postMessage(message);\n        } else if (btnReload.className === 'btn') {\n            reloadActiveTabContent();\n        }\n    });\n```\n\n```cpp\n        case MG_RELOAD:\n        {\n            CheckFailure(m_tabs.at(m_activeTabId)-\u003em_contentWebView-\u003eReload(), L\"\");\n        }\n        break;\n        case MG_CANCEL:\n        {\n            CheckFailure(m_tabs.at(m_activeTabId)-\u003em_contentWebView-\u003eCallDevToolsProtocolMethod(L\"Page.stopLoading\", L\"{}\", nullptr), L\"\");\n        }\n```\n\n## Some interesting features\n\n### Communicating the WebViews\n\nWe need to communicate the WebViews powering tabs and UI so that user interactions in one have the desired effect in the other. WebView2Browser makes use of set of very useful WebView2 APIs for this purpose, including [PostWebMessageAsJson](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2#postwebmessageasjson), [add_WebMessageReceived](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2#add_webmessagereceived) and [ICoreWebView2WebMessageReceivedEventHandler](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2webmessagereceivedeventhandler). On the JavaScript side, we're making use of the `window.chrome.webview` object exposed to call the `postMessage` method and add an event lister for received messages.\n\n```cpp\nHRESULT BrowserWindow::CreateBrowserControlsWebView()\n{\n    return m_uiEnv-\u003eCreateCoreWebView2Controller(m_hWnd, Callback\u003cICoreWebView2CreateCoreWebView2ControllerCompletedHandler\u003e(\n        [this](HRESULT result, ICoreWebView2Controller* controller) -\u003e HRESULT\n    {\n        // ...\n\n        RETURN_IF_FAILED(m_controlsWebView-\u003eadd_WebMessageReceived(m_uiMessageBroker.Get(), \u0026m_controlsUIMessageBrokerToken));\n\n        // ...\n\n        return S_OK;\n    }).Get());\n}\n```\n\n```cpp\nHRESULT BrowserWindow::PostJsonToWebView(web::json::value jsonObj, ICoreWebView2* webview)\n{\n    utility::stringstream_t stream;\n    jsonObj.serialize(stream);\n\n    return webview-\u003ePostWebMessageAsJson(stream.str().c_str());\n}\n\n// ...\n\nHRESULT BrowserWindow::HandleTabNavStarting(size_t tabId, ICoreWebView2* webview)\n{\n    web::json::value jsonObj = web::json::value::parse(L\"{}\");\n    jsonObj[L\"message\"] = web::json::value(MG_NAV_STARTING);\n    jsonObj[L\"args\"] = web::json::value::parse(L\"{}\");\n    jsonObj[L\"args\"][L\"tabId\"] = web::json::value::number(tabId);\n\n    return PostJsonToWebView(jsonObj, m_controlsWebView.Get());\n}\n```\n\n```javascript\nfunction init() {\n    window.chrome.webview.addEventListener('message', messageHandler);\n    refreshControls();\n    refreshTabs();\n\n    createNewTab(true);\n}\n\n// ...\n\nfunction reloadActiveTabContent() {\n    var message = {\n        message: commands.MG_RELOAD,\n        args: {}\n    };\n    window.chrome.webview.postMessage(message);\n}\n```\n\n### Tab handling\n\nA new tab will be created whenever the user clicks on the new tab button to the right of the open tabs. The controls WebView will post a message to the host application to create the WebView for that tab and create an object tracking its state.\n\n```javascript\nfunction createNewTab(shouldBeActive) {\n    const tabId = getNewTabId();\n\n    var message = {\n        message: commands.MG_CREATE_TAB,\n        args: {\n            tabId: parseInt(tabId),\n            active: shouldBeActive || false\n        }\n    };\n\n    window.chrome.webview.postMessage(message);\n\n    tabs.set(parseInt(tabId), {\n        title: 'New Tab',\n        uri: '',\n        uriToShow: '',\n        favicon: 'img/favicon.png',\n        isFavorite: false,\n        isLoading: false,\n        canGoBack: false,\n        canGoForward: false,\n        securityState: 'unknown',\n        historyItemId: INVALID_HISTORY_ID\n    });\n\n    loadTabUI(tabId);\n\n    if (shouldBeActive) {\n        switchToTab(tabId, false);\n    }\n}\n```\n\nOn the host app side, the registered [ICoreWebView2WebMessageReceivedEventHandler](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2webmessagereceivedeventhandler) will catch the message and create the WebView for that tab.\n\n```cpp\n        case MG_CREATE_TAB:\n        {\n            size_t id = args.at(L\"tabId\").as_number().to_uint32();\n            bool shouldBeActive = args.at(L\"active\").as_bool();\n            std::unique_ptr\u003cTab\u003e newTab = Tab::CreateNewTab(m_hWnd, m_contentEnv.Get(), id, shouldBeActive);\n\n            std::map\u003csize_t, std::unique_ptr\u003cTab\u003e\u003e::iterator it = m_tabs.find(id);\n            if (it == m_tabs.end())\n            {\n                m_tabs.insert(std::pair\u003csize_t,std::unique_ptr\u003cTab\u003e\u003e(id, std::move(newTab)));\n            }\n            else\n            {\n                m_tabs.at(id)-\u003em_contentWebView-\u003eClose();\n                it-\u003esecond = std::move(newTab);\n            }\n        }\n        break;\n```\n\n```cpp\nstd::unique_ptr\u003cTab\u003e Tab::CreateNewTab(HWND hWnd, ICoreWebView2Environment* env, size_t id, bool shouldBeActive)\n{\n    std::unique_ptr\u003cTab\u003e tab = std::make_unique\u003cTab\u003e();\n\n    tab-\u003em_parentHWnd = hWnd;\n    tab-\u003em_tabId = id;\n    tab-\u003eSetMessageBroker();\n    tab-\u003eInit(env, shouldBeActive);\n\n    return tab;\n}\n\nHRESULT Tab::Init(ICoreWebView2Environment* env, bool shouldBeActive)\n{\n    return env-\u003eCreateCoreWebView2Controller(m_parentHWnd, Callback\u003cICoreWebView2CreateCoreWebView2ControllerCompletedHandler\u003e(\n        [this, shouldBeActive](HRESULT result, ICoreWebView2Controller* controller) -\u003e HRESULT {\n        if (!SUCCEEDED(result))\n        {\n            OutputDebugString(L\"Tab WebView creation failed\\n\");\n            return result;\n        }\n        m_contentController = controller;\n        BrowserWindow::CheckFailure(m_contentController-\u003eget_CoreWebView2(\u0026m_contentWebView), L\"\");\n        BrowserWindow* browserWindow = reinterpret_cast\u003cBrowserWindow*\u003e(GetWindowLongPtr(m_parentHWnd, GWLP_USERDATA));\n        RETURN_IF_FAILED(m_contentWebView-\u003eadd_WebMessageReceived(m_messageBroker.Get(), \u0026m_messageBrokerToken));\n\n        // Register event handler for history change\n        RETURN_IF_FAILED(m_contentWebView-\u003eadd_HistoryChanged(Callback\u003cICoreWebView2HistoryChangedEventHandler\u003e(\n            [this, browserWindow](ICoreWebView2* webview, IUnknown* args) -\u003e HRESULT\n        {\n            BrowserWindow::CheckFailure(browserWindow-\u003eHandleTabHistoryUpdate(m_tabId, webview), L\"Can't update go back/forward buttons.\");\n\n            return S_OK;\n        }).Get(), \u0026m_historyUpdateForwarderToken));\n\n        // Register event handler for source change\n        RETURN_IF_FAILED(m_contentWebView-\u003eadd_SourceChanged(Callback\u003cICoreWebView2SourceChangedEventHandler\u003e(\n            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2SourceChangedEventArgs* args) -\u003e HRESULT\n        {\n            BrowserWindow::CheckFailure(browserWindow-\u003eHandleTabURIUpdate(m_tabId, webview), L\"Can't update address bar\");\n\n            return S_OK;\n        }).Get(), \u0026m_uriUpdateForwarderToken));\n\n        RETURN_IF_FAILED(m_contentWebView-\u003eadd_NavigationStarting(Callback\u003cICoreWebView2NavigationStartingEventHandler\u003e(\n            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) -\u003e HRESULT\n        {\n            BrowserWindow::CheckFailure(browserWindow-\u003eHandleTabNavStarting(m_tabId, webview), L\"Can't update reload button\");\n\n            return S_OK;\n        }).Get(), \u0026m_navStartingToken));\n\n        RETURN_IF_FAILED(m_contentWebView-\u003eadd_NavigationCompleted(Callback\u003cICoreWebView2NavigationCompletedEventHandler\u003e(\n            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2NavigationCompletedEventArgs* args) -\u003e HRESULT\n        {\n            BrowserWindow::CheckFailure(browserWindow-\u003eHandleTabNavCompleted(m_tabId, webview, args), L\"Can't update reload button\");\n            return S_OK;\n        }).Get(), \u0026m_navCompletedToken));\n\n        // Handle security state updates\n\n        RETURN_IF_FAILED(m_contentWebView-\u003eNavigate(L\"https://www.bing.com\"));\n        browserWindow-\u003eHandleTabCreated(m_tabId, shouldBeActive);\n\n        return S_OK;\n    }).Get());\n}\n```\n\nThe tab registers all handlers so it can forward updates to the controls WebView when events fire. The tab is ready and will be shown on the content area of the browser. Clicking on a tab in the controls WebView will post a message to the host application, which will in turn hide the WebView for the previously active tab and show the one for the clicked tab.\n\n```cpp\nHRESULT BrowserWindow::SwitchToTab(size_t tabId)\n{\n    size_t previousActiveTab = m_activeTabId;\n\n    RETURN_IF_FAILED(m_tabs.at(tabId)-\u003eResizeWebView());\n    RETURN_IF_FAILED(m_tabs.at(tabId)-\u003em_contentWebView-\u003eput_IsVisible(TRUE));\n    m_activeTabId = tabId;\n\n    if (previousActiveTab != INVALID_TAB_ID \u0026\u0026 previousActiveTab != m_activeTabId)\n    {\n        RETURN_IF_FAILED(m_tabs.at(previousActiveTab)-\u003em_contentWebView-\u003eput_IsVisible(FALSE));\n    }\n\n    return S_OK;\n}\n```\n\n### Updating the security icon\n\nWe use the [CallDevToolsProtocolMethod](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2#calldevtoolsprotocolmethod) to enable listening for security events. Whenever a `securityStateChanged` event is fired, we will use the new state to update the security icon on the controls WebView.\n\n```cpp\n        // Enable listening for security events to update secure icon\n        RETURN_IF_FAILED(m_contentWebView-\u003eCallDevToolsProtocolMethod(L\"Security.enable\", L\"{}\", nullptr));\n\n        BrowserWindow::CheckFailure(m_contentWebView-\u003eGetDevToolsProtocolEventReceiver(L\"Security.securityStateChanged\", \u0026m_securityStateChangedReceiver), L\"\");\n\n        // Forward security status updates to browser\n        RETURN_IF_FAILED(m_securityStateChangedReceiver-\u003eadd_DevToolsProtocolEventReceived(Callback\u003cICoreWebView2DevToolsProtocolEventReceivedEventHandler\u003e(\n            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -\u003e HRESULT\n        {\n            BrowserWindow::CheckFailure(browserWindow-\u003eHandleTabSecurityUpdate(m_tabId, webview, args), L\"Can't update security icon\");\n            return S_OK;\n        }).Get(), \u0026m_securityUpdateToken));\n```\n\n```cpp\nHRESULT BrowserWindow::HandleTabSecurityUpdate(size_t tabId, ICoreWebView2* webview, ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args)\n{\n    wil::unique_cotaskmem_string jsonArgs;\n    RETURN_IF_FAILED(args-\u003eget_ParameterObjectAsJson(\u0026jsonArgs));\n    web::json::value securityEvent = web::json::value::parse(jsonArgs.get());\n\n    web::json::value jsonObj = web::json::value::parse(L\"{}\");\n    jsonObj[L\"message\"] = web::json::value(MG_SECURITY_UPDATE);\n    jsonObj[L\"args\"] = web::json::value::parse(L\"{}\");\n    jsonObj[L\"args\"][L\"tabId\"] = web::json::value::number(tabId);\n    jsonObj[L\"args\"][L\"state\"] = securityEvent.at(L\"securityState\");\n\n    return PostJsonToWebView(jsonObj, m_controlsWebView.Get());\n}\n```\n\n```javascript\n        case commands.MG_SECURITY_UPDATE:\n            if (isValidTabId(args.tabId)) {\n                const tab = tabs.get(args.tabId);\n                tab.securityState = args.state;\n\n                if (args.tabId == activeTabId) {\n                    updateNavigationUI(message);\n                }\n            }\n            break;\n```\n\n### Populating the history\n\nWebView2Browser uses IndexedDB in the controls WebView to store history items, just an example of how WebView2 enables you to access standard web technologies as you would in the browser. The item for a navigation will be created as soon as the URI is updated. These items are then retrieved by the history UI in a tab making use of `window.chrome.postMessage`.\n\nIn this case, most functionality is implemented using JavaScript on both ends (controls WebView and content WebView loading the UI) so the host application is only acting as a message broker to communicate those ends.\n\n```javascript\n        case commands.MG_UPDATE_URI:\n            if (isValidTabId(args.tabId)) {\n                // ...\n\n                // Don't add history entry if URI has not changed\n                if (tab.uri == previousURI) {\n                    break;\n                }\n\n                // Filter URIs that should not appear in history\n                if (!tab.uri || tab.uri == 'about:blank') {\n                    tab.historyItemId = INVALID_HISTORY_ID;\n                    break;\n                }\n\n                if (tab.uriToShow \u0026\u0026 tab.uriToShow.substring(0, 10) == 'browser://') {\n                    tab.historyItemId = INVALID_HISTORY_ID;\n                    break;\n                }\n\n                addHistoryItem(historyItemFromTab(args.tabId), (id) =\u003e {\n                    tab.historyItemId = id;\n                });\n            }\n            break;\n```\n\n```javascript\nfunction addHistoryItem(item, callback) {\n    queryDB((db) =\u003e {\n        let transaction = db.transaction(['history'], 'readwrite');\n        let historyStore = transaction.objectStore('history');\n\n        // Check if an item for this URI exists on this day\n        let currentDate = new Date();\n        let year = currentDate.getFullYear();\n        let month = currentDate.getMonth();\n        let date = currentDate.getDate();\n        let todayDate = new Date(year, month, date);\n\n        let existingItemsIndex = historyStore.index('stampedURI');\n        let lowerBound = [item.uri, todayDate];\n        let upperBound = [item.uri, currentDate];\n        let range = IDBKeyRange.bound(lowerBound, upperBound);\n        let request = existingItemsIndex.openCursor(range);\n\n        request.onsuccess = function(event) {\n            let cursor = event.target.result;\n            if (cursor) {\n                // There's an entry for this URI, update the item\n                cursor.value.timestamp = item.timestamp;\n                let updateRequest = cursor.update(cursor.value);\n\n                updateRequest.onsuccess = function(event) {\n                    if (callback) {\n                        callback(event.target.result.primaryKey);\n                    }\n                };\n            } else {\n                // No entry for this URI, add item\n                let addItemRequest = historyStore.add(item);\n\n                addItemRequest.onsuccess = function(event) {\n                    if (callback) {\n                        callback(event.target.result);\n                    }\n                };\n            }\n        };\n\n    });\n}\n```\n\n## Handling JSON and URIs\n\nWebView2Browser uses Microsoft's [cpprestsdk (Casablanca)](https://github.com/Microsoft/cpprestsdk) to handle all JSON in the C++ side of things. IUri and CreateUri are also used to parse file paths into URIs and can be used to for other URIs as well.\n\n## Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmicrosoftedge%2Fwebview2browser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmicrosoftedge%2Fwebview2browser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmicrosoftedge%2Fwebview2browser/lists"}