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, [
this, action, webAction](
bool enabled){
224 qDebug(log) <<
"webAction:" << webAction << enabled;
225 emit sigWebActionEnabledChanged(webAction, enabled);
228 connect(action, &QAction::changed, [
this, action, webAction]{
229 emit sigWebActionEnabledChanged(webAction, action->isEnabled());
234void CFrmWebView::contextMenuEvent(QContextMenuEvent *event)
237#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
238 menu = createStandardContextMenu();
240 menu = page()->createStandardContextMenu();
242 const QList<QAction *> actions = menu->actions();
243 auto inspectElement = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement));
244 if (inspectElement == actions.cend()) {
245 auto viewSource = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::ViewSource));
246 if (viewSource == actions.cend())
247 menu->addSeparator();
248 QAction *action = menu->addAction(tr(
"Open inspector"));
249 connect(action, &QAction::triggered,
250 [
this]() { emit sigDevToolsRequested(page());});
252 (*inspectElement)->setText(tr(
"Inspect element"));
254#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
256 QMenu *editImageAnimation =
new QMenu(tr(
"Image animation policy"));
258 m_imageAnimationGroup =
new QActionGroup(editImageAnimation);
259 m_imageAnimationGroup->setExclusive(
true);
261 QAction *disableImageAnimation =
262 editImageAnimation->addAction(tr(
"Disable all image animation"));
263 disableImageAnimation->setCheckable(
true);
264 m_imageAnimationGroup->addAction(disableImageAnimation);
265 connect(disableImageAnimation, &QAction::triggered, [
this]() {
266 handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Disallow);
268 QAction *allowImageAnimationOnce =
269 editImageAnimation->addAction(tr(
"Allow animated images, but only once"));
270 allowImageAnimationOnce->setCheckable(
true);
271 m_imageAnimationGroup->addAction(allowImageAnimationOnce);
272 connect(allowImageAnimationOnce, &QAction::triggered,
273 [
this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::AnimateOnce); });
274 QAction *allowImageAnimation = editImageAnimation->addAction(tr(
"Allow all animated images"));
275 allowImageAnimation->setCheckable(
true);
276 m_imageAnimationGroup->addAction(allowImageAnimation);
277 connect(allowImageAnimation, &QAction::triggered, [
this]() {
278 handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Allow);
281 switch (page()->settings()->imageAnimationPolicy()) {
282 case QWebEngineSettings::ImageAnimationPolicy::Allow:
283 allowImageAnimation->setChecked(
true);
285 case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce:
286 allowImageAnimationOnce->setChecked(
true);
288 case QWebEngineSettings::ImageAnimationPolicy::Disallow:
289 disableImageAnimation->setChecked(
true);
292 allowImageAnimation->setChecked(
true);
296 menu->addMenu(editImageAnimation);
298 menu->popup(event->globalPos());
301void CFrmWebView::slotSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection)
303 qDebug(log) << Q_FUNC_INFO;
304 if(clientCertSelection.certificates().size() > 0) {
306 clientCertSelection.select(clientCertSelection.certificates().at(0));
310#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
311void CFrmWebView::slotCertificateError(QWebEngineCertificateError error)
313 qDebug(log) << Q_FUNC_INFO << error.type() << error.url();
317 if (!error.isMainFrame()) {
318 error.rejectCertificate();
323 QSettings set(RabbitCommon::CDir::Instance()->GetDirUserData()
324 + QDir::separator() +
"WebBrowser.ini",
325 QSettings::IniFormat);
326 if(set.value(
"Certificate/Error/Accept" + error.url().toString(),
false).toBool()) {
327 error.acceptCertificate();
331 szMsg = error.description() +
"\n\n";
332 szMsg += tr(
"If you wish so, you may continue with an unverified certificate. "
333 "Accepting an unverified certificate mean you may not be connected with the host you tried to connect to.") +
"\n\n"
334 + tr(
"Yes - Always accept an unverified certificate and continue") +
"\n"
335 + tr(
"Ok - Accept an unverified certificate and continue only this time") +
"\n"
336 + tr(
"Cancel - Reject an unverified certificate and break");
337 int nRet = QMessageBox::critical(window(), tr(
"Certificate Error"), szMsg,
338 QMessageBox::StandardButton::Ok
339 | QMessageBox::StandardButton::Yes
340 | QMessageBox::StandardButton::Cancel,
341 QMessageBox::StandardButton::Cancel);
343 case QMessageBox::StandardButton::Yes:
344 set.setValue(
"Certificate/Error/Accept" + error.url().toString(),
true);
345 case QMessageBox::StandardButton::Ok:
346 error.acceptCertificate();
348 case QMessageBox::Cancel:
349 error.rejectCertificate();
360void CFrmWebView::slotAuthenticationRequired(
const QUrl &requestUrl, QAuthenticator *auth)
362 qDebug(log) << Q_FUNC_INFO;
365 dlg.SetUser(tr(
"Set user and password") +
"\n" + requestUrl.toString(), &user);
366 if (dlg.exec() == QDialog::Accepted) {
367 auth->setUser(user.GetName());
368 auth->setPassword(user.GetPassword());
371 *auth = QAuthenticator();
375void CFrmWebView::slotProxyAuthenticationRequired(
const QUrl &url, QAuthenticator *auth,
376 const QString &proxyHost)
378 qDebug(log) << Q_FUNC_INFO;
381 dlg.SetUser(tr(
"Set user and password of proxy") +
"\n" + proxyHost.toHtmlEscaped(), &user);
382 if (dlg.exec() == QDialog::Accepted) {
383 auth->setUser(user.GetName());
384 auth->setPassword(user.GetPassword());
387 *auth = QAuthenticator();
416#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
417void CFrmWebView::slotPermissionRequested(QWebEnginePermission permission)
419 qDebug(log) << Q_FUNC_INFO;
420 QString title = tr(
"Permission Request");
421 QString question = questionForPermissionType(permission.permissionType()).arg(permission.origin().host());
422 if (!question.isEmpty() && QMessageBox::question(window(), title, question) == QMessageBox::Yes)
428void CFrmWebView::handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy)
430 qDebug(log) << Q_FUNC_INFO;
434 page()->settings()->setImageAnimationPolicy(policy);
438#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
440#include "DlgScreenCapture.h"
441void CFrmWebView::slotDesktopMediaRequest(
const QWebEngineDesktopMediaRequest &request)
443 qDebug(log) << Q_FUNC_INFO;
444 QString szMsg =
"- Screen:\n";
445 for(
int i = 0; i < request.screensModel()->rowCount(); i++) {
447 auto model = request.screensModel();
448 index = model->index(i, 0);
449 szMsg +=
" - " + QString::number(i) +
": " + model->data(index).toString() +
"\n";
451 szMsg +=
"- Windows:\n";
452 for(
int w = 0; w < request.windowsModel()->rowCount(); w++) {
454 auto model = request.windowsModel();
455 index = model->index(w, 0);
456 szMsg +=
" - " + QString::number(w) +
": " + model->data(index).toString() +
"\n";
458 qDebug(log) << szMsg;
460 if(request.screensModel()->rowCount() <= 0 && request.windowsModel()->rowCount() <= 0)
464 if(QDialog::Rejected == dlg.exec())
467 CDlgScreenCapture::Type type;
469 int nRet = dlg.GetIndex(type,
id);
472 QAbstractListModel* model =
nullptr;
474 case CDlgScreenCapture::Type::Screen: {
475 model = request.screensModel();
477 if(0 >
id || model->rowCount() <=
id)
479 index = model->index(
id);
480 request.selectScreen(index);
483 case CDlgScreenCapture::Type::Window: {
484 model = request.windowsModel();
486 if(0 >
id || model->rowCount() <=
id)
488 index = model->index(
id);
489 request.selectWindow(index);
496void CFrmWebView::slotWebAuthUxRequested(QWebEngineWebAuthUxRequest *request)
498 qDebug(log) << Q_FUNC_INFO;
501 delete m_pDlgWebAuth;
503 m_pDlgWebAuth =
new CDlgWebAuth(request, window());
504 m_pDlgWebAuth->setModal(
false);
505 m_pDlgWebAuth->setWindowFlags(m_pDlgWebAuth->windowFlags() & ~Qt::WindowContextHelpButtonHint);
507 connect(request, &QWebEngineWebAuthUxRequest::stateChanged,
this, &CFrmWebView::onStateChanged);
508 m_pDlgWebAuth->show();
512void CFrmWebView::onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state)
514 qDebug(log) << Q_FUNC_INFO;
516 if (QWebEngineWebAuthUxRequest::WebAuthUxState::Completed == state
517 || QWebEngineWebAuthUxRequest::WebAuthUxState::Cancelled == state) {
519 delete m_pDlgWebAuth;
520 m_pDlgWebAuth =
nullptr;
523 m_pDlgWebAuth->updateDisplay();
529void CFrmWebView::slotFullScreenRequested(QWebEngineFullScreenRequest request)
531 qDebug(log) <<
"slotFullScreenRequested";
532 if (request.toggleOn()) {
534 this->showFullScreen();
539 m_pBrowser->slotFullScreen(request.toggleOn());
544 QWebEngineRegisterProtocolHandlerRequest request)
546 qDebug(log) << Q_FUNC_INFO;
547 auto answer = QMessageBox::question(window(), tr(
"Permission Request"),
548 tr(
"Allow %1 to open all %2 links?")
549 .arg(request.origin().host())
550 .arg(request.scheme()));
551 if (answer == QMessageBox::Yes)
558#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
561 qDebug(log) << Q_FUNC_INFO;
563 switch (request.accessFlags()) {
564 case QWebEngineFileSystemAccessRequest::Read:
567 case QWebEngineFileSystemAccessRequest::Write:
568 accessType =
"write";
570 case QWebEngineFileSystemAccessRequest::Read | QWebEngineFileSystemAccessRequest::Write:
571 accessType =
"read and write";
577 auto answer = QMessageBox::question(window(), tr(
"File system access request"),
578 tr(
"Give %1 %2 access to %3?")
579 .arg(request.origin().host())
581 .arg(request.filePath().toString()));
582 if (answer == QMessageBox::Yes)
589QString CFrmWebView::AutoFillForm()
593 function autoFillForm(username, password) {
595 var usernameFields = [
596 'input[type="text"]',
597 'input[type="email"]',
598 'input[name="username"]',
599 'input[name="user"]',
600 'input[name="email"]',
601 'input[id="username"]',
607 var passwordFields = [
608 'input[type="password"]',
609 'input[name="password"]',
610 'input[name="pass"]',
611 'input[id="password"]',
616 var arrPassword = [];
619 usernameFields.forEach(function(selector) {
620 var field = document.querySelector(selector);
621 if (field && !field.value) {
624 //var event = new Event('input', { bubbles: true });
625 //field.dispatchEvent(event);
630 passwordFields.forEach(function(selector) {
631 var field = document.querySelector(selector);
632 if (field && !field.value) {
633 arrPassword.push(field);
635 //var event = new Event('input', { bubbles: true });
636 //field.dispatchEvent(event);
640 arrUser.forEach(function(input) {
641 if(!input.disabled && !input.readOnly)
642 input.value = username;
644 arrPassword.forEach(function(input) {
645 if(!input.disabled && !input.readOnly)
646 input.value = password;
652void CFrmWebView::SetupAutoFillScript() {
654 QString autoFillScript =
"(function() {";
655 autoFillScript += AutoFillForm();
656 autoFillScript += R
"(
658 window.autoFillForm = autoFillForm;
662 QWebEngineScript script;
663 script.setSourceCode(autoFillScript);
664 script.setInjectionPoint(QWebEngineScript::DocumentCreation);
665 script.setWorldId(QWebEngineScript::MainWorld);
666 page()->scripts().insert(script);
669void CFrmWebView::GlobalFillForm(
const QString &szUse,
const QString &szPassword)
671 QString js = QString(
"window.autoFillForm('%1', '%2');").arg(szUse, szPassword);
672 page()->runJavaScript(js);
675void CFrmWebView::FillForm(
const QString &szUse,
const QString &szPassword)
677 QString js = AutoFillForm();
678 js += QString(
"autoFillForm('%1', '%2');").arg(szUse, szPassword);
679 page()->runJavaScript(js);
682int CFrmWebView::GetUserAndPassword(QUrl url, QString &szUser, QString &szPassword)
691void CFrmWebView::InjectScriptQWebChannel()
697 auto loadResource = [](
const QString &rcPath)->QString{
699 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
700 qCritical(log) <<
"Cannot open resource" << rcPath;
703 return QString::fromUtf8(f.readAll());
706 QString qwebchannelJs = loadResource(
":/js/QWebChannel.js");
707 if (qwebchannelJs.isEmpty()) {
708 qCritical(log) <<
"QWebChannel.js not found in resources!";
713 QWebEngineScript channelScript;
714 channelScript.setName(
"qwebchannel.js");
715 channelScript.setInjectionPoint(QWebEngineScript::DocumentCreation);
716 channelScript.setRunsOnSubFrames(
true);
717 channelScript.setWorldId(QWebEngineScript::MainWorld);
718 channelScript.setSourceCode(qwebchannelJs);
719 page()->scripts().insert(channelScript);
722void CFrmWebView::InjectScriptAutoFill()
728 auto loadResource = [](
const QString &rcPath)->QString{
730 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
731 qCritical(log) <<
"Cannot open resource" << rcPath;
734 return QString::fromUtf8(f.readAll());
737 QString autofillJs = loadResource(
":/js/AutoFill.js");
738 if (autofillJs.isEmpty()) {
739 qCritical(log) <<
"AutoFill.js not found in resources!";
750 page()->runJavaScript(autofillJs);
void slotFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request)
[registerProtocolHandlerRequested]
void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request)
[registerProtocolHandlerRequested]
用户名与验证方式。此类仅在插件内有效。它的界面是 CParameterUserUI