diff --git a/src/lib/app/mainapplication.cpp b/src/lib/app/mainapplication.cpp index 87a3136f..52c05b44 100644 --- a/src/lib/app/mainapplication.cpp +++ b/src/lib/app/mainapplication.cpp @@ -1,1265 +1,1265 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ============================================================ */ #include "mainapplication.h" #include "history.h" #include "qztools.h" #include "updater.h" #include "autofill.h" #include "settings.h" #include "qzregexp.h" #include "autosaver.h" #include "datapaths.h" #include "tabwidget.h" #include "cookiejar.h" #include "bookmarks.h" #include "qzsettings.h" #include "proxystyle.h" #include "pluginproxy.h" #include "iconprovider.h" #include "browserwindow.h" #include "checkboxdialog.h" #include "networkmanager.h" #include "profilemanager.h" #include "restoremanager.h" #include "browsinglibrary.h" #include "downloadmanager.h" #include "clearprivatedata.h" #include "useragentmanager.h" #include "commandlineoptions.h" #include "searchenginesmanager.h" #include "desktopnotificationsfactory.h" #include "html5permissions/html5permissionsmanager.h" #include "scripts.h" #include "sessionmanager.h" #include "closedwindowsmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #include #include #endif #include #if defined(Q_OS_WIN) && !defined(Q_OS_OS2) #include "registerqappassociation.h" #endif MainApplication::MainApplication(int &argc, char** argv) : QtSingleApplication(argc, argv) , m_isPrivate(false) , m_isPortable(false) , m_isClosing(false) , m_isStartingAfterCrash(false) , m_history(0) , m_bookmarks(0) , m_autoFill(0) , m_cookieJar(0) , m_plugins(0) , m_browsingLibrary(0) , m_networkManager(0) , m_restoreManager(0) , m_sessionManager(0) , m_downloadManager(0) , m_userAgentManager(0) , m_searchEnginesManager(0) , m_closedWindowsManager(0) , m_html5PermissionsManager(0) , m_desktopNotifications(0) , m_webProfile(0) , m_autoSaver(0) #if defined(Q_OS_WIN) && !defined(Q_OS_OS2) , m_registerQAppAssociation(0) #endif { setAttribute(Qt::AA_UseHighDpiPixmaps); setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); setApplicationName(QLatin1String("falkon")); setOrganizationDomain(QLatin1String("org.kde")); setWindowIcon(QIcon::fromTheme(QSL("qupzilla"), QIcon(QSL(":icons/exeicons/qupzilla-window.png")))); setDesktopFileName(QSL("org.kde.Falkon")); #ifdef GIT_REVISION setApplicationVersion(QSL("%1 (%2)").arg(Qz::VERSION, GIT_REVISION)); #else setApplicationVersion(Qz::VERSION); #endif // Set fallback icon theme (eg. on Windows/Mac) if (QIcon::fromTheme(QSL("view-refresh")).isNull()) { QIcon::setThemeSearchPaths(QStringList() << QL1S(":/breeze-fallback")); QIcon::setThemeName(QSL("breeze-fallback")); } // QSQLITE database plugin is required if (!QSqlDatabase::isDriverAvailable(QSL("QSQLITE"))) { QMessageBox::critical(0, QSL("Error"), QSL("Qt SQLite database plugin is not available. Please install it and restart the application.")); m_isClosing = true; return; } #ifdef Q_OS_WIN // Set default app font (needed for N'ko) int fontId = QFontDatabase::addApplicationFont(QSL("font.ttf")); if (fontId != -1) { const QStringList families = QFontDatabase::applicationFontFamilies(fontId); if (!families.empty()) setFont(QFont(families.at(0))); } #endif QUrl startUrl; QString startProfile; QStringList messages; bool noAddons = false; bool newInstance = false; if (argc > 1) { CommandLineOptions cmd; foreach (const CommandLineOptions::ActionPair &pair, cmd.getActions()) { switch (pair.action) { case Qz::CL_StartWithoutAddons: noAddons = true; break; case Qz::CL_StartWithProfile: startProfile = pair.text; break; case Qz::CL_StartPortable: m_isPortable = true; break; case Qz::CL_NewTab: messages.append(QLatin1String("ACTION:NewTab")); m_postLaunchActions.append(OpenNewTab); break; case Qz::CL_NewWindow: messages.append(QLatin1String("ACTION:NewWindow")); break; case Qz::CL_ToggleFullScreen: messages.append(QLatin1String("ACTION:ToggleFullScreen")); m_postLaunchActions.append(ToggleFullScreen); break; case Qz::CL_ShowDownloadManager: messages.append(QLatin1String("ACTION:ShowDownloadManager")); m_postLaunchActions.append(OpenDownloadManager); break; case Qz::CL_StartPrivateBrowsing: m_isPrivate = true; break; case Qz::CL_StartNewInstance: newInstance = true; break; case Qz::CL_OpenUrlInCurrentTab: startUrl = QUrl::fromUserInput(pair.text); messages.append("ACTION:OpenUrlInCurrentTab" + pair.text); break; case Qz::CL_OpenUrlInNewWindow: startUrl = QUrl::fromUserInput(pair.text); messages.append("ACTION:OpenUrlInNewWindow" + pair.text); break; case Qz::CL_OpenUrl: startUrl = QUrl::fromUserInput(pair.text); messages.append("URL:" + pair.text); break; case Qz::CL_ExitAction: m_isClosing = true; return; default: break; } } } if (isPortable()) { std::cout << "Falkon: Running in Portable Mode." << std::endl; DataPaths::setPortableVersion(); } // Don't start single application in private browsing if (!isPrivate()) { QString appId = QLatin1String("FalkonWebBrowser"); if (isPortable()) { appId.append(QLatin1String("Portable")); } if (newInstance) { if (startProfile.isEmpty() || startProfile == QLatin1String("default")) { std::cout << "New instance cannot be started with default profile!" << std::endl; } else { // Generate unique appId so it is possible to start more separate instances // of the same profile. It is dangerous to run more instances of the same profile, // but if the user wants it, we should allow it. appId.append(startProfile + QString::number(QDateTime::currentMSecsSinceEpoch())); } } setAppId(appId); } // If there is nothing to tell other instance, we need to at least wake it if (messages.isEmpty()) { messages.append(QLatin1String(" ")); } if (isRunning()) { m_isClosing = true; foreach (const QString &message, messages) { sendMessage(message); } return; } #ifdef Q_OS_MACOS setQuitOnLastWindowClosed(false); // disable tabbing issue#2261 extern void disableWindowTabbing(); disableWindowTabbing(); #else setQuitOnLastWindowClosed(true); #endif QSettings::setDefaultFormat(QSettings::IniFormat); QDesktopServices::setUrlHandler(QSL("http"), this, "addNewTab"); QDesktopServices::setUrlHandler(QSL("https"), this, "addNewTab"); QDesktopServices::setUrlHandler(QSL("ftp"), this, "addNewTab"); ProfileManager profileManager; profileManager.initConfigDir(); profileManager.initCurrentProfile(startProfile); Settings::createSettings(DataPaths::currentProfilePath() + QLatin1String("/settings.ini")); m_webProfile = isPrivate() ? new QWebEngineProfile(this) : QWebEngineProfile::defaultProfile(); connect(m_webProfile, &QWebEngineProfile::downloadRequested, this, &MainApplication::downloadRequested); m_networkManager = new NetworkManager(this); setupUserScripts(); if (!isPrivate()) { m_sessionManager = new SessionManager(this); m_autoSaver = new AutoSaver(this); connect(m_autoSaver, SIGNAL(save()), m_sessionManager, SLOT(autoSaveLastSession())); Settings settings; settings.beginGroup(QSL("SessionRestore")); const bool wasRunning = settings.value(QSL("isRunning"), false).toBool(); const bool wasRestoring = settings.value(QSL("isRestoring"), false).toBool(); settings.setValue(QSL("isRunning"), true); settings.setValue(QSL("isRestoring"), wasRunning); settings.endGroup(); settings.sync(); m_isStartingAfterCrash = wasRunning && wasRestoring; if (wasRunning) { QTimer::singleShot(60 * 1000, this, [this]() { Settings().setValue(QSL("SessionRestore/isRestoring"), false); }); } // we have to ask about startup session before creating main window if (!m_isStartingAfterCrash && afterLaunch() == SelectSession) m_restoreManager = new RestoreManager(sessionManager()->askSessionFromUser()); } translateApp(); loadSettings(); m_plugins = new PluginProxy; m_autoFill = new AutoFill(this); if (!noAddons) m_plugins->loadPlugins(); BrowserWindow* window = createWindow(Qz::BW_FirstAppWindow, startUrl); connect(window, SIGNAL(startingCompleted()), this, SLOT(restoreOverrideCursor())); connect(this, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged())); if (!isPrivate()) { #ifndef DISABLE_CHECK_UPDATES Settings settings; bool checkUpdates = settings.value("Web-Browser-Settings/CheckUpdates", true).toBool(); if (checkUpdates) { new Updater(window); } #endif sessionManager()->backupSavedSessions(); if (m_isStartingAfterCrash || afterLaunch() == RestoreSession) { m_restoreManager = new RestoreManager(sessionManager()->lastActiveSessionPath()); if (!m_restoreManager->isValid()) { destroyRestoreManager(); } } if (!m_isStartingAfterCrash && m_restoreManager) { restoreSession(window, m_restoreManager->restoreData()); } } QTimer::singleShot(0, this, SLOT(postLaunch())); } MainApplication::~MainApplication() { IconProvider::instance()->saveIconsToDatabase(); // Wait for all QtConcurrent jobs to finish QThreadPool::globalInstance()->waitForDone(); // Delete all classes that are saving data in destructor delete m_bookmarks; delete m_cookieJar; delete m_plugins; Settings::syncSettings(); } bool MainApplication::isClosing() const { return m_isClosing; } bool MainApplication::isPrivate() const { return m_isPrivate; } bool MainApplication::isPortable() const { #ifdef PORTABLE_BUILD return true; #else return m_isPortable; #endif } bool MainApplication::isStartingAfterCrash() const { return m_isStartingAfterCrash; } int MainApplication::windowCount() const { return m_windows.count(); } QList MainApplication::windows() const { return m_windows; } BrowserWindow* MainApplication::getWindow() const { if (m_lastActiveWindow) { return m_lastActiveWindow.data(); } return m_windows.isEmpty() ? 0 : m_windows.at(0); } BrowserWindow* MainApplication::createWindow(Qz::BrowserWindowType type, const QUrl &startUrl) { if (windowCount() == 0 && type != Qz::BW_MacFirstWindow) { type = Qz::BW_FirstAppWindow; } BrowserWindow* window = new BrowserWindow(type, startUrl); connect(window, SIGNAL(destroyed(QObject*)), this, SLOT(windowDestroyed(QObject*))); m_windows.prepend(window); return window; } MainApplication::AfterLaunch MainApplication::afterLaunch() const { return static_cast(Settings().value(QSL("Web-URL-Settings/afterLaunch"), RestoreSession).toInt()); } void MainApplication::openSession(BrowserWindow* window, RestoreData &restoreData) { setOverrideCursor(Qt::BusyCursor); if (!window) window = createWindow(Qz::BW_OtherRestoredWindow); if (window->tabCount() != 0) { // This can only happen when recovering crashed session! // Don't restore tabs in current window as user already opened some new tabs. createWindow(Qz::BW_OtherRestoredWindow)->restoreWindow(restoreData.windows.takeAt(0)); } else { window->restoreWindow(restoreData.windows.takeAt(0)); } foreach (const BrowserWindow::SavedWindow &data, restoreData.windows) { BrowserWindow* window = createWindow(Qz::BW_OtherRestoredWindow); window->restoreWindow(data); } m_closedWindowsManager->restoreState(restoreData.closedWindows); restoreOverrideCursor(); } bool MainApplication::restoreSession(BrowserWindow* window, RestoreData restoreData) { if (m_isPrivate || !restoreData.isValid()) { return false; } openSession(window, restoreData); m_restoreManager->clearRestoreData(); destroyRestoreManager(); return true; } void MainApplication::destroyRestoreManager() { if (m_restoreManager && m_restoreManager->isValid()) { return; } delete m_restoreManager; m_restoreManager = 0; } void MainApplication::reloadSettings() { loadSettings(); emit settingsReloaded(); } QString MainApplication::styleName() const { return m_proxyStyle ? m_proxyStyle->name() : QString(); } void MainApplication::setProxyStyle(ProxyStyle *style) { m_proxyStyle = style; setStyle(style); } QString MainApplication::currentLanguageFile() const { return m_languageFile; } QString MainApplication::currentLanguage() const { QString lang = m_languageFile; if (lang.isEmpty()) { return "en_US"; } return lang.left(lang.length() - 3); } History* MainApplication::history() { if (!m_history) { m_history = new History(this); } return m_history; } Bookmarks* MainApplication::bookmarks() { if (!m_bookmarks) { m_bookmarks = new Bookmarks(this); } return m_bookmarks; } AutoFill* MainApplication::autoFill() { return m_autoFill; } CookieJar* MainApplication::cookieJar() { if (!m_cookieJar) { m_cookieJar = new CookieJar(this); } return m_cookieJar; } PluginProxy* MainApplication::plugins() { return m_plugins; } BrowsingLibrary* MainApplication::browsingLibrary() { if (!m_browsingLibrary) { m_browsingLibrary = new BrowsingLibrary(getWindow()); } return m_browsingLibrary; } NetworkManager *MainApplication::networkManager() { return m_networkManager; } RestoreManager* MainApplication::restoreManager() { return m_restoreManager; } SessionManager* MainApplication::sessionManager() { return m_sessionManager; } DownloadManager* MainApplication::downloadManager() { if (!m_downloadManager) { m_downloadManager = new DownloadManager(); } return m_downloadManager; } UserAgentManager* MainApplication::userAgentManager() { if (!m_userAgentManager) { m_userAgentManager = new UserAgentManager(this); } return m_userAgentManager; } SearchEnginesManager* MainApplication::searchEnginesManager() { if (!m_searchEnginesManager) { m_searchEnginesManager = new SearchEnginesManager(this); } return m_searchEnginesManager; } ClosedWindowsManager* MainApplication::closedWindowsManager() { if (!m_closedWindowsManager) { m_closedWindowsManager = new ClosedWindowsManager(this); } return m_closedWindowsManager; } HTML5PermissionsManager* MainApplication::html5PermissionsManager() { if (!m_html5PermissionsManager) { m_html5PermissionsManager = new HTML5PermissionsManager(this); } return m_html5PermissionsManager; } DesktopNotificationsFactory* MainApplication::desktopNotifications() { if (!m_desktopNotifications) { m_desktopNotifications = new DesktopNotificationsFactory(this); } return m_desktopNotifications; } QWebEngineProfile *MainApplication::webProfile() const { return m_webProfile; } QWebEngineSettings *MainApplication::webSettings() const { return m_webProfile->settings(); } // static MainApplication* MainApplication::instance() { return static_cast(QCoreApplication::instance()); } void MainApplication::addNewTab(const QUrl &url) { BrowserWindow* window = getWindow(); if (window) { window->tabWidget()->addView(url, url.isEmpty() ? Qz::NT_SelectedNewEmptyTab : Qz::NT_SelectedTabAtTheEnd); } } void MainApplication::startPrivateBrowsing(const QUrl &startUrl) { QUrl url = startUrl; if (QAction* act = qobject_cast(sender())) { url = act->data().toUrl(); } QStringList args; args.append(QSL("--private-browsing")); args.append(QSL("--profile=") + ProfileManager::currentProfile()); if (!url.isEmpty()) { args << url.toEncoded(); } if (!QProcess::startDetached(applicationFilePath(), args)) { qWarning() << "MainApplication: Cannot start new browser process for private browsing!" << applicationFilePath() << args; } } void MainApplication::reloadUserStyleSheet() { const QString userCssFile = Settings().value("Web-Browser-Settings/userStyleSheet", QString()).toString(); setUserStyleSheet(userCssFile); } void MainApplication::restoreOverrideCursor() { QApplication::restoreOverrideCursor(); } void MainApplication::changeOccurred() { if (m_autoSaver) m_autoSaver->changeOccurred(); } void MainApplication::quitApplication() { if (m_downloadManager && !m_downloadManager->canClose()) { m_downloadManager->show(); return; } if (m_sessionManager && m_windows.count() > 0) { m_sessionManager->autoSaveLastSession(); } m_isClosing = true; for (BrowserWindow *window : qAsConst(m_windows)) { window->close(); } // Saving settings in saveSettings() slot called from quit() so // everything gets saved also when quitting application in other // way than clicking Quit action in File menu or closing last window // eg. on Mac (#157) if (!isPrivate()) { removeLockFile(); } quit(); } void MainApplication::postLaunch() { if (m_postLaunchActions.contains(OpenDownloadManager)) { downloadManager()->show(); } if (m_postLaunchActions.contains(OpenNewTab)) { getWindow()->tabWidget()->addView(QUrl(), Qz::NT_SelectedNewEmptyTab); } if (m_postLaunchActions.contains(ToggleFullScreen)) { getWindow()->toggleFullScreen(); } QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, DataPaths::currentProfilePath()); connect(this, SIGNAL(messageReceived(QString)), this, SLOT(messageReceived(QString))); connect(this, SIGNAL(aboutToQuit()), this, SLOT(saveSettings())); createJumpList(); initPulseSupport(); QTimer::singleShot(5000, this, &MainApplication::runDeferredPostLaunchActions); } QByteArray MainApplication::saveState() const { RestoreData restoreData; restoreData.windows.reserve(m_windows.count()); for (BrowserWindow *window : qAsConst(m_windows)) { restoreData.windows.append(BrowserWindow::SavedWindow(window)); } if (m_restoreManager && m_restoreManager->isValid()) { QDataStream stream(&restoreData.crashedSession, QIODevice::WriteOnly); stream << m_restoreManager->restoreData(); } restoreData.closedWindows = m_closedWindowsManager->saveState(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << Qz::sessionVersion; stream << restoreData; return data; } void MainApplication::saveSettings() { if (isPrivate()) { return; } m_isClosing = true; Settings settings; settings.beginGroup("SessionRestore"); settings.setValue("isRunning", false); settings.setValue("isRestoring", false); settings.endGroup(); settings.beginGroup("Web-Browser-Settings"); bool deleteCache = settings.value("deleteCacheOnClose", false).toBool(); bool deleteHistory = settings.value("deleteHistoryOnClose", false).toBool(); bool deleteHtml5Storage = settings.value("deleteHTML5StorageOnClose", false).toBool(); settings.endGroup(); settings.beginGroup("Cookie-Settings"); bool deleteCookies = settings.value("deleteCookiesOnClose", false).toBool(); settings.endGroup(); if (deleteHistory) { m_history->clearHistory(); } if (deleteHtml5Storage) { ClearPrivateData::clearLocalStorage(); } if (deleteCookies) { m_cookieJar->deleteAllCookies(); } if (deleteCache) { QzTools::removeDir(mApp->webProfile()->cachePath()); } m_searchEnginesManager->saveSettings(); m_plugins->shutdown(); m_networkManager->shutdown(); qzSettings->saveSettings(); QFile::remove(DataPaths::currentProfilePath() + QLatin1String("/WebpageIcons.db")); sessionManager()->saveSettings(); } void MainApplication::messageReceived(const QString &message) { QWidget* actWin = getWindow(); QUrl actUrl; if (message.startsWith(QLatin1String("URL:"))) { const QUrl url = QUrl::fromUserInput(message.mid(4)); addNewTab(url); actWin = getWindow(); } else if (message.startsWith(QLatin1String("ACTION:"))) { const QString text = message.mid(7); if (text == QLatin1String("NewTab")) { addNewTab(); } else if (text == QLatin1String("NewWindow")) { actWin = createWindow(Qz::BW_NewWindow); } else if (text == QLatin1String("ShowDownloadManager")) { downloadManager()->show(); actWin = downloadManager(); } else if (text == QLatin1String("ToggleFullScreen") && actWin) { BrowserWindow* qz = static_cast(actWin); qz->toggleFullScreen(); } else if (text.startsWith(QLatin1String("OpenUrlInCurrentTab"))) { actUrl = QUrl::fromUserInput(text.mid(19)); } else if (text.startsWith(QLatin1String("OpenUrlInNewWindow"))) { createWindow(Qz::BW_NewWindow, QUrl::fromUserInput(text.mid(18))); return; } } else { // User attempted to start another instance, let's open a new window actWin = createWindow(Qz::BW_NewWindow); } if (!actWin) { if (!isClosing()) { // It can only occur if download manager window was still opened createWindow(Qz::BW_NewWindow, actUrl); } return; } actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); actWin->raise(); actWin->activateWindow(); actWin->setFocus(); BrowserWindow* win = qobject_cast(actWin); if (win && !actUrl.isEmpty()) { win->loadAddress(actUrl); } } void MainApplication::windowDestroyed(QObject* window) { // qobject_cast doesn't work because QObject::destroyed is emitted from destructor Q_ASSERT(static_cast(window)); Q_ASSERT(m_windows.contains(static_cast(window))); m_windows.removeOne(static_cast(window)); } void MainApplication::onFocusChanged() { BrowserWindow* activeBrowserWindow = qobject_cast(activeWindow()); if (activeBrowserWindow) { m_lastActiveWindow = activeBrowserWindow; emit activeWindowChanged(m_lastActiveWindow); } } void MainApplication::runDeferredPostLaunchActions() { checkDefaultWebBrowser(); checkOptimizeDatabase(); } void MainApplication::downloadRequested(QWebEngineDownloadItem *download) { downloadManager()->download(download); } void MainApplication::loadSettings() { Settings settings; settings.beginGroup("Themes"); QString activeTheme = settings.value("activeTheme", DEFAULT_THEME_NAME).toString(); settings.endGroup(); loadTheme(activeTheme); QWebEngineSettings* webSettings = m_webProfile->settings(); // Web browsing settings settings.beginGroup("Web-Browser-Settings"); webSettings->setAttribute(QWebEngineSettings::LocalStorageEnabled, settings.value("HTML5StorageEnabled", true).toBool()); webSettings->setAttribute(QWebEngineSettings::PluginsEnabled, settings.value("allowPlugins", true).toBool()); webSettings->setAttribute(QWebEngineSettings::JavascriptEnabled, settings.value("allowJavaScript", true).toBool()); webSettings->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, settings.value("allowJavaScriptOpenWindow", false).toBool()); webSettings->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, settings.value("allowJavaScriptAccessClipboard", true).toBool()); webSettings->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, settings.value("IncludeLinkInFocusChain", false).toBool()); webSettings->setAttribute(QWebEngineSettings::XSSAuditingEnabled, settings.value("XSSAuditing", false).toBool()); webSettings->setAttribute(QWebEngineSettings::PrintElementBackgrounds, settings.value("PrintElementBackground", true).toBool()); webSettings->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, settings.value("SpatialNavigation", false).toBool()); webSettings->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, settings.value("AnimateScrolling", true).toBool()); webSettings->setAttribute(QWebEngineSettings::HyperlinkAuditingEnabled, false); webSettings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); webSettings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); webSettings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); webSettings->setDefaultTextEncoding(settings.value("DefaultEncoding", webSettings->defaultTextEncoding()).toString()); setWheelScrollLines(settings.value("wheelScrollLines", wheelScrollLines()).toInt()); const QString userCss = settings.value("userStyleSheet", QString()).toString(); settings.endGroup(); setUserStyleSheet(userCss); settings.beginGroup("Browser-Fonts"); webSettings->setFontFamily(QWebEngineSettings::StandardFont, settings.value("StandardFont", webSettings->fontFamily(QWebEngineSettings::StandardFont)).toString()); webSettings->setFontFamily(QWebEngineSettings::CursiveFont, settings.value("CursiveFont", webSettings->fontFamily(QWebEngineSettings::CursiveFont)).toString()); webSettings->setFontFamily(QWebEngineSettings::FantasyFont, settings.value("FantasyFont", webSettings->fontFamily(QWebEngineSettings::FantasyFont)).toString()); webSettings->setFontFamily(QWebEngineSettings::FixedFont, settings.value("FixedFont", webSettings->fontFamily(QWebEngineSettings::FixedFont)).toString()); webSettings->setFontFamily(QWebEngineSettings::SansSerifFont, settings.value("SansSerifFont", webSettings->fontFamily(QWebEngineSettings::SansSerifFont)).toString()); webSettings->setFontFamily(QWebEngineSettings::SerifFont, settings.value("SerifFont", webSettings->fontFamily(QWebEngineSettings::SerifFont)).toString()); webSettings->setFontSize(QWebEngineSettings::DefaultFontSize, settings.value("DefaultFontSize", 15).toInt()); webSettings->setFontSize(QWebEngineSettings::DefaultFixedFontSize, settings.value("FixedFontSize", 14).toInt()); webSettings->setFontSize(QWebEngineSettings::MinimumFontSize, settings.value("MinimumFontSize", 3).toInt()); webSettings->setFontSize(QWebEngineSettings::MinimumLogicalFontSize, settings.value("MinimumLogicalFontSize", 5).toInt()); settings.endGroup(); QWebEngineProfile* profile = QWebEngineProfile::defaultProfile(); profile->setPersistentCookiesPolicy(QWebEngineProfile::AllowPersistentCookies); profile->setPersistentStoragePath(DataPaths::currentProfilePath()); QString defaultPath = DataPaths::path(DataPaths::Cache); if (!defaultPath.startsWith(DataPaths::currentProfilePath())) defaultPath.append(QLatin1Char('/') + ProfileManager::currentProfile()); const QString &cachePath = settings.value("Web-Browser-Settings/CachePath", defaultPath).toString(); profile->setCachePath(cachePath); const bool allowCache = settings.value(QSL("Web-Browser-Settings/AllowLocalCache"), true).toBool(); profile->setHttpCacheType(allowCache ? QWebEngineProfile::DiskHttpCache : QWebEngineProfile::MemoryHttpCache); const int cacheSize = settings.value(QSL("Web-Browser-Settings/LocalCacheSize"), 50).toInt() * 1000 * 1000; profile->setHttpCacheMaximumSize(cacheSize); settings.beginGroup(QSL("SpellCheck")); profile->setSpellCheckEnabled(settings.value(QSL("Enabled"), false).toBool()); profile->setSpellCheckLanguages(settings.value(QSL("Languages")).toStringList()); settings.endGroup(); if (isPrivate()) { webSettings->setAttribute(QWebEngineSettings::LocalStorageEnabled, false); history()->setSaving(false); } if (m_downloadManager) { m_downloadManager->loadSettings(); } qzSettings->loadSettings(); networkManager()->loadSettings(); userAgentManager()->loadSettings(); } void MainApplication::loadTheme(const QString &name) { QString activeThemePath; const QStringList themePaths = DataPaths::allPaths(DataPaths::Themes); foreach (const QString &path, themePaths) { const QString theme = QString("%1/%2").arg(path, name); if (QFile::exists(theme + QLatin1String("/main.css"))) { activeThemePath = theme; break; } } if (activeThemePath.isEmpty()) { qWarning() << "Cannot load theme " << name; activeThemePath = QString("%1/%2").arg(DataPaths::path(DataPaths::Themes), DEFAULT_THEME_NAME); } QString qss = QzTools::readAllFileContents(activeThemePath + QLatin1String("/main.css")); #if defined(Q_OS_MACOS) qss.append(QzTools::readAllFileContents(activeThemePath + QLatin1String("/mac.css"))); #elif defined(Q_OS_UNIX) qss.append(QzTools::readAllFileContents(activeThemePath + QLatin1String("/linux.css"))); #elif defined(Q_OS_WIN) || defined(Q_OS_OS2) qss.append(QzTools::readAllFileContents(activeThemePath + QLatin1String("/windows.css"))); #endif if (isRightToLeft()) { qss.append(QzTools::readAllFileContents(activeThemePath + QLatin1String("/rtl.css"))); } qss.append(QzTools::readAllFileContents(DataPaths::currentProfilePath() + QL1S("/userChrome.css"))); QString relativePath = QDir::current().relativeFilePath(activeThemePath); qss.replace(QzRegExp(QSL("url\\s*\\(\\s*([^\\*:\\);]+)\\s*\\)"), Qt::CaseSensitive), QString("url(%1/\\1)").arg(relativePath)); setStyleSheet(qss); } void MainApplication::translateApp() { QString file = Settings().value(QSL("Language/language"), QLocale::system().name()).toString(); // It can only be "C" locale, for which we will use default English language if (file.size() < 2) file.clear(); if (!file.isEmpty() && !file.endsWith(QL1S(".qm"))) file.append(QL1S(".qm")); // Either we load default language (with empty file), or we attempt to load xx.qm (xx_yy.qm) Q_ASSERT(file.isEmpty() || file.size() >= 5); QString translationPath = DataPaths::path(DataPaths::Translations); if (!file.isEmpty()) { const QStringList translationsPaths = DataPaths::allPaths(DataPaths::Translations); foreach (const QString &path, translationsPaths) { // If "xx_yy" translation doesn't exists, try to use "xx*" translation // It can only happen when language is chosen from system locale if (!QFile(QString("%1/%2").arg(path, file)).exists()) { QDir dir(path); QString lang = file.left(2) + QL1S("*.qm"); const QStringList translations = dir.entryList(QStringList(lang)); // If no translation can be found, default English will be used file = translations.isEmpty() ? QString() : translations.at(0); } if (!file.isEmpty() && QFile(QString("%1/%2").arg(path, file)).exists()) { translationPath = path; break; } } } // Load application translation QTranslator* app = new QTranslator(this); app->load(file, translationPath); // Load Qt translation (first try to load from Qt path) QTranslator* sys = new QTranslator(this); sys->load(QL1S("qt_") + file, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); // If there is no translation in Qt path for specified language, try to load it from our path if (sys->isEmpty()) { sys->load(QL1S("qt_") + file, translationPath); } m_languageFile = file; installTranslator(app); installTranslator(sys); } void MainApplication::checkDefaultWebBrowser() { if (isPortable()) { return; } #if defined(Q_OS_WIN) && !defined(Q_OS_OS2) Settings settings; bool checkNow = settings.value("Web-Browser-Settings/CheckDefaultBrowser", DEFAULT_CHECK_DEFAULTBROWSER).toBool(); if (!checkNow) { return; } bool checkAgain = true; if (!associationManager()->isDefaultForAllCapabilities()) { CheckBoxDialog dialog(QMessageBox::Yes | QMessageBox::No, getWindow()); dialog.setDefaultButton(QMessageBox::Yes); dialog.setText(tr("Falkon is not currently your default browser. Would you like to make it your default browser?")); dialog.setCheckBoxText(tr("Always perform this check when starting Falkon.")); dialog.setDefaultCheckState(Qt::Checked); dialog.setWindowTitle(tr("Default Browser")); dialog.setIcon(QMessageBox::Warning); if (dialog.exec() == QMessageBox::Yes) { if (!mApp->associationManager()->showNativeDefaultAppSettingsUi()) mApp->associationManager()->registerAllAssociation(); } checkAgain = dialog.isChecked(); } settings.setValue("Web-Browser-Settings/CheckDefaultBrowser", checkAgain); #endif } void MainApplication::checkOptimizeDatabase() { Settings settings; settings.beginGroup(QSL("Browser")); const int numberOfRuns = settings.value(QSL("RunsWithoutOptimizeDb"), 0).toInt(); settings.setValue(QSL("RunsWithoutOptimizeDb"), numberOfRuns + 1); if (numberOfRuns > 20) { std::cout << "Optimizing database..." << std::endl; IconProvider::instance()->clearOldIconsInDatabase(); settings.setValue(QSL("RunsWithoutOptimizeDb"), 0); } settings.endGroup(); } void MainApplication::setupUserScripts() { // WebChannel for SafeJsWorld QWebEngineScript script; script.setName(QSL("_falkon_webchannel")); script.setInjectionPoint(QWebEngineScript::DocumentCreation); script.setWorldId(WebPage::SafeJsWorld); script.setRunsOnSubFrames(true); script.setSourceCode(Scripts::setupWebChannel(script.worldId())); m_webProfile->scripts()->insert(script); // WebChannel for UnsafeJsWorld QWebEngineScript script2; script2.setName(QSL("_falkon_webchannel2")); script2.setInjectionPoint(QWebEngineScript::DocumentCreation); script2.setWorldId(WebPage::UnsafeJsWorld); script2.setRunsOnSubFrames(true); script2.setSourceCode(Scripts::setupWebChannel(script2.worldId())); m_webProfile->scripts()->insert(script2); - // window.external support + // document.window object addons QWebEngineScript script3; - script3.setName(QSL("_falkon_window_external")); + script3.setName(QSL("_falkon_window_object")); script3.setInjectionPoint(QWebEngineScript::DocumentCreation); script3.setWorldId(WebPage::UnsafeJsWorld); script3.setRunsOnSubFrames(true); - script3.setSourceCode(Scripts::setupWindowExternal()); + script3.setSourceCode(Scripts::setupWindowObject()); m_webProfile->scripts()->insert(script3); } void MainApplication::setUserStyleSheet(const QString &filePath) { QString userCss; #if !defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // Don't grey out selection on losing focus (to prevent graying out found text) QString highlightColor; QString highlightedTextColor; #ifdef Q_OS_MACOS highlightColor = QLatin1String("#b6d6fc"); highlightedTextColor = QLatin1String("#000"); #else QPalette pal = style()->standardPalette(); highlightColor = pal.color(QPalette::Highlight).name(); highlightedTextColor = pal.color(QPalette::HighlightedText).name(); #endif userCss += QString("::selection {background: %1; color: %2;} ").arg(highlightColor, highlightedTextColor); #endif userCss += QzTools::readAllFileContents(filePath).remove(QLatin1Char('\n')); const QString name = QStringLiteral("_falkon_userstylesheet"); QWebEngineScript oldScript = m_webProfile->scripts()->findScript(name); if (!oldScript.isNull()) { m_webProfile->scripts()->remove(oldScript); } if (userCss.isEmpty()) return; QWebEngineScript script; script.setName(name); script.setInjectionPoint(QWebEngineScript::DocumentReady); script.setWorldId(WebPage::SafeJsWorld); script.setRunsOnSubFrames(true); script.setSourceCode(Scripts::setCss(userCss)); m_webProfile->scripts()->insert(script); } void MainApplication::createJumpList() { #ifdef Q_OS_WIN QWinJumpList *jumpList = new QWinJumpList(this); jumpList->clear(); // Frequent QWinJumpListCategory *frequent = jumpList->frequent(); frequent->setVisible(true); const QVector mostList = m_history->mostVisited(7); for (const HistoryEntry &entry : mostList) { frequent->addLink(IconProvider::iconForUrl(entry.url), entry.title, applicationFilePath(), QStringList{entry.url.toEncoded()}); } // Tasks QWinJumpListCategory *tasks = jumpList->tasks(); tasks->setVisible(true); tasks->addLink(IconProvider::newTabIcon(), tr("Open new tab"), applicationFilePath(), {QSL("--new-tab")}); tasks->addLink(IconProvider::newWindowIcon(), tr("Open new window"), applicationFilePath(), {QSL("--new-window")}); tasks->addLink(IconProvider::privateBrowsingIcon(), tr("Open new private window"), applicationFilePath(), {QSL("--private-browsing")}); #endif } void MainApplication::initPulseSupport() { qputenv("PULSE_PROP_OVERRIDE_application.name", "Falkon"); qputenv("PULSE_PROP_OVERRIDE_application.icon_name", "qupzilla"); qputenv("PULSE_PROP_OVERRIDE_media.icon_name", "qupzilla"); } #if defined(Q_OS_WIN) && !defined(Q_OS_OS2) RegisterQAppAssociation* MainApplication::associationManager() { if (!m_registerQAppAssociation) { QString desc = tr("Falkon is a new, fast and secure open-source WWW browser. Falkon is licensed under GPL version 3 or (at your option) any later version. It is based on WebKit core and Qt Framework."); QString fileIconPath = QApplication::applicationFilePath() + ",1"; QString appIconPath = QApplication::applicationFilePath() + ",0"; m_registerQAppAssociation = new RegisterQAppAssociation("Falkon", QApplication::applicationFilePath(), appIconPath, desc, this); m_registerQAppAssociation->addCapability(".html", "Falkon.HTML", "HTML File", fileIconPath, RegisterQAppAssociation::FileAssociation); m_registerQAppAssociation->addCapability(".htm", "Falkon.HTM", "HTM File", fileIconPath, RegisterQAppAssociation::FileAssociation); m_registerQAppAssociation->addCapability("http", "Falkon.HTTP", "URL:HyperText Transfer Protocol", appIconPath, RegisterQAppAssociation::UrlAssociation); m_registerQAppAssociation->addCapability("https", "Falkon.HTTPS", "URL:HyperText Transfer Protocol with Privacy", appIconPath, RegisterQAppAssociation::UrlAssociation); } return m_registerQAppAssociation; } #endif #ifdef Q_OS_MACOS #include bool MainApplication::event(QEvent* e) { switch (e->type()) { case QEvent::FileOpen: { QFileOpenEvent *ev = static_cast(e); if (!ev->url().isEmpty()) { addNewTab(ev->url()); } else if (!ev->file().isEmpty()) { addNewTab(QUrl::fromLocalFile(ev->file())); } else { return false; } return true; } case QEvent::ApplicationActivate: if (!activeWindow() && m_windows.isEmpty()) createWindow(Qz::BW_NewWindow); break; default: break; } return QtSingleApplication::event(e); } #endif diff --git a/src/lib/tools/scripts.cpp b/src/lib/tools/scripts.cpp index d0e99112..b4d9042f 100644 --- a/src/lib/tools/scripts.cpp +++ b/src/lib/tools/scripts.cpp @@ -1,327 +1,330 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2015-2018 David Rosca * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ============================================================ */ #include "scripts.h" #include "qztools.h" #include "webpage.h" #include QString Scripts::setupWebChannel(quint32 worldId) { QString source = QL1S("// ==UserScript==\n" "// %1\n" "// ==/UserScript==\n\n" "(function() {" "%2" "" "function registerExternal(e) {" " window.external = e;" " if (window.external) {" " var event = document.createEvent('Event');" " event.initEvent('_falkon_external_created', true, true);" " window._falkon_external = true;" " document.dispatchEvent(event);" " }" "}" "" "if (self !== top) {" " if (top._falkon_external)" " registerExternal(top.external);" " else" " top.document.addEventListener('_falkon_external_created', function() {" " registerExternal(top.external);" " });" " return;" "}" "" "function registerWebChannel() {" " try {" " new QWebChannel(qt.webChannelTransport, function(channel) {" " var external = channel.objects.qz_object;" " external.extra = {};" " for (var key in channel.objects) {" " if (key != 'qz_object' && key.startsWith('qz_')) {" " external.extra[key.substr(3)] = channel.objects[key];" " }" " }" " registerExternal(external);" " });" " } catch (e) {" " setTimeout(registerWebChannel, 100);" " }" "}" "registerWebChannel();" "" "})()"); QString match; if (worldId == WebPage::SafeJsWorld) { match = QSL("@exclude falkon:*"); } else { match = QSL("@include falkon:*"); } return source.arg(match, QzTools::readAllFileContents(QSL(":/qtwebchannel/qwebchannel.js"))); } QString Scripts::setupFormObserver() { QString source = QL1S("(function() {" "function findUsername(inputs) {" " var usernameNames = ['user', 'name', 'login'];" " for (var i = 0; i < usernameNames.length; ++i) {" " for (var j = 0; j < inputs.length; ++j)" " if (inputs[j].type == 'text' && inputs[j].value.length && inputs[j].name.indexOf(usernameNames[i]) != -1)" " return inputs[j].value;" " }" " for (var i = 0; i < inputs.length; ++i)" " if (inputs[i].type == 'text' && inputs[i].value.length)" " return inputs[i].value;" " for (var i = 0; i < inputs.length; ++i)" " if (inputs[i].type == 'email' && inputs[i].value.length)" " return inputs[i].value;" " return '';" "}" "" "function registerForm(form) {" " form.addEventListener('submit', function() {" " var form = this;" " var data = '';" " var password = '';" " var inputs = form.getElementsByTagName('input');" " for (var i = 0; i < inputs.length; ++i) {" " var input = inputs[i];" " var type = input.type.toLowerCase();" " if (type != 'text' && type != 'password' && type != 'email')" " continue;" " if (!password && type == 'password')" " password = input.value;" " data += encodeURIComponent(input.name);" " data += '=';" " data += encodeURIComponent(input.value);" " data += '&';" " }" " if (!password)" " return;" " data = data.substring(0, data.length - 1);" " var url = window.location.href;" " var username = findUsername(inputs);" " external.autoFill.formSubmitted(url, username, password, data);" " }, true);" "}" "" "if (!document.documentElement) return;" "" "for (var i = 0; i < document.forms.length; ++i)" " registerForm(document.forms[i]);" "" "var observer = new MutationObserver(function(mutations) {" " for (var i = 0; i < mutations.length; ++i)" " for (var j = 0; j < mutations[i].addedNodes.length; ++j)" " if (mutations[i].addedNodes[j].tagName == 'FORM')" " registerForm(mutations[i].addedNodes[j]);" "});" "observer.observe(document.documentElement, { childList: true, subtree: true });" "" "})()"); return source; } -QString Scripts::setupWindowExternal() +QString Scripts::setupWindowObject() { QString source = QL1S("(function() {" "var external = {};" "external.AddSearchProvider = function(url) {" " window.location = 'falkon:AddSearchProvider?url=' + url;" "};" "external.IsSearchProviderInstalled = function(url) {" " console.warn('NOT IMPLEMENTED: IsSearchProviderInstalled()');" " return false;" "};" "window.external = external;" + "window.print = function() {" + " window.location = 'falkon:PrintPage';" + "};" "})()"); return source; } QString Scripts::setCss(const QString &css) { QString source = QL1S("(function() {" "var head = document.getElementsByTagName('head')[0];" "if (!head) return;" "var css = document.createElement('style');" "css.setAttribute('type', 'text/css');" "css.appendChild(document.createTextNode('%1'));" "head.appendChild(css);" "})()"); QString style = css; style.replace(QL1S("'"), QL1S("\\'")); style.replace(QL1S("\n"), QL1S("\\n")); return source.arg(style); } QString Scripts::sendPostData(const QUrl &url, const QByteArray &data) { QString source = QL1S("(function() {" "var form = document.createElement('form');" "form.setAttribute('method', 'POST');" "form.setAttribute('action', '%1');" "var val;" "%2" "form.submit();" "})()"); QString valueSource = QL1S("val = document.createElement('input');" "val.setAttribute('type', 'hidden');" "val.setAttribute('name', '%1');" "val.setAttribute('value', '%2');" "form.appendChild(val);"); QString values; QUrlQuery query(data); const auto &queryItems = query.queryItems(QUrl::FullyDecoded); for (int i = 0; i < queryItems.size(); ++i) { const auto &pair = queryItems[i]; QString value = pair.first; QString key = pair.second; value.replace(QL1S("'"), QL1S("\\'")); key.replace(QL1S("'"), QL1S("\\'")); values.append(valueSource.arg(value, key)); } return source.arg(url.toString(), values); } QString Scripts::completeFormData(const QByteArray &data) { QString source = QL1S("(function() {" "var data = '%1'.split('&');" "var inputs = document.getElementsByTagName('input');" "" "for (var i = 0; i < data.length; ++i) {" " var pair = data[i].split('=');" " if (pair.length != 2)" " continue;" " var key = decodeURIComponent(pair[0]);" " var val = decodeURIComponent(pair[1]);" " for (var j = 0; j < inputs.length; ++j) {" " var input = inputs[j];" " var type = input.type.toLowerCase();" " if (type != 'text' && type != 'password' && type != 'email')" " continue;" " if (input.name == key)" " input.value = val;" " }" "}" "" "})()"); QString d = data; d.replace(QL1S("'"), QL1S("\\'")); return source.arg(d); } QString Scripts::getOpenSearchLinks() { QString source = QL1S("(function() {" "var out = [];" "var links = document.getElementsByTagName('link');" "for (var i = 0; i < links.length; ++i) {" " var e = links[i];" " if (e.type == 'application/opensearchdescription+xml') {" " out.push({" " url: e.href," " title: e.title" " });" " }" "}" "return out;" "})()"); return source; } QString Scripts::getAllImages() { QString source = QL1S("(function() {" "var out = [];" "var imgs = document.getElementsByTagName('img');" "for (var i = 0; i < imgs.length; ++i) {" " var e = imgs[i];" " out.push({" " src: e.src," " alt: e.alt" " });" "}" "return out;" "})()"); return source; } QString Scripts::getAllMetaAttributes() { QString source = QL1S("(function() {" "var out = [];" "var meta = document.getElementsByTagName('meta');" "for (var i = 0; i < meta.length; ++i) {" " var e = meta[i];" " out.push({" " name: e.getAttribute('name')," " content: e.getAttribute('content')," " httpequiv: e.getAttribute('http-equiv')" " });" "}" "return out;" "})()"); return source; } QString Scripts::getFormData(const QPointF &pos) { QString source = QL1S("(function() {" "var e = document.elementFromPoint(%1, %2);" "if (!e || e.tagName != 'INPUT')" " return;" "var fe = e.parentElement;" "while (fe) {" " if (fe.tagName == 'FORM')" " break;" " fe = fe.parentElement;" "}" "if (!fe)" " return;" "var res = {" " method: fe.method.toLowerCase()," " action: fe.action," " inputName: e.name," " inputs: []," "};" "for (var i = 0; i < fe.length; ++i) {" " var input = fe.elements[i];" " res.inputs.push([input.name, input.value]);" "}" "return res;" "})()"); return source.arg(pos.x()).arg(pos.y()); } diff --git a/src/lib/tools/scripts.h b/src/lib/tools/scripts.h index 4965bc72..d3fc6c0d 100644 --- a/src/lib/tools/scripts.h +++ b/src/lib/tools/scripts.h @@ -1,44 +1,44 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2015-2018 David Rosca * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ============================================================ */ #ifndef SCRIPTS_H #define SCRIPTS_H #include #include "qzcommon.h" class QWebEngineView; class FALKON_EXPORT Scripts { public: static QString setupWebChannel(quint32 worldId); static QString setupFormObserver(); - static QString setupWindowExternal(); + static QString setupWindowObject(); static QString setCss(const QString &css); static QString sendPostData(const QUrl &url, const QByteArray &data); static QString completeFormData(const QByteArray &data); static QString getOpenSearchLinks(); static QString getAllImages(); static QString getAllMetaAttributes(); static QString getFormData(const QPointF &pos); }; #endif // SCRIPTS_H diff --git a/src/lib/webengine/webpage.cpp b/src/lib/webengine/webpage.cpp index d6dcfbd9..134e8172 100644 --- a/src/lib/webengine/webpage.cpp +++ b/src/lib/webengine/webpage.cpp @@ -1,656 +1,661 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ============================================================ */ #include "webpage.h" #include "tabbedwebview.h" #include "browserwindow.h" #include "pluginproxy.h" #include "downloadmanager.h" #include "mainapplication.h" #include "checkboxdialog.h" #include "widget.h" #include "qztools.h" #include "speeddial.h" #include "autofill.h" #include "popupwebview.h" #include "popupwindow.h" #include "iconprovider.h" #include "qzsettings.h" #include "useragentmanager.h" #include "delayedfilewatcher.h" #include "searchenginesmanager.h" #include "html5permissions/html5permissionsmanager.h" #include "javascript/externaljsobject.h" #include "tabwidget.h" #include "networkmanager.h" #include "webhittestresult.h" #include "ui_jsconfirm.h" #include "ui_jsalert.h" #include "ui_jsprompt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include QString WebPage::s_lastUploadLocation = QDir::homePath(); QUrl WebPage::s_lastUnsupportedUrl; QTime WebPage::s_lastUnsupportedUrlTime; static const bool kEnableJsOutput = qEnvironmentVariableIsSet("FALKON_ENABLE_JS_OUTPUT"); static const bool kEnableJsNonBlockDialogs = qEnvironmentVariableIsSet("FALKON_ENABLE_JS_NONBLOCK_DIALOGS"); WebPage::WebPage(QObject* parent) : QWebEnginePage(mApp->webProfile(), parent) , m_fileWatcher(0) , m_runningLoop(0) , m_loadProgress(-1) , m_blockAlerts(false) , m_secureStatus(false) { setupWebChannelForUrl(QUrl()); connect(this, &QWebEnginePage::loadProgress, this, &WebPage::progress); connect(this, &QWebEnginePage::loadFinished, this, &WebPage::finished); connect(this, &QWebEnginePage::urlChanged, this, &WebPage::urlChanged); connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebPage::featurePermissionRequested); connect(this, &QWebEnginePage::windowCloseRequested, this, &WebPage::windowCloseRequested); connect(this, &QWebEnginePage::fullScreenRequested, this, &WebPage::fullScreenRequested); connect(this, &QWebEnginePage::renderProcessTerminated, this, &WebPage::renderProcessTerminated); connect(this, &QWebEnginePage::authenticationRequired, this, [this](const QUrl &url, QAuthenticator *auth) { mApp->networkManager()->authentication(url, auth, view()); }); connect(this, &QWebEnginePage::proxyAuthenticationRequired, this, [this](const QUrl &, QAuthenticator *auth, const QString &proxyHost) { mApp->networkManager()->proxyAuthentication(proxyHost, auth, view()); }); // Workaround QWebEnginePage not scrolling to anchors when opened in background tab m_contentsResizedConnection = connect(this, &QWebEnginePage::contentsSizeChanged, this, [this]() { const QString fragment = url().fragment(); if (!fragment.isEmpty()) { const QString src = QSL("var els = document.querySelectorAll(\"[name='%1']\"); if (els.length) els[0].scrollIntoView();"); runJavaScript(src.arg(fragment)); } disconnect(m_contentsResizedConnection); }); } WebPage::~WebPage() { mApp->plugins()->emitWebPageDeleted(this); if (m_runningLoop) { m_runningLoop->exit(1); m_runningLoop = 0; } } WebView *WebPage::view() const { return static_cast(QWebEnginePage::view()); } bool WebPage::execPrintPage(QPrinter *printer, int timeout) { QPointer loop = new QEventLoop; bool result = false; QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit); print(printer, [loop, &result](bool res) { if (loop && loop->isRunning()) { result = res; loop->quit(); } }); loop->exec(); delete loop; return result; } QVariant WebPage::execJavaScript(const QString &scriptSource, quint32 worldId, int timeout) { QPointer loop = new QEventLoop; QVariant result; QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit); runJavaScript(scriptSource, worldId, [loop, &result](const QVariant &res) { if (loop && loop->isRunning()) { result = res; loop->quit(); } }); loop->exec(QEventLoop::ExcludeUserInputEvents); delete loop; return result; } QPointF WebPage::mapToViewport(const QPointF &pos) const { return QPointF(pos.x() / zoomFactor(), pos.y() / zoomFactor()); } WebHitTestResult WebPage::hitTestContent(const QPoint &pos) const { return WebHitTestResult(this, pos); } void WebPage::scroll(int x, int y) { runJavaScript(QSL("window.scrollTo(window.scrollX + %1, window.scrollY + %2)").arg(x).arg(y), SafeJsWorld); } void WebPage::setScrollPosition(const QPointF &pos) { const QPointF v = mapToViewport(pos.toPoint()); runJavaScript(QSL("window.scrollTo(%1, %2)").arg(v.x()).arg(v.y()), SafeJsWorld); } bool WebPage::isRunningLoop() { return m_runningLoop; } bool WebPage::isLoading() const { return m_loadProgress < 100; } void WebPage::urlChanged(const QUrl &url) { Q_UNUSED(url) if (isLoading()) { m_blockAlerts = false; } } void WebPage::progress(int prog) { m_loadProgress = prog; bool secStatus = url().scheme() == QL1S("https"); if (secStatus != m_secureStatus) { m_secureStatus = secStatus; emit privacyChanged(secStatus); } } void WebPage::finished() { progress(100); // File scheme watcher if (url().scheme() == QLatin1String("file")) { QFileInfo info(url().toLocalFile()); if (info.isFile()) { if (!m_fileWatcher) { m_fileWatcher = new DelayedFileWatcher(this); connect(m_fileWatcher, SIGNAL(delayedFileChanged(QString)), this, SLOT(watchedFileChanged(QString))); } const QString filePath = url().toLocalFile(); if (QFile::exists(filePath) && !m_fileWatcher->files().contains(filePath)) { m_fileWatcher->addPath(filePath); } } } else if (m_fileWatcher && !m_fileWatcher->files().isEmpty()) { m_fileWatcher->removePaths(m_fileWatcher->files()); } // AutoFill m_passwordEntries = mApp->autoFill()->completePage(this, url()); } void WebPage::watchedFileChanged(const QString &file) { if (url().toLocalFile() == file) { triggerAction(QWebEnginePage::Reload); } } void WebPage::handleUnknownProtocol(const QUrl &url) { const QString protocol = url.scheme(); if (protocol == QLatin1String("mailto")) { desktopServicesOpen(url); return; } if (qzSettings->blockedProtocols.contains(protocol)) { qDebug() << "WebPage::handleUnknownProtocol Protocol" << protocol << "is blocked!"; return; } if (qzSettings->autoOpenProtocols.contains(protocol)) { desktopServicesOpen(url); return; } CheckBoxDialog dialog(QMessageBox::Yes | QMessageBox::No, view()); dialog.setDefaultButton(QMessageBox::Yes); const QString wrappedUrl = QzTools::alignTextToWidth(url.toString(), "
", dialog.fontMetrics(), 450); const QString text = tr("Falkon cannot handle %1: links. The requested link " "is
  • %2
Do you want Falkon to try " "open this link in system application?").arg(protocol, wrappedUrl); dialog.setText(text); dialog.setCheckBoxText(tr("Remember my choice for this protocol")); dialog.setWindowTitle(tr("External Protocol Request")); dialog.setIcon(QMessageBox::Question); switch (dialog.exec()) { case QMessageBox::Yes: if (dialog.isChecked()) { qzSettings->autoOpenProtocols.append(protocol); qzSettings->saveSettings(); } QDesktopServices::openUrl(url); break; case QMessageBox::No: if (dialog.isChecked()) { qzSettings->blockedProtocols.append(protocol); qzSettings->saveSettings(); } break; default: break; } } void WebPage::desktopServicesOpen(const QUrl &url) { // Open same url only once in 2 secs const int sameUrlTimeout = 2 * 1000; if (s_lastUnsupportedUrl != url || s_lastUnsupportedUrlTime.isNull() || s_lastUnsupportedUrlTime.elapsed() > sameUrlTimeout) { s_lastUnsupportedUrl = url; s_lastUnsupportedUrlTime.restart(); QDesktopServices::openUrl(url); } else { qWarning() << "WebPage::desktopServicesOpen Url" << url << "has already been opened!\n" "Ignoring it to prevent infinite loop!"; } } void WebPage::windowCloseRequested() { if (!view()) return; view()->closeView(); } void WebPage::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) { view()->requestFullScreen(fullScreenRequest.toggleOn()); const bool accepted = fullScreenRequest.toggleOn() == view()->isFullScreen(); if (accepted) fullScreenRequest.accept(); else fullScreenRequest.reject(); } void WebPage::featurePermissionRequested(const QUrl &origin, const QWebEnginePage::Feature &feature) { if (feature == MouseLock && view()->isFullScreen()) setFeaturePermission(origin, feature, PermissionGrantedByUser); else mApp->html5PermissionsManager()->requestPermissions(this, origin, feature); } void WebPage::renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) { Q_UNUSED(exitCode) if (terminationStatus == NormalTerminationStatus) return; QTimer::singleShot(0, this, [this]() { QString page = QzTools::readAllFileContents(":html/tabcrash.html"); page.replace(QL1S("%IMAGE%"), QzTools::pixmapToDataUrl(IconProvider::standardIcon(QStyle::SP_MessageBoxWarning).pixmap(45)).toString()); page.replace(QL1S("%TITLE%"), tr("Failed loading page")); page.replace(QL1S("%HEADING%"), tr("Failed loading page")); page.replace(QL1S("%LI-1%"), tr("Something went wrong while loading this page.")); page.replace(QL1S("%LI-2%"), tr("Try reloading the page or closing some tabs to make more memory available.")); page.replace(QL1S("%RELOAD-PAGE%"), tr("Reload page")); page = QzTools::applyDirectionToPage(page); load(url()); // Workaround for QtWebEngine crash setHtml(page.toUtf8(), url()); }); } void WebPage::setupWebChannelForUrl(const QUrl &url) { QWebChannel *channel = webChannel(); if (!channel) { channel = new QWebChannel(this); ExternalJsObject::setupWebChannel(channel, this); } if (url.scheme() == QL1S("falkon")) { setWebChannel(channel, UnsafeJsWorld); } else { setWebChannel(channel, SafeJsWorld); } } bool WebPage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) { if (!mApp->plugins()->acceptNavigationRequest(this, url, type, isMainFrame)) return false; - if (url.scheme() == QL1S("falkon") && url.path() == QL1S("AddSearchProvider")) { - QUrlQuery query(url); - mApp->searchEnginesManager()->addEngine(QUrl(query.queryItemValue(QSL("url")))); - return false; + if (url.scheme() == QL1S("falkon")) { + if (url.path() == QL1S("AddSearchProvider")) { + QUrlQuery query(url); + mApp->searchEnginesManager()->addEngine(QUrl(query.queryItemValue(QSL("url")))); + return false; + } else if (url.path() == QL1S("PrintPage")) { + emit printRequested(); + return false; + } } const bool result = QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); if (result && isMainFrame) { const bool isWeb = url.scheme() == QL1S("http") || url.scheme() == QL1S("https") || url.scheme() == QL1S("file"); const bool globalJsEnabled = mApp->webSettings()->testAttribute(QWebEngineSettings::JavascriptEnabled); settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, isWeb ? globalJsEnabled : true); setupWebChannelForUrl(url); } return result; } bool WebPage::certificateError(const QWebEngineCertificateError &error) { return mApp->networkManager()->certificateError(error, view()); } QStringList WebPage::chooseFiles(QWebEnginePage::FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes) { Q_UNUSED(acceptedMimeTypes); QStringList files; QString suggestedFileName = s_lastUploadLocation; if (!oldFiles.isEmpty()) suggestedFileName = oldFiles.at(0); switch (mode) { case FileSelectOpen: files = QStringList(QzTools::getOpenFileName("WebPage-ChooseFile", view(), tr("Choose file..."), suggestedFileName)); break; case FileSelectOpenMultiple: files = QzTools::getOpenFileNames("WebPage-ChooseFile", view(), tr("Choose files..."), suggestedFileName); break; default: files = QWebEnginePage::chooseFiles(mode, oldFiles, acceptedMimeTypes); break; } if (!files.isEmpty()) s_lastUploadLocation = files.at(0); return files; } bool WebPage::hasMultipleUsernames() const { return m_passwordEntries.count() > 1; } QVector WebPage::autoFillData() const { return m_passwordEntries; } bool WebPage::javaScriptPrompt(const QUrl &securityOrigin, const QString &msg, const QString &defaultValue, QString* result) { if (!kEnableJsNonBlockDialogs) { return QWebEnginePage::javaScriptPrompt(securityOrigin, msg, defaultValue, result); } if (m_runningLoop) { return false; } ResizableFrame* widget = new ResizableFrame(view()->overlayWidget()); widget->setObjectName("jsFrame"); Ui_jsPrompt* ui = new Ui_jsPrompt(); ui->setupUi(widget); ui->message->setText(msg); ui->lineEdit->setText(defaultValue); ui->lineEdit->setFocus(); widget->resize(view()->size()); widget->show(); connect(view(), SIGNAL(viewportResized(QSize)), widget, SLOT(slotResize(QSize))); connect(ui->lineEdit, SIGNAL(returnPressed()), ui->buttonBox->button(QDialogButtonBox::Ok), SLOT(animateClick())); QEventLoop eLoop; m_runningLoop = &eLoop; connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), &eLoop, SLOT(quit())); if (eLoop.exec() == 1) { return result; } m_runningLoop = 0; QString x = ui->lineEdit->text(); bool _result = ui->buttonBox->clickedButtonRole() == QDialogButtonBox::AcceptRole; *result = x; delete widget; view()->setFocus(); return _result; } bool WebPage::javaScriptConfirm(const QUrl &securityOrigin, const QString &msg) { if (!kEnableJsNonBlockDialogs) { return QWebEnginePage::javaScriptConfirm(securityOrigin, msg); } if (m_runningLoop) { return false; } ResizableFrame* widget = new ResizableFrame(view()->overlayWidget()); widget->setObjectName("jsFrame"); Ui_jsConfirm* ui = new Ui_jsConfirm(); ui->setupUi(widget); ui->message->setText(msg); ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); widget->resize(view()->size()); widget->show(); connect(view(), SIGNAL(viewportResized(QSize)), widget, SLOT(slotResize(QSize))); QEventLoop eLoop; m_runningLoop = &eLoop; connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), &eLoop, SLOT(quit())); if (eLoop.exec() == 1) { return false; } m_runningLoop = 0; bool result = ui->buttonBox->clickedButtonRole() == QDialogButtonBox::AcceptRole; delete widget; view()->setFocus(); return result; } void WebPage::javaScriptAlert(const QUrl &securityOrigin, const QString &msg) { Q_UNUSED(securityOrigin) if (m_blockAlerts || m_runningLoop) { return; } if (!kEnableJsNonBlockDialogs) { QString title = tr("JavaScript alert"); if (!url().host().isEmpty()) { title.append(QString(" - %1").arg(url().host())); } CheckBoxDialog dialog(QMessageBox::Ok, view()); dialog.setDefaultButton(QMessageBox::Ok); dialog.setWindowTitle(title); dialog.setText(msg); dialog.setCheckBoxText(tr("Prevent this page from creating additional dialogs")); dialog.setIcon(QMessageBox::Information); dialog.exec(); m_blockAlerts = dialog.isChecked(); return; } ResizableFrame* widget = new ResizableFrame(view()->overlayWidget()); widget->setObjectName("jsFrame"); Ui_jsAlert* ui = new Ui_jsAlert(); ui->setupUi(widget); ui->message->setText(msg); ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); widget->resize(view()->size()); widget->show(); connect(view(), SIGNAL(viewportResized(QSize)), widget, SLOT(slotResize(QSize))); QEventLoop eLoop; m_runningLoop = &eLoop; connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), &eLoop, SLOT(quit())); if (eLoop.exec() == 1) { return; } m_runningLoop = 0; m_blockAlerts = ui->preventAlerts->isChecked(); delete widget; view()->setFocus(); } void WebPage::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID) { if (!kEnableJsOutput) { return; } switch (level) { case InfoMessageLevel: std::cout << "[I] "; break; case WarningMessageLevel: std::cout << "[W] "; break; case ErrorMessageLevel: std::cout << "[E] "; break; } std::cout << qPrintable(sourceID) << ":" << lineNumber << " " << qPrintable(message); } QWebEnginePage* WebPage::createWindow(QWebEnginePage::WebWindowType type) { TabbedWebView *tView = qobject_cast(view()); BrowserWindow *window = tView ? tView->browserWindow() : mApp->getWindow(); auto createTab = [=](Qz::NewTabPositionFlags pos) { int index = window->tabWidget()->addView(QUrl(), pos); TabbedWebView* view = window->weView(index); view->setPage(new WebPage); // Workaround focus issue when creating tab if (pos.testFlag(Qz::NT_SelectedTab)) { QPointer pview = view; QTimer::singleShot(0, this, [pview]() { if (pview && pview->webTab()->isCurrentTab()) { pview->setFocus(); } }); } return view->page(); }; switch (type) { case QWebEnginePage::WebBrowserWindow: { BrowserWindow *window = mApp->createWindow(Qz::BW_NewWindow); WebPage *page = new WebPage; window->setStartPage(page); return page; } case QWebEnginePage::WebDialog: if (!qzSettings->openPopupsInTabs) { PopupWebView* view = new PopupWebView; view->setPage(new WebPage); PopupWindow* popup = new PopupWindow(view); popup->show(); window->addDeleteOnCloseWidget(popup); return view->page(); } // else fallthrough case QWebEnginePage::WebBrowserTab: return createTab(Qz::NT_CleanSelectedTab); case QWebEnginePage::WebBrowserBackgroundTab: return createTab(Qz::NT_CleanNotSelectedTab); default: break; } return Q_NULLPTR; } diff --git a/src/lib/webengine/webpage.h b/src/lib/webengine/webpage.h index 62574001..deb5f0f1 100644 --- a/src/lib/webengine/webpage.h +++ b/src/lib/webengine/webpage.h @@ -1,115 +1,116 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ============================================================ */ #ifndef WEBPAGE_H #define WEBPAGE_H #include #include #include #include #include "qzcommon.h" #include "passwordmanager.h" class QEventLoop; class QWebEngineDownloadItem; class WebView; class WebHitTestResult; class DelayedFileWatcher; class FALKON_EXPORT WebPage : public QWebEnginePage { Q_OBJECT public: enum JsWorld { UnsafeJsWorld = QWebEngineScript::MainWorld, SafeJsWorld = QWebEngineScript::ApplicationWorld }; explicit WebPage(QObject* parent = 0); ~WebPage(); WebView *view() const; bool execPrintPage(QPrinter *printer, int timeout = 1000); QVariant execJavaScript(const QString &scriptSource, quint32 worldId = UnsafeJsWorld, int timeout = 500); QPointF mapToViewport(const QPointF &pos) const; WebHitTestResult hitTestContent(const QPoint &pos) const; void scroll(int x, int y); void setScrollPosition(const QPointF &pos); bool javaScriptPrompt(const QUrl &securityOrigin, const QString &msg, const QString &defaultValue, QString* result) Q_DECL_OVERRIDE; bool javaScriptConfirm(const QUrl &securityOrigin, const QString &msg) Q_DECL_OVERRIDE; void javaScriptAlert(const QUrl &securityOrigin, const QString &msg) Q_DECL_OVERRIDE; void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID) override; bool hasMultipleUsernames() const; QVector autoFillData() const; bool isRunningLoop(); bool isLoading() const; signals: void privacyChanged(bool status); + void printRequested(); protected slots: void progress(int prog); void finished(); private slots: void urlChanged(const QUrl &url); void watchedFileChanged(const QString &file); void windowCloseRequested(); void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); void featurePermissionRequested(const QUrl &origin, const QWebEnginePage::Feature &feature); void renderProcessTerminated(RenderProcessTerminationStatus terminationStatus, int exitCode); private: void setupWebChannelForUrl(const QUrl &url); bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) Q_DECL_OVERRIDE; bool certificateError(const QWebEngineCertificateError &error) Q_DECL_OVERRIDE; QStringList chooseFiles(FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes) Q_DECL_OVERRIDE; QWebEnginePage* createWindow(QWebEnginePage::WebWindowType type) Q_DECL_OVERRIDE; void handleUnknownProtocol(const QUrl &url); void desktopServicesOpen(const QUrl &url); static QString s_lastUploadLocation; static QUrl s_lastUnsupportedUrl; static QTime s_lastUnsupportedUrlTime; DelayedFileWatcher* m_fileWatcher; QEventLoop* m_runningLoop; QVector m_passwordEntries; int m_loadProgress; bool m_blockAlerts; bool m_secureStatus; QMetaObject::Connection m_contentsResizedConnection; friend class WebView; }; #endif // WEBPAGE_H diff --git a/src/lib/webengine/webview.cpp b/src/lib/webengine/webview.cpp index 71f8541b..82681582 100644 --- a/src/lib/webengine/webview.cpp +++ b/src/lib/webengine/webview.cpp @@ -1,1366 +1,1367 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ============================================================ */ #include "webview.h" #include "webpage.h" #include "mainapplication.h" #include "qztools.h" #include "iconprovider.h" #include "history.h" #include "pluginproxy.h" #include "downloadmanager.h" #include "siteinfo.h" #include "searchenginesmanager.h" #include "browsinglibrary.h" #include "bookmarkstools.h" #include "settings.h" #include "qzsettings.h" #include "enhancedmenu.h" #include "locationbar.h" #include "webinspector.h" #include "scripts.h" #include "webhittestresult.h" #include "webscrollbarmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include bool WebView::s_forceContextMenuOnMouseRelease = false; WebView::WebView(QWidget* parent) : QWebEngineView(parent) , m_progress(100) , m_backgroundActivity(false) , m_page(0) , m_firstLoad(false) { connect(this, &QWebEngineView::loadStarted, this, &WebView::slotLoadStarted); connect(this, &QWebEngineView::loadProgress, this, &WebView::slotLoadProgress); connect(this, &QWebEngineView::loadFinished, this, &WebView::slotLoadFinished); connect(this, &QWebEngineView::iconChanged, this, &WebView::slotIconChanged); connect(this, &QWebEngineView::urlChanged, this, &WebView::slotUrlChanged); connect(this, &QWebEngineView::titleChanged, this, &WebView::slotTitleChanged); m_currentZoomLevel = zoomLevels().indexOf(100); setAcceptDrops(true); installEventFilter(this); if (parentWidget()) { parentWidget()->installEventFilter(this); } WebInspector::registerView(this); // Hack to find widget that receives input events QStackedLayout *l = qobject_cast(layout()); connect(l, &QStackedLayout::currentChanged, this, [this]() { QTimer::singleShot(0, this, [this]() { m_rwhvqt = focusProxy(); if (!m_rwhvqt) { qCritical() << "Focus proxy is null!"; return; } m_rwhvqt->installEventFilter(this); }); }); } WebView::~WebView() { WebInspector::unregisterView(this); WebScrollBarManager::instance()->removeWebView(this); } QIcon WebView::icon(bool allowNull) const { if (!QWebEngineView::icon().isNull()) { return QWebEngineView::icon(); } if (url().scheme() == QLatin1String("ftp")) { return IconProvider::standardIcon(QStyle::SP_ComputerIcon); } if (url().scheme() == QLatin1String("file")) { return IconProvider::standardIcon(QStyle::SP_DriveHDIcon); } return IconProvider::iconForUrl(url(), allowNull); } QString WebView::title(bool allowEmpty) const { QString title = QWebEngineView::title(); if (allowEmpty) { return title; } if (title.isEmpty()) { title = url().toString(QUrl::RemoveFragment); } if (title.isEmpty() || title == QL1S("about:blank")) { return tr("Empty Page"); } return title; } WebPage* WebView::page() const { return m_page; } void WebView::setPage(WebPage *page) { if (m_page == page) { return; } if (m_page) { m_page->setView(nullptr); m_page->deleteLater(); } m_page = page; m_page->setParent(this); QWebEngineView::setPage(m_page); - connect(m_page, SIGNAL(privacyChanged(bool)), this, SIGNAL(privacyChanged(bool))); + connect(m_page, &WebPage::privacyChanged, this, &WebView::privacyChanged); + connect(m_page, &WebPage::printRequested, this, &WebView::printPage); // Set default zoom level zoomReset(); // Actions needs to be initialized for every QWebEnginePage change initializeActions(); // Scrollbars must be added only after QWebEnginePage is set WebScrollBarManager::instance()->addWebView(this); emit pageChanged(m_page); mApp->plugins()->emitWebPageCreated(m_page); } void WebView::load(const QUrl &url) { if (m_page && !m_page->acceptNavigationRequest(url, QWebEnginePage::NavigationTypeTyped, true)) { return; } QWebEngineView::load(url); if (!m_firstLoad) { m_firstLoad = true; WebInspector::pushView(this); } } void WebView::load(const LoadRequest &request) { const QUrl reqUrl = request.url(); if (reqUrl.isEmpty()) return; if (reqUrl.scheme() == QL1S("javascript")) { const QString scriptSource = reqUrl.toString().mid(11); // Is the javascript source percent encoded or not? // Looking for % character in source should work in most cases if (scriptSource.contains(QL1C('%'))) page()->runJavaScript(QUrl::fromPercentEncoding(scriptSource.toUtf8())); else page()->runJavaScript(scriptSource); return; } if (isUrlValid(reqUrl)) { loadRequest(request); return; } // Make sure to correctly load hosts like localhost (eg. without the dot) if (!reqUrl.isEmpty() && reqUrl.scheme().isEmpty() && !QzTools::containsSpace(reqUrl.path()) && // See #1622 !reqUrl.path().contains(QL1C('.')) ) { QUrl u(QSL("http://") + reqUrl.path()); if (u.isValid()) { // This is blocking... QHostInfo info = QHostInfo::fromName(u.path()); if (info.error() == QHostInfo::NoError) { LoadRequest req = request; req.setUrl(u); loadRequest(req); return; } } } if (qzSettings->searchFromAddressBar) { const LoadRequest searchRequest = mApp->searchEnginesManager()->searchResult(request.urlString()); loadRequest(searchRequest); } } bool WebView::isLoading() const { return m_progress < 100; } int WebView::loadingProgress() const { return m_progress; } bool WebView::backgroundActivity() const { return m_backgroundActivity; } int WebView::zoomLevel() const { return m_currentZoomLevel; } void WebView::setZoomLevel(int level) { m_currentZoomLevel = level; applyZoom(); } QPointF WebView::mapToViewport(const QPointF &pos) const { return page()->mapToViewport(pos); } QRect WebView::scrollBarGeometry(Qt::Orientation orientation) const { QScrollBar *s = WebScrollBarManager::instance()->scrollBar(orientation, const_cast(this)); return s && s->isVisible() ? s->geometry() : QRect(); } QWidget *WebView::inputWidget() const { return m_rwhvqt ? m_rwhvqt : const_cast(this); } // static bool WebView::isUrlValid(const QUrl &url) { // Valid url must have scheme and actually contains something (therefore scheme:// is invalid) return url.isValid() && !url.scheme().isEmpty() && (!url.host().isEmpty() || !url.path().isEmpty() || url.hasQuery()); } // static QList WebView::zoomLevels() { return QList() << 30 << 40 << 50 << 67 << 80 << 90 << 100 << 110 << 120 << 133 << 150 << 170 << 200 << 220 << 233 << 250 << 270 << 285 << 300; } // static bool WebView::forceContextMenuOnMouseRelease() { return s_forceContextMenuOnMouseRelease; } // static void WebView::setForceContextMenuOnMouseRelease(bool force) { s_forceContextMenuOnMouseRelease = force; } void WebView::addNotification(QWidget* notif) { emit showNotification(notif); } void WebView::applyZoom() { setZoomFactor(qreal(zoomLevels().at(m_currentZoomLevel)) / 100.0); emit zoomLevelChanged(m_currentZoomLevel); } void WebView::zoomIn() { if (m_currentZoomLevel < zoomLevels().count() - 1) { m_currentZoomLevel++; applyZoom(); } } void WebView::zoomOut() { if (m_currentZoomLevel > 0) { m_currentZoomLevel--; applyZoom(); } } void WebView::zoomReset() { if (m_currentZoomLevel != qzSettings->defaultZoomLevel) { m_currentZoomLevel = qzSettings->defaultZoomLevel; applyZoom(); } } void WebView::editUndo() { triggerPageAction(QWebEnginePage::Undo); } void WebView::editRedo() { triggerPageAction(QWebEnginePage::Redo); } void WebView::editCut() { triggerPageAction(QWebEnginePage::Cut); } void WebView::editCopy() { triggerPageAction(QWebEnginePage::Copy); } void WebView::editPaste() { triggerPageAction(QWebEnginePage::Paste); } void WebView::editSelectAll() { triggerPageAction(QWebEnginePage::SelectAll); } void WebView::editDelete() { QKeyEvent ev(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier); QApplication::sendEvent(this, &ev); } void WebView::reloadBypassCache() { triggerPageAction(QWebEnginePage::ReloadAndBypassCache); } void WebView::back() { QWebEngineHistory* history = page()->history(); if (history->canGoBack()) { history->back(); emit urlChanged(url()); } } void WebView::forward() { QWebEngineHistory* history = page()->history(); if (history->canGoForward()) { history->forward(); emit urlChanged(url()); } } void WebView::printPage() { Q_ASSERT(m_page); QPrintDialog* dialog = new QPrintDialog(this); dialog->setOptions(QAbstractPrintDialog::PrintToFile | QAbstractPrintDialog::PrintShowPageSize); #ifndef Q_OS_WIN dialog->setOption(QAbstractPrintDialog::PrintPageRange); dialog->setOption(QAbstractPrintDialog::PrintCollateCopies); #endif dialog->printer()->setCreator(tr("Falkon %1 (%2)").arg(Qz::VERSION, Qz::WWWADDRESS)); dialog->printer()->setDocName(QzTools::getFileNameFromUrl(url())); if (dialog->exec() == QDialog::Accepted) { if (dialog->printer()->outputFormat() == QPrinter::PdfFormat) { m_page->printToPdf(dialog->printer()->outputFileName(), dialog->printer()->pageLayout()); delete dialog; } else { m_page->print(dialog->printer(), [=](bool success) { Q_UNUSED(success); delete dialog; }); } } } void WebView::slotLoadStarted() { m_progress = 0; } void WebView::slotLoadProgress(int progress) { if (m_progress < 100) { m_progress = progress; } // QtWebEngine sometimes forgets applied zoom factor if (!qFuzzyCompare(zoomFactor(), zoomLevels().at(m_currentZoomLevel) / 100.0)) { applyZoom(); } } void WebView::slotLoadFinished(bool ok) { m_progress = 100; if (ok) mApp->history()->addHistoryEntry(this); } void WebView::slotIconChanged() { IconProvider::instance()->saveIcon(this); } void WebView::slotUrlChanged(const QUrl &url) { Q_UNUSED(url) // Don't save blank page / speed dial in tab history if (!history()->canGoForward() && history()->backItems(1).size() == 1) { const QString s = LocationBar::convertUrlToText(history()->backItem().url()); if (s.isEmpty()) history()->clear(); } } void WebView::slotTitleChanged(const QString &title) { Q_UNUSED(title) if (!isVisible() && !isLoading() && !m_backgroundActivity) { m_backgroundActivity = true; emit backgroundActivityChanged(m_backgroundActivity); } } void WebView::openUrlInNewWindow() { if (QAction* action = qobject_cast(sender())) { mApp->createWindow(Qz::BW_NewWindow, action->data().toUrl()); } } void WebView::sendTextByMail() { if (QAction* action = qobject_cast(sender())) { const QUrl mailUrl = QUrl::fromEncoded("mailto:%20?body=" + QUrl::toPercentEncoding(action->data().toString())); QDesktopServices::openUrl(mailUrl); } } void WebView::sendPageByMail() { const QUrl mailUrl = QUrl::fromEncoded("mailto:%20?body=" + QUrl::toPercentEncoding(url().toEncoded()) + "&subject=" + QUrl::toPercentEncoding(title())); QDesktopServices::openUrl(mailUrl); } void WebView::copyLinkToClipboard() { if (QAction* action = qobject_cast(sender())) { QApplication::clipboard()->setText(action->data().toUrl().toEncoded()); } } void WebView::savePageAs() { triggerPageAction(QWebEnginePage::SavePage); } void WebView::copyImageToClipboard() { triggerPageAction(QWebEnginePage::CopyImageToClipboard); } void WebView::downloadLinkToDisk() { triggerPageAction(QWebEnginePage::DownloadLinkToDisk); } void WebView::downloadImageToDisk() { triggerPageAction(QWebEnginePage::DownloadImageToDisk); } void WebView::downloadMediaToDisk() { triggerPageAction(QWebEnginePage::DownloadMediaToDisk); } void WebView::openUrlInNewTab(const QUrl &url, Qz::NewTabPositionFlags position) { loadInNewTab(url, position); } void WebView::openActionUrl() { if (QAction* action = qobject_cast(sender())) { load(action->data().toUrl()); } } void WebView::showSource() { // view-source: doesn't work on itself and custom schemes if (url().scheme() == QL1S("view-source") || url().scheme() == QL1S("falkon") || url().scheme() == QL1S("qrc")) { page()->toHtml([](const QString &html) { std::cout << html.toLocal8Bit().constData() << std::endl; }); return; } triggerPageAction(QWebEnginePage::ViewSource); } void WebView::showSiteInfo() { SiteInfo* s = new SiteInfo(this); s->show(); } void WebView::searchSelectedText() { SearchEngine engine = mApp->searchEnginesManager()->defaultEngine(); if (QAction* act = qobject_cast(sender())) { if (act->data().isValid()) { engine = act->data().value(); } } const LoadRequest req = mApp->searchEnginesManager()->searchResult(engine, selectedText()); loadInNewTab(req, Qz::NT_SelectedTab); } void WebView::searchSelectedTextInBackgroundTab() { SearchEngine engine = mApp->searchEnginesManager()->defaultEngine(); if (QAction* act = qobject_cast(sender())) { if (act->data().isValid()) { engine = act->data().value(); } } const LoadRequest req = mApp->searchEnginesManager()->searchResult(engine, selectedText()); loadInNewTab(req, Qz::NT_NotSelectedTab); } void WebView::bookmarkLink() { if (QAction* action = qobject_cast(sender())) { if (action->data().isNull()) { BookmarksTools::addBookmarkDialog(this, url(), title()); } else { const QVariantList bData = action->data().value(); const QString bookmarkTitle = bData.at(1).toString().isEmpty() ? title() : bData.at(1).toString(); BookmarksTools::addBookmarkDialog(this, bData.at(0).toUrl(), bookmarkTitle); } } } void WebView::openUrlInSelectedTab() { if (QAction* action = qobject_cast(sender())) { openUrlInNewTab(action->data().toUrl(), Qz::NT_CleanSelectedTab); } } void WebView::openUrlInBackgroundTab() { if (QAction* action = qobject_cast(sender())) { openUrlInNewTab(action->data().toUrl(), Qz::NT_CleanNotSelectedTab); } } void WebView::userDefinedOpenUrlInNewTab(const QUrl &url, bool invert) { Qz::NewTabPositionFlags position = qzSettings->newTabPosition; if (invert) { if (position & Qz::NT_SelectedTab) { position &= ~Qz::NT_SelectedTab; position |= Qz::NT_NotSelectedTab; } else { position &= ~Qz::NT_NotSelectedTab; position |= Qz::NT_SelectedTab; } } QUrl actionUrl; if (!url.isEmpty()) { actionUrl = url; } else if (QAction* action = qobject_cast(sender())) { actionUrl = action->data().toUrl(); } openUrlInNewTab(actionUrl, position); } void WebView::userDefinedOpenUrlInBgTab(const QUrl &url) { QUrl actionUrl; if (!url.isEmpty()) { actionUrl = url; } else if (QAction* action = qobject_cast(sender())) { actionUrl = action->data().toUrl(); } userDefinedOpenUrlInNewTab(actionUrl, true); } void WebView::showEvent(QShowEvent *event) { QWebEngineView::showEvent(event); if (m_backgroundActivity) { m_backgroundActivity = false; emit backgroundActivityChanged(m_backgroundActivity); } } void WebView::createContextMenu(QMenu *menu, WebHitTestResult &hitTest) { // cppcheck-suppress variableScope int spellCheckActionCount = 0; const QWebEngineContextMenuData &contextMenuData = page()->contextMenuData(); hitTest.updateWithContextMenuData(contextMenuData); if (!contextMenuData.misspelledWord().isEmpty()) { QFont boldFont = menu->font(); boldFont.setBold(true); for (const QString &suggestion : contextMenuData.spellCheckerSuggestions()) { QAction *action = menu->addAction(suggestion); action->setFont(boldFont); connect(action, &QAction::triggered, this, [=]() { page()->replaceMisspelledWord(suggestion); }); } if (menu->actions().isEmpty()) { menu->addAction(tr("No suggestions"))->setEnabled(false); } menu->addSeparator(); spellCheckActionCount = menu->actions().count(); } if (!hitTest.linkUrl().isEmpty() && hitTest.linkUrl().scheme() != QL1S("javascript")) { createLinkContextMenu(menu, hitTest); } if (!hitTest.imageUrl().isEmpty()) { createImageContextMenu(menu, hitTest); } if (!hitTest.mediaUrl().isEmpty()) { createMediaContextMenu(menu, hitTest); } if (hitTest.isContentEditable()) { // This only checks if the menu is empty (only spellchecker actions added) if (menu->actions().count() == spellCheckActionCount) { menu->addAction(pageAction(QWebEnginePage::Undo)); menu->addAction(pageAction(QWebEnginePage::Redo)); menu->addSeparator(); menu->addAction(pageAction(QWebEnginePage::Cut)); menu->addAction(pageAction(QWebEnginePage::Copy)); menu->addAction(pageAction(QWebEnginePage::Paste)); } if (hitTest.tagName() == QL1S("input")) { QAction *act = menu->addAction(QString()); act->setVisible(false); checkForForm(act, hitTest.pos()); } } if (!selectedText().isEmpty()) { createSelectedTextContextMenu(menu, hitTest); } if (menu->isEmpty()) { createPageContextMenu(menu); } menu->addSeparator(); mApp->plugins()->populateWebViewMenu(menu, this, hitTest); } void WebView::createPageContextMenu(QMenu* menu) { QAction* action = menu->addAction(tr("&Back"), this, SLOT(back())); action->setIcon(IconProvider::standardIcon(QStyle::SP_ArrowBack)); action->setEnabled(history()->canGoBack()); action = menu->addAction(tr("&Forward"), this, SLOT(forward())); action->setIcon(IconProvider::standardIcon(QStyle::SP_ArrowForward)); action->setEnabled(history()->canGoForward()); // Special menu for Speed Dial page if (url().toString() == QL1S("falkon:speeddial")) { menu->addSeparator(); menu->addAction(QIcon::fromTheme("list-add"), tr("&Add New Page"), this, SLOT(addSpeedDial())); menu->addAction(IconProvider::settingsIcon(), tr("&Configure Speed Dial"), this, SLOT(configureSpeedDial())); menu->addSeparator(); menu->addAction(QIcon::fromTheme(QSL("view-refresh")), tr("Reload All Dials"), this, SLOT(reloadAllSpeedDials())); return; } QAction *reloadAction = pageAction(QWebEnginePage::Reload); action = menu->addAction(reloadAction->icon(), reloadAction->text(), reloadAction, &QAction::trigger); action->setVisible(reloadAction->isEnabled()); connect(reloadAction, &QAction::changed, action, [=]() { action->setVisible(reloadAction->isEnabled()); }); QAction *stopAction = pageAction(QWebEnginePage::Stop); action = menu->addAction(stopAction->icon(), stopAction->text(), stopAction, &QAction::trigger); action->setVisible(stopAction->isEnabled()); connect(stopAction, &QAction::changed, action, [=]() { action->setVisible(stopAction->isEnabled()); }); menu->addSeparator(); menu->addAction(QIcon::fromTheme("bookmark-new"), tr("Book&mark page"), this, SLOT(bookmarkLink())); menu->addAction(QIcon::fromTheme("document-save"), tr("&Save page as..."), this, SLOT(savePageAs())); menu->addAction(QIcon::fromTheme("edit-copy"), tr("&Copy page link"), this, SLOT(copyLinkToClipboard()))->setData(url()); menu->addAction(QIcon::fromTheme("mail-message-new"), tr("Send page link..."), this, SLOT(sendPageByMail())); menu->addSeparator(); menu->addAction(QIcon::fromTheme("edit-select-all"), tr("Select &all"), this, SLOT(editSelectAll())); menu->addSeparator(); if (url().scheme() == QLatin1String("http") || url().scheme() == QLatin1String("https")) { const QUrl w3url = QUrl::fromEncoded("http://validator.w3.org/check?uri=" + QUrl::toPercentEncoding(url().toEncoded())); menu->addAction(QIcon(":icons/sites/w3.png"), tr("Validate page"), this, SLOT(openUrlInSelectedTab()))->setData(w3url); QByteArray langCode = mApp->currentLanguage().left(2).toUtf8(); const QUrl gturl = QUrl::fromEncoded("http://translate.google.com/translate?sl=auto&tl=" + langCode + "&u=" + QUrl::toPercentEncoding(url().toEncoded())); menu->addAction(QIcon(":icons/sites/translate.png"), tr("Translate page"), this, SLOT(openUrlInSelectedTab()))->setData(gturl); } menu->addSeparator(); menu->addAction(QIcon::fromTheme("text-html"), tr("Show so&urce code"), this, SLOT(showSource())); if (SiteInfo::canShowSiteInfo(url())) menu->addAction(QIcon::fromTheme("dialog-information"), tr("Show info ab&out site"), this, SLOT(showSiteInfo())); } void WebView::createLinkContextMenu(QMenu* menu, const WebHitTestResult &hitTest) { menu->addSeparator(); Action* act = new Action(IconProvider::newTabIcon(), tr("Open link in new &tab")); act->setData(hitTest.linkUrl()); connect(act, SIGNAL(triggered()), this, SLOT(userDefinedOpenUrlInNewTab())); connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInBgTab())); menu->addAction(act); menu->addAction(IconProvider::newWindowIcon(), tr("Open link in new &window"), this, SLOT(openUrlInNewWindow()))->setData(hitTest.linkUrl()); menu->addAction(IconProvider::privateBrowsingIcon(), tr("Open link in &private window"), mApp, SLOT(startPrivateBrowsing()))->setData(hitTest.linkUrl()); menu->addSeparator(); QVariantList bData; bData << hitTest.linkUrl() << hitTest.linkTitle(); menu->addAction(QIcon::fromTheme("bookmark-new"), tr("B&ookmark link"), this, SLOT(bookmarkLink()))->setData(bData); menu->addAction(QIcon::fromTheme("document-save"), tr("&Save link as..."), this, SLOT(downloadLinkToDisk())); menu->addAction(QIcon::fromTheme("mail-message-new"), tr("Send link..."), this, SLOT(sendTextByMail()))->setData(hitTest.linkUrl().toEncoded()); menu->addAction(QIcon::fromTheme("edit-copy"), tr("&Copy link address"), this, SLOT(copyLinkToClipboard()))->setData(hitTest.linkUrl()); menu->addSeparator(); if (!selectedText().isEmpty()) { pageAction(QWebEnginePage::Copy)->setIcon(QIcon::fromTheme("edit-copy")); menu->addAction(pageAction(QWebEnginePage::Copy)); } } void WebView::createImageContextMenu(QMenu* menu, const WebHitTestResult &hitTest) { menu->addSeparator(); Action* act = new Action(tr("Show i&mage")); act->setData(hitTest.imageUrl()); connect(act, SIGNAL(triggered()), this, SLOT(openActionUrl())); connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInNewTab())); menu->addAction(act); menu->addAction(tr("Copy image"), this, SLOT(copyImageToClipboard())); menu->addAction(QIcon::fromTheme("edit-copy"), tr("Copy image ad&dress"), this, SLOT(copyLinkToClipboard()))->setData(hitTest.imageUrl()); menu->addSeparator(); menu->addAction(QIcon::fromTheme("document-save"), tr("&Save image as..."), this, SLOT(downloadImageToDisk())); menu->addAction(QIcon::fromTheme("mail-message-new"), tr("Send image..."), this, SLOT(sendTextByMail()))->setData(hitTest.imageUrl().toEncoded()); menu->addSeparator(); if (!selectedText().isEmpty()) { pageAction(QWebEnginePage::Copy)->setIcon(QIcon::fromTheme("edit-copy")); menu->addAction(pageAction(QWebEnginePage::Copy)); } } void WebView::createSelectedTextContextMenu(QMenu* menu, const WebHitTestResult &hitTest) { Q_UNUSED(hitTest) QString selectedText = page()->selectedText(); menu->addSeparator(); if (!menu->actions().contains(pageAction(QWebEnginePage::Copy))) { menu->addAction(pageAction(QWebEnginePage::Copy)); } menu->addAction(QIcon::fromTheme("mail-message-new"), tr("Send text..."), this, SLOT(sendTextByMail()))->setData(selectedText); menu->addSeparator(); QString langCode = mApp->currentLanguage().left(2).toUtf8(); QUrl googleTranslateUrl = QUrl(QString("https://translate.google.com/#auto/%1/%2").arg(langCode, selectedText)); Action* gtwact = new Action(QIcon(":icons/sites/translate.png"), tr("Google Translate")); gtwact->setData(googleTranslateUrl); connect(gtwact, SIGNAL(triggered()), this, SLOT(openUrlInSelectedTab())); connect(gtwact, SIGNAL(ctrlTriggered()), this, SLOT(openUrlInBackgroundTab())); menu->addAction(gtwact); Action* dictact = new Action(QIcon::fromTheme("accessories-dictionary"), tr("Dictionary")); dictact->setData(QUrl("http://" + (!langCode.isEmpty() ? langCode + "." : langCode) + "wiktionary.org/wiki/Special:Search?search=" + selectedText)); connect(dictact, SIGNAL(triggered()), this, SLOT(openUrlInSelectedTab())); connect(dictact, SIGNAL(ctrlTriggered()), this, SLOT(openUrlInBackgroundTab())); menu->addAction(dictact); // #379: Remove newlines QString selectedString = selectedText.trimmed().remove(QLatin1Char('\n')); if (!selectedString.contains(QLatin1Char('.'))) { // Try to add .com selectedString.append(QLatin1String(".com")); } QUrl guessedUrl = QUrl::fromUserInput(selectedString); if (isUrlValid(guessedUrl)) { Action* act = new Action(QIcon::fromTheme("document-open-remote"), tr("Go to &web address")); act->setData(guessedUrl); connect(act, SIGNAL(triggered()), this, SLOT(openActionUrl())); connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInNewTab())); menu->addAction(act); } menu->addSeparator(); selectedText.truncate(20); // KDE is displaying newlines in menu actions ... weird -,- selectedText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\t'), QLatin1Char(' ')); SearchEngine engine = mApp->searchEnginesManager()->defaultEngine(); Action* act = new Action(engine.icon, tr("Search \"%1 ..\" with %2").arg(selectedText, engine.name)); connect(act, SIGNAL(triggered()), this, SLOT(searchSelectedText())); connect(act, SIGNAL(ctrlTriggered()), this, SLOT(searchSelectedTextInBackgroundTab())); menu->addAction(act); // Search with ... Menu* swMenu = new Menu(tr("Search with..."), menu); swMenu->setCloseOnMiddleClick(true); SearchEnginesManager* searchManager = mApp->searchEnginesManager(); foreach (const SearchEngine &en, searchManager->allEngines()) { Action* act = new Action(en.icon, en.name); act->setData(QVariant::fromValue(en)); connect(act, SIGNAL(triggered()), this, SLOT(searchSelectedText())); connect(act, SIGNAL(ctrlTriggered()), this, SLOT(searchSelectedTextInBackgroundTab())); swMenu->addAction(act); } menu->addMenu(swMenu); } void WebView::createMediaContextMenu(QMenu *menu, const WebHitTestResult &hitTest) { bool paused = hitTest.mediaPaused(); bool muted = hitTest.mediaMuted(); menu->addSeparator(); menu->addAction(paused ? tr("&Play") : tr("&Pause"), this, SLOT(toggleMediaPause()))->setIcon(QIcon::fromTheme(paused ? "media-playback-start" : "media-playback-pause")); menu->addAction(muted ? tr("Un&mute") : tr("&Mute"), this, SLOT(toggleMediaMute()))->setIcon(QIcon::fromTheme(muted ? "audio-volume-muted" : "audio-volume-high")); menu->addSeparator(); menu->addAction(QIcon::fromTheme("edit-copy"), tr("&Copy Media Address"), this, SLOT(copyLinkToClipboard()))->setData(hitTest.mediaUrl()); menu->addAction(QIcon::fromTheme("mail-message-new"), tr("&Send Media Address"), this, SLOT(sendTextByMail()))->setData(hitTest.mediaUrl().toEncoded()); menu->addAction(QIcon::fromTheme("document-save"), tr("Save Media To &Disk"), this, SLOT(downloadMediaToDisk())); } void WebView::checkForForm(QAction *action, const QPoint &pos) { m_clickedPos = mapToViewport(pos); QPointer act = action; page()->runJavaScript(Scripts::getFormData(m_clickedPos), WebPage::SafeJsWorld, [this, act](const QVariant &res) { const QVariantMap &map = res.toMap(); if (!act || map.isEmpty()) return; const QUrl url = map.value(QSL("action")).toUrl(); const QString method = map.value(QSL("method")).toString(); if (!url.isEmpty() && (method == QL1S("get") || method == QL1S("post"))) { act->setVisible(true); act->setIcon(QIcon::fromTheme(QSL("edit-find"), QIcon(QSL(":icons/menu/search-icon.svg")))); act->setText(tr("Create Search Engine")); connect(act.data(), &QAction::triggered, this, &WebView::createSearchEngine); } }); } void WebView::createSearchEngine() { page()->runJavaScript(Scripts::getFormData(m_clickedPos), WebPage::SafeJsWorld, [this](const QVariant &res) { mApp->searchEnginesManager()->addEngineFromForm(res.toMap(), this); }); } void WebView::addSpeedDial() { page()->runJavaScript("addSpeedDial()"); } void WebView::configureSpeedDial() { page()->runJavaScript("configureSpeedDial()"); } void WebView::reloadAllSpeedDials() { page()->runJavaScript("reloadAll()"); } void WebView::toggleMediaPause() { triggerPageAction(QWebEnginePage::ToggleMediaPlayPause); } void WebView::toggleMediaMute() { triggerPageAction(QWebEnginePage::ToggleMediaMute); } void WebView::initializeActions() { QAction* undoAction = pageAction(QWebEnginePage::Undo); undoAction->setText(tr("&Undo")); undoAction->setShortcut(QKeySequence("Ctrl+Z")); undoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); undoAction->setIcon(QIcon::fromTheme(QSL("edit-undo"))); QAction* redoAction = pageAction(QWebEnginePage::Redo); redoAction->setText(tr("&Redo")); redoAction->setShortcut(QKeySequence("Ctrl+Shift+Z")); redoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); redoAction->setIcon(QIcon::fromTheme(QSL("edit-redo"))); QAction* cutAction = pageAction(QWebEnginePage::Cut); cutAction->setText(tr("&Cut")); cutAction->setShortcut(QKeySequence("Ctrl+X")); cutAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); cutAction->setIcon(QIcon::fromTheme(QSL("edit-cut"))); QAction* copyAction = pageAction(QWebEnginePage::Copy); copyAction->setText(tr("&Copy")); copyAction->setShortcut(QKeySequence("Ctrl+C")); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); copyAction->setIcon(QIcon::fromTheme(QSL("edit-copy"))); QAction* pasteAction = pageAction(QWebEnginePage::Paste); pasteAction->setText(tr("&Paste")); pasteAction->setShortcut(QKeySequence("Ctrl+V")); pasteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); pasteAction->setIcon(QIcon::fromTheme(QSL("edit-paste"))); QAction* selectAllAction = pageAction(QWebEnginePage::SelectAll); selectAllAction->setText(tr("Select All")); selectAllAction->setShortcut(QKeySequence("Ctrl+A")); selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); selectAllAction->setIcon(QIcon::fromTheme(QSL("edit-select-all"))); QAction* reloadAction = pageAction(QWebEnginePage::Reload); reloadAction->setText(tr("&Reload")); reloadAction->setIcon(QIcon::fromTheme(QSL("view-refresh"))); QAction* stopAction = pageAction(QWebEnginePage::Stop); stopAction->setText(tr("S&top")); stopAction->setIcon(QIcon::fromTheme(QSL("process-stop"))); // Make action shortcuts available for webview addAction(undoAction); addAction(redoAction); addAction(cutAction); addAction(copyAction); addAction(pasteAction); addAction(selectAllAction); } void WebView::_wheelEvent(QWheelEvent *event) { if (mApp->plugins()->processWheelEvent(Qz::ON_WebView, this, event)) { event->accept(); return; } if (event->modifiers() & Qt::ControlModifier) { m_wheelHelper.processEvent(event); while (WheelHelper::Direction direction = m_wheelHelper.takeDirection()) { switch (direction) { case WheelHelper::WheelUp: case WheelHelper::WheelLeft: zoomIn(); break; case WheelHelper::WheelDown: case WheelHelper::WheelRight: zoomOut(); break; default: break; } } event->accept(); return; } m_wheelHelper.reset(); // QtWebEngine ignores QApplication::wheelScrollLines() and instead always scrolls 3 lines if (event->spontaneous()) { const qreal multiplier = QApplication::wheelScrollLines() / 3.0; if (multiplier != 1.0) { QWheelEvent e(event->pos(), event->globalPos(), event->pixelDelta(), event->angleDelta() * multiplier, 0, Qt::Horizontal, event->buttons(), event->modifiers(), event->phase(), event->source(), event->inverted()); QApplication::sendEvent(m_rwhvqt, &e); event->accept(); } } } void WebView::_mousePressEvent(QMouseEvent *event) { m_clickedUrl = QUrl(); m_clickedPos = QPointF(); if (mApp->plugins()->processMousePress(Qz::ON_WebView, this, event)) { event->accept(); return; } switch (event->button()) { case Qt::XButton1: back(); event->accept(); break; case Qt::XButton2: forward(); event->accept(); break; case Qt::MiddleButton: m_clickedUrl = page()->hitTestContent(event->pos()).linkUrl(); if (!m_clickedUrl.isEmpty()) event->accept(); break; case Qt::LeftButton: m_clickedUrl = page()->hitTestContent(event->pos()).linkUrl(); break; default: break; } } void WebView::_mouseReleaseEvent(QMouseEvent *event) { if (mApp->plugins()->processMouseRelease(Qz::ON_WebView, this, event)) { event->accept(); return; } switch (event->button()) { case Qt::MiddleButton: if (!m_clickedUrl.isEmpty()) { const QUrl link = page()->hitTestContent(event->pos()).linkUrl(); if (m_clickedUrl == link && isUrlValid(link)) { userDefinedOpenUrlInNewTab(link, event->modifiers() & Qt::ShiftModifier); event->accept(); } } break; case Qt::LeftButton: if (!m_clickedUrl.isEmpty()) { const QUrl link = page()->hitTestContent(event->pos()).linkUrl(); if (m_clickedUrl == link && isUrlValid(link)) { if (event->modifiers() & Qt::ControlModifier) { userDefinedOpenUrlInNewTab(link, event->modifiers() & Qt::ShiftModifier); event->accept(); } } } break; case Qt::RightButton: if (s_forceContextMenuOnMouseRelease) { QContextMenuEvent ev(QContextMenuEvent::Mouse, event->pos(), event->globalPos(), event->modifiers()); _contextMenuEvent(&ev); event->accept(); } break; default: break; } } void WebView::_mouseMoveEvent(QMouseEvent *event) { if (mApp->plugins()->processMouseMove(Qz::ON_WebView, this, event)) { event->accept(); } } void WebView::_keyPressEvent(QKeyEvent *event) { if (mApp->plugins()->processKeyPress(Qz::ON_WebView, this, event)) { event->accept(); return; } switch (event->key()) { case Qt::Key_ZoomIn: zoomIn(); event->accept(); break; case Qt::Key_ZoomOut: zoomOut(); event->accept(); break; case Qt::Key_Plus: if (event->modifiers() & Qt::ControlModifier) { zoomIn(); event->accept(); } break; case Qt::Key_Minus: if (event->modifiers() & Qt::ControlModifier) { zoomOut(); event->accept(); } break; case Qt::Key_0: if (event->modifiers() & Qt::ControlModifier) { zoomReset(); event->accept(); } break; case Qt::Key_M: if (event->modifiers() & Qt::ControlModifier) { page()->setAudioMuted(!page()->isAudioMuted()); event->accept(); } break; default: break; } } void WebView::_keyReleaseEvent(QKeyEvent *event) { if (mApp->plugins()->processKeyRelease(Qz::ON_WebView, this, event)) { event->accept(); } switch (event->key()) { case Qt::Key_Escape: if (isFullScreen()) { triggerPageAction(QWebEnginePage::ExitFullScreen); event->accept(); } break; default: break; } } void WebView::_contextMenuEvent(QContextMenuEvent *event) { Q_UNUSED(event) } void WebView::resizeEvent(QResizeEvent *event) { QWebEngineView::resizeEvent(event); emit viewportResized(size()); } void WebView::contextMenuEvent(QContextMenuEvent *event) { // Context menu is created in mouseReleaseEvent if (s_forceContextMenuOnMouseRelease) return; const QPoint pos = event->pos(); const QContextMenuEvent::Reason reason = event->reason(); QTimer::singleShot(0, this, [this, pos, reason]() { QContextMenuEvent ev(reason, pos); _contextMenuEvent(&ev); }); } void WebView::loadRequest(const LoadRequest &req) { #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) QWebEngineView::load(req.webRequest()); #else if (req.operation() == LoadRequest::GetOperation) load(req.url()); else page()->runJavaScript(Scripts::sendPostData(req.url(), req.data()), WebPage::SafeJsWorld); #endif } bool WebView::eventFilter(QObject *obj, QEvent *event) { // Keyboard events are sent to parent widget if (obj == this && event->type() == QEvent::ParentChange && parentWidget()) { parentWidget()->installEventFilter(this); } // Forward events to WebView #define HANDLE_EVENT(f, t) \ { \ bool wasAccepted = event->isAccepted(); \ event->setAccepted(false); \ f(static_cast(event)); \ bool ret = event->isAccepted(); \ event->setAccepted(wasAccepted); \ return ret; \ } if (obj == m_rwhvqt) { switch (event->type()) { case QEvent::MouseButtonPress: HANDLE_EVENT(_mousePressEvent, QMouseEvent); case QEvent::MouseButtonRelease: HANDLE_EVENT(_mouseReleaseEvent, QMouseEvent); case QEvent::MouseMove: HANDLE_EVENT(_mouseMoveEvent, QMouseEvent); case QEvent::Wheel: HANDLE_EVENT(_wheelEvent, QWheelEvent); default: break; } } if (obj == parentWidget()) { switch (event->type()) { case QEvent::KeyPress: HANDLE_EVENT(_keyPressEvent, QKeyEvent); case QEvent::KeyRelease: HANDLE_EVENT(_keyReleaseEvent, QKeyEvent); default: break; } } #undef HANDLE_EVENT // Block already handled events if (obj == this) { switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: case QEvent::Wheel: return true; case QEvent::Hide: if (isFullScreen()) { triggerPageAction(QWebEnginePage::ExitFullScreen); } break; default: break; } } const bool res = QWebEngineView::eventFilter(obj, event); if (obj == m_rwhvqt) { switch (event->type()) { case QEvent::FocusIn: case QEvent::FocusOut: emit focusChanged(hasFocus()); break; default: break; } } return res; }