/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "browserapplication.h" #include "browsermainwindow.h" #include "cookiejar.h" #include "downloadmanager.h" #include "featurepermissionbar.h" #include "ui_passworddialog.h" #include "ui_proxy.h" #include "tabwidget.h" #include "webview.h" #include #include #include #include #include #include #if defined(QWEBENGINEPAGE_HITTESTCONTENT) #include #endif #ifndef QT_NO_UITOOLS #include #endif //QT_NO_UITOOLS #include #include #include WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) : QWebEnginePage(profile, parent) , m_keyboardModifiers(Qt::NoModifier) , m_pressedButtons(Qt::NoButton) { #if defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) setNetworkAccessManager(BrowserApplication::networkAccessManager()); #endif #if defined(QWEBENGINEPAGE_UNSUPPORTEDCONTENT) connect(this, SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(handleUnsupportedContent(QNetworkReply*))); #endif connect(this, SIGNAL(authenticationRequired(const QUrl &, QAuthenticator*)), SLOT(authenticationRequired(const QUrl &, QAuthenticator*))); connect(this, SIGNAL(proxyAuthenticationRequired(const QUrl &, QAuthenticator *, const QString &)), SLOT(proxyAuthenticationRequired(const QUrl &, QAuthenticator *, const QString &))); } BrowserMainWindow *WebPage::mainWindow() { QObject *w = this->parent(); while (w) { if (BrowserMainWindow *mw = qobject_cast(w)) return mw; w = w->parent(); } return BrowserApplication::instance()->mainWindow(); } bool WebPage::acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) { Q_UNUSED(type); if (isMainFrame) { m_loadingUrl = url; emit loadingUrl(m_loadingUrl); } return true; } bool WebPage::certificateError(const QWebEngineCertificateError &error) { if (error.isOverridable()) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setText(error.errorDescription()); msgBox.setInformativeText(tr("If you wish so, you may continue with an unverified certificate. " "Accepting an unverified certificate means " "you may not be connected with the host you tried to connect to.\n" "Do you wish to override the security check and continue?")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); return msgBox.exec() == QMessageBox::Yes; } QMessageBox::critical(view(), tr("Certificate Error"), error.errorDescription(), QMessageBox::Ok, QMessageBox::NoButton); return false; } class PopupWindow : public QWidget { Q_OBJECT public: PopupWindow(QWebEngineProfile *profile) : m_addressBar(new QLineEdit(this)) , m_view(new WebView(this)) { m_view->setPage(new WebPage(profile, m_view)); QVBoxLayout *layout = new QVBoxLayout; layout->setMargin(0); setLayout(layout); layout->addWidget(m_addressBar); layout->addWidget(m_view); m_view->setFocus(); connect(m_view, &WebView::titleChanged, this, &QWidget::setWindowTitle); connect(m_view, &WebView::urlChanged, this, &PopupWindow::setUrl); connect(page(), &WebPage::geometryChangeRequested, this, &PopupWindow::adjustGeometry); connect(page(), &WebPage::windowCloseRequested, this, &QWidget::close); } QWebEnginePage* page() const { return m_view->page(); } private Q_SLOTS: void setUrl(const QUrl &url) { m_addressBar->setText(url.toString()); } void adjustGeometry(const QRect &newGeometry) { const int x1 = frameGeometry().left() - geometry().left(); const int y1 = frameGeometry().top() - geometry().top(); const int x2 = frameGeometry().right() - geometry().right(); const int y2 = frameGeometry().bottom() - geometry().bottom(); setGeometry(newGeometry.adjusted(x1, y1 - m_addressBar->height(), x2, y2)); } private: QLineEdit *m_addressBar; WebView *m_view; }; #include "webview.moc" QWebEnginePage *WebPage::createWindow(QWebEnginePage::WebWindowType type) { if (type == QWebEnginePage::WebBrowserTab) { return mainWindow()->tabWidget()->newTab()->page(); } else if (type == QWebEnginePage::WebBrowserWindow) { BrowserApplication::instance()->newMainWindow(); BrowserMainWindow *mainWindow = BrowserApplication::instance()->mainWindow(); return mainWindow->currentTab()->page(); } else { PopupWindow *popup = new PopupWindow(profile()); popup->setAttribute(Qt::WA_DeleteOnClose); popup->show(); return popup->page(); } } #if !defined(QT_NO_UITOOLS) QObject *WebPage::createPlugin(const QString &classId, const QUrl &url, const QStringList ¶mNames, const QStringList ¶mValues) { Q_UNUSED(url); Q_UNUSED(paramNames); Q_UNUSED(paramValues); QUiLoader loader; return loader.createWidget(classId, view()); } #endif // !defined(QT_NO_UITOOLS) #if defined(QWEBENGINEPAGE_UNSUPPORTEDCONTENT) void WebPage::handleUnsupportedContent(QNetworkReply *reply) { QString errorString = reply->errorString(); if (m_loadingUrl != reply->url()) { // sub resource of this page qWarning() << "Resource" << reply->url().toEncoded() << "has unknown Content-Type, will be ignored."; reply->deleteLater(); return; } if (reply->error() == QNetworkReply::NoError && !reply->header(QNetworkRequest::ContentTypeHeader).isValid()) { errorString = "Unknown Content-Type"; } QFile file(QLatin1String(":/notfound.html")); bool isOpened = file.open(QIODevice::ReadOnly); Q_ASSERT(isOpened); Q_UNUSED(isOpened) QString title = tr("Error loading page: %1").arg(reply->url().toString()); QString html = QString(QLatin1String(file.readAll())) .arg(title) .arg(errorString) .arg(reply->url().toString()); QBuffer imageBuffer; imageBuffer.open(QBuffer::ReadWrite); QIcon icon = view()->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, view()); QPixmap pixmap = icon.pixmap(QSize(32,32)); if (pixmap.save(&imageBuffer, "PNG")) { html.replace(QLatin1String("IMAGE_BINARY_DATA_HERE"), QString(QLatin1String(imageBuffer.buffer().toBase64()))); } QList frames; frames.append(mainFrame()); while (!frames.isEmpty()) { QWebEngineFrame *frame = frames.takeFirst(); if (frame->url() == reply->url()) { frame->setHtml(html, reply->url()); return; } QList children = frame->childFrames(); foreach (QWebEngineFrame *frame, children) frames.append(frame); } if (m_loadingUrl == reply->url()) { mainFrame()->setHtml(html, reply->url()); } } #endif void WebPage::authenticationRequired(const QUrl &requestUrl, QAuthenticator *auth) { BrowserMainWindow *mainWindow = BrowserApplication::instance()->mainWindow(); QDialog dialog(mainWindow); dialog.setWindowFlags(Qt::Sheet); Ui::PasswordDialog passwordDialog; passwordDialog.setupUi(&dialog); passwordDialog.iconLabel->setText(QString()); passwordDialog.iconLabel->setPixmap(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, mainWindow).pixmap(32, 32)); QString introMessage = tr("Enter username and password for \"%1\" at %2"); introMessage = introMessage.arg(auth->realm()).arg(requestUrl.toString().toHtmlEscaped()); passwordDialog.introLabel->setText(introMessage); passwordDialog.introLabel->setWordWrap(true); if (dialog.exec() == QDialog::Accepted) { QByteArray key = BrowserApplication::authenticationKey(requestUrl, auth->realm()); auth->setUser(passwordDialog.userNameLineEdit->text()); auth->setPassword(passwordDialog.passwordLineEdit->text()); auth->setOption("key", key); BrowserApplication::instance()->setLastAuthenticator(auth); } else { // Set authenticator null if dialog is cancelled *auth = QAuthenticator(); } } void WebPage::proxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost) { Q_UNUSED(requestUrl); BrowserMainWindow *mainWindow = BrowserApplication::instance()->mainWindow(); QDialog dialog(mainWindow); dialog.setWindowFlags(Qt::Sheet); Ui::ProxyDialog proxyDialog; proxyDialog.setupUi(&dialog); proxyDialog.iconLabel->setText(QString()); proxyDialog.iconLabel->setPixmap(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, mainWindow).pixmap(32, 32)); QString introMessage = tr("Connect to proxy \"%1\" using:"); introMessage = introMessage.arg(proxyHost.toHtmlEscaped()); proxyDialog.introLabel->setText(introMessage); proxyDialog.introLabel->setWordWrap(true); if (dialog.exec() == QDialog::Accepted) { QString user = proxyDialog.userNameLineEdit->text(); QByteArray key = BrowserApplication::proxyAuthenticationKey(user, proxyHost, auth->realm()); auth->setUser(user); auth->setPassword(proxyDialog.passwordLineEdit->text()); auth->setOption("key", key); BrowserApplication::instance()->setLastProxyAuthenticator(auth); } else { // Set authenticator null if dialog is cancelled *auth = QAuthenticator(); } } WebView::WebView(QWidget* parent) : QWebEngineView(parent) , m_progress(0) , m_page(0) , m_iconReply(0) { connect(this, SIGNAL(loadProgress(int)), this, SLOT(setProgress(int))); connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool))); connect(this, &QWebEngineView::renderProcessTerminated, [=](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) { const char *status = ""; switch (termStatus) { case QWebEnginePage::NormalTerminationStatus: status = "(normal exit)"; break; case QWebEnginePage::AbnormalTerminationStatus: status = "(abnormal exit)"; break; case QWebEnginePage::CrashedTerminationStatus: status = "(crashed)"; break; case QWebEnginePage::KilledTerminationStatus: status = "(killed)"; break; } qInfo() << "Render process exited with code" << statusCode << status; QTimer::singleShot(0, [this] { reload(); }); }); } void WebView::setPage(WebPage *_page) { if (m_page) m_page->deleteLater(); m_page = _page; QWebEngineView::setPage(_page); #if defined(QWEBENGINEPAGE_STATUSBARMESSAGE) connect(page(), SIGNAL(statusBarMessage(QString)), SLOT(setStatusBarText(QString))); #endif connect(page(), SIGNAL(loadingUrl(QUrl)), this, SIGNAL(urlChanged(QUrl))); connect(page(), SIGNAL(iconUrlChanged(QUrl)), this, SLOT(onIconUrlChanged(QUrl))); connect(page(), &WebPage::featurePermissionRequested, this, &WebView::onFeaturePermissionRequested); #if defined(QWEBENGINEPAGE_UNSUPPORTEDCONTENT) page()->setForwardUnsupportedContent(true); #endif } void WebView::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = page()->createStandardContextMenu(); const QList actions = menu->actions(); QList::const_iterator it = qFind(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::OpenLinkInThisWindow)); if (it != actions.cend()) { (*it)->setText(tr("Open Link in This Window")); ++it; menu->insertAction(*it, page()->action(QWebEnginePage::OpenLinkInNewWindow)); menu->insertAction(*it, page()->action(QWebEnginePage::OpenLinkInNewTab)); } menu->popup(event->globalPos()); } void WebView::wheelEvent(QWheelEvent *event) { #if defined(QWEBENGINEPAGE_SETTEXTSIZEMULTIPLIER) if (QApplication::keyboardModifiers() & Qt::ControlModifier) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 15; setTextSizeMultiplier(textSizeMultiplier() + numSteps * 0.1); event->accept(); return; } #endif QWebEngineView::wheelEvent(event); } void WebView::openLinkInNewTab() { pageAction(QWebEnginePage::OpenLinkInNewTab)->trigger(); } void WebView::onFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature) { FeaturePermissionBar *permissionBar = new FeaturePermissionBar(this); connect(permissionBar, &FeaturePermissionBar::featurePermissionProvided, page(), &QWebEnginePage::setFeaturePermission); // Discard the bar on new loads (if we navigate away or reload). connect(page(), &QWebEnginePage::loadStarted, permissionBar, &QObject::deleteLater); permissionBar->requestPermission(securityOrigin, feature); } void WebView::setProgress(int progress) { m_progress = progress; } void WebView::loadFinished(bool success) { if (success && 100 != m_progress) { qWarning() << "Received finished signal while progress is still:" << progress() << "Url:" << url(); } m_progress = 0; } void WebView::loadUrl(const QUrl &url) { m_initialUrl = url; load(url); } QString WebView::lastStatusBarText() const { return m_statusBarText; } QUrl WebView::url() const { QUrl url = QWebEngineView::url(); if (!url.isEmpty()) return url; return m_initialUrl; } QIcon WebView::icon() const { if (!m_icon.isNull()) return m_icon; return BrowserApplication::instance()->defaultIcon(); } void WebView::onIconUrlChanged(const QUrl &url) { QNetworkRequest iconRequest(url); m_iconReply = BrowserApplication::networkAccessManager()->get(iconRequest); m_iconReply->setParent(this); connect(m_iconReply, SIGNAL(finished()), this, SLOT(iconLoaded())); } void WebView::iconLoaded() { m_icon = QIcon(); if (m_iconReply) { QByteArray data = m_iconReply->readAll(); QPixmap pixmap; pixmap.loadFromData(data); m_icon.addPixmap(pixmap); m_iconReply->deleteLater(); m_iconReply = 0; } emit iconChanged(); } void WebView::mousePressEvent(QMouseEvent *event) { m_page->m_pressedButtons = event->buttons(); m_page->m_keyboardModifiers = event->modifiers(); QWebEngineView::mousePressEvent(event); } void WebView::mouseReleaseEvent(QMouseEvent *event) { QWebEngineView::mouseReleaseEvent(event); if (!event->isAccepted() && (m_page->m_pressedButtons & Qt::MidButton)) { QUrl url(QApplication::clipboard()->text(QClipboard::Selection)); if (!url.isEmpty() && url.isValid() && !url.scheme().isEmpty()) { setUrl(url); } } } void WebView::setStatusBarText(const QString &string) { m_statusBarText = string; }