Rabbit Remote Control 0.1.0-bate6
Loading...
Searching...
No Matches
FrmWebView.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <QContextMenuEvent>
4#include <QMenu>
5#include <QMessageBox>
6#include <QAuthenticator>
7#include <QTimer>
8#include <QStyle>
9#include <QLoggingCategory>
10#include <QWebEngineProfile>
11#include <QWebEngineScript>
12#include <QWebEngineScriptCollection>
13
14#include "RabbitCommonDir.h"
15
16#include "FrmWebView.h"
17#include "FrmWebBrowser.h"
18#include "DlgUserPassword.h"
19
20static Q_LOGGING_CATEGORY(log, "WebBrowser.View")
21
22CFrmWebView::CFrmWebView(CFrmWebBrowser *pFrmWebBrowser, QWidget *parent)
23 : QWebEngineView(parent)
24 , m_pBrowser(pFrmWebBrowser)
25#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
26 , m_pDlgWebAuth(nullptr)
27#endif
28 , m_pWebChannel(nullptr)
29 , m_pPasswordStore(nullptr)
30{
31 bool check = false;
32 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
33 setMinimumSize(200, 100);
34 Q_ASSERT(m_pBrowser);
35 check = connect(this, &QWebEngineView::loadStarted, [this]() {
36 m_loadProgress = 0;
37 emit favIconChanged(favIcon());
38 });
39 Q_ASSERT(check);
40 check = connect(this, &QWebEngineView::loadProgress, [this](int progress) {
41 m_loadProgress = progress;
42 });
43 Q_ASSERT(check);
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();
49 return;
50 QString szUser, szPassword;
51 int nRet = GetUserAndPassword(this->url(), szUser, szPassword);
52 if(0 == nRet) {
53 SetupAutoFillScript();
54 GlobalFillForm(szUser, szPassword);
55 //FillForm(szUser, szPassword);
56 }
57 });
58 Q_ASSERT(check);
59 check = connect(this, &QWebEngineView::iconChanged, [this](const QIcon &) {
60 emit favIconChanged(favIcon());
61 });
62 Q_ASSERT(check);
63 check = connect(this, &QWebEngineView::renderProcessTerminated,
64 [this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) {
65 QString status;
66 switch (termStatus) {
67 case QWebEnginePage::NormalTerminationStatus:
68 status = tr("Render process normal exit");
69 break;
70 case QWebEnginePage::AbnormalTerminationStatus:
71 status = tr("Render process abnormal exit");
72 break;
73 case QWebEnginePage::CrashedTerminationStatus:
74 status = tr("Render process crashed");
75 break;
76 case QWebEnginePage::KilledTerminationStatus:
77 status = tr("Render process killed");
78 break;
79 }
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);
85 });
86 Q_ASSERT(check);
87}
88
89CFrmWebView::~CFrmWebView()
90{
91 qDebug(log) << Q_FUNC_INFO;
92 if (m_imageAnimationGroup)
93 delete m_imageAnimationGroup;
94
95 m_imageAnimationGroup = nullptr;
96}
97
98#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
99inline QString questionForPermissionType(QWebEnginePermission::PermissionType permissionType)
100{
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:
123 break;
124 }
125 return QString();
126}
127#endif
128
129void CFrmWebView::setPage(QWebEnginePage *page)
130{
131 Q_ASSERT(page);
132 CreateWebActionTrigger(page, QWebEnginePage::Forward);
133 CreateWebActionTrigger(page, QWebEnginePage::Back);
134 CreateWebActionTrigger(page, QWebEnginePage::Reload);
135 CreateWebActionTrigger(page, QWebEnginePage::Stop);
136
137 if (auto oldPage = QWebEngineView::page()) {
138 oldPage->disconnect(this);
139 }
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);
159 #endif
160 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
161 connect(page, &QWebEnginePage::fileSystemAccessRequested, this,
163 #endif
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);
169 #endif
170
171 Q_ASSERT(m_pBrowser);
172 if(m_pBrowser->m_pPara->GetAutoFillUserAndPassword()) {
173 if(!m_pWebChannel)
174 m_pWebChannel = new QWebChannel(this);
175 if(!m_pPasswordStore)
176 m_pPasswordStore = new CPasswordStore(m_pBrowser->m_pPara, this);
177 if(m_pWebChannel && m_pPasswordStore) {
178 // 创建并注册 QWebChannel 对象,暴露 passwordStore 给 JS
179 m_pWebChannel->registerObject(QStringLiteral("PasswordManager"), m_pPasswordStore);
180 page->setWebChannel(m_pWebChannel);
181 InjectScriptQWebChannel();
182 } else
183 qCritical(log) << "Don't register PasswordManager";
184 }
185}
186
187int CFrmWebView::progress() const
188{
189 return m_loadProgress;
190}
191
192QIcon CFrmWebView::favIcon() const
193{
194 QIcon favIcon = icon();
195 if (!favIcon.isNull())
196 return favIcon;
197
198 if (m_loadProgress < 0) {
199 static QIcon errorIcon("dialog-error");
200 return errorIcon;
201 }
202 if (m_loadProgress < 100) {
203 static QIcon loadingIcon = QIcon::fromTheme("view-refresh");
204 return loadingIcon;
205 }
206
207 static QIcon defaultIcon("text-html");
208 return defaultIcon;
209}
210
211QWebEngineView *CFrmWebView::createWindow(QWebEnginePage::WebWindowType type)
212{
213 if(m_pBrowser)
214 return m_pBrowser->CreateWindow(
215 type, this->page()->profile()->isOffTheRecord());
216 return this;
217}
218
219void CFrmWebView::CreateWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction webAction)
220{
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);
227 });
228#else
229 connect(action, &QAction::changed, [this, action, webAction]{
230 emit sigWebActionEnabledChanged(webAction, action->isEnabled());
231 });
232#endif
233}
234
235void CFrmWebView::contextMenuEvent(QContextMenuEvent *event)
236{
237 QMenu *menu;
238#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
239 menu = createStandardContextMenu();
240#else
241 menu = page()->createStandardContextMenu();
242#endif
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());});
252 } else {
253 (*inspectElement)->setText(tr("Inspect element"));
254 }
255#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
256 // add context menu for image policy
257 QMenu *editImageAnimation = new QMenu(tr("Image animation policy"));
258
259 m_imageAnimationGroup = new QActionGroup(editImageAnimation);
260 m_imageAnimationGroup->setExclusive(true);
261
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);
268 });
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);
280 });
281
282 switch (page()->settings()->imageAnimationPolicy()) {
283 case QWebEngineSettings::ImageAnimationPolicy::Allow:
284 allowImageAnimation->setChecked(true);
285 break;
286 case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce:
287 allowImageAnimationOnce->setChecked(true);
288 break;
289 case QWebEngineSettings::ImageAnimationPolicy::Disallow:
290 disableImageAnimation->setChecked(true);
291 break;
292 default:
293 allowImageAnimation->setChecked(true);
294 break;
295 }
296
297 menu->addMenu(editImageAnimation);
298#endif
299 menu->popup(event->globalPos());
300}
301
302void CFrmWebView::slotSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection)
303{
304 qDebug(log) << Q_FUNC_INFO;
305 if(clientCertSelection.certificates().size() > 0) {
306 // Just select one.
307 clientCertSelection.select(clientCertSelection.certificates().at(0));
308 }
309}
310
311#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
312void CFrmWebView::slotCertificateError(QWebEngineCertificateError error)
313{
314 qDebug(log) << Q_FUNC_INFO << error.type() << error.url();
315
316 // Automatically block certificate errors from page resources without prompting the user.
317 // This mirrors the behavior found in other major browsers.
318 if (!error.isMainFrame()) {
319 error.rejectCertificate();
320 return;
321 }
322
323 error.defer();
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();
329 return;
330 }
331 QString szMsg;
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);
343 switch(nRet) {
344 case QMessageBox::StandardButton::Yes:
345 set.setValue("Certificate/Error/Accept" + error.url().toString(), true);
346 case QMessageBox::StandardButton::Ok:
347 error.acceptCertificate();
348 break;
349 case QMessageBox::Cancel:
350 error.rejectCertificate();
351 break;
352 }
353}
354#endif
355
356// Test example:
357// - https://postman-echo.com/basic-auth
358// - https://httpbin.org/basic-auth/user/passwd
359// - httpbin.org 提供基本的 HTTP 认证测试
360// - 用户名: user, 密码: passwd
361void CFrmWebView::slotAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth)
362{
363 qDebug(log) << Q_FUNC_INFO;
364 CParameterUser user(nullptr);
365 CDlgUserPassword dlg(this);
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());
370 } else {
371 // Set authenticator null if dialog is cancelled
372 *auth = QAuthenticator();
373 }
374}
375
376void CFrmWebView::slotProxyAuthenticationRequired(const QUrl &url, QAuthenticator *auth,
377 const QString &proxyHost)
378{
379 qDebug(log) << Q_FUNC_INFO;
380 CParameterUser user(nullptr);
381 CDlgUserPassword dlg(this);
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());
386 } else {
387 // Set authenticator null if dialog is cancelled
388 *auth = QAuthenticator();
389 }
390 /*
391 QDialog dialog(window());
392 dialog.setModal(true);
393 dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
394
395 Ui::PasswordDialog passwordDialog;
396 passwordDialog.setupUi(&dialog);
397
398 passwordDialog.m_iconLabel->setText(QString());
399 QIcon icon(window()->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, window()));
400 passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32));
401
402 QString introMessage = tr("Connect to proxy \"%1\" using:");
403 introMessage = introMessage.arg(proxyHost.toHtmlEscaped());
404 passwordDialog.m_infoLabel->setText(introMessage);
405 passwordDialog.m_infoLabel->setWordWrap(true);
406
407 if (dialog.exec() == QDialog::Accepted) {
408 auth->setUser(passwordDialog.m_userNameLineEdit->text());
409 auth->setPassword(passwordDialog.m_passwordLineEdit->text());
410 } else {
411 // Set authenticator null if dialog is cancelled
412 *auth = QAuthenticator();
413 }
414 */
415}
416
417#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
418void CFrmWebView::slotPermissionRequested(QWebEnginePermission permission)
419{
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)
424 permission.grant();
425 else
426 permission.deny();
427}
428
429void CFrmWebView::handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy)
430{
431 qDebug(log) << Q_FUNC_INFO;
432 if (!page())
433 return;
434
435 page()->settings()->setImageAnimationPolicy(policy);
436}
437#endif
438
439#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
440// Test example: https://thetbw.github.io/web-screen-recorder-demo/
441#include "DlgScreenCapture.h"
442void CFrmWebView::slotDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request)
443{
444 qDebug(log) << Q_FUNC_INFO;
445 QString szMsg = "- Screen:\n";
446 for(int i = 0; i < request.screensModel()->rowCount(); i++) {
447 QModelIndex index;
448 auto model = request.screensModel();
449 index = model->index(i, 0);
450 szMsg += " - " + QString::number(i) + ": " + model->data(index).toString() + "\n";
451 }
452 szMsg += "- Windows:\n";
453 for(int w = 0; w < request.windowsModel()->rowCount(); w++) {
454 QModelIndex index;
455 auto model = request.windowsModel();
456 index = model->index(w, 0);
457 szMsg += " - " + QString::number(w) + ": " + model->data(index).toString() + "\n";
458 }
459 qDebug(log) << szMsg;
460
461 if(request.screensModel()->rowCount() <= 0 && request.windowsModel()->rowCount() <= 0)
462 return;
463
464 CDlgScreenCapture dlg(request);
465 if(QDialog::Rejected == dlg.exec())
466 return;
467 // select the primary screen
468 CDlgScreenCapture::Type type;
469 int id = -1;
470 int nRet = dlg.GetIndex(type, id);
471 if(nRet) return;
472 QModelIndex index;
473 QAbstractListModel* model = nullptr;
474 switch(type) {
475 case CDlgScreenCapture::Type::Screen: {
476 model = request.screensModel();
477 if(!model) return;
478 if(0 > id || model->rowCount() <= id)
479 return;
480 index = model->index(id);
481 request.selectScreen(index);
482 break;
483 }
484 case CDlgScreenCapture::Type::Window: {
485 model = request.windowsModel();
486 if(!model) return;
487 if(0 > id || model->rowCount() <= id)
488 return;
489 index = model->index(id);
490 request.selectWindow(index);
491 break;
492 }
493 }
494
495}
496
497void CFrmWebView::slotWebAuthUxRequested(QWebEngineWebAuthUxRequest *request)
498{
499 qDebug(log) << Q_FUNC_INFO;
500
501 if (m_pDlgWebAuth)
502 delete m_pDlgWebAuth;
503
504 m_pDlgWebAuth = new CDlgWebAuth(request, window());
505 m_pDlgWebAuth->setModal(false);
506 m_pDlgWebAuth->setWindowFlags(m_pDlgWebAuth->windowFlags() & ~Qt::WindowContextHelpButtonHint);
507
508 connect(request, &QWebEngineWebAuthUxRequest::stateChanged, this, &CFrmWebView::onStateChanged);
509 m_pDlgWebAuth->show();
510
511}
512
513void CFrmWebView::onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state)
514{
515 qDebug(log) << Q_FUNC_INFO;
516
517 if (QWebEngineWebAuthUxRequest::WebAuthUxState::Completed == state
518 || QWebEngineWebAuthUxRequest::WebAuthUxState::Cancelled == state) {
519 if (m_pDlgWebAuth) {
520 delete m_pDlgWebAuth;
521 m_pDlgWebAuth = nullptr;
522 }
523 } else {
524 m_pDlgWebAuth->updateDisplay();
525 }
526}
527#endif // #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
528
529// Test example: https://www.webmfiles.org/demo-files/
530void CFrmWebView::slotFullScreenRequested(QWebEngineFullScreenRequest request)
531{
532 qDebug(log) << "slotFullScreenRequested";
533 if (request.toggleOn()) {
534 request.accept();
535 this->showFullScreen();
536 } else {
537 request.accept();
538 this->showNormal();
539 }
540 m_pBrowser->slotFullScreen(request.toggleOn());
541}
542
545 QWebEngineRegisterProtocolHandlerRequest request)
546{
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)
553 request.accept();
554 else
555 request.reject();
556}
558
559#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
560void CFrmWebView::slotFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request)
561{
562 qDebug(log) << Q_FUNC_INFO;
563 QString accessType;
564 switch (request.accessFlags()) {
565 case QWebEngineFileSystemAccessRequest::Read:
566 accessType = "read";
567 break;
568 case QWebEngineFileSystemAccessRequest::Write:
569 accessType = "write";
570 break;
571 case QWebEngineFileSystemAccessRequest::Read | QWebEngineFileSystemAccessRequest::Write:
572 accessType = "read and write";
573 break;
574 default:
575 Q_UNREACHABLE();
576 }
577
578 auto answer = QMessageBox::question(window(), tr("File system access request"),
579 tr("Give %1 %2 access to %3?")
580 .arg(request.origin().host())
581 .arg(accessType)
582 .arg(request.filePath().toString()));
583 if (answer == QMessageBox::Yes)
584 request.accept();
585 else
586 request.reject();
587}
588#endif
589
590QString CFrmWebView::AutoFillForm()
591{
592 return R"(
593 // 自动填充用户名和密码字段
594 function autoFillForm(username, password) {
595 // 查找用户名输入框
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"]',
603 'input[id="user"]',
604 'input[id="email"]'
605 ];
606
607 // 查找密码输入框
608 var passwordFields = [
609 'input[type="password"]',
610 'input[name="password"]',
611 'input[name="pass"]',
612 'input[id="password"]',
613 'input[id="pass"]'
614 ];
615
616 var arrUser = [];
617 var arrPassword = [];
618
619 // 尝试填充用户名
620 usernameFields.forEach(function(selector) {
621 var field = document.querySelector(selector);
622 if (field && !field.value) {
623 arrUser.push(field);
624 // 触发 change 事件
625 //var event = new Event('input', { bubbles: true });
626 //field.dispatchEvent(event);
627 }
628 });
629
630 // 尝试填充密码
631 passwordFields.forEach(function(selector) {
632 var field = document.querySelector(selector);
633 if (field && !field.value) {
634 arrPassword.push(field);
635 // 触发 change 事件
636 //var event = new Event('input', { bubbles: true });
637 //field.dispatchEvent(event);
638 }
639 });
640
641 arrUser.forEach(function(input) {
642 if(!input.disabled && !input.readOnly)
643 input.value = username;
644 });
645 arrPassword.forEach(function(input) {
646 if(!input.disabled && !input.readOnly)
647 input.value = password;
648 });
649 }
650 )";
651}
652
653void CFrmWebView::SetupAutoFillScript() {
654 // 创建自动填充的 JavaScript
655 QString autoFillScript = "(function() {";
656 autoFillScript += AutoFillForm();
657 autoFillScript += R"(
658 // 暴露函数到全局作用域
659 window.autoFillForm = autoFillForm;
660 })();
661 )";
662
663 QWebEngineScript script;
664 script.setSourceCode(autoFillScript);
665 script.setInjectionPoint(QWebEngineScript::DocumentCreation);
666 script.setWorldId(QWebEngineScript::MainWorld);
667 page()->scripts().insert(script);
668}
669
670void CFrmWebView::GlobalFillForm(const QString &szUse, const QString &szPassword)
671{
672 QString js = QString("window.autoFillForm('%1', '%2');").arg(szUse, szPassword);
673 page()->runJavaScript(js);
674}
675
676void CFrmWebView::FillForm(const QString &szUse, const QString &szPassword)
677{
678 QString js = AutoFillForm();
679 js += QString("autoFillForm('%1', '%2');").arg(szUse, szPassword);
680 page()->runJavaScript(js);
681}
682
683int CFrmWebView::GetUserAndPassword(QUrl url, QString &szUser, QString &szPassword)
684{
685 int nRet = 0;
686 //TODO:
687 szUser = "aa2";
688 szPassword = "p12";
689 return nRet;
690}
691
692void CFrmWebView::InjectScriptQWebChannel()
693{
694 Q_ASSERT(page());
695
696 // 从资源载入 qwebchannel.js 和 autofill.js
697 // 你需要在 resources.qrc 中把 js/qwebchannel.js 和 js/autofill.js 注册为资源
698 auto loadResource = [](const QString &rcPath)->QString{
699 QFile f(rcPath);
700 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
701 qCritical(log) << "Cannot open resource" << rcPath;
702 return QString();
703 }
704 return QString::fromUtf8(f.readAll());
705 };
706
707 QString qwebchannelJs = loadResource(":/js/QWebChannel.js");
708 if (qwebchannelJs.isEmpty()) {
709 qCritical(log) << "QWebChannel.js not found in resources!";
710 return;
711 }
712
713 // 先注入 QWebChannel.js,再注入 AutoFill 脚本。将两者合并到一个 QWebEngineScript 也可以。
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);
721}
722
723void CFrmWebView::InjectScriptAutoFill()
724{
725 Q_ASSERT(page());
726
727 // 从资源载入 qwebchannel.js 和 autofill.js
728 // 你需要在 resources.qrc 中把 js/qwebchannel.js 和 js/autofill.js 注册为资源
729 auto loadResource = [](const QString &rcPath)->QString{
730 QFile f(rcPath);
731 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
732 qCritical(log) << "Cannot open resource" << rcPath;
733 return QString();
734 }
735 return QString::fromUtf8(f.readAll());
736 };
737
738 QString autofillJs = loadResource(":/js/AutoFill.js");
739 if (autofillJs.isEmpty()) {
740 qCritical(log) << "AutoFill.js not found in resources!";
741 return;
742 }
743 // QWebEngineScript autoScript;
744 // autoScript.setName("autofill.js");
745 // autoScript.setInjectionPoint(QWebEngineScript::DocumentReady);
746 // autoScript.setRunsOnSubFrames(true);
747 // autoScript.setWorldId(QWebEngineScript::MainWorld);
748 // autoScript.setSourceCode(autofillJs);
749 // page()->scripts().insert(autoScript);
750
751 page()->runJavaScript(autofillJs);
752 //qDebug(log) << "autofillJs:" << autofillJs;
753 // page()->runJavaScript("typeof qt !== 'undefined' ? 'qt-ok' : 'no-qt'",
754 // [](const QVariant &v){ qCritical(log) << v.toString(); });
755 // page()->runJavaScript("typeof qt !== 'undefined' && typeof qt.webChannelTransport !== 'undefined' ? 'transport-ok' : 'no-transport'",
756 // [](const QVariant &v){ qCritical(log) << v.toString(); });
757}
void slotFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request)
[registerProtocolHandlerRequested]
void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request)
[registerProtocolHandlerRequested]
It contains user and password It only valid in plugin.