File indexing completed on 2024-05-12 04:58:29

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "webpage.h"
0019 #include "tabbedwebview.h"
0020 #include "browserwindow.h"
0021 #include "pluginproxy.h"
0022 #include "downloadmanager.h"
0023 #include "mainapplication.h"
0024 #include "checkboxdialog.h"
0025 #include "qztools.h"
0026 #include "speeddial.h"
0027 #include "autofill.h"
0028 #include "popupwebview.h"
0029 #include "popupwindow.h"
0030 #include "iconprovider.h"
0031 #include "qzsettings.h"
0032 #include "useragentmanager.h"
0033 #include "delayedfilewatcher.h"
0034 #include "searchenginesmanager.h"
0035 #include "html5permissions/html5permissionsmanager.h"
0036 #include "javascript/externaljsobject.h"
0037 #include "tabwidget.h"
0038 #include "networkmanager.h"
0039 #include "webhittestresult.h"
0040 #include "ui_jsconfirm.h"
0041 #include "ui_jsalert.h"
0042 #include "ui_jsprompt.h"
0043 #include "passwordmanager.h"
0044 #include "scripts.h"
0045 #include "ocssupport.h"
0046 
0047 #include <iostream>
0048 
0049 #include <QDir>
0050 #include <QMouseEvent>
0051 #include <QWebChannel>
0052 #include <QWebEngineHistory>
0053 #include <QWebEngineSettings>
0054 #include <QTimer>
0055 #include <QDesktopServices>
0056 #include <QMessageBox>
0057 #include <QFileDialog>
0058 #include <QAuthenticator>
0059 #include <QPushButton>
0060 #include <QUrlQuery>
0061 #include <QtWebEngineWidgetsVersion>
0062 
0063 #include <QWebEngineRegisterProtocolHandlerRequest>
0064 
0065 QString WebPage::s_lastUploadLocation = QDir::homePath();
0066 QUrl WebPage::s_lastUnsupportedUrl;
0067 QElapsedTimer WebPage::s_lastUnsupportedUrlTime;
0068 QStringList s_supportedSchemes;
0069 
0070 static const bool kEnableJsOutput = qEnvironmentVariableIsSet("FALKON_ENABLE_JS_OUTPUT");
0071 static const bool kEnableJsNonBlockDialogs = qEnvironmentVariableIsSet("FALKON_ENABLE_JS_NONBLOCK_DIALOGS");
0072 
0073 WebPage::WebPage(QObject* parent)
0074     : QWebEnginePage(mApp->webProfile(), parent)
0075     , m_fileWatcher(nullptr)
0076     , m_runningLoop(nullptr)
0077     , m_loadProgress(100)
0078     , m_blockAlerts(false)
0079     , m_secureStatus(false)
0080 {
0081     auto *channel = new QWebChannel(this);
0082     ExternalJsObject::setupWebChannel(channel, this);
0083     setWebChannel(channel, SafeJsWorld);
0084 
0085     connect(this, &QWebEnginePage::loadProgress, this, &WebPage::progress);
0086     connect(this, &QWebEnginePage::loadFinished, this, &WebPage::finished);
0087     connect(this, &QWebEnginePage::urlChanged, this, &WebPage::urlChanged);
0088     connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebPage::featurePermissionRequested);
0089     connect(this, &QWebEnginePage::windowCloseRequested, this, &WebPage::windowCloseRequested);
0090     connect(this, &QWebEnginePage::fullScreenRequested, this, &WebPage::fullScreenRequested);
0091     connect(this, &QWebEnginePage::renderProcessTerminated, this, &WebPage::renderProcessTerminated);
0092     connect(this, &QWebEnginePage::certificateError, this, &WebPage::onCertificateError);
0093 
0094     connect(this, &QWebEnginePage::authenticationRequired, this, [this](const QUrl &url, QAuthenticator *auth) {
0095         mApp->networkManager()->authentication(url, auth, view());
0096     });
0097 
0098     connect(this, &QWebEnginePage::proxyAuthenticationRequired, this, [this](const QUrl &, QAuthenticator *auth, const QString &proxyHost) {
0099         mApp->networkManager()->proxyAuthentication(proxyHost, auth, view());
0100     });
0101 
0102     // Workaround QWebEnginePage not scrolling to anchors when opened in background tab
0103     m_contentsResizedConnection = connect(this, &QWebEnginePage::contentsSizeChanged, this, [this]() {
0104         const QString fragment = url().fragment();
0105         if (!fragment.isEmpty()) {
0106             runJavaScript(Scripts::scrollToAnchor(fragment));
0107         }
0108         disconnect(m_contentsResizedConnection);
0109     });
0110 
0111     // Workaround for broken load started/finished signals in QtWebEngine 5.10, 5.11
0112     connect(this, &QWebEnginePage::loadProgress, this, [this](int progress) {
0113         if (progress == 100) {
0114             Q_EMIT loadFinished(true);
0115         }
0116     });
0117 
0118     connect(this, &QWebEnginePage::registerProtocolHandlerRequested, this, [this](QWebEngineRegisterProtocolHandlerRequest request) {
0119         delete m_registerProtocolHandlerRequest;
0120         m_registerProtocolHandlerRequest = new QWebEngineRegisterProtocolHandlerRequest(request);
0121     });
0122     connect(this, &QWebEnginePage::printRequested, this, &WebPage::printRequested);
0123     connect(this, &QWebEnginePage::selectClientCertificate, this, [this](QWebEngineClientCertificateSelection selection) {
0124         // TODO: It should prompt user
0125         selection.select(selection.certificates().at(0));
0126     });
0127 }
0128 
0129 WebPage::~WebPage()
0130 {
0131     delete m_registerProtocolHandlerRequest;
0132 
0133     if (m_runningLoop) {
0134         m_runningLoop->exit(1);
0135         m_runningLoop = nullptr;
0136     }
0137 }
0138 
0139 WebView *WebPage::view() const
0140 {
0141     return static_cast<WebView*>(QWebEngineView::forPage(this));
0142 }
0143 
0144 bool WebPage::execPrintPage(QPrinter *printer, int timeout)
0145 {
0146     QPointer<QEventLoop> loop = new QEventLoop;
0147     bool result = false;
0148     QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit);
0149 
0150     connect(view(), &QWebEngineView::printFinished, this, [loop, &result](bool res) {
0151         if (loop && loop->isRunning()) {
0152             result = res;
0153             loop->quit();
0154         }
0155     });
0156     view()->print(printer);
0157 
0158     loop->exec();
0159     delete loop;
0160 
0161     return result;
0162 }
0163 
0164 QVariant WebPage::execJavaScript(const QString &scriptSource, quint32 worldId, int timeout)
0165 {
0166     QPointer<QEventLoop> loop = new QEventLoop;
0167     QVariant result;
0168     QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit);
0169 
0170     runJavaScript(scriptSource, worldId, [loop, &result](const QVariant &res) {
0171         if (loop && loop->isRunning()) {
0172             result = res;
0173             loop->quit();
0174         }
0175     });
0176 
0177     loop->exec(QEventLoop::ExcludeUserInputEvents);
0178     delete loop;
0179 
0180     return result;
0181 }
0182 
0183 QPointF WebPage::mapToViewport(const QPointF &pos) const
0184 {
0185     return QPointF(pos.x() / zoomFactor(), pos.y() / zoomFactor());
0186 }
0187 
0188 WebHitTestResult WebPage::hitTestContent(const QPoint &pos) const
0189 {
0190     return WebHitTestResult(this, pos);
0191 }
0192 
0193 void WebPage::scroll(int x, int y)
0194 {
0195     runJavaScript(QSL("window.scrollTo(window.scrollX + %1, window.scrollY + %2)").arg(x).arg(y), SafeJsWorld);
0196 }
0197 
0198 void WebPage::setScrollPosition(const QPointF &pos)
0199 {
0200     const QPointF v = mapToViewport(pos.toPoint());
0201     runJavaScript(QSL("window.scrollTo(%1, %2)").arg(v.x()).arg(v.y()), SafeJsWorld);
0202 }
0203 
0204 bool WebPage::isRunningLoop()
0205 {
0206     return m_runningLoop;
0207 }
0208 
0209 bool WebPage::isLoading() const
0210 {
0211     return m_loadProgress < 100;
0212 }
0213 
0214 // static
0215 QStringList WebPage::internalSchemes()
0216 {
0217     return QStringList{
0218         QSL("http"),
0219         QSL("https"),
0220         QSL("file"),
0221         QSL("ftp"),
0222         QSL("data"),
0223         QSL("about"),
0224         QSL("view-source"),
0225         QSL("chrome")
0226     };
0227 }
0228 
0229 // static
0230 QStringList WebPage::supportedSchemes()
0231 {
0232     if (s_supportedSchemes.isEmpty()) {
0233         s_supportedSchemes = internalSchemes();
0234     }
0235     return s_supportedSchemes;
0236 }
0237 
0238 // static
0239 void WebPage::addSupportedScheme(const QString &scheme)
0240 {
0241     s_supportedSchemes = supportedSchemes();
0242     if (!s_supportedSchemes.contains(scheme)) {
0243         s_supportedSchemes.append(scheme);
0244     }
0245 }
0246 
0247 // static
0248 void WebPage::removeSupportedScheme(const QString &scheme)
0249 {
0250     s_supportedSchemes.removeOne(scheme);
0251 }
0252 
0253 void WebPage::urlChanged(const QUrl &url)
0254 {
0255     Q_UNUSED(url)
0256 
0257     if (isLoading()) {
0258         m_blockAlerts = false;
0259     }
0260 }
0261 
0262 void WebPage::progress(int prog)
0263 {
0264     m_loadProgress = prog;
0265 
0266     bool secStatus = url().scheme() == QL1S("https");
0267 
0268     if (secStatus != m_secureStatus) {
0269         m_secureStatus = secStatus;
0270         Q_EMIT privacyChanged(secStatus);
0271     }
0272 }
0273 
0274 void WebPage::finished()
0275 {
0276     progress(100);
0277 
0278     // File scheme watcher
0279     if (url().scheme() == QLatin1String("file")) {
0280         QFileInfo info(url().toLocalFile());
0281         if (info.isFile()) {
0282             if (!m_fileWatcher) {
0283                 m_fileWatcher = new DelayedFileWatcher(this);
0284                 connect(m_fileWatcher, &DelayedFileWatcher::delayedFileChanged, this, &WebPage::watchedFileChanged);
0285             }
0286 
0287             const QString filePath = url().toLocalFile();
0288 
0289             if (QFile::exists(filePath) && !m_fileWatcher->files().contains(filePath)) {
0290                 m_fileWatcher->addPath(filePath);
0291             }
0292         }
0293     }
0294     else if (m_fileWatcher && !m_fileWatcher->files().isEmpty()) {
0295         m_fileWatcher->removePaths(m_fileWatcher->files());
0296     }
0297 
0298     // AutoFill
0299     m_autoFillUsernames = mApp->autoFill()->completePage(this, url());
0300 }
0301 
0302 void WebPage::watchedFileChanged(const QString &file)
0303 {
0304     if (url().toLocalFile() == file) {
0305         triggerAction(QWebEnginePage::Reload);
0306     }
0307 }
0308 
0309 void WebPage::handleUnknownProtocol(const QUrl &url)
0310 {
0311     const QString protocol = url.scheme();
0312 
0313     if (protocol == QLatin1String("mailto")) {
0314         desktopServicesOpen(url);
0315         return;
0316     }
0317 
0318     if (qzSettings->blockedProtocols.contains(protocol)) {
0319         qDebug() << "WebPage::handleUnknownProtocol Protocol" << protocol << "is blocked!";
0320         return;
0321     }
0322 
0323     if (qzSettings->autoOpenProtocols.contains(protocol)) {
0324         desktopServicesOpen(url);
0325         return;
0326     }
0327 
0328     CheckBoxDialog dialog(QMessageBox::Yes | QMessageBox::No, view());
0329     dialog.setDefaultButton(QMessageBox::Yes);
0330 
0331     const QString wrappedUrl = QzTools::alignTextToWidth(url.toString(), QSL("<br/>"), dialog.fontMetrics(), 450);
0332     const QString text = tr("Falkon cannot handle <b>%1:</b> links. The requested link "
0333                             "is <ul><li>%2</li></ul>Do you want Falkon to try "
0334                             "open this link in system application?").arg(protocol, wrappedUrl);
0335 
0336     dialog.setText(text);
0337     dialog.setCheckBoxText(tr("Remember my choice for this protocol"));
0338     dialog.setWindowTitle(tr("External Protocol Request"));
0339     dialog.setIcon(QMessageBox::Question);
0340 
0341     switch (dialog.exec()) {
0342     case QMessageBox::Yes:
0343         if (dialog.isChecked()) {
0344             qzSettings->autoOpenProtocols.append(protocol);
0345             qzSettings->saveSettings();
0346         }
0347 
0348 
0349         QDesktopServices::openUrl(url);
0350         break;
0351 
0352     case QMessageBox::No:
0353         if (dialog.isChecked()) {
0354             qzSettings->blockedProtocols.append(protocol);
0355             qzSettings->saveSettings();
0356         }
0357 
0358         break;
0359 
0360     default:
0361         break;
0362     }
0363 }
0364 
0365 void WebPage::desktopServicesOpen(const QUrl &url)
0366 {
0367     // Open same url only once in 2 secs
0368     const int sameUrlTimeout = 2 * 1000;
0369 
0370     if ((s_lastUnsupportedUrl != url) || (!s_lastUnsupportedUrlTime.isValid()) || (s_lastUnsupportedUrlTime.elapsed() > sameUrlTimeout)) {
0371         s_lastUnsupportedUrl = url;
0372         s_lastUnsupportedUrlTime.restart();
0373         QDesktopServices::openUrl(url);
0374     }
0375     else {
0376         qWarning() << "WebPage::desktopServicesOpen Url" << url << "has already been opened!\n"
0377                    "Ignoring it to prevent infinite loop!";
0378     }
0379 }
0380 
0381 void WebPage::windowCloseRequested()
0382 {
0383     if (!view())
0384         return;
0385     view()->closeView();
0386 }
0387 
0388 void WebPage::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest)
0389 {
0390     view()->requestFullScreen(fullScreenRequest.toggleOn());
0391 
0392     const bool accepted = fullScreenRequest.toggleOn() == view()->isFullScreen();
0393 
0394     if (accepted)
0395         fullScreenRequest.accept();
0396     else
0397         fullScreenRequest.reject();
0398 }
0399 
0400 void WebPage::featurePermissionRequested(const QUrl &origin, const QWebEnginePage::Feature &feature)
0401 {
0402     if (feature == MouseLock && view()->isFullScreen())
0403         setFeaturePermission(origin, feature, PermissionGrantedByUser);
0404     else
0405         mApp->html5PermissionsManager()->requestPermissions(this, origin, feature);
0406 }
0407 
0408 void WebPage::renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode)
0409 {
0410     Q_UNUSED(exitCode)
0411 
0412     if (terminationStatus == NormalTerminationStatus)
0413         return;
0414 
0415     QTimer::singleShot(0, this, [this]() {
0416         QString page = QzTools::readAllFileContents(QSL(":html/tabcrash.html"));
0417         page.replace(QL1S("%IMAGE%"), QzTools::pixmapToDataUrl(IconProvider::standardIcon(QStyle::SP_MessageBoxWarning).pixmap(45)).toString());
0418         page.replace(QL1S("%TITLE%"), tr("Failed loading page"));
0419         page.replace(QL1S("%HEADING%"), tr("Failed loading page"));
0420         page.replace(QL1S("%LI-1%"), tr("Something went wrong while loading this page."));
0421         page.replace(QL1S("%LI-2%"), tr("Try reloading the page or closing some tabs to make more memory available."));
0422         page.replace(QL1S("%RELOAD-PAGE%"), tr("Reload page"));
0423         page = QzTools::applyDirectionToPage(page);
0424         setHtml(page, url());
0425     });
0426 }
0427 
0428 bool WebPage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame)
0429 {
0430     if (mApp->isClosing()) {
0431         return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame);
0432     }
0433 
0434     if (!mApp->plugins()->acceptNavigationRequest(this, url, type, isMainFrame))
0435         return false;
0436 
0437     if (url.scheme() == QL1S("falkon")) {
0438         if (url.path() == QL1S("AddSearchProvider")) {
0439             QUrlQuery query(url);
0440             mApp->searchEnginesManager()->addEngine(QUrl(query.queryItemValue(QSL("url"))));
0441             return false;
0442         }
0443     }
0444 
0445     if (url.scheme() == QL1S("ocs") && OcsSupport::instance()->handleUrl(url)) {
0446         return false;
0447     }
0448 
0449     const bool result = QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame);
0450 
0451     if (result) {
0452         if (isMainFrame) {
0453             const bool isWeb = url.scheme() == QL1S("http") || url.scheme() == QL1S("https") || url.scheme() == QL1S("file");
0454             const bool globalJsEnabled = mApp->webSettings()->testAttribute(QWebEngineSettings::JavascriptEnabled);
0455             settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, isWeb ? globalJsEnabled : true);
0456         }
0457         Q_EMIT navigationRequestAccepted(url, type, isMainFrame);
0458     }
0459 
0460     return result;
0461 }
0462 
0463 void WebPage::onCertificateError(QWebEngineCertificateError error)
0464 {
0465     auto mutableError = const_cast<QWebEngineCertificateError&>(error);
0466     if (mApp->networkManager()->certificateError(mutableError, view()))
0467         mutableError.acceptCertificate();
0468     else
0469         mutableError.rejectCertificate();
0470 }
0471 
0472 QStringList WebPage::chooseFiles(QWebEnginePage::FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes)
0473 {
0474     Q_UNUSED(acceptedMimeTypes);
0475 
0476     QStringList files;
0477     QString suggestedFileName = s_lastUploadLocation;
0478     if (!oldFiles.isEmpty())
0479         suggestedFileName = oldFiles.at(0);
0480 
0481     switch (mode) {
0482     case FileSelectOpen:
0483         files = QStringList(QzTools::getOpenFileName(QSL("WebPage-ChooseFile"), view(), tr("Choose file..."), suggestedFileName));
0484         break;
0485 
0486     case FileSelectOpenMultiple:
0487         files = QzTools::getOpenFileNames(QSL("WebPage-ChooseFile"), view(), tr("Choose files..."), suggestedFileName);
0488         break;
0489 
0490     default:
0491         files = QWebEnginePage::chooseFiles(mode, oldFiles, acceptedMimeTypes);
0492         break;
0493     }
0494 
0495     if (!files.isEmpty())
0496         s_lastUploadLocation = files.at(0);
0497 
0498     return files;
0499 }
0500 
0501 QStringList WebPage::autoFillUsernames() const
0502 {
0503     return m_autoFillUsernames;
0504 }
0505 
0506 QUrl WebPage::registerProtocolHandlerRequestUrl() const
0507 {
0508     if (m_registerProtocolHandlerRequest && url().host() == m_registerProtocolHandlerRequest->origin().host()) {
0509         return m_registerProtocolHandlerRequest->origin();
0510     }
0511     return {};
0512 }
0513 
0514 QString WebPage::registerProtocolHandlerRequestScheme() const
0515 {
0516     if (m_registerProtocolHandlerRequest && url().host() == m_registerProtocolHandlerRequest->origin().host()) {
0517         return m_registerProtocolHandlerRequest->scheme();
0518     }
0519     return {};
0520 }
0521 
0522 bool WebPage::javaScriptPrompt(const QUrl &securityOrigin, const QString &msg, const QString &defaultValue, QString* result)
0523 {
0524     if (!kEnableJsNonBlockDialogs) {
0525         return QWebEnginePage::javaScriptPrompt(securityOrigin, msg, defaultValue, result);
0526     }
0527 
0528     if (m_runningLoop) {
0529         return false;
0530     }
0531 
0532     auto *widget = new QFrame(view()->overlayWidget());
0533 
0534     widget->setObjectName("jsFrame");
0535     auto* ui = new Ui_jsPrompt();
0536     ui->setupUi(widget);
0537     ui->message->setText(msg);
0538     ui->lineEdit->setText(defaultValue);
0539     ui->lineEdit->setFocus();
0540     widget->resize(view()->size());
0541     widget->show();
0542 
0543     QAbstractButton *clicked = nullptr;
0544     connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [&](QAbstractButton *button) {
0545         clicked = button;
0546     });
0547 
0548     connect(view(), &WebView::viewportResized, widget, QOverload<const QSize &>::of(&QFrame::resize));
0549     connect(ui->lineEdit, SIGNAL(returnPressed()), ui->buttonBox->button(QDialogButtonBox::Ok), SLOT(animateClick()));
0550 
0551     QEventLoop eLoop;
0552     m_runningLoop = &eLoop;
0553     connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit);
0554 
0555     if (eLoop.exec() == 1) {
0556         return result;
0557     }
0558     m_runningLoop = nullptr;
0559 
0560     QString x = ui->lineEdit->text();
0561     bool _result = ui->buttonBox->buttonRole(clicked) == QDialogButtonBox::AcceptRole;
0562     *result = x;
0563 
0564     delete widget;
0565     view()->setFocus();
0566 
0567     return _result;
0568 }
0569 
0570 bool WebPage::javaScriptConfirm(const QUrl &securityOrigin, const QString &msg)
0571 {
0572     if (!kEnableJsNonBlockDialogs) {
0573         return QWebEnginePage::javaScriptConfirm(securityOrigin, msg);
0574     }
0575 
0576     if (m_runningLoop) {
0577         return false;
0578     }
0579 
0580     auto *widget = new QFrame(view()->overlayWidget());
0581 
0582     widget->setObjectName("jsFrame");
0583     auto* ui = new Ui_jsConfirm();
0584     ui->setupUi(widget);
0585     ui->message->setText(msg);
0586     ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
0587     widget->resize(view()->size());
0588     widget->show();
0589 
0590     QAbstractButton *clicked = nullptr;
0591     connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [&](QAbstractButton *button) {
0592         clicked = button;
0593     });
0594 
0595     connect(view(), &WebView::viewportResized, widget, QOverload<const QSize &>::of(&QFrame::resize));
0596 
0597     QEventLoop eLoop;
0598     m_runningLoop = &eLoop;
0599     connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit);
0600 
0601     if (eLoop.exec() == 1) {
0602         return false;
0603     }
0604     m_runningLoop = nullptr;
0605 
0606     bool result = ui->buttonBox->buttonRole(clicked) == QDialogButtonBox::AcceptRole;
0607 
0608     delete widget;
0609     view()->setFocus();
0610 
0611     return result;
0612 }
0613 
0614 void WebPage::javaScriptAlert(const QUrl &securityOrigin, const QString &msg)
0615 {
0616     Q_UNUSED(securityOrigin)
0617 
0618     if (m_blockAlerts || m_runningLoop) {
0619         return;
0620     }
0621 
0622     if (!kEnableJsNonBlockDialogs) {
0623         QString title = tr("JavaScript alert");
0624         if (!url().host().isEmpty()) {
0625             title.append(QSL(" - %1").arg(url().host()));
0626         }
0627 
0628         CheckBoxDialog dialog(QMessageBox::Ok, view());
0629         dialog.setDefaultButton(QMessageBox::Ok);
0630         dialog.setWindowTitle(title);
0631         dialog.setText(msg);
0632         dialog.setCheckBoxText(tr("Prevent this page from creating additional dialogs"));
0633         dialog.setIcon(QMessageBox::Information);
0634         dialog.exec();
0635 
0636         m_blockAlerts = dialog.isChecked();
0637         return;
0638     }
0639 
0640     auto *widget = new QFrame(view()->overlayWidget());
0641 
0642     widget->setObjectName("jsFrame");
0643     auto* ui = new Ui_jsAlert();
0644     ui->setupUi(widget);
0645     ui->message->setText(msg);
0646     ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
0647     widget->resize(view()->size());
0648     widget->show();
0649 
0650     connect(view(), &WebView::viewportResized, widget, QOverload<const QSize &>::of(&QFrame::resize));
0651 
0652     QEventLoop eLoop;
0653     m_runningLoop = &eLoop;
0654     connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit);
0655 
0656     if (eLoop.exec() == 1) {
0657         return;
0658     }
0659     m_runningLoop = nullptr;
0660 
0661     m_blockAlerts = ui->preventAlerts->isChecked();
0662 
0663     delete widget;
0664 
0665     view()->setFocus();
0666 }
0667 
0668 void WebPage::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID)
0669 {
0670     if (!kEnableJsOutput) {
0671         return;
0672     }
0673 
0674     switch (level) {
0675     case InfoMessageLevel:
0676         std::cout << "[I] ";
0677         break;
0678 
0679     case WarningMessageLevel:
0680         std::cout << "[W] ";
0681         break;
0682 
0683     case ErrorMessageLevel:
0684         std::cout << "[E] ";
0685         break;
0686     }
0687 
0688     std::cout << qPrintable(sourceID) << ":" << lineNumber << " " << qPrintable(message);
0689 }
0690 
0691 QWebEnginePage* WebPage::createWindow(QWebEnginePage::WebWindowType type)
0692 {
0693     auto *tView = qobject_cast<TabbedWebView*>(view());
0694     BrowserWindow *window = tView ? tView->browserWindow() : mApp->getWindow();
0695 
0696     auto createTab = [=](Qz::NewTabPositionFlags pos) {
0697         int index = window->tabWidget()->addView(QUrl(), pos);
0698         TabbedWebView* view = window->weView(index);
0699         view->setPage(new WebPage);
0700         if (tView) {
0701             tView->webTab()->addChildTab(view->webTab());
0702         }
0703         // Workaround focus issue when creating tab
0704         if (pos.testFlag(Qz::NT_SelectedTab)) {
0705             QPointer<TabbedWebView> pview = view;
0706             pview->setFocus();
0707             QTimer::singleShot(100, this, [pview]() {
0708                 if (pview && pview->webTab()->isCurrentTab()) {
0709                     pview->setFocus();
0710                 }
0711             });
0712         }
0713         return view->page();
0714     };
0715 
0716     switch (type) {
0717     case QWebEnginePage::WebBrowserWindow: {
0718         BrowserWindow *window = mApp->createWindow(Qz::BW_NewWindow);
0719         auto *page = new WebPage;
0720         window->setStartPage(page);
0721         return page;
0722     }
0723 
0724     case QWebEnginePage::WebDialog:
0725         if (!qzSettings->openPopupsInTabs) {
0726             auto* view = new PopupWebView;
0727             view->setPage(new WebPage);
0728             auto* popup = new PopupWindow(view);
0729             popup->show();
0730             window->addDeleteOnCloseWidget(popup);
0731             return view->page();
0732         }
0733         // else fallthrough
0734 
0735     case QWebEnginePage::WebBrowserTab:
0736         return createTab(Qz::NT_CleanSelectedTab);
0737 
0738     case QWebEnginePage::WebBrowserBackgroundTab:
0739         return createTab(Qz::NT_CleanNotSelectedTab);
0740 
0741     default:
0742         break;
0743     }
0744 
0745     return nullptr;
0746 }