├── .gitattributes ├── .gitmodules ├── vcpkg.json ├── NativeWebView.qml ├── main.cpp ├── .gitignore ├── LICENSE ├── customwebview.h ├── Main.qml ├── CMakeLists.txt ├── README.md ├── customwebview_mac.mm └── customwebview_win.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vcpkg"] 2 | path = vcpkg 3 | url = https://github.com/Microsoft/vcpkg.git 4 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nativewebview-qt-integration", 3 | "version-string": "0.1.0", 4 | "dependencies": [ 5 | { 6 | "name": "webview2", 7 | "platform": "windows" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /NativeWebView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | 3 | WindowContainer { 4 | property url url 5 | 6 | window: webViewWindow 7 | 8 | Component.onCompleted: { 9 | console.log("WindowContainer - component completed"); 10 | webViewWindow.reset(); 11 | } 12 | 13 | Connections { 14 | target: webViewWindow 15 | function onIsInitializedChanged() { 16 | if (webViewWindow.isInitialized) { 17 | console.log("NativeWebView is properly initalized now..."); 18 | webViewWindow.updateWebViewBounds(width, height); 19 | } 20 | } 21 | } 22 | 23 | onUrlChanged: { 24 | webViewWindow.url = url; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "customwebview.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char *argv[]) { 9 | QGuiApplication app(argc, argv); 10 | 11 | QQmlApplicationEngine engine; 12 | 13 | // Our custom webviews for each platform, we're using native webviews instead 14 | // of bundling full Chromium thru QtWebEngine 15 | 16 | CustomWebView *customWebView = new CustomWebView(); 17 | 18 | engine.rootContext()->setContextProperty("webViewWindow", customWebView); 19 | 20 | QObject::connect( 21 | &engine, &QQmlApplicationEngine::objectCreationFailed, &app, 22 | []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); 23 | engine.loadFromModule("NativeWebView", "Main"); 24 | 25 | return app.exec(); 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | *.slo 3 | *.lo 4 | *.o 5 | *.a 6 | *.la 7 | *.lai 8 | *.so 9 | *.so.* 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | object_script.*.Release 15 | object_script.*.Debug 16 | *_plugin_import.cpp 17 | /.qmake.cache 18 | /.qmake.stash 19 | *.pro.user 20 | *.pro.user.* 21 | *.qbs.user 22 | *.qbs.user.* 23 | *.moc 24 | moc_*.cpp 25 | moc_*.h 26 | qrc_*.cpp 27 | ui_*.h 28 | *.qmlc 29 | *.jsc 30 | Makefile* 31 | *build-* 32 | *.qm 33 | *.prl 34 | 35 | # Qt unit tests 36 | target_wrapper.* 37 | 38 | # QtCreator 39 | *.autosave 40 | 41 | # QtCreator Qml 42 | *.qmlproject.user 43 | *.qmlproject.user.* 44 | 45 | # QtCreator CMake 46 | CMakeLists.txt.user* 47 | 48 | # QtCreator 4.8< compilation database 49 | compile_commands.json 50 | 51 | # QtCreator local machine specific files for imported projects 52 | *creator.user* 53 | 54 | *_qmlcache.qrc 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Bob Jelica 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /customwebview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #ifdef Q_OS_WIN 6 | #include 7 | #include 8 | class ICoreWebView2Controller; 9 | class ICoreWebView2; 10 | #elif defined(Q_OS_MAC) 11 | Q_FORWARD_DECLARE_OBJC_CLASS(WKWebView); 12 | Q_FORWARD_DECLARE_OBJC_CLASS(WKNavigation); 13 | #endif 14 | 15 | class CustomWebView : public QWindow { 16 | Q_OBJECT 17 | Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) 18 | Q_PROPERTY(bool isInitialized READ isInitialized NOTIFY isInitializedChanged) 19 | 20 | public: 21 | explicit CustomWebView(QWindow *parent = nullptr); 22 | ~CustomWebView(); 23 | 24 | bool isInitialized() const { return m_isInitialized; } 25 | QUrl url() const { return m_url; } 26 | 27 | Q_INVOKABLE void setUrl(const QUrl &url); 28 | Q_INVOKABLE void reset(); 29 | 30 | signals: 31 | void isInitializedChanged(); 32 | void urlChanged(const QUrl &url); 33 | 34 | public slots: 35 | void updateWebViewBounds(int width, int height); 36 | void cleanup(); 37 | 38 | protected: 39 | void resizeEvent(QResizeEvent *event) override; 40 | 41 | private slots: 42 | void initialize(); 43 | 44 | private: 45 | bool m_isInitialized; 46 | QUrl m_pendingUrl; 47 | QUrl m_url; 48 | QWindow *m_childWindow; 49 | 50 | #ifdef Q_OS_WIN 51 | void setupNavigationEventHandler(); 52 | void initializeWebView(); 53 | Microsoft::WRL::ComPtr m_webViewController; 54 | Microsoft::WRL::ComPtr m_webView; 55 | HWND m_childWindowHandle; 56 | 57 | QString m_userDataFolder; 58 | EventRegistrationToken m_navigationStartingToken; 59 | #elif defined(Q_OS_MAC) 60 | WKWebView *_wkWebView; 61 | 62 | public: 63 | WKNavigation *wkNavigation; 64 | #endif 65 | }; 66 | -------------------------------------------------------------------------------- /Main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Window 3 | import QtQuick.Controls 4 | import QtQuick.Layouts 5 | 6 | ApplicationWindow { 7 | height: 780 8 | title: qsTr("Custom WebView Example") 9 | visible: true 10 | width: 1024 11 | 12 | ColumnLayout { 13 | anchors.fill: parent 14 | spacing: 10 15 | 16 | RowLayout { 17 | Layout.fillWidth: true 18 | 19 | TextField { 20 | id: urlInput 21 | 22 | Layout.fillWidth: true 23 | placeholderText: "Enter URL" 24 | text: webView.url 25 | 26 | onAccepted: webView.url = urlInput.text 27 | } 28 | Button { 29 | text: "Load" 30 | 31 | onClicked: webView.url = urlInput.text 32 | } 33 | } 34 | 35 | // You could just use NativeWebView {} anywhere here, but i wanted to test the integration 36 | // in a more complex app, like scrolling the view etc. 37 | ScrollView { 38 | Layout.fillHeight: true 39 | Layout.fillWidth: true 40 | clip: true 41 | 42 | ColumnLayout { 43 | spacing: 20 44 | width: parent.width 45 | 46 | Rectangle { 47 | Layout.fillWidth: true 48 | color: "lightblue" 49 | height: 100 50 | 51 | Text { 52 | anchors.centerIn: parent 53 | text: "Item above WebView" 54 | } 55 | } 56 | Item { 57 | Layout.fillWidth: true 58 | height: 600 59 | 60 | NativeWebView { 61 | id: webView 62 | 63 | anchors.fill: parent 64 | url: "https://mollo.io" 65 | } 66 | } 67 | Rectangle { 68 | Layout.fillWidth: true 69 | color: "lightgreen" 70 | height: 100 71 | 72 | Text { 73 | anchors.centerIn: parent 74 | text: "Item below WebView" 75 | } 76 | } 77 | Repeater { 78 | model: 5 79 | 80 | delegate: Rectangle { 81 | Layout.fillWidth: true 82 | color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1) 83 | height: 100 84 | 85 | Text { 86 | anchors.centerIn: parent 87 | text: "Scrollable Item " + (index + 1) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | # Setup VCPKG script with CMake (note: should be placed before project() call) 4 | list(APPEND VCPKG_FEATURE_FLAGS "versions") 5 | set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file") 6 | 7 | message(" [INFO] VCPKG CMAKE_TOOLCHAIN_FILE = ${CMAKE_TOOLCHAIN_FILE}") 8 | message(" [INFO] VCPKG TRIPLET = ${VCPKG_TARGET_TRIPLET}") 9 | 10 | project(NativeWebView VERSION 0.1 LANGUAGES C CXX) 11 | 12 | set(CMAKE_CXX_STANDARD 20) 13 | set(CMAKE_CXX_STANDARD_REQUIRED OFF) 14 | 15 | if(WIN32) 16 | set(VCPKG_TARGET_TRIPLET "x64-windows" CACHE STRING "") 17 | endif() 18 | 19 | 20 | find_package(Qt6 6.5 REQUIRED COMPONENTS Core Quick QuickWidgets) 21 | 22 | qt_standard_project_setup(REQUIRES 6.5) 23 | 24 | # WebView2 setup 25 | if(MSVC) 26 | find_package(unofficial-webview2 CONFIG REQUIRED) 27 | endif() 28 | 29 | set(PROJECT_SOURCES 30 | main.cpp 31 | customwebview.h 32 | 33 | ) 34 | 35 | # Platform-specific implementations of Custom Web View. 36 | # On macOS we use the OS-provided WebKit (Safari engine) 37 | # On Windows we use WebView2, which is the Edge engine and already provided by the OS 38 | if (APPLE) 39 | set(PROJECT_SOURCES ${PROJECT_SOURCES} customwebview_mac.mm) 40 | elseif (WIN32 AND MSVC) 41 | set(PROJECT_SOURCES ${PROJECT_SOURCES} customwebview_win.cpp) 42 | elseif (UNIX AND ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 43 | set(PROJECT_SOURCES ${PROJECT_SOURCES} customwebview_nix.cpp) 44 | endif () 45 | 46 | qt_add_executable(NativeWebView 47 | ${PROJECT_SOURCES} 48 | ) 49 | 50 | qt_add_qml_module(NativeWebView 51 | URI NativeWebView 52 | VERSION 1.0 53 | QML_FILES 54 | Main.qml 55 | NativeWebView.qml 56 | SOURCES 57 | ) 58 | 59 | target_link_libraries(NativeWebView PRIVATE 60 | Qt::Core 61 | Qt::Quick 62 | ) 63 | 64 | if (APPLE) 65 | target_link_libraries(${PROJECT_NAME} 66 | PUBLIC 67 | "-framework WebKit" 68 | ) 69 | elseif(WIN32) 70 | target_link_libraries(${PROJECT_NAME} PRIVATE user32.lib gdi32.lib) 71 | target_link_libraries(${PROJECT_NAME} PRIVATE unofficial::webview2::webview2) 72 | endif() 73 | 74 | 75 | # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. 76 | # If you are developing for iOS or macOS you should consider setting an 77 | # explicit, fixed bundle identifier manually though. 78 | set_target_properties(NativeWebView PROPERTIES 79 | # MACOSX_BUNDLE_GUI_IDENTIFIER com.example.NativeWebView 80 | MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} 81 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} 82 | MACOSX_BUNDLE TRUE 83 | WIN32_EXECUTABLE TRUE 84 | ) 85 | 86 | 87 | 88 | include(GNUInstallDirs) 89 | install(TARGETS NativeWebView 90 | BUNDLE DESTINATION . 91 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 92 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 93 | ) 94 | 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom WebView Qt/QML Component 2 | 3 | A lightweight custom WebView component for Qt/QML applications that utilizes the native OS-bundled WebView to display web content. This approach significantly reduces the application size by avoiding the need to bundle the full QtWebEngine. 4 | Repo is a full example application showing the usage. 5 | 6 | ## Features 7 | 8 | - Lightweight alternative to QtWebEngine 9 | - Uses native OS WebView components 10 | - Seamless integration with Qt/QML applications 11 | - Supports Qt 6.7 and above 12 | - Works on Windows (using WebView2 Edge engine) and macOS (using WebKit) 13 | - No *nix support currently, but contributions are welcome 14 | 15 | ## Why? 16 | 17 | We needed to show web content in our application [Mollo](https://mollo.io), and developed this since we didn't want to bundle Chromium in our app just to show some web content. 18 | 19 | ## Requirements 20 | 21 | This project relies on WindowContainer, which was introduced in Qt 6.7. We use it to integrate foreign native windows (i.e., the WebView itself). 22 | 23 | ## Installation 24 | 25 | To use Custom WebView Qt/QML Component in your project, you need to have Qt 6.7 or higher installed on your system. 26 | 27 | ### Prerequisites 28 | 29 | - Qt 6.7+ 30 | - CMake 3.14+ 31 | - A C++17 compatible compiler 32 | 33 | ### Building from Source 34 | 35 | 1. Clone the repository: 36 | ``` 37 | git clone https://github.com/yourusername/custom-webview-qt.git 38 | cd custom-webview-qt 39 | ``` 40 | 41 | 2. Fetch submodules: 42 | ``` 43 | git submodule update --init --recursive 44 | ``` 45 | 46 | 3. Create a build directory: 47 | ``` 48 | mkdir build && cd build 49 | ``` 50 | 51 | 4. Configure the project with CMake: 52 | ``` 53 | cmake .. 54 | ``` 55 | 56 | 5. Build the project: 57 | ``` 58 | cmake --build . 59 | ``` 60 | 61 | 6. (Optional) Install the library: 62 | ``` 63 | sudo cmake --install . 64 | ``` 65 | 66 | ## Usage 67 | 68 | To use the Custom WebView component in your application: 69 | 70 | 1. In your main.cpp, add the following: 71 | 72 | ```cpp 73 | #include 74 | 75 | // ... 76 | 77 | CustomWebView *customWebView = new CustomWebView(); 78 | engine.rootContext()->setContextProperty("webViewWindow", customWebView); 79 | ``` 80 | 81 | 2. Then use the provided NativeWebView.qml in your QML as any other QML component: 82 | 83 | ```qml 84 | import QtQuick 85 | import QtQuick.Window 86 | 87 | Window { 88 | width: 640 89 | height: 480 90 | visible: true 91 | title: qsTr("Custom WebView Example") 92 | 93 | NativeWebView { 94 | anchors.fill: parent 95 | url: "https://mollo.io" 96 | } 97 | } 98 | ``` 99 | 100 | ## Contributing 101 | 102 | Contributions are welcome! Please feel free to submit a Pull Request, especially for Linux support. 103 | 104 | ## License 105 | 106 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 107 | -------------------------------------------------------------------------------- /customwebview_mac.mm: -------------------------------------------------------------------------------- 1 | // customwebview.mm 2 | #include "customwebview.h" 3 | 4 | #ifdef Q_OS_MAC 5 | #include 6 | #include 7 | #import 8 | 9 | @interface CustomWebViewDelegate : NSObject { 10 | CustomWebView *qWebView; 11 | } 12 | - (CustomWebViewDelegate *)initWithWebView:(CustomWebView *)webViewPrivate; 13 | - (void)webView:(WKWebView *)webView 14 | didFinishNavigation:(WKNavigation *)navigation; 15 | @end 16 | 17 | @implementation CustomWebViewDelegate 18 | 19 | - (CustomWebViewDelegate *)initWithWebView:(CustomWebView *)webViewPrivate { 20 | if ((self = [super init])) { 21 | Q_ASSERT(webViewPrivate); 22 | qWebView = webViewPrivate; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)webView:(WKWebView *)webView 28 | didFinishNavigation:(WKNavigation *)navigation { 29 | Q_UNUSED(webView); 30 | if (qWebView->wkNavigation != navigation) 31 | return; 32 | Q_EMIT qWebView->urlChanged(qWebView->url()); 33 | } 34 | 35 | @end 36 | 37 | CustomWebView::CustomWebView(QWindow *parent) 38 | : QWindow(parent), m_isInitialized(false), m_childWindow(nullptr), 39 | _wkWebView(nil) { 40 | setFlags(Qt::FramelessWindowHint); 41 | } 42 | 43 | CustomWebView::~CustomWebView() { cleanup(); } 44 | 45 | void CustomWebView::initialize() { 46 | if (!m_isInitialized) { 47 | try { 48 | WKWebView *webView = 49 | [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, width(), height())]; 50 | #ifdef DEBUG 51 | [webView.configuration.preferences setValue:@YES 52 | forKey:@"developerExtrasEnabled"]; 53 | #endif 54 | WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore]; 55 | NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes]; 56 | NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0]; 57 | [dataStore removeDataOfTypes:websiteDataTypes 58 | modifiedSince:dateFrom 59 | completionHandler:^{ 60 | NSLog(@"All website data has been cleared"); 61 | }]; 62 | 63 | _wkWebView = webView; 64 | _wkWebView.navigationDelegate = 65 | [[CustomWebViewDelegate alloc] initWithWebView:this]; 66 | 67 | // Create a child QWindow from the WKWebView 68 | m_childWindow = QWindow::fromWinId(WId(_wkWebView)); 69 | if (m_childWindow) { 70 | m_childWindow->setParent(this); 71 | m_childWindow->setFlags(Qt::WindowType::Widget); 72 | } 73 | 74 | m_isInitialized = true; 75 | Q_EMIT isInitializedChanged(); 76 | 77 | // Load the pending URL if it exists 78 | if (!m_pendingUrl.isEmpty()) { 79 | setUrl(m_pendingUrl); 80 | m_pendingUrl.clear(); 81 | } 82 | 83 | } catch (const std::exception &e) { 84 | qDebug() << __FUNCTION__ << e.what(); 85 | } 86 | } 87 | } 88 | 89 | void CustomWebView::cleanup() { 90 | if (_wkWebView) { 91 | [_wkWebView removeFromSuperview]; 92 | [_wkWebView release]; 93 | _wkWebView = nil; 94 | } 95 | 96 | if (m_childWindow) { 97 | m_childWindow->setParent(nullptr); 98 | delete m_childWindow; 99 | m_childWindow = nullptr; 100 | } 101 | 102 | m_isInitialized = false; 103 | Q_EMIT isInitializedChanged(); 104 | } 105 | 106 | void CustomWebView::reset() { 107 | cleanup(); 108 | initialize(); 109 | } 110 | 111 | void CustomWebView::setUrl(const QUrl &url) { 112 | if (m_isInitialized && _wkWebView) { 113 | NSURL *nsurl = url.toNSURL(); 114 | [_wkWebView loadRequest:[NSURLRequest requestWithURL:nsurl]]; 115 | qDebug() << __FUNCTION__ << "Navigated to: " << url; 116 | } else { 117 | m_pendingUrl = url; 118 | } 119 | } 120 | 121 | void CustomWebView::updateWebViewBounds(int width, int height) { 122 | if (_wkWebView) { 123 | [_wkWebView setFrame:NSMakeRect(0, 0, width, height)]; 124 | } 125 | 126 | if (m_childWindow) { 127 | m_childWindow->setGeometry(0, 0, width, height); 128 | } 129 | } 130 | 131 | void CustomWebView::resizeEvent(QResizeEvent *event) { 132 | QWindow::resizeEvent(event); 133 | updateWebViewBounds(event->size().width(), event->size().height()); 134 | } 135 | 136 | #endif // Q_OS_MAC 137 | -------------------------------------------------------------------------------- /customwebview_win.cpp: -------------------------------------------------------------------------------- 1 | #include "customwebview.h" 2 | 3 | #include "WebView2.h" 4 | #include 5 | #include 6 | 7 | using namespace Microsoft::WRL; 8 | 9 | CustomWebView::CustomWebView(QWindow *parent) 10 | : QWindow(parent), m_isInitialized(false), m_pendingUrl(), 11 | m_childWindow(nullptr), m_childWindowHandle(nullptr) { 12 | // Set up a custom User Data Folder 13 | QString appDataLocation = 14 | QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); 15 | m_userDataFolder = QDir(appDataLocation).filePath("WebView2UserData"); 16 | QDir().mkpath(m_userDataFolder); 17 | 18 | setFlags(Qt::FramelessWindowHint); 19 | } 20 | 21 | CustomWebView::~CustomWebView() { cleanup(); } 22 | 23 | void CustomWebView::reset() { 24 | cleanup(); 25 | initialize(); 26 | } 27 | 28 | void CustomWebView::initialize() { 29 | if (!m_isInitialized) { 30 | // Create a child window 31 | m_childWindowHandle = ::CreateWindowEx( 32 | 0, L"Static", L"", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, 33 | reinterpret_cast(winId()), NULL, nullptr, nullptr); 34 | 35 | if (m_childWindowHandle) { 36 | // Create a QWindow from the child window handle 37 | m_childWindow = 38 | QWindow::fromWinId(reinterpret_cast(m_childWindowHandle)); 39 | if (m_childWindow) { 40 | // Set the child window as a child of this CustomWebView 41 | m_childWindow->setParent(this); 42 | m_childWindow->setFlags(Qt::WindowType::Widget); 43 | 44 | initializeWebView(); 45 | } 46 | } 47 | } 48 | } 49 | 50 | void CustomWebView::cleanup() { 51 | if (m_webView) { 52 | m_webView->remove_NavigationStarting(m_navigationStartingToken); 53 | } 54 | if (m_webViewController) { 55 | m_webViewController->Close(); 56 | m_webViewController = nullptr; 57 | } 58 | m_webView = nullptr; 59 | if (m_childWindow) { 60 | delete m_childWindow; 61 | m_childWindow = nullptr; 62 | } 63 | if (m_childWindowHandle) { 64 | ::DestroyWindow(m_childWindowHandle); 65 | m_childWindowHandle = nullptr; 66 | } 67 | 68 | m_isInitialized = false; 69 | emit isInitializedChanged(); 70 | } 71 | 72 | void CustomWebView::initializeWebView() { 73 | // Create WebView2 Environment Options 74 | auto options = Microsoft::WRL::Make(); 75 | options->put_AllowSingleSignOnUsingOSPrimaryAccount(FALSE); 76 | 77 | // Convert QString to LPCWSTR 78 | std::wstring userDataFolderW = m_userDataFolder.toStdWString(); 79 | LPCWSTR userDataFolderLPCW = userDataFolderW.c_str(); 80 | 81 | HRESULT hr = CreateCoreWebView2EnvironmentWithOptions( 82 | nullptr, userDataFolderLPCW, options.Get(), 83 | Callback( 84 | [this](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { 85 | if (SUCCEEDED(result)) { 86 | env->CreateCoreWebView2Controller( 87 | m_childWindowHandle, 88 | Callback< 89 | ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( 90 | [this](HRESULT result, 91 | ICoreWebView2Controller *controller) -> HRESULT { 92 | if (SUCCEEDED(result)) { 93 | m_webViewController = controller; 94 | m_webViewController->AddRef(); 95 | 96 | m_webViewController->get_CoreWebView2(&m_webView); 97 | m_webView->AddRef(); 98 | 99 | setupNavigationEventHandler(); 100 | 101 | // Hide context menu if not in debug mode 102 | #ifndef DEBUG 103 | ICoreWebView2Settings *settings; 104 | m_webView->get_Settings(&settings); 105 | settings->put_AreDefaultContextMenusEnabled(FALSE); 106 | #endif 107 | 108 | m_webViewController->put_IsVisible(TRUE); 109 | m_isInitialized = true; 110 | emit isInitializedChanged(); 111 | 112 | // Load the pending URL if it exists 113 | if (!m_pendingUrl.isEmpty()) { 114 | setUrl(m_pendingUrl); 115 | m_pendingUrl.clear(); 116 | } 117 | } 118 | return S_OK; 119 | }) 120 | .Get()); 121 | } 122 | return S_OK; 123 | }) 124 | .Get()); 125 | 126 | Q_UNUSED(hr); 127 | } 128 | 129 | void CustomWebView::setupNavigationEventHandler() { 130 | if (m_webView) { 131 | m_webView->add_NavigationStarting( 132 | Callback( 133 | [this](ICoreWebView2 *sender, 134 | ICoreWebView2NavigationStartingEventArgs *args) -> HRESULT { 135 | Q_UNUSED(sender); 136 | LPWSTR url; 137 | args->get_Uri(&url); 138 | QString qUrl = QString::fromWCharArray(url); 139 | CoTaskMemFree(url); 140 | 141 | QUrl newUrl(qUrl); 142 | if (m_url != newUrl) { 143 | m_url = newUrl; 144 | emit urlChanged(m_url); 145 | } 146 | return S_OK; 147 | }) 148 | .Get(), 149 | &m_navigationStartingToken); 150 | } 151 | } 152 | 153 | void CustomWebView::setUrl(const QUrl &url) { 154 | if (m_isInitialized && m_webView) { 155 | m_webView->Navigate(reinterpret_cast(url.toString().utf16())); 156 | m_url = url; 157 | emit urlChanged(m_url); 158 | } else { 159 | m_pendingUrl = url; 160 | } 161 | } 162 | 163 | void CustomWebView::updateWebViewBounds(int width, int height) { 164 | if (m_webViewController && m_webView) { 165 | RECT bounds; 166 | bounds.left = 0; 167 | bounds.top = 0; 168 | bounds.right = width * devicePixelRatio(); 169 | bounds.bottom = height * devicePixelRatio(); 170 | m_webViewController->put_Bounds(bounds); 171 | 172 | // Update the child window size as well 173 | if (m_childWindow) { 174 | m_childWindow->setGeometry(0, 0, width, height); 175 | } 176 | 177 | qDebug() << __FUNCTION__ << "new bounds, height: " << height 178 | << ", width: " << width 179 | << ", device pixel ratio: " << devicePixelRatio(); 180 | } 181 | } 182 | 183 | void CustomWebView::resizeEvent(QResizeEvent *event) { 184 | QWindow::resizeEvent(event); 185 | updateWebViewBounds(event->size().width(), event->size().height()); 186 | } 187 | --------------------------------------------------------------------------------