3#include <QContextMenuEvent>
6#include <QAuthenticator>
9#include <QLoggingCategory>
10#include <QWebEngineProfile>
11#include <QWebEngineScript>
12#include <QWebEngineScriptCollection>
14#include "FrmWebView.h"
15#include "FrmWebBrowser.h"
16#include "DlgUserPassword.h"
18static Q_LOGGING_CATEGORY(log,
"WebBrowser.View")
21 : QWebEngineView(parent)
22 , m_pBrowser(pFrmWebBrowser)
23#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
24 , m_pDlgWebAuth(
nullptr)
26 , m_pWebChannel(
nullptr)
27 , m_pPasswordStore(
nullptr)
30 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
31 setMinimumSize(200, 100);
33 check = connect(
this, &QWebEngineView::loadStarted, [
this]() {
35 emit favIconChanged(favIcon());
38 check = connect(
this, &QWebEngineView::loadProgress, [
this](
int progress) {
39 m_loadProgress = progress;
42 check = connect(
this, &QWebEngineView::loadFinished, [
this](
bool success) {
43 m_loadProgress = success ? 100 : -1;
44 emit favIconChanged(favIcon());
45 if(m_pBrowser->m_pPara->GetAutoFillUserAndPassword() && m_pWebChannel && m_pPasswordStore)
46 InjectScriptAutoFill();
48 QString szUser, szPassword;
49 int nRet = GetUserAndPassword(this->url(), szUser, szPassword);
51 SetupAutoFillScript();
52 GlobalFillForm(szUser, szPassword);
57 check = connect(
this, &QWebEngineView::iconChanged, [
this](
const QIcon &) {
58 emit favIconChanged(favIcon());
61 check = connect(
this, &QWebEngineView::renderProcessTerminated,
62 [
this](QWebEnginePage::RenderProcessTerminationStatus termStatus,
int statusCode) {
65 case QWebEnginePage::NormalTerminationStatus:
66 status = tr(
"Render process normal exit");
68 case QWebEnginePage::AbnormalTerminationStatus:
69 status = tr(
"Render process abnormal exit");
71 case QWebEnginePage::CrashedTerminationStatus:
72 status = tr(
"Render process crashed");
74 case QWebEnginePage::KilledTerminationStatus:
75 status = tr(
"Render process killed");
78 QMessageBox::StandardButton btn = QMessageBox::question(window(), status,
79 tr(
"Render process exited with code: %1\n"
80 "Do you want to reload the page ?").arg(statusCode));
81 if (btn == QMessageBox::Yes)
82 QTimer::singleShot(0,
this, &CFrmWebView::reload);
87CFrmWebView::~CFrmWebView()
89 qDebug(log) << Q_FUNC_INFO;
90 if (m_imageAnimationGroup)
91 delete m_imageAnimationGroup;
93 m_imageAnimationGroup =
nullptr;
96#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
97inline QString questionForPermissionType(QWebEnginePermission::PermissionType permissionType)
99 switch (permissionType) {
100 case QWebEnginePermission::PermissionType::Geolocation:
101 return QObject::tr(
"Allow %1 to access your location information?");
102 case QWebEnginePermission::PermissionType::MediaAudioCapture:
103 return QObject::tr(
"Allow %1 to access your microphone?");
104 case QWebEnginePermission::PermissionType::MediaVideoCapture:
105 return QObject::tr(
"Allow %1 to access your webcam?");
106 case QWebEnginePermission::PermissionType::MediaAudioVideoCapture:
107 return QObject::tr(
"Allow %1 to access your microphone and webcam?");
108 case QWebEnginePermission::PermissionType::MouseLock:
109 return QObject::tr(
"Allow %1 to lock your mouse cursor?");
110 case QWebEnginePermission::PermissionType::DesktopVideoCapture:
111 return QObject::tr(
"Allow %1 to capture video of your desktop?");
112 case QWebEnginePermission::PermissionType::DesktopAudioVideoCapture:
113 return QObject::tr(
"Allow %1 to capture audio and video of your desktop?");
114 case QWebEnginePermission::PermissionType::Notifications:
115 return QObject::tr(
"Allow %1 to show notification on your desktop?");
116 case QWebEnginePermission::PermissionType::ClipboardReadWrite:
117 return QObject::tr(
"Allow %1 to read from and write to the clipboard?");
118 case QWebEnginePermission::PermissionType::LocalFontsAccess:
119 return QObject::tr(
"Allow %1 to access fonts stored on this machine?");
120 case QWebEnginePermission::PermissionType::Unsupported:
127void CFrmWebView::setPage(QWebEnginePage *page)
130 CreateWebActionTrigger(page,QWebEnginePage::Forward);
131 CreateWebActionTrigger(page,QWebEnginePage::Back);
132 CreateWebActionTrigger(page,QWebEnginePage::Reload);
133 CreateWebActionTrigger(page,QWebEnginePage::Stop);
135 if (
auto oldPage = QWebEngineView::page()) {
136 oldPage->disconnect(
this);
138 QWebEngineView::setPage(page);
139 connect(page, &QWebEnginePage::linkHovered,
this, &CFrmWebView::sigLinkHovered);
140 connect(page, &QWebEnginePage::windowCloseRequested,
141 this, &CFrmWebView::sigCloseRequested);
142 connect(page, &QWebEnginePage::selectClientCertificate,
143 this, &CFrmWebView::slotSelectClientCertificate);
144 connect(page, &QWebEnginePage::authenticationRequired,
this,
145 &CFrmWebView::slotAuthenticationRequired);
146 connect(page, &QWebEnginePage::proxyAuthenticationRequired,
this,
147 &CFrmWebView::slotProxyAuthenticationRequired);
148 connect(page, &QWebEnginePage::registerProtocolHandlerRequested,
this,
150 #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
151 connect(page, &QWebEnginePage::certificateError,
152 this, &CFrmWebView::slotCertificateError);
153 connect(page, &QWebEnginePage::permissionRequested,
this,
154 &CFrmWebView::slotPermissionRequested);
156 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
157 connect(page, &QWebEnginePage::fileSystemAccessRequested,
this,
160 #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
161 connect(page, &QWebEnginePage::desktopMediaRequested,
this,
162 &CFrmWebView::slotDesktopMediaRequest);
163 connect(page, &QWebEnginePage::webAuthUxRequested,
164 this, &CFrmWebView::slotWebAuthUxRequested);
167 Q_ASSERT(m_pBrowser);
168 if(m_pBrowser->m_pPara->GetAutoFillUserAndPassword()) {
170 m_pWebChannel =
new QWebChannel(
this);
171 if(!m_pPasswordStore)
173 if(m_pWebChannel && m_pPasswordStore) {
175 m_pWebChannel->registerObject(QStringLiteral(
"PasswordManager"), m_pPasswordStore);
176 page->setWebChannel(m_pWebChannel);
177 InjectScriptQWebChannel();
179 qCritical(log) <<
"Don't register PasswordManager";
183int CFrmWebView::progress()
const
185 return m_loadProgress;
188QIcon CFrmWebView::favIcon()
const
190 QIcon favIcon = icon();
191 if (!favIcon.isNull())
194 if (m_loadProgress < 0) {
195 static QIcon errorIcon(
"dialog-error");
198 if (m_loadProgress < 100) {
199 static QIcon loadingIcon = QIcon::fromTheme(
"view-refresh");
203 static QIcon defaultIcon(
"text-html");
207QWebEngineView *CFrmWebView::createWindow(QWebEnginePage::WebWindowType type)
210 return m_pBrowser->CreateWindow(
211 type, this->page()->profile()->isOffTheRecord());
215void CFrmWebView::CreateWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction webAction)
217 QAction *action = page->action(webAction);
218#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
219 connect(action, &QAction::enabledChanged, [
this, action, webAction](
bool enabled){
220 qDebug(log) <<
"webAction:" << webAction << enabled;
221 emit sigWebActionEnabledChanged(webAction, enabled);
224 connect(action, &QAction::changed, [
this, action, webAction]{
225 emit sigWebActionEnabledChanged(webAction, action->isEnabled());
230void CFrmWebView::contextMenuEvent(QContextMenuEvent *event)
233#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
234 menu = createStandardContextMenu();
236 menu = page()->createStandardContextMenu();
238 const QList<QAction *> actions = menu->actions();
239 auto inspectElement = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement));
240 if (inspectElement == actions.cend()) {
241 auto viewSource = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::ViewSource));
242 if (viewSource == actions.cend())
243 menu->addSeparator();
244 QAction *action = menu->addAction(tr(
"Open inspector"));
245 connect(action, &QAction::triggered,
246 [
this]() { emit sigDevToolsRequested(page());});
248 (*inspectElement)->setText(tr(
"Inspect element"));
250#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
252 QMenu *editImageAnimation =
new QMenu(tr(
"Image animation policy"));
254 m_imageAnimationGroup =
new QActionGroup(editImageAnimation);
255 m_imageAnimationGroup->setExclusive(
true);
257 QAction *disableImageAnimation =
258 editImageAnimation->addAction(tr(
"Disable all image animation"));
259 disableImageAnimation->setCheckable(
true);
260 m_imageAnimationGroup->addAction(disableImageAnimation);
261 connect(disableImageAnimation, &QAction::triggered, [
this]() {
262 handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Disallow);
264 QAction *allowImageAnimationOnce =
265 editImageAnimation->addAction(tr(
"Allow animated images, but only once"));
266 allowImageAnimationOnce->setCheckable(
true);
267 m_imageAnimationGroup->addAction(allowImageAnimationOnce);
268 connect(allowImageAnimationOnce, &QAction::triggered,
269 [
this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::AnimateOnce); });
270 QAction *allowImageAnimation = editImageAnimation->addAction(tr(
"Allow all animated images"));
271 allowImageAnimation->setCheckable(
true);
272 m_imageAnimationGroup->addAction(allowImageAnimation);
273 connect(allowImageAnimation, &QAction::triggered, [
this]() {
274 handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Allow);
277 switch (page()->settings()->imageAnimationPolicy()) {
278 case QWebEngineSettings::ImageAnimationPolicy::Allow:
279 allowImageAnimation->setChecked(
true);
281 case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce:
282 allowImageAnimationOnce->setChecked(
true);
284 case QWebEngineSettings::ImageAnimationPolicy::Disallow:
285 disableImageAnimation->setChecked(
true);
288 allowImageAnimation->setChecked(
true);
292 menu->addMenu(editImageAnimation);
294 menu->popup(event->globalPos());
297void CFrmWebView::slotSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection)
299 qDebug(log) << Q_FUNC_INFO;
300 if(clientCertSelection.certificates().size() > 0) {
302 clientCertSelection.select(clientCertSelection.certificates().at(0));
306#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
307void CFrmWebView::slotCertificateError(QWebEngineCertificateError error)
309 qDebug(log) << Q_FUNC_INFO;
313 if (!error.isMainFrame()) {
314 error.rejectCertificate();
320 szMsg = error.description() +
"\n\n";
321 szMsg += tr(
"If you wish so, you may continue with an unverified certificate. "
322 "Accepting an unverified certificate mean you may not be connected with the host you tried to connect to.") +
323 "\n\n" + tr(
"Do you wish to override the security check and continue ?");
324 int nRet = QMessageBox::critical(window(), tr(
"Certificate Error"), szMsg,
325 QMessageBox::StandardButton::Yes
326 | QMessageBox::StandardButton::No,
327 QMessageBox::StandardButton::No);
328 if(QMessageBox::StandardButton::Yes == nRet)
329 error.acceptCertificate();
331 error.rejectCertificate();
340void CFrmWebView::slotAuthenticationRequired(
const QUrl &requestUrl, QAuthenticator *auth)
342 qDebug(log) << Q_FUNC_INFO;
345 dlg.SetUser(tr(
"Set user and password") +
"\n" + requestUrl.toString(), &user);
346 if (dlg.exec() == QDialog::Accepted) {
347 auth->setUser(user.GetName());
348 auth->setPassword(user.GetPassword());
351 *auth = QAuthenticator();
355void CFrmWebView::slotProxyAuthenticationRequired(
const QUrl &url, QAuthenticator *auth,
356 const QString &proxyHost)
358 qDebug(log) << Q_FUNC_INFO;
361 dlg.SetUser(tr(
"Set user and password of proxy") +
"\n" + proxyHost.toHtmlEscaped(), &user);
362 if (dlg.exec() == QDialog::Accepted) {
363 auth->setUser(user.GetName());
364 auth->setPassword(user.GetPassword());
367 *auth = QAuthenticator();
396#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
397void CFrmWebView::slotPermissionRequested(QWebEnginePermission permission)
399 qDebug(log) << Q_FUNC_INFO;
400 QString title = tr(
"Permission Request");
401 QString question = questionForPermissionType(permission.permissionType()).arg(permission.origin().host());
402 if (!question.isEmpty() && QMessageBox::question(window(), title, question) == QMessageBox::Yes)
408void CFrmWebView::handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy)
410 qDebug(log) << Q_FUNC_INFO;
414 page()->settings()->setImageAnimationPolicy(policy);
418#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
419void CFrmWebView::slotDesktopMediaRequest(
const QWebEngineDesktopMediaRequest &request)
421 qDebug(log) << Q_FUNC_INFO;
423 request.selectScreen(request.screensModel()->index(0));
426void CFrmWebView::slotWebAuthUxRequested(QWebEngineWebAuthUxRequest *request)
428 qDebug(log) << Q_FUNC_INFO;
431 delete m_pDlgWebAuth;
433 m_pDlgWebAuth =
new CDlgWebAuth(request, window());
434 m_pDlgWebAuth->setModal(
false);
435 m_pDlgWebAuth->setWindowFlags(m_pDlgWebAuth->windowFlags() & ~Qt::WindowContextHelpButtonHint);
437 connect(request, &QWebEngineWebAuthUxRequest::stateChanged,
this, &CFrmWebView::onStateChanged);
438 m_pDlgWebAuth->show();
442void CFrmWebView::onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state)
444 qDebug(log) << Q_FUNC_INFO;
446 if (QWebEngineWebAuthUxRequest::WebAuthUxState::Completed == state
447 || QWebEngineWebAuthUxRequest::WebAuthUxState::Cancelled == state) {
449 delete m_pDlgWebAuth;
450 m_pDlgWebAuth =
nullptr;
453 m_pDlgWebAuth->updateDisplay();
460 QWebEngineRegisterProtocolHandlerRequest request)
462 qDebug(log) << Q_FUNC_INFO;
463 auto answer = QMessageBox::question(window(), tr(
"Permission Request"),
464 tr(
"Allow %1 to open all %2 links?")
465 .arg(request.origin().host())
466 .arg(request.scheme()));
467 if (answer == QMessageBox::Yes)
474#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
477 qDebug(log) << Q_FUNC_INFO;
479 switch (request.accessFlags()) {
480 case QWebEngineFileSystemAccessRequest::Read:
483 case QWebEngineFileSystemAccessRequest::Write:
484 accessType =
"write";
486 case QWebEngineFileSystemAccessRequest::Read | QWebEngineFileSystemAccessRequest::Write:
487 accessType =
"read and write";
493 auto answer = QMessageBox::question(window(), tr(
"File system access request"),
494 tr(
"Give %1 %2 access to %3?")
495 .arg(request.origin().host())
497 .arg(request.filePath().toString()));
498 if (answer == QMessageBox::Yes)
505QString CFrmWebView::AutoFillForm()
509 function autoFillForm(username, password) {
511 var usernameFields = [
512 'input[type="text"]',
513 'input[type="email"]',
514 'input[name="username"]',
515 'input[name="user"]',
516 'input[name="email"]',
517 'input[id="username"]',
523 var passwordFields = [
524 'input[type="password"]',
525 'input[name="password"]',
526 'input[name="pass"]',
527 'input[id="password"]',
532 var arrPassword = [];
535 usernameFields.forEach(function(selector) {
536 var field = document.querySelector(selector);
537 if (field && !field.value) {
540 //var event = new Event('input', { bubbles: true });
541 //field.dispatchEvent(event);
546 passwordFields.forEach(function(selector) {
547 var field = document.querySelector(selector);
548 if (field && !field.value) {
549 arrPassword.push(field);
551 //var event = new Event('input', { bubbles: true });
552 //field.dispatchEvent(event);
556 arrUser.forEach(function(input) {
557 if(!input.disabled && !input.readOnly)
558 input.value = username;
560 arrPassword.forEach(function(input) {
561 if(!input.disabled && !input.readOnly)
562 input.value = password;
568void CFrmWebView::SetupAutoFillScript() {
570 QString autoFillScript =
"(function() {";
571 autoFillScript += AutoFillForm();
572 autoFillScript += R
"(
574 window.autoFillForm = autoFillForm;
578 QWebEngineScript script;
579 script.setSourceCode(autoFillScript);
580 script.setInjectionPoint(QWebEngineScript::DocumentCreation);
581 script.setWorldId(QWebEngineScript::MainWorld);
582 page()->scripts().insert(script);
585void CFrmWebView::GlobalFillForm(
const QString &szUse,
const QString &szPassword)
587 QString js = QString(
"window.autoFillForm('%1', '%2');").arg(szUse, szPassword);
588 page()->runJavaScript(js);
591void CFrmWebView::FillForm(
const QString &szUse,
const QString &szPassword)
593 QString js = AutoFillForm();
594 js += QString(
"autoFillForm('%1', '%2');").arg(szUse, szPassword);
595 page()->runJavaScript(js);
598int CFrmWebView::GetUserAndPassword(QUrl url, QString &szUser, QString &szPassword)
607void CFrmWebView::InjectScriptQWebChannel()
613 auto loadResource = [](
const QString &rcPath)->QString{
615 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
616 qCritical(log) <<
"Cannot open resource" << rcPath;
619 return QString::fromUtf8(f.readAll());
622 QString qwebchannelJs = loadResource(
":/js/QWebChannel.js");
623 if (qwebchannelJs.isEmpty()) {
624 qCritical(log) <<
"QWebChannel.js not found in resources!";
629 QWebEngineScript channelScript;
630 channelScript.setName(
"qwebchannel.js");
631 channelScript.setInjectionPoint(QWebEngineScript::DocumentCreation);
632 channelScript.setRunsOnSubFrames(
true);
633 channelScript.setWorldId(QWebEngineScript::MainWorld);
634 channelScript.setSourceCode(qwebchannelJs);
635 page()->scripts().insert(channelScript);
638void CFrmWebView::InjectScriptAutoFill()
644 auto loadResource = [](
const QString &rcPath)->QString{
646 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
647 qCritical(log) <<
"Cannot open resource" << rcPath;
650 return QString::fromUtf8(f.readAll());
653 QString autofillJs = loadResource(
":/js/AutoFill.js");
654 if (autofillJs.isEmpty()) {
655 qCritical(log) <<
"AutoFill.js not found in resources!";
666 page()->runJavaScript(autofillJs);
void slotFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request)
[registerProtocolHandlerRequested]
void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request)
[registerProtocolHandlerRequested]
用户名与验证方式。此类仅在插件内有效。它的界面是 CParameterUserUI