#include "rktwebview_qt.h" #include "webviewqt.h" #include "rktwebview.h" #include "webviewwindow.h" #include "rktutils.h" #include #include #include #include #include #include #include #include #include #include #include "command.h" #include #include #include static inline char *copyString(const char *s) { int l = strlen(s); char *cpy = static_cast(malloc(l + 1)); memcpy(cpy, s, l + 1); return cpy; } void Rktwebview_qt::processCommand(Command *cmd) { switch(cmd->cmd) { case COMMAND_QUIT: { _app->quit(); cmd->done = true; } break; case COMMAND_CREATE: { rkt_wv_context_t context = cmd->args[0].toInt(); rktwebview_t parent = cmd->args[1].toInt(); void *f = cmd->args[2].value(); event_cb_t js_event_cb = reinterpret_cast (f); WebviewWindow *p; if (_views.contains(parent)) { p = _views[parent]; } else { p = nullptr; } if (!_contexts.contains(context)) { cmd->result = -1; cmd->done = true; } else { QWebEngineProfile *profile = _contexts[context]; WebviewWindow *w = new WebviewWindow(profile, p); WebViewQt *view = new WebViewQt(nextHandle(), w); w->addView(view, this); int id = view->id(); _views[id] = w; _view_js_callbacks[id] = js_event_cb; w->show(); while(!w->windowCreated()) { doEvents(); } cmd->result = id; cmd->done = true; } } break; case COMMAND_CLOSE: { doEvents(); int wv = cmd->args[0].toInt(); if (_views.contains(wv)) { WebviewWindow *w= _views[wv]; _views.remove(wv); w->closeView(); cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_SET_OU_TOKEN: { doEvents(); int wv = cmd->args[0].toInt(); QString ou_token = cmd->args[1].toString(); if (_views.contains(wv)) { WebviewWindow *w= _views[wv]; w->setOUToken(ou_token); cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_SET_URL: { doEvents(); int wv = cmd->args[0].toInt(); QString url = cmd->args[1].toString(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; WebViewQt *v = w->view(); QUrl u(url); v->setUrl(u); cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_SET_HTML: { doEvents(); int wv = cmd->args[0].toInt(); QString html = cmd->args[1].toString(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; WebViewQt *v = w->view(); v->setHtml(html); cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_SET_TITLE: { doEvents(); int wv = cmd->args[0].toInt(); QString title = cmd->args[1].toString(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; w->setWindowTitle(title); cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_RUN_JS: { doEvents(); int wv = cmd->args[0].toInt(); QString js = cmd->args[1].toString(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; w->runJs(js); cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_CALL_JS: { doEvents(); int wv = cmd->args[0].toInt(); QString js = cmd->args[1].toString(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; w->callJs(js, cmd); } else { cmd->result = false; cmd->js_result_ok = false; cmd->done = true; } } break; case COMMAND_DEV_TOOLS: { doEvents(); int wv = cmd->args[0].toInt(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; w->openDevTools(); cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_SHOW_WIN: case COMMAND_HIDE_WIN: case COMMAND_PRESENT_WIN: case COMMAND_MAX_WIN: case COMMAND_MIN_WIN: case COMMAND_SHOW_NORMAL_WIN: { doEvents(); int wv = cmd->args[0].toInt(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; int c = cmd->cmd; if (c == COMMAND_SHOW_WIN) w->show(); else if (c == COMMAND_HIDE_WIN) w->hide(); else if (c == COMMAND_MAX_WIN) w->showMaximized(); else if (c == COMMAND_MIN_WIN) { w->showMinimized(); } else if (c == COMMAND_SHOW_NORMAL_WIN) w->showNormal(); else if (c == COMMAND_PRESENT_WIN) { w->show(); w->raise(); w->activateWindow(); } cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_WINDOW_STATUS: { doEvents(); int wv = cmd->args[0].toInt(); window_state_t ws = window_state_t::invalid; if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; if (w->isHidden()) { ws = window_state_t::hidden; } else if (w->isMinimized()) { ws = window_state_t::minimized; } else if (w->isMaximized()) { if (w->isActiveWindow()) { ws = window_state_t::maximized_active; } else { ws = window_state_t::maximized; } } else if (w->isVisible()) { if (w->isActiveWindow()) { ws = window_state_t::normal_active; } else { ws = window_state_t::normal; } } } cmd->result = static_cast(ws); cmd->done = true; } break; case COMMAND_MOVE: { doEvents(); int wv = cmd->args[0].toInt(); int x = cmd->args[1].toInt(); int y = cmd->args[2].toInt(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; int move_count = w->moveCount(); w->move(x, y); while (w->moveCount() == move_count) { doEvents(); } cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_RESIZE: { doEvents(); int wv = cmd->args[0].toInt(); int width = cmd->args[1].toInt(); int height = cmd->args[2].toInt(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; int resize_count = w->resizeCount(); w->resize(width, height); while (w->resizeCount() == resize_count) { doEvents(); } cmd->result = true; } else { cmd->result = false; } cmd->done = true; } break; case COMMAND_CHOOSE_DIR: { int wv = cmd->args[0].toInt(); QString title = cmd->args[1].toString(); QString base_dir = cmd->args[2].toString(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; QString dir = QFileDialog::getExistingDirectory(w, title, base_dir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir == "") { QJsonObject obj; obj["state"] = "canceled"; obj["dir"] = base_dir; cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact)); } else { QJsonObject obj; obj["state"] = "choosen"; obj["dir"] = dir; cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact)); } } else { cmd->js_result_ok = false; cmd->result = false; } cmd->done = true; } break; case COMMAND_FILE_OPEN: case COMMAND_FILE_SAVE: { int wv = cmd->args[0].toInt(); QString title = cmd->args[1].toString(); QString base_dir = cmd->args[2].toString(); QString exts = cmd->args[3].toString(); if (_views.contains(wv)) { WebviewWindow *w = _views[wv]; QString file; QString selected_filter; if (cmd->cmd == COMMAND_FILE_OPEN) { file = QFileDialog::getOpenFileName(w, title, base_dir, exts, &selected_filter); } else { file = QFileDialog::getSaveFileName(w, title, base_dir, exts, &selected_filter); } if (file == "") { QJsonObject obj; obj["state"] = "canceled"; obj["file"] = ""; obj["used-filter"] = selected_filter; cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact)); } else { QJsonObject obj; obj["state"] = "choosen"; obj["file"] = file; obj["used-filter"] = selected_filter; cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact)); } } else { cmd->js_result_ok = false; cmd->result = false; } cmd->done = true; } break; default: { cmd->result = false; cmd->done = true; } break; } } void Rktwebview_qt::removeView(int id) { if (_views.contains(id)) { //WebviewWindow *win = _views[id]; _views.remove(id); _view_js_callbacks.remove(id); } } int Rktwebview_qt::nextHandle() { int h = ++_current_handle; return h; } rkt_wv_context_t Rktwebview_qt::newContext(const char *boilerplate_js, const char *optional_server_cert_pem) { QWebEngineProfileBuilder b; if (optional_server_cert_pem != nullptr) { QByteArray scp = QByteArray(optional_server_cert_pem); QList certs; QSslCertificate cert(scp); certs.append(cert); b.setAdditionalTrustedCertificates(certs); } _context_counter += 1; QString name = QString::asprintf("profile-%d", _context_counter); QString code = "if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }\n" "window.rkt_evt_frame_el = null;\n" "window.rkt_evt_frame_win = null;\n" "window.rkt_send_event = function(obj) {\n" " //console.log('Sending event: ' + obj);\n" " window.rkt_event_queue.push(obj);\n" " if (window.rkt_evt_frame_el) {\n" " window.rkt_evt_frame_win.print();\n" " }\n" "};\n" "window.rkt_get_events = function() {\n" " let q = window.rkt_event_queue;\n" " window.rkt_event_queue = [];\n" " let json_q = JSON.stringify(q);\n" " return json_q;\n" "};\n" "// add hidden hover element to body if necessary\n" "setInterval(function () {\n" " if (window.rkt_evt_frame_el === null || window.rkt_evt_frame_el === undefined) {\n" " window.rkt_evt_frame_el = document.createElement('iframe');\n" " window.rkt_evt_frame_el.style.display = 'none';\n" " window.rkt_evt_frame_el.setAttribute('id', 'rkt-evt-frame');\n" " window.rkt_evt_frame_el.setAttribute('name', 'rkt-evt-frame');\n" " document.body.append(window.rkt_evt_frame_el);\n" " window.rkt_evt_frame_win = window.rkt_evt_frame_el.contentWindow;\n" " } else {" " if (window.rkt_event_queue.length > 0) {\n" " window.rkt_evt_frame_win.print();\n" " }\n" " }\n" "},\n" "10);\n" ""; QList scripts; QWebEngineProfile *p = b.createProfile(name); QWebEngineScriptCollection *col = p->scripts(); QWebEngineScript s1; s1.setInjectionPoint(QWebEngineScript::InjectionPoint::DocumentReady); s1.setName("eventing"); s1.setSourceCode(code); s1.setWorldId(QWebEngineScript::MainWorld); scripts.append(s1); QWebEngineScript s2; s2.setInjectionPoint(QWebEngineScript::InjectionPoint::DocumentReady); s2.setName("boilerplate"); s2.setSourceCode(boilerplate_js); s2.setWorldId(QWebEngineScript::MainWorld); scripts.append(s2); col->insert(scripts); QWebEngineScriptCollection *c = p->scripts(); QList l = c->toList(); _contexts[_context_counter] = p; return _context_counter; } int Rktwebview_qt::rktWebViewCreate(rkt_wv_context_t context, rktwebview_t parent, event_cb_t js_evt_cb) { Command c(COMMAND_CREATE); c.args.push_back(context); c.args.push_back(parent); void *function = reinterpret_cast(js_evt_cb); QVariant f(QVariant::fromValue(function)); c.args.push_back(f); postCommand(&c); while(!c.done) { doEvents(); } int id = c.result.toInt(); return id; } void Rktwebview_qt::rktWebViewClose(int wv) { Command c(COMMAND_CLOSE); c.args.push_back(wv); postCommand(&c); while(!c.done) { doEvents(); } } void Rktwebview_qt::rktSetOUToken(rktwebview_t wv, const char *ou_token) { Command c(COMMAND_SET_OU_TOKEN); c.args.push_back(wv); QString ou_tok(ou_token); c.args.push_back(ou_tok); postCommand(&c); while(!c.done) { doEvents(); } } result_t Rktwebview_qt::rktSetUrl(rktwebview_t wv, const char *url) { Command c(COMMAND_SET_URL); QString _url(url); c.args.push_back(wv); c.args.push_back(_url); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : result_t::set_navigate_failed; } result_t Rktwebview_qt::rktSetHtml(rktwebview_t wv, const char *html) { Command c(COMMAND_SET_HTML); QString _html(html); c.args.push_back(wv); c.args.push_back(_html); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : result_t::set_html_failed; } rkt_data_t *Rktwebview_qt::rktCallJs(rktwebview_t wv, const char *js) { Command c(COMMAND_CALL_JS); QString _js(js); c.args.push_back(wv); c.args.push_back(_js); postCommand(&c); while(!c.done) { doEvents(); } rkt_data_t *r = static_cast(malloc(sizeof(rkt_data_t))); r->kind = js_result; r->data.js_result.result = c.js_result_ok ? result_t::oke : result_t::eval_js_failed; r->data.js_result.value = copyString(c.result.toString().toUtf8()); return r; } result_t Rktwebview_qt::rktRunJs(rktwebview_t wv, const char *js) { Command c(COMMAND_RUN_JS); QString _js(js); c.args.push_back(wv); c.args.push_back(_js); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : result_t::eval_js_failed; } result_t Rktwebview_qt::rktMove(rktwebview_t wv, int x, int y) { Command c(COMMAND_MOVE); c.args.push_back(wv); c.args.push_back(x); c.args.push_back(y); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : result_t::move_failed; } result_t Rktwebview_qt::rktResize(rktwebview_t wv, int w, int h) { Command c(COMMAND_RESIZE); c.args.push_back(wv); c.args.push_back(w); c.args.push_back(h); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : result_t::resize_failed; } result_t Rktwebview_qt::rktHideWindow(rktwebview_t w) { return doWindow(w, COMMAND_HIDE_WIN, result_t::failed); } result_t Rktwebview_qt::rktShowWindow(rktwebview_t w) { return doWindow(w, COMMAND_SHOW_WIN, result_t::failed); } result_t Rktwebview_qt::rktPresentWindow(rktwebview_t w) { return doWindow(w, COMMAND_PRESENT_WIN, result_t::failed); } result_t Rktwebview_qt::rktMaximizeWindow(rktwebview_t w) { return doWindow(w, COMMAND_MAX_WIN, result_t::failed); } result_t Rktwebview_qt::rktMinimizeWindow(rktwebview_t w) { return doWindow(w, COMMAND_MIN_WIN, result_t::failed); } result_t Rktwebview_qt::rktShowNormalWindow(rktwebview_t w) { return doWindow(w, COMMAND_SHOW_NORMAL_WIN, result_t::failed); } window_state_t Rktwebview_qt::rktWindowState(rktwebview_t w) { Command c(COMMAND_WINDOW_STATUS); c.args.push_back(w); postCommand(&c); while(!c.done) { doEvents(); } int r = c.result.toInt(); window_state_t ws = static_cast(r); return ws; } result_t Rktwebview_qt::fileDlg(rktwebview_t w, const char *title, const char *base, const char *filters,QFileDialog::FileMode mode, QFileDialog::AcceptMode am, QString evt_ok, QString evt_cancel) { if (_views.contains(w)) { WebviewWindow *win = _views[w]; QFileDialog *dlg = new QFileDialog(win, title, base); dlg->setFileMode(mode); dlg->setAcceptMode(am); QString pe = filters; QStringList p = pe.split(";;"); dlg->setNameFilters(p); dlg->setProperty("rktwebview", w); dlg->setProperty("evt-ok", evt_ok); dlg->setProperty("evt-cancel", evt_cancel); connect(dlg, &QFileDialog::accepted, this, [this]() { QFileDialog *dlg = static_cast(sender()); rktwebview_t w = dlg->property("rktwebview").toInt(); QString evt_ok = dlg->property("evt-ok").toString(); QStringList l = dlg->selectedFiles(); QString file; if (l.size() > 0) { file = dlg->selectedFiles().first(); } EventContainer e(evt_ok); e["choosen"] = file; e["filter"] = dlg->selectedNameFilter(); e["dir"] = dlg->directory().path(); this->triggerEvent(w, e); }); connect(dlg, &QDialog::rejected, this, [this]() { QFileDialog *dlg = static_cast(sender()); rktwebview_t w = dlg->property("rktwebview").toInt(); QString evt_cancel = dlg->property("evt-cancel").toString(); EventContainer e(evt_cancel); this->triggerEvent(w, e); }); dlg->show(); return result_t::oke; } else { return result_t::failed; } } result_t Rktwebview_qt::rktChooseDir(rktwebview_t w, const char *title, const char *base_dir) { return fileDlg(w, title, base_dir, "", QFileDialog::Directory, QFileDialog::AcceptOpen, "choose-dir-ok", "choose-dir-cancel"); } result_t Rktwebview_qt::rktFileOpen(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts) { return fileDlg(w, title, base_dir, permitted_exts, QFileDialog::ExistingFile, QFileDialog::AcceptOpen, "file-open-ok", "file-open-cancel"); } result_t Rktwebview_qt::rktFileSave(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts) { return fileDlg(w, title, base_dir, permitted_exts, QFileDialog::AnyFile, QFileDialog::AcceptSave, "file-save-ok", "file-save-cancel"); } result_t Rktwebview_qt::rktWindowSetTitle(rktwebview_t wv, const char *title) { Command c(COMMAND_SET_TITLE); c.args.push_back(wv); c.args.push_back(title); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : result_t::failed; } result_t Rktwebview_qt::rktMessageBox(rktwebview_t w, const char *title, const char *message, const char *submessage, rkt_messagetype_t type) { if (_views.contains(w)) { WebviewWindow *win = _views[w]; QMessageBox *msg = new QMessageBox(win); auto prepButton = [msg, w, this](QMessageBox::StandardButton b, QString event) { msg->addButton(b); QAbstractButton *btn = msg->button(b); QVariant v = QVariant::fromValue(msg); btn->setProperty("messagebox", v); btn->setProperty("event", event); btn->setProperty("rktwebview", w); connect(btn, &QAbstractButton::clicked, this, [this]() { QAbstractButton *btn = static_cast(sender()); QMessageBox *mb = btn->property("messagebox").value(); rktwebview_t w = btn->property("rktwebview").toInt(); EventContainer e(btn->property("event").toString()); this->triggerEvent(w, e); mb->close(); }); }; msg->setWindowTitle(title); QString m = QString("

") + message + "

"; m += QString::asprintf("

%s

", submessage); msg->setInformativeText(m); QMessageBox::Icon icn; if (type == rkt_messagetype_t::error) { icn = QMessageBox::Icon::Critical; prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok"); } else if (type == rkt_messagetype_t::warning) { icn = QMessageBox::Icon::Warning; prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok"); } else if (type == rkt_messagetype_t::yes_no || type == rkt_messagetype_t::oke_cancel) { icn = QMessageBox::Icon::Question; if (type == rkt_messagetype_t::yes_no) { prepButton(QMessageBox::StandardButton::No, "msgbox-no"); prepButton(QMessageBox::StandardButton::Yes, "msgbox-yes"); } else { prepButton(QMessageBox::StandardButton::Cancel, "msgbox-cancel"); prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok"); } } else { // informatio icn = QMessageBox::Icon::Information; prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok"); } msg->setIcon(icn); msg->show(); return result_t::oke; } else { return result_t::failed; } } result_t Rktwebview_qt::doWindow(rktwebview_t w, int cmd, result_t on_error) { Command c(cmd); c.args.push_back(w); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : on_error; } bool Rktwebview_qt::rktValid(rktwebview_t wv) { return _views.contains(wv); } result_t Rktwebview_qt::rktOpenDevtools(rktwebview_t wv) { Command c(COMMAND_DEV_TOOLS); c.args.push_back(wv); postCommand(&c); while(!c.done) { doEvents(); } bool r = c.result.toBool(); return r ? result_t::oke : result_t::eval_js_failed; } void Rktwebview_qt::onPageLoad(rktwebview_t w) { } void Rktwebview_qt::pageLoaded(rktwebview_t w, bool ok) { if (!ok) { // Inject code of the profile to this page WebviewWindow *win = _views[w]; if (win->navigationEventSent()) { return; } QWebEngineProfile *p = win->profile(); QWebEngineScriptCollection *col = p->scripts(); QList l = col->toList(); int i; for(i = 0; i < l.size(); i++) { runJs(w, l[i].sourceCode().toUtf8().constData()); } } // trigger page loaded. EventContainer e("page-loaded"); e["oke"] = ok; triggerEvent(w, e); } void Rktwebview_qt::triggerEvent(rktwebview_t wv, const EventContainer &e) { triggerEvent(wv, mkEventJson(e)); } void Rktwebview_qt::triggerEvent(rktwebview_t wv, const QString &msg) { if (_view_js_callbacks.contains(wv)) { event_cb_t js_event_cb = _view_js_callbacks[wv]; char *evt = copyString(msg.toUtf8().constData()); rkt_data_t *d = static_cast(malloc(sizeof(rkt_data_t))); d->kind = rkt_data_kind_t::event; d->data.event.wv = wv; d->data.event.event = evt; js_event_cb(d); } } void Rktwebview_qt::rktQuit() { QList keys = _views.keys(); int i; for(i = 0; i < keys.size(); i++) { int view_handle = keys[i]; rktWebViewClose(view_handle); } Command c(COMMAND_QUIT); postCommand(&c); while(!c.done) { doEvents(); } } void Rktwebview_qt::runJs(rktwebview_t wv, const char *js) { if (_views.contains(wv)) { QString _js(js); WebviewWindow *win = _views[wv]; win->runJs(_js); } } void Rktwebview_qt::postCommand(Command *cmd) { CommandEvent *e = new CommandEvent(cmd); QApplication::postEvent(this, e); } void Rktwebview_qt::handleCommandEvent(CommandEvent *e) { Command *c = e->cmd(); processCommand(c); } void Rktwebview_qt::customEvent(QEvent *event) { if (event->type() == COMMAND_EVENT) { handleCommandEvent(static_cast(event)); } } void Rktwebview_qt::doEvents() { //QTime ct = QTime::currentTime(); //QTime expire = QTime::currentTime().addMSecs(2); //while(QTime::currentTime() <= expire) { _app->processEvents(); //} // Qt 6.10 --> this leads to a core dump // together with the stopEventloop stuff. // Qt 6.4 seem stable. /*if (_evt_loop_depth == 0) { _evt_loop_depth += 1; _evt_loop_timer.setSingleShot(true); _evt_loop_timer.start(2); //_evt_loop.exec(); _app->exec(); }*/ } void Rktwebview_qt::stopEventloop() { //_evt_loop.exit(0); //_app->exit(0); //_evt_loop_depth -= 1; } Rktwebview_qt::Rktwebview_qt(Rktwebview_qt **handler) : QObject() { _argc = 1; _argv[0] = const_cast("Rktwebview_qt"); _context_counter = 0; _current_handle = 0; _handler = handler; _evt_loop_depth = 0; _app = new QApplication(_argc, _argv); // See Qt 6.10 remark at doEvents. //connect(&_evt_loop_timer, &QTimer::timeout, this, &Rktwebview_qt::stopEventloop); // Because we are using processEvents only (Qt 6.10), we need this dispatcher to // handle deferred Deletes. const auto *eventDispatcher = QThread::currentThread()->eventDispatcher(); QObject::connect(eventDispatcher, &QAbstractEventDispatcher::aboutToBlock, QThread::currentThread(), []{ if (QThread::currentThread()->loopLevel() == 0) QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } ); *_handler = nullptr; } Rktwebview_qt::~Rktwebview_qt() { QList win_keys = _views.keys(); int i, N; for(i = 0, N = win_keys.size(); i < N; i++) { WebviewWindow *w = _views[win_keys[i]]; delete w; } QList c_keys = _contexts.keys(); for(i = 0, N = c_keys.size(); i < N; i++) { QWebEngineProfile *p = _contexts[c_keys[i]]; delete p; } }