3#include <QContextMenuEvent>
6#include <QAuthenticator>
9#include <QLoggingCategory>
10#include <QWebEngineProfile>
11#include <QWebEngineScript>
12#include <QWebEngineScriptCollection>
14#include "RabbitCommonDir.h"
16#include "FrmWebView.h"
17#include "FrmWebBrowser.h"
18#include "DlgUserPassword.h"
20static Q_LOGGING_CATEGORY(log,
"WebBrowser.View")
23 : QWebEngineView(parent)
24 , m_pBrowser(pFrmWebBrowser)
25#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
26 , m_pDlgWebAuth(
nullptr)
28 , m_pWebChannel(
nullptr)
29 , m_pPasswordStore(
nullptr)
32 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
33 setMinimumSize(200, 100);
35 check = connect(
this, &QWebEngineView::loadStarted, [
this]() {
37 emit favIconChanged(favIcon());
40 check = connect(
this, &QWebEngineView::loadProgress, [
this](
int progress) {
41 m_loadProgress = progress;
44 check = connect(
this, &QWebEngineView::loadFinished, [
this](
bool success) {
45 m_loadProgress = success ? 100 : -1;
46 emit favIconChanged(favIcon());
47 if(m_pBrowser->m_pPara->GetAutoFillUserAndPassword() && m_pWebChannel && m_pPasswordStore)
48 InjectScriptAutoFill();
50 QString szUser, szPassword;
51 int nRet = GetUserAndPassword(this->url(), szUser, szPassword);
53 SetupAutoFillScript();
54 GlobalFillForm(szUser, szPassword);
59 check = connect(
this, &QWebEngineView::iconChanged, [
this](
const QIcon &) {
60 emit favIconChanged(favIcon());
63 check = connect(
this, &QWebEngineView::renderProcessTerminated,
64 [
this](QWebEnginePage::RenderProcessTerminationStatus termStatus,
int statusCode) {
67 case QWebEnginePage::NormalTerminationStatus:
68 status = tr(
"Render process normal exit");
70 case QWebEnginePage::AbnormalTerminationStatus:
71 status = tr(
"Render process abnormal exit");
73 case QWebEnginePage::CrashedTerminationStatus:
74 status = tr(
"Render process crashed");
76 case QWebEnginePage::KilledTerminationStatus:
77 status = tr(
"Render process killed");
80 QMessageBox::StandardButton btn = QMessageBox::question(window(), status,
81 tr(
"Render process exited with code: %1\n"
82 "Do you want to reload the page ?").arg(statusCode));
83 if (btn == QMessageBox::Yes)
84 QTimer::singleShot(0,
this, &CFrmWebView::reload);
89CFrmWebView::~CFrmWebView()
91 qDebug(log) << Q_FUNC_INFO;
92 if (m_imageAnimationGroup)
93 delete m_imageAnimationGroup;
95 m_imageAnimationGroup =
nullptr;
98#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
99inline QString questionForPermissionType(QWebEnginePermission::PermissionType permissionType)
101 switch (permissionType) {
102 case QWebEnginePermission::PermissionType::Geolocation:
103 return QObject::tr(
"Allow %1 to access your location information?");
104 case QWebEnginePermission::PermissionType::MediaAudioCapture:
105 return QObject::tr(
"Allow %1 to access your microphone?");
106 case QWebEnginePermission::PermissionType::MediaVideoCapture:
107 return QObject::tr(
"Allow %1 to access your webcam?");
108 case QWebEnginePermission::PermissionType::MediaAudioVideoCapture:
109 return QObject::tr(
"Allow %1 to access your microphone and webcam?");
110 case QWebEnginePermission::PermissionType::MouseLock:
111 return QObject::tr(
"Allow %1 to lock your mouse cursor?");
112 case QWebEnginePermission::PermissionType::DesktopVideoCapture:
113 return QObject::tr(
"Allow %1 to capture video of your desktop?");
114 case QWebEnginePermission::PermissionType::DesktopAudioVideoCapture:
115 return QObject::tr(
"Allow %1 to capture audio and video of your desktop?");
116 case QWebEnginePermission::PermissionType::Notifications:
117 return QObject::tr(
"Allow %1 to show notification on your desktop?");
118 case QWebEnginePermission::PermissionType::ClipboardReadWrite:
119 return QObject::tr(
"Allow %1 to read from and write to the clipboard?");
120 case QWebEnginePermission::PermissionType::LocalFontsAccess:
121 return QObject::tr(
"Allow %1 to access fonts stored on this machine?");
122 case QWebEnginePermission::PermissionType::Unsupported:
129void CFrmWebView::setPage(QWebEnginePage *page)
132 CreateWebActionTrigger(page, QWebEnginePage::Forward);
133 CreateWebActionTrigger(page, QWebEnginePage::Back);
134 CreateWebActionTrigger(page, QWebEnginePage::Reload);
135 CreateWebActionTrigger(page, QWebEnginePage::Stop);
137 if (
auto oldPage = QWebEngineView::page()) {
138 oldPage->disconnect(
this);
140 QWebEngineView::setPage(page);
141 connect(page, &QWebEnginePage::linkHovered,
this, &CFrmWebView::sigLinkHovered);
142 connect(page, &QWebEnginePage::windowCloseRequested,
143 this, &CFrmWebView::sigCloseRequested);
144 connect(page, &QWebEnginePage::selectClientCertificate,
145 this, &CFrmWebView::slotSelectClientCertificate);
146 connect(page, &QWebEnginePage::authenticationRequired,
this,
147 &CFrmWebView::slotAuthenticationRequired);
148 connect(page, &QWebEnginePage::proxyAuthenticationRequired,
this,
149 &CFrmWebView::slotProxyAuthenticationRequired);
150 connect(page, &QWebEnginePage::fullScreenRequested,
this,
151 &CFrmWebView::slotFullScreenRequested);
152 connect(page, &QWebEnginePage::registerProtocolHandlerRequested,
this,
154 #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
155 connect(page, &QWebEnginePage::certificateError,
156 this, &CFrmWebView::slotCertificateError);
157 connect(page, &QWebEnginePage::permissionRequested,
this,
158 &CFrmWebView::slotPermissionRequested);
160 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
161 connect(page, &QWebEnginePage::fileSystemAccessRequested,
this,
164 #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
165 connect(page, &QWebEnginePage::desktopMediaRequested,
this,
166 &CFrmWebView::slotDesktopMediaRequest);
167 connect(page, &QWebEnginePage::webAuthUxRequested,
168 this, &CFrmWebView::slotWebAuthUxRequested);
171 Q_ASSERT(m_pBrowser);
172 if(m_pBrowser->m_pPara->GetAutoFillUserAndPassword()) {
174 m_pWebChannel =
new QWebChannel(
this);
175 if(!m_pPasswordStore)
177 if(m_pWebChannel && m_pPasswordStore) {
179 m_pWebChannel->registerObject(QStringLiteral(
"PasswordManager"), m_pPasswordStore);
180 page->setWebChannel(m_pWebChannel);
181 InjectScriptQWebChannel();
183 qCritical(log) <<
"Don't register PasswordManager";
187int CFrmWebView::progress()
const
189 return m_loadProgress;
192QIcon CFrmWebView::favIcon()
const
194 QIcon favIcon = icon();
195 if (!favIcon.isNull())
198 if (m_loadProgress < 0) {
199 static QIcon errorIcon(
"dialog-error");
202 if (m_loadProgress < 100) {
203 static QIcon loadingIcon = QIcon::fromTheme(
"view-refresh");
207 static QIcon defaultIcon(
"text-html");
211QWebEngineView *CFrmWebView::createWindow(QWebEnginePage::WebWindowType type)
214 return m_pBrowser->CreateWindow(
215 type, this->page()->profile()->isOffTheRecord());
219void CFrmWebView::CreateWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction webAction)
221 QAction *action = page->action(webAction);
222#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
223 connect(action, &QAction::enabledChanged,
224 [
this, action, webAction](
bool enabled) {
225 qDebug(log) <<
"webAction:" << webAction << enabled;
226 emit sigWebActionEnabledChanged(webAction, enabled);
229 connect(action, &QAction::changed, [
this, action, webAction]{
230 emit sigWebActionEnabledChanged(webAction, action->isEnabled());
235void CFrmWebView::contextMenuEvent(QContextMenuEvent *event)
238#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
239 menu = createStandardContextMenu();
241 menu = page()->createStandardContextMenu();
243 const QList<QAction *> actions = menu->actions();
244 auto inspectElement = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement));
245 if (inspectElement == actions.cend()) {
246 auto viewSource = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::ViewSource));
247 if (viewSource == actions.cend())
248 menu->addSeparator();
249 QAction *action = menu->addAction(tr(
"Open inspector"));
250 connect(action, &QAction::triggered,
251 [
this]() { emit sigDevToolsRequested(page());});
253 (*inspectElement)->setText(tr(
"Inspect element"));
255#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
257 QMenu *editImageAnimation =
new QMenu(tr(
"Image animation policy"));
259 m_imageAnimationGroup =
new QActionGroup(editImageAnimation);
260 m_imageAnimationGroup->setExclusive(
true);
262 QAction *disableImageAnimation =
263 editImageAnimation->addAction(tr(
"Disable all image animation"));
264 disableImageAnimation->setCheckable(
true);
265 m_imageAnimationGroup->addAction(disableImageAnimation);
266 connect(disableImageAnimation, &QAction::triggered, [
this]() {
267 handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Disallow);
269 QAction *allowImageAnimationOnce =
270 editImageAnimation->addAction(tr(
"Allow animated images, but only once"));
271 allowImageAnimationOnce->setCheckable(
true);
272 m_imageAnimationGroup->addAction(allowImageAnimationOnce);
273 connect(allowImageAnimationOnce, &QAction::triggered,
274 [
this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::AnimateOnce); });
275 QAction *allowImageAnimation = editImageAnimation->addAction(tr(
"Allow all animated images"));
276 allowImageAnimation->setCheckable(
true);
277 m_imageAnimationGroup->addAction(allowImageAnimation);
278 connect(allowImageAnimation, &QAction::triggered, [
this]() {
279 handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Allow);
282 switch (page()->settings()->imageAnimationPolicy()) {
283 case QWebEngineSettings::ImageAnimationPolicy::Allow:
284 allowImageAnimation->setChecked(
true);
286 case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce:
287 allowImageAnimationOnce->setChecked(
true);
289 case QWebEngineSettings::ImageAnimationPolicy::Disallow:
290 disableImageAnimation->setChecked(
true);
293 allowImageAnimation->setChecked(
true);
297 menu->addMenu(editImageAnimation);
299 menu->popup(event->globalPos());
302void CFrmWebView::slotSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection)
304 qDebug(log) << Q_FUNC_INFO;
305 if(clientCertSelection.certificates().size() > 0) {
307 clientCertSelection.select(clientCertSelection.certificates().at(0));
311#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
312void CFrmWebView::slotCertificateError(QWebEngineCertificateError error)
314 qDebug(log) << Q_FUNC_INFO << error.type() << error.url();
318 if (!error.isMainFrame()) {
319 error.rejectCertificate();
324 QSettings set(RabbitCommon::CDir::Instance()->GetDirUserData()
325 + QDir::separator() +
"WebBrowser.ini",
326 QSettings::IniFormat);
327 if(set.value(
"Certificate/Error/Accept" + error.url().toString(),
false).toBool()) {
328 error.acceptCertificate();
332 szMsg = error.description() +
"\n\n";
333 szMsg += tr(
"If you wish so, you may continue with an unverified certificate. "
334 "Accepting an unverified certificate mean you may not be connected with the host you tried to connect to.") +
"\n\n"
335 + tr(
"Yes - Always accept an unverified certificate and continue") +
"\n"
336 + tr(
"Ok - Accept an unverified certificate and continue only this time") +
"\n"
337 + tr(
"Cancel - Reject an unverified certificate and break");
338 int nRet = QMessageBox::critical(window(), tr(
"Certificate Error"), szMsg,
339 QMessageBox::StandardButton::Ok
340 | QMessageBox::StandardButton::Yes
341 | QMessageBox::StandardButton::Cancel,
342 QMessageBox::StandardButton::Cancel);
344 case QMessageBox::StandardButton::Yes:
345 set.setValue(
"Certificate/Error/Accept" + error.url().toString(),
true);
346 case QMessageBox::StandardButton::Ok:
347 error.acceptCertificate();
349 case QMessageBox::Cancel:
350 error.rejectCertificate();
361void CFrmWebView::slotAuthenticationRequired(
const QUrl &requestUrl, QAuthenticator *auth)
363 qDebug(log) << Q_FUNC_INFO;
366 dlg.SetUser(tr(
"Set user and password") +
"\n" + requestUrl.toString(), &user);
367 if (dlg.exec() == QDialog::Accepted) {
368 auth->setUser(user.GetName());
369 auth->setPassword(user.GetPassword());
372 *auth = QAuthenticator();
376void CFrmWebView::slotProxyAuthenticationRequired(
const QUrl &url, QAuthenticator *auth,
377 const QString &proxyHost)
379 qDebug(log) << Q_FUNC_INFO;
382 dlg.SetUser(tr(
"Set user and password of proxy") +
"\n" + proxyHost.toHtmlEscaped(), &user);
383 if (dlg.exec() == QDialog::Accepted) {
384 auth->setUser(user.GetName());
385 auth->setPassword(user.GetPassword());
388 *auth = QAuthenticator();
417#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
418void CFrmWebView::slotPermissionRequested(QWebEnginePermission permission)
420 qDebug(log) << Q_FUNC_INFO;
421 QString title = tr(
"Permission Request");
422 QString question = questionForPermissionType(permission.permissionType()).arg(permission.origin().host());
423 if (!question.isEmpty() && QMessageBox::question(window(), title, question) == QMessageBox::Yes)
429void CFrmWebView::handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy)
431 qDebug(log) << Q_FUNC_INFO;
435 page()->settings()->setImageAnimationPolicy(policy);
439#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
441#include "DlgScreenCapture.h"
442void CFrmWebView::slotDesktopMediaRequest(
const QWebEngineDesktopMediaRequest &request)
444 qDebug(log) << Q_FUNC_INFO;
445 QString szMsg =
"- Screen:\n";
446 for(
int i = 0; i < request.screensModel()->rowCount(); i++) {
448 auto model = request.screensModel();
449 index = model->index(i, 0);
450 szMsg +=
" - " + QString::number(i) +
": " + model->data(index).toString() +
"\n";
452 szMsg +=
"- Windows:\n";
453 for(
int w = 0; w < request.windowsModel()->rowCount(); w++) {
455 auto model = request.windowsModel();
456 index = model->index(w, 0);
457 szMsg +=
" - " + QString::number(w) +
": " + model->data(index).toString() +
"\n";
459 qDebug(log) << szMsg;
461 if(request.screensModel()->rowCount() <= 0 && request.windowsModel()->rowCount() <= 0)
465 if(QDialog::Rejected == dlg.exec())
468 CDlgScreenCapture::Type type;
470 int nRet = dlg.GetIndex(type,
id);
473 QAbstractListModel* model =
nullptr;
475 case CDlgScreenCapture::Type::Screen: {
476 model = request.screensModel();
478 if(0 >
id || model->rowCount() <=
id)
480 index = model->index(
id);
481 request.selectScreen(index);
484 case CDlgScreenCapture::Type::Window: {
485 model = request.windowsModel();
487 if(0 >
id || model->rowCount() <=
id)
489 index = model->index(
id);
490 request.selectWindow(index);
497void CFrmWebView::slotWebAuthUxRequested(QWebEngineWebAuthUxRequest *request)
499 qDebug(log) << Q_FUNC_INFO;
502 delete m_pDlgWebAuth;
504 m_pDlgWebAuth =
new CDlgWebAuth(request, window());
505 m_pDlgWebAuth->setModal(
false);
506 m_pDlgWebAuth->setWindowFlags(m_pDlgWebAuth->windowFlags() & ~Qt::WindowContextHelpButtonHint);
508 connect(request, &QWebEngineWebAuthUxRequest::stateChanged,
this, &CFrmWebView::onStateChanged);
509 m_pDlgWebAuth->show();
513void CFrmWebView::onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state)
515 qDebug(log) << Q_FUNC_INFO;
517 if (QWebEngineWebAuthUxRequest::WebAuthUxState::Completed == state
518 || QWebEngineWebAuthUxRequest::WebAuthUxState::Cancelled == state) {
520 delete m_pDlgWebAuth;
521 m_pDlgWebAuth =
nullptr;
524 m_pDlgWebAuth->updateDisplay();
530void CFrmWebView::slotFullScreenRequested(QWebEngineFullScreenRequest request)
532 qDebug(log) <<
"slotFullScreenRequested";
533 if (request.toggleOn()) {
535 this->showFullScreen();
540 m_pBrowser->slotFullScreen(request.toggleOn());
545 QWebEngineRegisterProtocolHandlerRequest request)
547 qDebug(log) << Q_FUNC_INFO;
548 auto answer = QMessageBox::question(window(), tr(
"Permission Request"),
549 tr(
"Allow %1 to open all %2 links?")
550 .arg(request.origin().host())
551 .arg(request.scheme()));
552 if (answer == QMessageBox::Yes)
559#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
562 qDebug(log) << Q_FUNC_INFO;
564 switch (request.accessFlags()) {
565 case QWebEngineFileSystemAccessRequest::Read:
568 case QWebEngineFileSystemAccessRequest::Write:
569 accessType =
"write";
571 case QWebEngineFileSystemAccessRequest::Read | QWebEngineFileSystemAccessRequest::Write:
572 accessType =
"read and write";
578 auto answer = QMessageBox::question(window(), tr(
"File system access request"),
579 tr(
"Give %1 %2 access to %3?")
580 .arg(request.origin().host())
582 .arg(request.filePath().toString()));
583 if (answer == QMessageBox::Yes)
590QString CFrmWebView::AutoFillForm()
594 function autoFillForm(username, password) {
596 var usernameFields = [
597 'input[type="text"]',
598 'input[type="email"]',
599 'input[name="username"]',
600 'input[name="user"]',
601 'input[name="email"]',
602 'input[id="username"]',
608 var passwordFields = [
609 'input[type="password"]',
610 'input[name="password"]',
611 'input[name="pass"]',
612 'input[id="password"]',
617 var arrPassword = [];
620 usernameFields.forEach(function(selector) {
621 var field = document.querySelector(selector);
622 if (field && !field.value) {
625 //var event = new Event('input', { bubbles: true });
626 //field.dispatchEvent(event);
631 passwordFields.forEach(function(selector) {
632 var field = document.querySelector(selector);
633 if (field && !field.value) {
634 arrPassword.push(field);
636 //var event = new Event('input', { bubbles: true });
637 //field.dispatchEvent(event);
641 arrUser.forEach(function(input) {
642 if(!input.disabled && !input.readOnly)
643 input.value = username;
645 arrPassword.forEach(function(input) {
646 if(!input.disabled && !input.readOnly)
647 input.value = password;
653void CFrmWebView::SetupAutoFillScript() {
655 QString autoFillScript =
"(function() {";
656 autoFillScript += AutoFillForm();
657 autoFillScript += R
"(
659 window.autoFillForm = autoFillForm;
663 QWebEngineScript script;
664 script.setSourceCode(autoFillScript);
665 script.setInjectionPoint(QWebEngineScript::DocumentCreation);
666 script.setWorldId(QWebEngineScript::MainWorld);
667 page()->scripts().insert(script);
670void CFrmWebView::GlobalFillForm(
const QString &szUse,
const QString &szPassword)
672 QString js = QString(
"window.autoFillForm('%1', '%2');").arg(szUse, szPassword);
673 page()->runJavaScript(js);
676void CFrmWebView::FillForm(
const QString &szUse,
const QString &szPassword)
678 QString js = AutoFillForm();
679 js += QString(
"autoFillForm('%1', '%2');").arg(szUse, szPassword);
680 page()->runJavaScript(js);
683int CFrmWebView::GetUserAndPassword(QUrl url, QString &szUser, QString &szPassword)
692void CFrmWebView::InjectScriptQWebChannel()
698 auto loadResource = [](
const QString &rcPath)->QString{
700 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
701 qCritical(log) <<
"Cannot open resource" << rcPath;
704 return QString::fromUtf8(f.readAll());
707 QString qwebchannelJs = loadResource(
":/js/QWebChannel.js");
708 if (qwebchannelJs.isEmpty()) {
709 qCritical(log) <<
"QWebChannel.js not found in resources!";
714 QWebEngineScript channelScript;
715 channelScript.setName(
"qwebchannel.js");
716 channelScript.setInjectionPoint(QWebEngineScript::DocumentCreation);
717 channelScript.setRunsOnSubFrames(
true);
718 channelScript.setWorldId(QWebEngineScript::MainWorld);
719 channelScript.setSourceCode(qwebchannelJs);
720 page()->scripts().insert(channelScript);
723void CFrmWebView::InjectScriptAutoFill()
729 auto loadResource = [](
const QString &rcPath)->QString{
731 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
732 qCritical(log) <<
"Cannot open resource" << rcPath;
735 return QString::fromUtf8(f.readAll());
738 QString autofillJs = loadResource(
":/js/AutoFill.js");
739 if (autofillJs.isEmpty()) {
740 qCritical(log) <<
"AutoFill.js not found in resources!";
751 page()->runJavaScript(autofillJs);
void slotFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request)
[registerProtocolHandlerRequested]
void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request)
[registerProtocolHandlerRequested]
用户名与验证方式。此类仅在插件内有效。它的界面是 CParameterUserUI