< Summary

Information
Class: /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/qt_webview.cpp
Assembly: Default
File(s): /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/qt_webview.cpp
Line coverage
22%
Covered lines: 70
Uncovered lines: 237
Coverable lines: 307
Total lines: 498
Line coverage: 22.8%
Branch coverage
7%
Covered branches: 9
Total branches: 120
Branch coverage: 7.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

File(s)

/home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/qt_webview.cpp

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik
 2// SPDX-License-Identifier: MIT OR Apache-2.0
 3
 4// Qt-backed webview registry (see qt_webview.hpp for the threading contract:
 5// every entry point here runs on the Qt GUI thread).
 6//
 7// The page-event callback `qt_on_webview_event` comes from the cxx bridge.
 8
 9#include "qt_webview.hpp"
 10
 11#include "qt_scheme.hpp"
 12#include "qt_window.hpp"
 13#include "tauri-runtime-qt/src/qt.rs.h"
 14#include "tauri-runtime-qt/src/bridge/webview.rs.h"
 15
 16#include <QtCore/QByteArray>
 17#include <QtCore/QJsonArray>
 18#include <QtCore/QJsonDocument>
 19#include <QtCore/QJsonValue>
 20#include <QtCore/QObject>
 21#include <QtCore/QString>
 22#include <QtCore/QUrl>
 23#include <QtCore/QVariant>
 24#include <QtGui/QColor>
 25#include <QtPrintSupport/QPrintDialog>
 26#include <QtPrintSupport/QPrinter>
 27#include <QtWebEngineCore/QWebEngineCookieStore>
 28#include <QtWebEngineCore/QWebEnginePage>
 29#include <QtWebEngineCore/QWebEngineProfile>
 30#include <QtWebEngineCore/QWebEngineScript>
 31#include <QtWebEngineCore/QWebEngineScriptCollection>
 32#include <QtWebEngineWidgets/QWebEngineView>
 33#include <QtWidgets/QDialog>
 34#include <QtWidgets/QLayout>
 35
 36#include <cmath>
 37#include <cstddef>
 38#include <unordered_map>
 39
 40namespace {
 41
 42struct Entry {
 43    QWebEngineView* view = nullptr;
 44    QWebEngineView* devtools_view = nullptr;
 45    QWebEnginePage* devtools_page = nullptr;
 46};
 47
 48std::unordered_map<uint64_t, Entry> g_webviews;
 49uint64_t g_next_id = 1;
 50uint64_t g_next_script_id = 1;
 51
 29452Entry* find_entry(uint64_t id) {
 29453    auto it = g_webviews.find(id);
 29454    return it == g_webviews.end() ? nullptr : &it->second;
 29455}
 56
 21057QString to_qstring(rust::Str s) {
 21058    return QString::fromUtf8(s.data(), static_cast<qsizetype>(s.size()));
 21059}
 60
 61/// Serializes a `runJavaScript` completion value to compact JSON text.
 62///
 63/// `QJsonDocument::fromVariant()` alone is not enough: it converts only JSON
 64/// *containers* (`QVariantMap`/`QVariantHash`/`QVariantList`/`QStringList`) and
 65/// returns a **null document** for every scalar (`QString`, `int`, `bool`, ...;
 66/// verified against Qt 6.11), which would make every scalar result look like
 67/// `null`. So the value is wrapped in a one-element array and the brackets are
 68/// stripped again: a compact JSON array is exactly `[<value>]` with no
 69/// whitespace, and a value can never contain an unbalanced top-level bracket
 70/// (JSON escapes them inside strings). This keeps Qt's own escaping.
 71///
 72/// JS `undefined` (or anything Qt cannot represent as a `QJsonValue`) is
 73/// reported as the JSON literal `null`, matching what JSON can express.
 074QByteArray json_text(const QVariant& value) {
 075    const QJsonValue json_value = QJsonValue::fromVariant(value);
 076    if (json_value.isUndefined()) {
 077        return QByteArrayLiteral("null");
 078    }
 079    const QJsonDocument document(QJsonArray{json_value});
 080    const QByteArray wrapped = document.toJson(QJsonDocument::Compact);
 081    return wrapped.mid(1, wrapped.size() - 2);
 082}
 83
 84/// Emits a page event to Rust.
 85/// Kinds (must match the bridge documentation in `qt.rs`):
 86///   1 = page started loading (url)
 87///   2 = page finished loading (url) -- emitted for success and failure
 88///   3 = url changed (url)
 89///   4 = title changed (title)
 90///   5 = load failed (url) -- additionally emitted when loadFinished(false)
 12691void emit_event(uint64_t id, int kind, const QString& payload) {
 12692    const QByteArray utf8 = payload.toUtf8();
 12693    qt_on_webview_event(id, kind,
 12694                        rust::Str(utf8.constData(), static_cast<std::size_t>(utf8.size())));
 12695}
 96
 97/// Takes `view` out of its parent's layout, if it is managed by one.
 98///
 99/// GEOMETRY OWNERSHIP (the decision the runtime makes): the window agent's
 100/// `qt_window_attach_webview()` puts every attached view into the window's
 101/// zero-margin `QVBoxLayout`, which makes the webview fill the window client
 102/// area and follow every window resize — the right default for tauri, where a
 103/// window's webview *is* its client area. `set_bounds` / `set_size` /
 104/// `set_position`, in contrast, mean "place the webview exactly here", which a
 105/// layout cannot express (it would immediately re-apply its own geometry), so
 106/// the first such call detaches the view from the layout and Qt stops managing
 107/// its geometry. Consequence, documented in `ops/webview.rs`: from then on the
 108/// webview no longer resizes with its window (tauri's `set_auto_resize` is a
 109/// no-op on this runtime), and re-attaching the view to a window
 110/// (`qt_webview_reparent`) puts it back under layout management.
 0111void detach_from_layout(QWebEngineView* view) {
 0112    QWidget* parent = view->parentWidget();
 0113    if (parent == nullptr) {
 0114        return;
 0115    }
 0116    if (QLayout* layout = parent->layout()) {
 0117        layout->removeWidget(view);
 0118    }
 0119}
 120
 121} // namespace
 122
 21123uint64_t qt_webview_create(uint64_t window_id, rust::Str url) {
 124    // The view is created unparented; the window agent embeds it through
 125    // qt_window_attach_webview(window_id, qt_webview_native_handle(id)).
 21126    (void)window_id;
 127
 21128    auto* view = new QWebEngineView();
 21129    const uint64_t id = g_next_id++;
 130
 21131    Entry entry;
 21132    entry.view = view;
 21133    g_webviews.emplace(id, entry);
 134
 21135    QWebEnginePage* page = view->page();
 21136    QObject::connect(page, &QWebEnginePage::loadStarted, view,
 21137                     [id, view]() { emit_event(id, 1, view->url().toString()); });
 21138    QObject::connect(page, &QWebEnginePage::loadFinished, view, [id, view](bool ok) {
 21139        emit_event(id, 2, view->url().toString());
 21140        if (!ok) {
 0141            emit_event(id, 5, view->url().toString());
 0142        }
 21143    });
 21144    QObject::connect(page, &QWebEnginePage::urlChanged, view,
 21145                     [id](const QUrl& u) { emit_event(id, 3, u.toString()); });
 21146    QObject::connect(page, &QWebEnginePage::titleChanged, view,
 63147                     [id](const QString& t) { emit_event(id, 4, t); });
 148
 149    // TAURIQT_EVAL_DEBUG smoke test: for the first webview, ask Rust once when
 150    // the first page load finishes. Rust checks the environment variable and,
 151    // when it is `1`, runs `document.title = 'eval-ok'` through the regular
 152    // eval_with_callback path and prints the callback result (see
 153    // `ops/webview.rs::debug_hook_first_load`). Without the variable this is a
 154    // single cheap call into Rust per process.
 21155    if (id == 1) {
 21156        QObject::connect(page, &QWebEnginePage::loadFinished, view, [id](bool) {
 21157            static bool fired = false;
 21158            if (!fired) {
 21159                fired = true;
 21160                qt_webview_on_first_load(id);
 21161            }
 21162        });
 21163    }
 164
 165    // Let the scheme layer find this webview's profile for handler install, and
 166    // install the handlers before the first navigation: a `tauri://localhost`
 167    // load issued earlier would be rejected for having no scheme handler.
 21168    qt_scheme_bind_webview(id, view);
 21169    qt_scheme_install_handler(id);
 170
 171    // Backstop: if the widget is destroyed behind our back, drop the handle.
 21172    QObject::connect(view, &QObject::destroyed, view, [id]() { g_webviews.erase(id); });
 173
 21174    if (url.size() != 0) {
 0175        view->load(QUrl(to_qstring(url)));
 0176    }
 21177    return id;
 21178}
 179
 21180uint64_t qt_webview_native_handle(uint64_t id) {
 21181    Entry* e = find_entry(id);
 21182    return e != nullptr ? reinterpret_cast<uint64_t>(e->view) : 0;
 21183}
 184
 21185void qt_webview_navigate(uint64_t id, rust::Str url) {
 21186    Entry* e = find_entry(id);
 21187    if (e == nullptr) {
 0188        return;
 0189    }
 21190    e->view->load(QUrl(to_qstring(url)));
 21191}
 192
 63193rust::String qt_webview_url(uint64_t id) {
 63194    Entry* e = find_entry(id);
 63195    if (e == nullptr) {
 0196        return rust::String();
 0197    }
 63198    return rust::String(e->view->url().toString().toStdString());
 63199}
 200
 0201void qt_webview_reload(uint64_t id) {
 0202    Entry* e = find_entry(id);
 0203    if (e == nullptr) {
 0204        return;
 0205    }
 0206    e->view->reload();
 0207}
 208
 0209void qt_webview_eval(uint64_t id, rust::Str script) {
 0210    Entry* e = find_entry(id);
 0211    if (e == nullptr) {
 0212        return;
 0213    }
 214    // Default worldId = 0 (MainWorld), fire-and-forget (no result callback).
 0215    e->view->page()->runJavaScript(to_qstring(script));
 0216}
 217
 189218void qt_webview_add_init_script(uint64_t id, rust::Str script, bool main_frame_only) {
 189219    Entry* e = find_entry(id);
 189220    if (e == nullptr) {
 0221        return;
 0222    }
 223    // Per-page collection; consumed by the next navigation, so this must run
 224    // before the first load() for tauri's bootstrap scripts to see the page.
 189225    QWebEngineScript s;
 189226    s.setName(QStringLiteral("tauri_init_%1").arg(g_next_script_id++));
 189227    s.setSourceCode(to_qstring(script));
 189228    s.setInjectionPoint(QWebEngineScript::DocumentCreation);
 189229    s.setWorldId(QWebEngineScript::MainWorld);
 189230    s.setRunsOnSubFrames(!main_frame_only);
 189231    e->view->page()->scripts().insert(s);
 189232}
 233
 0234void qt_webview_set_zoom(uint64_t id, double factor) {
 0235    Entry* e = find_entry(id);
 0236    if (e == nullptr) {
 0237        return;
 0238    }
 0239    e->view->page()->setZoomFactor(static_cast<qreal>(factor));
 0240}
 241
 0242void qt_webview_set_background_color(uint64_t id, int r, int g, int b, int a) {
 0243    Entry* e = find_entry(id);
 0244    if (e == nullptr) {
 0245        return;
 0246    }
 0247    e->view->page()->setBackgroundColor(QColor(r, g, b, a));
 0248}
 249
 0250void qt_webview_show(uint64_t id) {
 0251    Entry* e = find_entry(id);
 0252    if (e == nullptr) {
 0253        return;
 0254    }
 0255    e->view->show();
 0256    e->view->raise();
 0257}
 258
 0259void qt_webview_hide(uint64_t id) {
 0260    Entry* e = find_entry(id);
 0261    if (e == nullptr) {
 0262        return;
 0263    }
 0264    e->view->hide();
 0265}
 266
 0267QtWebviewGeometry qt_webview_geometry(uint64_t id) {
 0268    QtWebviewGeometry geometry{};
 0269    Entry* e = find_entry(id);
 0270    if (e == nullptr || e->view == nullptr) {
 0271        geometry.ok = false;  // all other fields stay zero
 0272        return geometry;
 0273    }
 274
 275    // `QWidget::geometry()` is in device-independent pixels; tauri's
 276    // `Physical*` values are device pixels, so scale by the widget's dpr.
 277    // `QWidget::devicePixelRatioF()` follows the widget's window, which is the
 278    // correct scale under Wayland fractional scaling (a `QScreen`'s dpr can
 279    // differ from the window's there).
 0280    const QRect rect = e->view->geometry();
 0281    const qreal dpr = e->view->devicePixelRatioF();
 0282    geometry.ok = true;
 0283    geometry.x = static_cast<std::int32_t>(std::lround(rect.x() * dpr));
 0284    geometry.y = static_cast<std::int32_t>(std::lround(rect.y() * dpr));
 0285    geometry.width = static_cast<std::int32_t>(std::lround(rect.width() * dpr));
 0286    geometry.height = static_cast<std::int32_t>(std::lround(rect.height() * dpr));
 0287    return geometry;
 0288}
 289
 0290bool qt_webview_set_size(uint64_t id, int width, int height, bool physical) {
 0291    Entry* e = find_entry(id);
 0292    if (e == nullptr || e->view == nullptr) {
 0293        return false;
 0294    }
 0295    detach_from_layout(e->view);
 0296    const qreal dpr = e->view->devicePixelRatioF();
 0297    const int logical_width = physical ? static_cast<int>(std::lround(width / dpr)) : width;
 0298    const int logical_height = physical ? static_cast<int>(std::lround(height / dpr)) : height;
 0299    e->view->resize(logical_width, logical_height);
 0300    return true;
 0301}
 302
 0303bool qt_webview_set_position(uint64_t id, int x, int y, bool physical) {
 0304    Entry* e = find_entry(id);
 0305    if (e == nullptr || e->view == nullptr) {
 0306        return false;
 0307    }
 0308    detach_from_layout(e->view);
 0309    const qreal dpr = e->view->devicePixelRatioF();
 0310    const int logical_x = physical ? static_cast<int>(std::lround(x / dpr)) : x;
 0311    const int logical_y = physical ? static_cast<int>(std::lround(y / dpr)) : y;
 312    // QWidget::move is relative to the parent widget, i.e. to the window's
 313    // client-area origin, which is what tauri's webview position means.
 0314    e->view->move(logical_x, logical_y);
 0315    return true;
 0316}
 317
 0318bool qt_webview_set_focus(uint64_t id) {
 0319    Entry* e = find_entry(id);
 0320    if (e == nullptr || e->view == nullptr) {
 0321        return false;
 0322    }
 0323    e->view->setFocus();
 0324    return true;
 0325}
 326
 0327bool qt_webview_eval_with_callback(uint64_t id, uint64_t request_id, rust::Str script) {
 0328    Entry* e = find_entry(id);
 0329    if (e == nullptr || e->view == nullptr || e->view->page() == nullptr) {
 0330        return false;
 0331    }
 332
 0333    const QString source = to_qstring(script);
 0334    e->view->page()->runJavaScript(source, [request_id](const QVariant& value) {
 335        // Qt hands the JS completion value back as a QVariant; `json_text`
 336        // serializes it as a JSON string so Rust/tauri see the same shape wry
 337        // produces (an undefined/unrepresentable value becomes `null`).
 0338        const QByteArray json = json_text(value);
 0339        qt_on_eval_result(request_id,
 0340                          rust::Str(json.constData(), static_cast<std::size_t>(json.size())));
 0341    });
 0342    return true;
 0343}
 344
 0345bool qt_webview_reparent(uint64_t id, uint64_t window_native) {
 0346    Entry* e = find_entry(id);
 0347    if (e == nullptr || e->view == nullptr) {
 0348        return false;
 0349    }
 350
 351    // The window registry's accessors return 0 for an unknown handle, and a
 352    // window that exists always has a native window id, so `winId() == 0` is a
 353    // truthful "no such window" probe (there is no `qt_window_widget()` accessor
 354    // to ask directly).
 0355    if (qt_window_winid(window_native) == 0) {
 0356        return false;
 0357    }
 358
 359    // The window agent adopts the widget: `qt_window_attach_webview()` reparents
 360    // it into the target window (dropping it from its previous layout),
 361    // (re)creates the window's zero-margin layout, adds the view and shows it.
 362    // That is exactly "reparent into another window", and it puts the view back
 363    // under layout management (see `detach_from_layout`).
 0364    detach_from_layout(e->view);
 0365    qt_window_attach_webview(
 0366        window_native, static_cast<uint64_t>(reinterpret_cast<quintptr>(e->view)));
 0367    return true;
 0368}
 369
 0370bool qt_webview_clear_browsing_data(uint64_t id) {
 0371    Entry* e = find_entry(id);
 0372    if (e == nullptr || e->view == nullptr || e->view->page() == nullptr) {
 0373        return false;
 0374    }
 0375    QWebEngineProfile* profile = e->view->page()->profile();
 0376    if (profile == nullptr) {
 0377        return false;
 0378    }
 379
 380    // What Qt 6.11 can clear on a live profile:
 381    //   * the HTTP cache (in-memory for off-the-record profiles),
 382    //   * every cookie of the profile's cookie store,
 383    //   * the visited-links list.
 384    // Both cache clearing and cookie deletion are asynchronous inside
 385    // QtWebEngine: `clearHttpCache()` signals `clearHttpCacheCompleted()` and
 386    // `deleteAllCookies()` has no completion signal at all, so this is
 387    // fire-and-forget by design (the tauri method returns `Result<()>` and
 388    // blocking the GUI thread on the GUI thread's own work would deadlock).
 389    //
 390    // What it CANNOT clear (no API on a live profile):
 391    //   * localStorage / sessionStorage,
 392    //   * IndexedDB databases,
 393    //   * service-worker registrations and Cache Storage,
 394    //   * WebSQL is gone in Chromium, and HTTP auth caches are not exposed.
 395    // Those live under the profile's `persistentStoragePath()`; the only way to
 396    // drop them is to remove that directory while no profile is alive, which
 397    // this runtime does not do. Note also that tauri's `clear_all_browsing_data`
 398    // is documented as clearing "all" browsing data; on this runtime it clears
 399    // the list above and leaves the listed stores in place.
 0400    profile->clearHttpCache();
 0401    profile->clearAllVisitedLinks();
 0402    if (QWebEngineCookieStore* store = profile->cookieStore()) {
 0403        store->deleteAllCookies();
 0404    }
 0405    return true;
 0406}
 407
 0408void qt_webview_open_devtools(uint64_t id) {
 0409    Entry* e = find_entry(id);
 0410    if (e == nullptr || e->devtools_view != nullptr) {
 0411        return;
 0412    }
 413
 414    // Qt's only inspector mechanism: bind a second page as the devtools page of
 415    // the inspected page; binding navigates it to the DevTools frontend.
 0416    QWebEnginePage* page = e->view->page();
 0417    auto* dev_page = new QWebEnginePage(page->profile(), e->view);
 0418    auto* dev_view = new QWebEngineView(dev_page);
 0419    page->setDevToolsPage(dev_page);
 0420    dev_view->resize(1000, 720);
 0421    dev_view->show();
 422
 0423    e->devtools_page = dev_page;
 0424    e->devtools_view = dev_view;
 425
 0426    QObject::connect(dev_view, &QObject::destroyed, dev_view, [id]() {
 0427        Entry* cur = find_entry(id);
 0428        if (cur != nullptr) {
 0429            cur->devtools_view = nullptr;
 0430            cur->devtools_page = nullptr;
 0431        }
 0432    });
 0433}
 434
 0435void qt_webview_close_devtools(uint64_t id) {
 0436    Entry* e = find_entry(id);
 0437    if (e == nullptr) {
 0438        return;
 0439    }
 0440    if (e->view != nullptr && e->view->page() != nullptr) {
 441        // Unbinds the devtools page (clears its inspected page too).
 0442        e->view->page()->setDevToolsPage(nullptr);
 0443    }
 0444    if (e->devtools_view != nullptr) {
 0445        e->devtools_view->hide();
 0446        e->devtools_view->deleteLater();
 0447        e->devtools_view = nullptr;
 0448    }
 0449    if (e->devtools_page != nullptr) {
 450        // Dev page is parented to the main view; delete it after its view.
 0451        e->devtools_page->deleteLater();
 0452        e->devtools_page = nullptr;
 0453    }
 0454}
 455
 0456bool qt_webview_is_devtools_open(uint64_t id) {
 0457    Entry* e = find_entry(id);
 0458    return e != nullptr && e->devtools_view != nullptr && e->devtools_view->isVisible();
 0459}
 460
 0461void qt_webview_print(uint64_t id) {
 0462    Entry* e = find_entry(id);
 0463    if (e == nullptr) {
 0464        return;
 0465    }
 466    // Qt 6.11 has no print->dialog API; emulate wry with QPrinter +
 467    // QPrintDialog and print the page on accept. Requires user interaction.
 0468    QPrinter printer(QPrinter::HighResolution);
 0469    QPrintDialog dialog(&printer, e->view);
 0470    if (dialog.exec() == QDialog::Accepted) {
 0471        e->view->print(&printer);
 0472    }
 0473}
 474
 0475void qt_webview_close(uint64_t id) {
 0476    auto it = g_webviews.find(id);
 0477    if (it == g_webviews.end()) {
 0478        return;
 0479    }
 0480    Entry e = it->second;
 0481    g_webviews.erase(it);
 482
 0483    if (e.view != nullptr && e.view->page() != nullptr) {
 0484        e.view->page()->setDevToolsPage(nullptr);
 0485    }
 0486    if (e.devtools_view != nullptr) {
 0487        e.devtools_view->hide();
 0488        e.devtools_view->deleteLater();
 0489    }
 0490    if (e.devtools_page != nullptr) {
 0491        e.devtools_page->deleteLater();
 0492    }
 0493    if (e.view != nullptr) {
 494        // QWebEngineView owns the page it created in its default constructor.
 0495        e.view->hide();
 0496        e.view->deleteLater();
 0497    }
 0498}

Methods/Properties

qt_webview.cpp:_ZN12_GLOBAL__N_110find_entryEm
qt_webview.cpp:_ZN12_GLOBAL__N_110to_qstringEN4rust10cxxbridge13StrE
qt_webview.cpp:_ZN12_GLOBAL__N_19json_textERK8QVariant
qt_webview.cpp:_ZN12_GLOBAL__N_110emit_eventEmiRK7QString
qt_webview.cpp:_ZN12_GLOBAL__N_118detach_from_layoutEP14QWebEngineView
_Z17qt_webview_createmN4rust10cxxbridge13StrE
qt_webview.cpp:_ZZ17qt_webview_createmN4rust10cxxbridge13StrEENK3$_0clEv
qt_webview.cpp:_ZZ17qt_webview_createmN4rust10cxxbridge13StrEENK3$_1clEb
qt_webview.cpp:_ZZ17qt_webview_createmN4rust10cxxbridge13StrEENK3$_2clERK4QUrl
qt_webview.cpp:_ZZ17qt_webview_createmN4rust10cxxbridge13StrEENK3$_3clERK7QString
qt_webview.cpp:_ZZ17qt_webview_createmN4rust10cxxbridge13StrEENK3$_4clEb
qt_webview.cpp:_ZZ17qt_webview_createmN4rust10cxxbridge13StrEENK3$_5clEv
_Z24qt_webview_native_handlem
_Z19qt_webview_navigatemN4rust10cxxbridge13StrE
_Z14qt_webview_urlm
_Z17qt_webview_reloadm
_Z15qt_webview_evalmN4rust10cxxbridge13StrE
_Z26qt_webview_add_init_scriptmN4rust10cxxbridge13StrEb
_Z19qt_webview_set_zoommd
_Z31qt_webview_set_background_colormiiii
_Z15qt_webview_showm
_Z15qt_webview_hidem
_Z19qt_webview_geometrym
_Z19qt_webview_set_sizemiib
_Z23qt_webview_set_positionmiib
_Z20qt_webview_set_focusm
_Z29qt_webview_eval_with_callbackmmN4rust10cxxbridge13StrE
qt_webview.cpp:_ZZ29qt_webview_eval_with_callbackmmN4rust10cxxbridge13StrEENK3$_0clERK8QVariant
_Z19qt_webview_reparentmm
_Z30qt_webview_clear_browsing_datam
_Z24qt_webview_open_devtoolsm
qt_webview.cpp:_ZZ24qt_webview_open_devtoolsmENK3$_0clEv
_Z25qt_webview_close_devtoolsm
_Z27qt_webview_is_devtools_openm
_Z16qt_webview_printm
_Z16qt_webview_closem