玉兔远程控制 0.1.0-bate5
载入中...
搜索中...
未找到
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, [this, action, webAction](bool enabled){
224 qDebug(log) << "webAction:" << webAction << enabled;
225 emit sigWebActionEnabledChanged(webAction, enabled);
226 });
227#else
228 connect(action, &QAction::changed, [this, action, webAction]{
229 emit sigWebActionEnabledChanged(webAction, action->isEnabled());
230 });
231#endif
232}
233
234void CFrmWebView::contextMenuEvent(QContextMenuEvent *event)
235{
236 QMenu *menu;
237#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
238 menu = createStandardContextMenu();
239#else
240 menu = page()->createStandardContextMenu();
241#endif
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());});
251 } else {
252 (*inspectElement)->setText(tr("Inspect element"));
253 }
254#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
255 // add context menu for image policy
256 QMenu *editImageAnimation = new QMenu(tr("Image animation policy"));
257
258 m_imageAnimationGroup = new QActionGroup(editImageAnimation);
259 m_imageAnimationGroup->setExclusive(true);
260
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);
267 });
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);
279 });
280
281 switch (page()->settings()->imageAnimationPolicy()) {
282 case QWebEngineSettings::ImageAnimationPolicy::Allow:
283 allowImageAnimation->setChecked(true);
284 break;
285 case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce:
286 allowImageAnimationOnce->setChecked(true);
287 break;
288 case QWebEngineSettings::ImageAnimationPolicy::Disallow:
289 disableImageAnimation->setChecked(true);
290 break;
291 default:
292 allowImageAnimation->setChecked(true);
293 break;
294 }
295
296 menu->addMenu(editImageAnimation);
297#endif
298 menu->popup(event->globalPos());
299}
300
301void CFrmWebView::slotSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection)
302{
303 qDebug(log) << Q_FUNC_INFO;
304 if(clientCertSelection.certificates().size() > 0) {
305 // Just select one.
306 clientCertSelection.select(clientCertSelection.certificates().at(0));
307 }
308}
309
310#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
311void CFrmWebView::slotCertificateError(QWebEngineCertificateError error)
312{
313 qDebug(log) << Q_FUNC_INFO << error.type() << error.url();
314
315 // Automatically block certificate errors from page resources without prompting the user.
316 // This mirrors the behavior found in other major browsers.
317 if (!error.isMainFrame()) {
318 error.rejectCertificate();
319 return;
320 }
321
322 error.defer();
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();
328 return;
329 }
330 QString szMsg;
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);
342 switch(nRet) {
343 case QMessageBox::StandardButton::Yes:
344 set.setValue("Certificate/Error/Accept" + error.url().toString(), true);
345 case QMessageBox::StandardButton::Ok:
346 error.acceptCertificate();
347 break;
348 case QMessageBox::Cancel:
349 error.rejectCertificate();
350 break;
351 }
352}
353#endif
354
355// Test example:
356// - https://postman-echo.com/basic-auth
357// - https://httpbin.org/basic-auth/user/passwd
358// - httpbin.org 提供基本的 HTTP 认证测试
359// - 用户名: user, 密码: passwd
360void CFrmWebView::slotAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth)
361{
362 qDebug(log) << Q_FUNC_INFO;
363 CParameterUser user(nullptr);
364 CDlgUserPassword dlg(this);
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());
369 } else {
370 // Set authenticator null if dialog is cancelled
371 *auth = QAuthenticator();
372 }
373}
374
375void CFrmWebView::slotProxyAuthenticationRequired(const QUrl &url, QAuthenticator *auth,
376 const QString &proxyHost)
377{
378 qDebug(log) << Q_FUNC_INFO;
379 CParameterUser user(nullptr);
380 CDlgUserPassword dlg(this);
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());
385 } else {
386 // Set authenticator null if dialog is cancelled
387 *auth = QAuthenticator();
388 }
389 /*
390 QDialog dialog(window());
391 dialog.setModal(true);
392 dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
393
394 Ui::PasswordDialog passwordDialog;
395 passwordDialog.setupUi(&dialog);
396
397 passwordDialog.m_iconLabel->setText(QString());
398 QIcon icon(window()->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, window()));
399 passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32));
400
401 QString introMessage = tr("Connect to proxy \"%1\" using:");
402 introMessage = introMessage.arg(proxyHost.toHtmlEscaped());
403 passwordDialog.m_infoLabel->setText(introMessage);
404 passwordDialog.m_infoLabel->setWordWrap(true);
405
406 if (dialog.exec() == QDialog::Accepted) {
407 auth->setUser(passwordDialog.m_userNameLineEdit->text());
408 auth->setPassword(passwordDialog.m_passwordLineEdit->text());
409 } else {
410 // Set authenticator null if dialog is cancelled
411 *auth = QAuthenticator();
412 }
413 */
414}
415
416#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
417void CFrmWebView::slotPermissionRequested(QWebEnginePermission permission)
418{
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)
423 permission.grant();
424 else
425 permission.deny();
426}
427
428void CFrmWebView::handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy)
429{
430 qDebug(log) << Q_FUNC_INFO;
431 if (!page())
432 return;
433
434 page()->settings()->setImageAnimationPolicy(policy);
435}
436#endif
437
438#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
439// Test example: https://thetbw.github.io/web-screen-recorder-demo/
440#include "DlgScreenCapture.h"
441void CFrmWebView::slotDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request)
442{
443 qDebug(log) << Q_FUNC_INFO;
444 QString szMsg = "- Screen:\n";
445 for(int i = 0; i < request.screensModel()->rowCount(); i++) {
446 QModelIndex index;
447 auto model = request.screensModel();
448 index = model->index(i, 0);
449 szMsg += " - " + QString::number(i) + ": " + model->data(index).toString() + "\n";
450 }
451 szMsg += "- Windows:\n";
452 for(int w = 0; w < request.windowsModel()->rowCount(); w++) {
453 QModelIndex index;
454 auto model = request.windowsModel();
455 index = model->index(w, 0);
456 szMsg += " - " + QString::number(w) + ": " + model->data(index).toString() + "\n";
457 }
458 qDebug(log) << szMsg;
459
460 if(request.screensModel()->rowCount() <= 0 && request.windowsModel()->rowCount() <= 0)
461 return;
462
463 CDlgScreenCapture dlg(request);
464 if(QDialog::Rejected == dlg.exec())
465 return;
466 // select the primary screen
467 CDlgScreenCapture::Type type;
468 int id = -1;
469 int nRet = dlg.GetIndex(type, id);
470 if(nRet) return;
471 QModelIndex index;
472 QAbstractListModel* model = nullptr;
473 switch(type) {
474 case CDlgScreenCapture::Type::Screen: {
475 model = request.screensModel();
476 if(!model) return;
477 if(0 > id || model->rowCount() <= id)
478 return;
479 index = model->index(id);
480 request.selectScreen(index);
481 break;
482 }
483 case CDlgScreenCapture::Type::Window: {
484 model = request.windowsModel();
485 if(!model) return;
486 if(0 > id || model->rowCount() <= id)
487 return;
488 index = model->index(id);
489 request.selectWindow(index);
490 break;
491 }
492 }
493
494}
495
496void CFrmWebView::slotWebAuthUxRequested(QWebEngineWebAuthUxRequest *request)
497{
498 qDebug(log) << Q_FUNC_INFO;
499
500 if (m_pDlgWebAuth)
501 delete m_pDlgWebAuth;
502
503 m_pDlgWebAuth = new CDlgWebAuth(request, window());
504 m_pDlgWebAuth->setModal(false);
505 m_pDlgWebAuth->setWindowFlags(m_pDlgWebAuth->windowFlags() & ~Qt::WindowContextHelpButtonHint);
506
507 connect(request, &QWebEngineWebAuthUxRequest::stateChanged, this, &CFrmWebView::onStateChanged);
508 m_pDlgWebAuth->show();
509
510}
511
512void CFrmWebView::onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state)
513{
514 qDebug(log) << Q_FUNC_INFO;
515
516 if (QWebEngineWebAuthUxRequest::WebAuthUxState::Completed == state
517 || QWebEngineWebAuthUxRequest::WebAuthUxState::Cancelled == state) {
518 if (m_pDlgWebAuth) {
519 delete m_pDlgWebAuth;
520 m_pDlgWebAuth = nullptr;
521 }
522 } else {
523 m_pDlgWebAuth->updateDisplay();
524 }
525}
526#endif
527
528// Test example: https://www.webmfiles.org/demo-files/
529void CFrmWebView::slotFullScreenRequested(QWebEngineFullScreenRequest request)
530{
531 qDebug(log) << "slotFullScreenRequested";
532 if (request.toggleOn()) {
533 request.accept();
534 this->showFullScreen();
535 } else {
536 request.accept();
537 this->showNormal();
538 }
539 m_pBrowser->slotFullScreen(request.toggleOn());
540}
541
544 QWebEngineRegisterProtocolHandlerRequest request)
545{
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)
552 request.accept();
553 else
554 request.reject();
555}
557
558#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
559void CFrmWebView::slotFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request)
560{
561 qDebug(log) << Q_FUNC_INFO;
562 QString accessType;
563 switch (request.accessFlags()) {
564 case QWebEngineFileSystemAccessRequest::Read:
565 accessType = "read";
566 break;
567 case QWebEngineFileSystemAccessRequest::Write:
568 accessType = "write";
569 break;
570 case QWebEngineFileSystemAccessRequest::Read | QWebEngineFileSystemAccessRequest::Write:
571 accessType = "read and write";
572 break;
573 default:
574 Q_UNREACHABLE();
575 }
576
577 auto answer = QMessageBox::question(window(), tr("File system access request"),
578 tr("Give %1 %2 access to %3?")
579 .arg(request.origin().host())
580 .arg(accessType)
581 .arg(request.filePath().toString()));
582 if (answer == QMessageBox::Yes)
583 request.accept();
584 else
585 request.reject();
586}
587#endif
588
589QString CFrmWebView::AutoFillForm()
590{
591 return R"(
592 // 自动填充用户名和密码字段
593 function autoFillForm(username, password) {
594 // 查找用户名输入框
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"]',
602 'input[id="user"]',
603 'input[id="email"]'
604 ];
605
606 // 查找密码输入框
607 var passwordFields = [
608 'input[type="password"]',
609 'input[name="password"]',
610 'input[name="pass"]',
611 'input[id="password"]',
612 'input[id="pass"]'
613 ];
614
615 var arrUser = [];
616 var arrPassword = [];
617
618 // 尝试填充用户名
619 usernameFields.forEach(function(selector) {
620 var field = document.querySelector(selector);
621 if (field && !field.value) {
622 arrUser.push(field);
623 // 触发 change 事件
624 //var event = new Event('input', { bubbles: true });
625 //field.dispatchEvent(event);
626 }
627 });
628
629 // 尝试填充密码
630 passwordFields.forEach(function(selector) {
631 var field = document.querySelector(selector);
632 if (field && !field.value) {
633 arrPassword.push(field);
634 // 触发 change 事件
635 //var event = new Event('input', { bubbles: true });
636 //field.dispatchEvent(event);
637 }
638 });
639
640 arrUser.forEach(function(input) {
641 if(!input.disabled && !input.readOnly)
642 input.value = username;
643 });
644 arrPassword.forEach(function(input) {
645 if(!input.disabled && !input.readOnly)
646 input.value = password;
647 });
648 }
649 )";
650}
651
652void CFrmWebView::SetupAutoFillScript() {
653 // 创建自动填充的 JavaScript
654 QString autoFillScript = "(function() {";
655 autoFillScript += AutoFillForm();
656 autoFillScript += R"(
657 // 暴露函数到全局作用域
658 window.autoFillForm = autoFillForm;
659 })();
660 )";
661
662 QWebEngineScript script;
663 script.setSourceCode(autoFillScript);
664 script.setInjectionPoint(QWebEngineScript::DocumentCreation);
665 script.setWorldId(QWebEngineScript::MainWorld);
666 page()->scripts().insert(script);
667}
668
669void CFrmWebView::GlobalFillForm(const QString &szUse, const QString &szPassword)
670{
671 QString js = QString("window.autoFillForm('%1', '%2');").arg(szUse, szPassword);
672 page()->runJavaScript(js);
673}
674
675void CFrmWebView::FillForm(const QString &szUse, const QString &szPassword)
676{
677 QString js = AutoFillForm();
678 js += QString("autoFillForm('%1', '%2');").arg(szUse, szPassword);
679 page()->runJavaScript(js);
680}
681
682int CFrmWebView::GetUserAndPassword(QUrl url, QString &szUser, QString &szPassword)
683{
684 int nRet = 0;
685 //TODO:
686 szUser = "aa2";
687 szPassword = "p12";
688 return nRet;
689}
690
691void CFrmWebView::InjectScriptQWebChannel()
692{
693 Q_ASSERT(page());
694
695 // 从资源载入 qwebchannel.js 和 autofill.js
696 // 你需要在 resources.qrc 中把 js/qwebchannel.js 和 js/autofill.js 注册为资源
697 auto loadResource = [](const QString &rcPath)->QString{
698 QFile f(rcPath);
699 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
700 qCritical(log) << "Cannot open resource" << rcPath;
701 return QString();
702 }
703 return QString::fromUtf8(f.readAll());
704 };
705
706 QString qwebchannelJs = loadResource(":/js/QWebChannel.js");
707 if (qwebchannelJs.isEmpty()) {
708 qCritical(log) << "QWebChannel.js not found in resources!";
709 return;
710 }
711
712 // 先注入 QWebChannel.js,再注入 AutoFill 脚本。将两者合并到一个 QWebEngineScript 也可以。
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);
720}
721
722void CFrmWebView::InjectScriptAutoFill()
723{
724 Q_ASSERT(page());
725
726 // 从资源载入 qwebchannel.js 和 autofill.js
727 // 你需要在 resources.qrc 中把 js/qwebchannel.js 和 js/autofill.js 注册为资源
728 auto loadResource = [](const QString &rcPath)->QString{
729 QFile f(rcPath);
730 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
731 qCritical(log) << "Cannot open resource" << rcPath;
732 return QString();
733 }
734 return QString::fromUtf8(f.readAll());
735 };
736
737 QString autofillJs = loadResource(":/js/AutoFill.js");
738 if (autofillJs.isEmpty()) {
739 qCritical(log) << "AutoFill.js not found in resources!";
740 return;
741 }
742 // QWebEngineScript autoScript;
743 // autoScript.setName("autofill.js");
744 // autoScript.setInjectionPoint(QWebEngineScript::DocumentReady);
745 // autoScript.setRunsOnSubFrames(true);
746 // autoScript.setWorldId(QWebEngineScript::MainWorld);
747 // autoScript.setSourceCode(autofillJs);
748 // page()->scripts().insert(autoScript);
749
750 page()->runJavaScript(autofillJs);
751 //qDebug(log) << "autofillJs:" << autofillJs;
752 // page()->runJavaScript("typeof qt !== 'undefined' ? 'qt-ok' : 'no-qt'",
753 // [](const QVariant &v){ qCritical(log) << v.toString(); });
754 // page()->runJavaScript("typeof qt !== 'undefined' && typeof qt.webChannelTransport !== 'undefined' ? 'transport-ok' : 'no-transport'",
755 // [](const QVariant &v){ qCritical(log) << v.toString(); });
756}
void slotFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request)
[registerProtocolHandlerRequested]
void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request)
[registerProtocolHandlerRequested]
用户名与验证方式。此类仅在插件内有效。它的界面是 CParameterUserUI