41#include "ui_jsconfirm.h"
42#include "ui_jsalert.h"
43#include "ui_jsprompt.h"
53#include <QWebEngineHistory>
54#include <QWebEngineSettings>
56#include <QDesktopServices>
59#include <QAuthenticator>
62#include <QtWebEngineWidgetsVersion>
64#include <QWebEngineRegisterProtocolHandlerRequest>
66QString WebPage::s_lastUploadLocation = QDir::homePath();
67QUrl WebPage::s_lastUnsupportedUrl;
68QElapsedTimer WebPage::s_lastUnsupportedUrlTime;
71static const bool kEnableJsOutput = qEnvironmentVariableIsSet(
"FALKON_ENABLE_JS_OUTPUT");
72static const bool kEnableJsNonBlockDialogs = qEnvironmentVariableIsSet(
"FALKON_ENABLE_JS_NONBLOCK_DIALOGS");
75 : QWebEnginePage(
mApp->webProfile(), parent)
76 , m_fileWatcher(nullptr)
77 , m_runningLoop(nullptr)
79 , m_blockAlerts(false)
80 , m_secureStatus(false)
82 auto *channel =
new QWebChannel(
this);
88 connect(
this, &QWebEnginePage::urlChanged,
this, &WebPage::urlChanged);
89 connect(
this, &QWebEnginePage::featurePermissionRequested,
this, &WebPage::featurePermissionRequested);
90 connect(
this, &QWebEnginePage::windowCloseRequested,
this, &WebPage::windowCloseRequested);
91 connect(
this, &QWebEnginePage::fullScreenRequested,
this, &WebPage::fullScreenRequested);
92 connect(
this, &QWebEnginePage::renderProcessTerminated,
this, &WebPage::renderProcessTerminated);
93 connect(
this, &QWebEnginePage::certificateError,
this, &WebPage::onCertificateError);
95 connect(
this, &QWebEnginePage::authenticationRequired,
this, [
this](
const QUrl &url, QAuthenticator *auth) {
96 mApp->networkManager()->authentication(url, auth,
view());
99 connect(
this, &QWebEnginePage::proxyAuthenticationRequired,
this, [
this](
const QUrl &, QAuthenticator *auth,
const QString &proxyHost) {
100 mApp->networkManager()->proxyAuthentication(proxyHost, auth,
view());
104 m_contentsResizedConnection = connect(
this, &QWebEnginePage::contentsSizeChanged,
this, [
this]() {
105 const QString fragment = url().fragment();
106 if (!fragment.isEmpty()) {
109 disconnect(m_contentsResizedConnection);
113 connect(
this, &QWebEnginePage::loadProgress,
this, [
this](
int progress) {
115 Q_EMIT loadFinished(
true);
119 connect(
this, &QWebEnginePage::registerProtocolHandlerRequested,
this, [
this](QWebEngineRegisterProtocolHandlerRequest request) {
120 delete m_registerProtocolHandlerRequest;
121 m_registerProtocolHandlerRequest =
new QWebEngineRegisterProtocolHandlerRequest(request);
124 connect(
this, &QWebEnginePage::selectClientCertificate,
this, [
this](QWebEngineClientCertificateSelection selection) {
126 selection.select(selection.certificates().at(0));
132 delete m_registerProtocolHandlerRequest;
135 m_runningLoop->exit(1);
136 m_runningLoop =
nullptr;
142 return static_cast<WebView*
>(QWebEngineView::forPage(
this));
147 QPointer<QEventLoop> loop =
new QEventLoop;
149 QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit);
151 connect(
view(), &QWebEngineView::printFinished,
this, [loop, &result](
bool res) {
152 if (loop && loop->isRunning()) {
157 view()->print(printer);
167 QPointer<QEventLoop> loop =
new QEventLoop;
169 QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit);
171 runJavaScript(scriptSource, worldId, [loop, &result](
const QVariant &res) {
172 if (loop && loop->isRunning()) {
178 loop->exec(QEventLoop::ExcludeUserInputEvents);
186 return QPointF(pos.x() / zoomFactor(), pos.y() / zoomFactor());
196 runJavaScript(
QSL(
"window.scrollTo(window.scrollX + %1, window.scrollY + %2)").arg(x).arg(y),
SafeJsWorld);
202 runJavaScript(
QSL(
"window.scrollTo(%1, %2)").arg(v.x()).arg(v.y()),
SafeJsWorld);
207 return m_runningLoop;
212 return m_loadProgress < 100;
254void WebPage::urlChanged(
const QUrl &url)
259 m_blockAlerts =
false;
265 m_loadProgress = prog;
267 bool secStatus = url().scheme() ==
QL1S(
"https");
269 if (secStatus != m_secureStatus) {
270 m_secureStatus = secStatus;
280 if (url().scheme() == QLatin1String(
"file")) {
281 QFileInfo info(url().toLocalFile());
283 if (!m_fileWatcher) {
288 const QString filePath = url().toLocalFile();
290 if (QFile::exists(filePath) && !m_fileWatcher->files().contains(filePath)) {
291 m_fileWatcher->addPath(filePath);
295 else if (m_fileWatcher && !m_fileWatcher->files().isEmpty()) {
296 m_fileWatcher->removePaths(m_fileWatcher->files());
300 m_autoFillUsernames =
mApp->autoFill()->completePage(
this, url());
303void WebPage::watchedFileChanged(
const QString &file)
305 if (url().toLocalFile() == file) {
306 triggerAction(QWebEnginePage::Reload);
310void WebPage::handleUnknownProtocol(
const QUrl &url)
312 const QString protocol = url.scheme();
314 if (protocol == QLatin1String(
"mailto")) {
315 desktopServicesOpen(url);
319 if (
qzSettings->blockedProtocols.contains(protocol)) {
320 qDebug() <<
"WebPage::handleUnknownProtocol Protocol" << protocol <<
"is blocked!";
324 if (
qzSettings->autoOpenProtocols.contains(protocol)) {
325 desktopServicesOpen(url);
330 dialog.setDefaultButton(QMessageBox::Yes);
333 const QString text = tr(
"Falkon cannot handle <b>%1:</b> links. The requested link "
334 "is <ul><li>%2</li></ul>Do you want Falkon to try "
335 "open this link in system application?").arg(protocol, wrappedUrl);
337 dialog.setText(text);
338 dialog.setCheckBoxText(tr(
"Remember my choice for this protocol"));
339 dialog.setWindowTitle(tr(
"External Protocol Request"));
340 dialog.setIcon(QMessageBox::Question);
342 switch (dialog.exec()) {
343 case QMessageBox::Yes:
344 if (dialog.isChecked()) {
345 qzSettings->autoOpenProtocols.append(protocol);
350 QDesktopServices::openUrl(url);
353 case QMessageBox::No:
354 if (dialog.isChecked()) {
355 qzSettings->blockedProtocols.append(protocol);
366void WebPage::desktopServicesOpen(
const QUrl &url)
369 const int sameUrlTimeout = 2 * 1000;
371 if ((s_lastUnsupportedUrl != url) || (!s_lastUnsupportedUrlTime.isValid()) || (s_lastUnsupportedUrlTime.elapsed() > sameUrlTimeout)) {
372 s_lastUnsupportedUrl = url;
373 s_lastUnsupportedUrlTime.restart();
374 QDesktopServices::openUrl(url);
377 qWarning() <<
"WebPage::desktopServicesOpen Url" << url <<
"has already been opened!\n"
378 "Ignoring it to prevent infinite loop!";
382void WebPage::windowCloseRequested()
389void WebPage::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest)
393 const bool accepted = fullScreenRequest.toggleOn() ==
view()->
isFullScreen();
396 fullScreenRequest.accept();
398 fullScreenRequest.reject();
401void WebPage::featurePermissionRequested(
const QUrl &origin,
const QWebEnginePage::Feature &feature)
403 if (feature == MouseLock &&
view()->isFullScreen())
404 setFeaturePermission(origin, feature, PermissionGrantedByUser);
406 mApp->html5PermissionsManager()->requestPermissions(
this, origin, feature);
409void WebPage::renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus,
int exitCode)
413 if (terminationStatus == NormalTerminationStatus)
416 QTimer::singleShot(0,
this, [
this]() {
419 page.replace(
QL1S(
"%TITLE%"), tr(
"Failed loading page"));
420 page.replace(
QL1S(
"%HEADING%"), tr(
"Failed loading page"));
421 page.replace(
QL1S(
"%LI-1%"), tr(
"Something went wrong while loading this page."));
422 page.replace(
QL1S(
"%LI-2%"), tr(
"Try reloading the page or closing some tabs to make more memory available."));
423 page.replace(
QL1S(
"%RELOAD-PAGE%"), tr(
"Reload page"));
425 setHtml(page, url());
429bool WebPage::acceptNavigationRequest(
const QUrl &url, QWebEnginePage::NavigationType type,
bool isMainFrame)
431 if (
mApp->isClosing()) {
432 return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame);
435 if (!
mApp->plugins()->acceptNavigationRequest(
this, url, type, isMainFrame))
438 if (url.scheme() ==
QL1S(
"falkon")) {
439 if (url.path() ==
QL1S(
"AddSearchProvider")) {
440 QUrlQuery query(url);
441 mApp->searchEnginesManager()->addEngine(QUrl(query.queryItemValue(
QSL(
"url"))));
450 const bool result = QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame);
454 const bool isWeb = url.scheme() ==
QL1S(
"http") || url.scheme() ==
QL1S(
"https") || url.scheme() ==
QL1S(
"file");
457 auto webAttributes =
mApp->siteSettingsManager()->getWebAttributes(url);
458 if (!webAttributes.empty()) {
459 for (
auto it = webAttributes.begin(); it != webAttributes.end(); ++it) {
460 settings()->setAttribute(it.key(), it.value());
464 auto const webAttributes =
mApp->siteSettingsManager()->getSupportedAttribute();
465 for (
auto attribute : webAttributes) {
466 settings()->setAttribute(attribute,
mApp->webSettings()->testAttribute(attribute));
471 settings()->setAttribute(QWebEngineSettings::AutoLoadImages,
true);
472 settings()->setAttribute(QWebEngineSettings::JavascriptEnabled,
true);
473 settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows,
false);
474 settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard,
true);
475 settings()->setAttribute(QWebEngineSettings::JavascriptCanPaste,
false);
476 settings()->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript,
false);
477 settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled,
true);
478 settings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled,
mApp->webSettings()->testAttribute(QWebEngineSettings::FullScreenSupportEnabled));
479 settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent,
false);
480 settings()->setAttribute(QWebEngineSettings::AllowGeolocationOnInsecureOrigins,
false);
481 settings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,
mApp->webSettings()->testAttribute(QWebEngineSettings::PlaybackRequiresUserGesture));
482 settings()->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly,
false);
491void WebPage::onCertificateError(QWebEngineCertificateError error)
493 auto mutableError =
const_cast<QWebEngineCertificateError&
>(error);
494 if (
mApp->networkManager()->certificateError(mutableError,
view()))
495 mutableError.acceptCertificate();
497 mutableError.rejectCertificate();
500QStringList WebPage::chooseFiles(QWebEnginePage::FileSelectionMode mode,
const QStringList &oldFiles,
const QStringList &acceptedMimeTypes)
502 Q_UNUSED(acceptedMimeTypes);
505 QString suggestedFileName = s_lastUploadLocation;
506 if (!oldFiles.isEmpty())
507 suggestedFileName = oldFiles.at(0);
514 case FileSelectOpenMultiple:
519 files = QWebEnginePage::chooseFiles(mode, oldFiles, acceptedMimeTypes);
523 if (!files.isEmpty())
524 s_lastUploadLocation = files.at(0);
531 return m_autoFillUsernames;
536 if (m_registerProtocolHandlerRequest && url().host() == m_registerProtocolHandlerRequest->origin().host()) {
537 return m_registerProtocolHandlerRequest->origin();
544 if (m_registerProtocolHandlerRequest && url().host() == m_registerProtocolHandlerRequest->origin().host()) {
545 return m_registerProtocolHandlerRequest->scheme();
552 if (!kEnableJsNonBlockDialogs) {
553 return QWebEnginePage::javaScriptPrompt(securityOrigin, msg, defaultValue, result);
560 auto *widget =
new QFrame(
view()->overlayWidget());
562 widget->setObjectName(
"jsFrame");
563 auto* ui =
new Ui_jsPrompt();
565 ui->message->setText(msg);
566 ui->lineEdit->setText(defaultValue);
567 ui->lineEdit->setFocus();
568 widget->resize(
view()->size());
571 QAbstractButton *clicked =
nullptr;
572 connect(ui->buttonBox, &QDialogButtonBox::clicked,
this, [&](QAbstractButton *button) {
577 connect(ui->lineEdit, SIGNAL(returnPressed()), ui->buttonBox->button(QDialogButtonBox::Ok), SLOT(animateClick()));
580 m_runningLoop = &eLoop;
581 connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit);
583 if (eLoop.exec() == 1) {
586 m_runningLoop =
nullptr;
588 QString x = ui->lineEdit->text();
589 bool _result = ui->buttonBox->buttonRole(clicked) == QDialogButtonBox::AcceptRole;
600 if (!kEnableJsNonBlockDialogs) {
601 return QWebEnginePage::javaScriptConfirm(securityOrigin, msg);
608 auto *widget =
new QFrame(
view()->overlayWidget());
610 widget->setObjectName(
"jsFrame");
611 auto* ui =
new Ui_jsConfirm();
613 ui->message->setText(msg);
614 ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
615 widget->resize(
view()->size());
618 QAbstractButton *clicked =
nullptr;
619 connect(ui->buttonBox, &QDialogButtonBox::clicked,
this, [&](QAbstractButton *button) {
626 m_runningLoop = &eLoop;
627 connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit);
629 if (eLoop.exec() == 1) {
632 m_runningLoop =
nullptr;
634 bool result = ui->buttonBox->buttonRole(clicked) == QDialogButtonBox::AcceptRole;
644 Q_UNUSED(securityOrigin)
646 if (m_blockAlerts || m_runningLoop) {
650 if (!kEnableJsNonBlockDialogs) {
651 QString title = tr(
"JavaScript alert");
652 if (!url().host().isEmpty()) {
653 title.append(
QSL(
" - %1").arg(url().host()));
657 dialog.setDefaultButton(QMessageBox::Ok);
658 dialog.setWindowTitle(title);
660 dialog.
setCheckBoxText(tr(
"Prevent this page from creating additional dialogs"));
661 dialog.setIcon(QMessageBox::Information);
668 auto *widget =
new QFrame(
view()->overlayWidget());
670 widget->setObjectName(
"jsFrame");
671 auto* ui =
new Ui_jsAlert();
673 ui->message->setText(msg);
674 ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
675 widget->resize(
view()->size());
681 m_runningLoop = &eLoop;
682 connect(ui->buttonBox, &QDialogButtonBox::clicked, &eLoop, &QEventLoop::quit);
684 if (eLoop.exec() == 1) {
687 m_runningLoop =
nullptr;
689 m_blockAlerts = ui->preventAlerts->isChecked();
698 if (!kEnableJsOutput) {
703 case InfoMessageLevel:
707 case WarningMessageLevel:
711 case ErrorMessageLevel:
716 std::cout << qPrintable(sourceID) <<
":" << lineNumber <<
" " << qPrintable(message);
719QWebEnginePage* WebPage::createWindow(QWebEnginePage::WebWindowType type)
721 auto *tView = qobject_cast<TabbedWebView*>(
view());
724 auto createTab = [=](Qz::NewTabPositionFlags pos) {
729 tView->webTab()->addChildTab(
view->webTab());
733 QPointer<TabbedWebView> pview =
view;
735 QTimer::singleShot(100,
this, [pview]() {
736 if (pview && pview->webTab()->isCurrentTab()) {
745 case QWebEnginePage::WebBrowserWindow: {
752 case QWebEnginePage::WebDialog:
763 case QWebEnginePage::WebBrowserTab:
766 case QWebEnginePage::WebBrowserBackgroundTab:
void setStartPage(WebPage *page)
TabWidget * tabWidget() const
void addDeleteOnCloseWidget(QWidget *widget)
TabbedWebView * weView() const
void setCheckBoxText(const QString &text)
void delayedFileChanged(const QString &path)
static void setupWebChannel(QWebChannel *webChannel, WebPage *page)
static QIcon standardIcon(QStyle::StandardPixmap icon)
static OcsSupport * instance()
static QString scrollToAnchor(const QString &anchor)
void privacyChanged(bool status)
QVariant execJavaScript(const QString &scriptSource, quint32 worldId=UnsafeJsWorld, int timeout=500)
bool javaScriptPrompt(const QUrl &securityOrigin, const QString &msg, const QString &defaultValue, QString *result) override
bool execPrintPage(QPrinter *printer, int timeout=1000)
static QStringList internalSchemes()
bool javaScriptConfirm(const QUrl &securityOrigin, const QString &msg) override
static void removeSupportedScheme(const QString &scheme)
void navigationRequestAccepted(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame)
QStringList autoFillUsernames() const
void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID) override
void setScrollPosition(const QPointF &pos)
WebPage(QObject *parent=nullptr)
static QStringList supportedSchemes()
WebHitTestResult hitTestContent(const QPoint &pos) const
void scroll(int x, int y)
QString registerProtocolHandlerRequestScheme() const
void javaScriptAlert(const QUrl &securityOrigin, const QString &msg) override
static void addSupportedScheme(const QString &scheme)
QPointF mapToViewport(const QPointF &pos) const
QUrl registerProtocolHandlerRequestUrl() const
virtual void closeView()=0
virtual void requestFullScreen(bool enable)=0
virtual bool isFullScreen()=0
void setPage(WebPage *page)
void viewportResized(QSize)
QStringList s_supportedSchemes