1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 * Copyright (C) 2014 Jan Bajer aka bajasoft <jbajer@gmail.com>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **************************************************************************/
20 
21 #include "QtWebKitWebBackend.h"
22 #include "QtWebKitHistoryInterface.h"
23 #include "QtWebKitPage.h"
24 #include "QtWebKitWebWidget.h"
25 #include "../../../../core/NetworkManagerFactory.h"
26 #include "../../../../core/SettingsManager.h"
27 
28 #include <QtCore/QCoreApplication>
29 #include <QtCore/QDir>
30 #include <QtCore/QPointer>
31 #include <QtCore/QRegularExpression>
32 #include <QtWebKit/QWebHistoryInterface>
33 #include <QtWebKit/QWebSettings>
34 #include <QtWebKitWidgets/QWebFrame>
35 
36 namespace Otter
37 {
38 
39 QtWebKitWebBackend* QtWebKitWebBackend::m_instance(nullptr);
40 QPointer<WebWidget> QtWebKitWebBackend::m_activeWidget(nullptr);
41 QHash<QString, QString> QtWebKitWebBackend::m_userAgentComponents;
42 QMap<QString, QString> QtWebKitWebBackend::m_userAgents;
43 int QtWebKitWebBackend::m_enableMediaOption(-1);
44 int QtWebKitWebBackend::m_enableMediaSourceOption(-1);
45 int QtWebKitWebBackend::m_enableWebSecurityOption(-1);
46 
QtWebKitWebBackend(QObject * parent)47 QtWebKitWebBackend::QtWebKitWebBackend(QObject *parent) : WebBackend(parent),
48 	m_isInitialized(false)
49 {
50 	m_instance = this;
51 	m_enableMediaOption = SettingsManager::registerOption(QLatin1String("QtWebKitBackend/EnableMedia"), SettingsManager::BooleanType, true);
52 	m_enableMediaSourceOption = SettingsManager::registerOption(QLatin1String("QtWebKitBackend/EnableMediaSource"), SettingsManager::BooleanType, false);
53 	m_enableWebSecurityOption = SettingsManager::registerOption(QLatin1String("QtWebKitBackend/EnableWebSecurity"), SettingsManager::BooleanType, true);
54 
55 	const QString cachePath(SessionsManager::getCachePath());
56 
57 	if (!cachePath.isEmpty())
58 	{
59 		QDir().mkpath(cachePath);
60 
61 		QWebSettings::setIconDatabasePath(cachePath);
62 		QWebSettings::setOfflineStoragePath(cachePath + QLatin1String("/offlineStorage/"));
63 		QWebSettings::setOfflineWebApplicationCachePath(cachePath + QLatin1String("/offlineWebApplicationCache/"));
64 		QWebSettings::globalSettings()->setLocalStoragePath(cachePath + QLatin1String("/localStorage/"));
65 	}
66 
67 	QtWebKitPage *page(new QtWebKitPage());
68 
69 	m_userAgentComponents = {{QLatin1String("platform"), QRegularExpression(QLatin1String("(\\([^\\)]+\\))")).match(page->getDefaultUserAgent()).captured(1)}, {QLatin1String("engineVersion"), QLatin1String("AppleWebKit/") + qWebKitVersion() + QLatin1String(" (KHTML, like Gecko)")}, {QLatin1String("applicationVersion"), QCoreApplication::applicationName() + QLatin1Char('/') + QCoreApplication::applicationVersion()}};
70 
71 	page->deleteLater();
72 }
73 
handleOptionChanged(int identifier)74 void QtWebKitWebBackend::handleOptionChanged(int identifier)
75 {
76 	switch (identifier)
77 	{
78 		case SettingsManager::Browser_OfflineStorageLimitOption:
79 			QWebSettings::globalSettings()->setOfflineStorageDefaultQuota(SettingsManager::getOption(SettingsManager::Browser_OfflineStorageLimitOption).toInt() * 1024);
80 
81 			return;
82 		case SettingsManager::Browser_OfflineWebApplicationCacheLimitOption:
83 			QWebSettings::globalSettings()->setOfflineWebApplicationCacheQuota(SettingsManager::getOption(SettingsManager::Browser_OfflineWebApplicationCacheLimitOption).toInt() * 1024);
84 
85 			return;
86 		case SettingsManager::Browser_PrintElementBackgroundsOption:
87 			QWebSettings::globalSettings()->setAttribute(QWebSettings::PrintElementBackgrounds, SettingsManager::getOption(SettingsManager::Browser_PrintElementBackgroundsOption).toBool());
88 
89 			return;
90 		case SettingsManager::Cache_PagesInMemoryLimitOption:
91 			QWebSettings::setMaximumPagesInCache(SettingsManager::getOption(SettingsManager::Cache_PagesInMemoryLimitOption).toInt());
92 
93 			return;
94 		case SettingsManager::Interface_EnableSmoothScrollingOption:
95 			QWebSettings::globalSettings()->setAttribute(QWebSettings::ScrollAnimatorEnabled, SettingsManager::getOption(SettingsManager::Interface_EnableSmoothScrollingOption).toBool());
96 
97 			return;
98 		default:
99 			break;
100 	}
101 
102 	if (SettingsManager::getOptionName(identifier).startsWith(QLatin1String("Content/")))
103 	{
104 		QWebSettings::globalSettings()->setAttribute(QWebSettings::ZoomTextOnly, SettingsManager::getOption(SettingsManager::Content_ZoomTextOnlyOption).toBool());
105 		QWebSettings::globalSettings()->setFontSize(QWebSettings::DefaultFontSize, SettingsManager::getOption(SettingsManager::Content_DefaultFontSizeOption).toInt());
106 		QWebSettings::globalSettings()->setFontSize(QWebSettings::DefaultFixedFontSize, SettingsManager::getOption(SettingsManager::Content_DefaultFixedFontSizeOption).toInt());
107 		QWebSettings::globalSettings()->setFontSize(QWebSettings::MinimumFontSize, SettingsManager::getOption(SettingsManager::Content_MinimumFontSizeOption).toInt());
108 		QWebSettings::globalSettings()->setFontFamily(QWebSettings::StandardFont, SettingsManager::getOption(SettingsManager::Content_StandardFontOption).toString());
109 		QWebSettings::globalSettings()->setFontFamily(QWebSettings::FixedFont, SettingsManager::getOption(SettingsManager::Content_FixedFontOption).toString());
110 		QWebSettings::globalSettings()->setFontFamily(QWebSettings::SerifFont, SettingsManager::getOption(SettingsManager::Content_SerifFontOption).toString());
111 		QWebSettings::globalSettings()->setFontFamily(QWebSettings::SansSerifFont, SettingsManager::getOption(SettingsManager::Content_SansSerifFontOption).toString());
112 		QWebSettings::globalSettings()->setFontFamily(QWebSettings::CursiveFont, SettingsManager::getOption(SettingsManager::Content_CursiveFontOption).toString());
113 		QWebSettings::globalSettings()->setFontFamily(QWebSettings::FantasyFont, SettingsManager::getOption(SettingsManager::Content_FantasyFontOption).toString());
114 	}
115 	else if (SettingsManager::getOptionName(identifier).startsWith(QLatin1String("Permissions/")))
116 	{
117 		const bool arePluginsEnabled(SettingsManager::getOption(SettingsManager::Permissions_EnablePluginsOption).toString() != QLatin1String("disabled"));
118 
119 		QWebSettings::globalSettings()->setAttribute(QWebSettings::DnsPrefetchEnabled, true);
120 		QWebSettings::globalSettings()->setAttribute(QWebSettings::AutoLoadImages, (SettingsManager::getOption(SettingsManager::Permissions_EnableImagesOption).toString() != QLatin1String("onlyCached")));
121 		QWebSettings::globalSettings()->setAttribute(QWebSettings::PluginsEnabled, arePluginsEnabled);
122 		QWebSettings::globalSettings()->setAttribute(QWebSettings::JavaEnabled, arePluginsEnabled);
123 		QWebSettings::globalSettings()->setAttribute(QWebSettings::JavascriptEnabled, SettingsManager::getOption(SettingsManager::Permissions_EnableJavaScriptOption).toBool());
124 		QWebSettings::globalSettings()->setAttribute(QWebSettings::JavascriptCanAccessClipboard, SettingsManager::getOption(SettingsManager::Permissions_ScriptsCanAccessClipboardOption).toBool());
125 		QWebSettings::globalSettings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, (SettingsManager::getOption(SettingsManager::Permissions_ScriptsCanOpenWindowsOption).toString() != QLatin1String("blockAll")));
126 		QWebSettings::globalSettings()->setAttribute(QWebSettings::WebGLEnabled, SettingsManager::getOption(SettingsManager::Permissions_EnableWebglOption).toBool());
127 		QWebSettings::globalSettings()->setAttribute(QWebSettings::LocalStorageEnabled, SettingsManager::getOption(SettingsManager::Permissions_EnableLocalStorageOption).toBool());
128 		QWebSettings::globalSettings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, SettingsManager::getOption(SettingsManager::Permissions_EnableOfflineStorageDatabaseOption).toBool());
129 		QWebSettings::globalSettings()->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, SettingsManager::getOption(SettingsManager::Permissions_EnableOfflineWebApplicationCacheOption).toBool());
130 	}
131 }
132 
setActiveWidget(WebWidget * widget)133 void QtWebKitWebBackend::setActiveWidget(WebWidget *widget)
134 {
135 	m_activeWidget = widget;
136 
137 	emit activeDictionaryChanged(getActiveDictionary());
138 }
139 
createWidget(const QVariantMap & parameters,ContentsWidget * parent)140 WebWidget* QtWebKitWebBackend::createWidget(const QVariantMap &parameters, ContentsWidget *parent)
141 {
142 	if (!m_isInitialized)
143 	{
144 		m_isInitialized = true;
145 
146 		QWebHistoryInterface::setDefaultInterface(new QtWebKitHistoryInterface(this));
147 
148 		QStringList pluginSearchPaths(QWebSettings::pluginSearchPaths());
149 		pluginSearchPaths.append(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()));
150 
151 		QWebSettings::setPluginSearchPaths(pluginSearchPaths);
152 		QWebSettings::setMaximumPagesInCache(SettingsManager::getOption(SettingsManager::Cache_PagesInMemoryLimitOption).toInt());
153 		QWebSettings::globalSettings()->setAttribute(QWebSettings::DnsPrefetchEnabled, true);
154 		QWebSettings::globalSettings()->setAttribute(QWebSettings::FullScreenSupportEnabled, true);
155 		QWebSettings::globalSettings()->setAttribute(QWebSettings::JavascriptCanCloseWindows, true);
156 		QWebSettings::globalSettings()->setAttribute(QWebSettings::XSSAuditingEnabled, true);
157 		QWebSettings::globalSettings()->setAttribute(QWebSettings::PrintElementBackgrounds, SettingsManager::getOption(SettingsManager::Browser_PrintElementBackgroundsOption).toBool());
158 		QWebSettings::globalSettings()->setAttribute(QWebSettings::ScrollAnimatorEnabled, SettingsManager::getOption(SettingsManager::Interface_EnableSmoothScrollingOption).toBool());
159 		QWebSettings::globalSettings()->setOfflineStorageDefaultQuota(SettingsManager::getOption(SettingsManager::Browser_OfflineStorageLimitOption).toInt() * 1024);
160 		QWebSettings::globalSettings()->setOfflineWebApplicationCacheQuota(SettingsManager::getOption(SettingsManager::Browser_OfflineWebApplicationCacheLimitOption).toInt() * 1024);
161 
162 		handleOptionChanged(SettingsManager::Content_DefaultFontSizeOption);
163 		handleOptionChanged(SettingsManager::Permissions_EnableFullScreenOption);
164 
165 		connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &QtWebKitWebBackend::handleOptionChanged);
166 	}
167 
168 	QtWebKitWebWidget *widget(new QtWebKitWebWidget(parameters, this, nullptr, parent));
169 
170 	connect(widget, &QtWebKitWebWidget::widgetActivated, this, &QtWebKitWebBackend::setActiveWidget);
171 
172 	return widget;
173 }
174 
getInstance()175 QtWebKitWebBackend* QtWebKitWebBackend::getInstance()
176 {
177 	return m_instance;
178 }
179 
getName() const180 QString QtWebKitWebBackend::getName() const
181 {
182 	return QLatin1String("qtwebkit");
183 }
184 
getTitle() const185 QString QtWebKitWebBackend::getTitle() const
186 {
187 	return tr("WebKit Backend");
188 }
189 
getDescription() const190 QString QtWebKitWebBackend::getDescription() const
191 {
192 	return tr("Backend utilizing QtWebKit module");
193 }
194 
getVersion() const195 QString QtWebKitWebBackend::getVersion() const
196 {
197 	return QCoreApplication::applicationVersion();
198 }
199 
getEngineVersion() const200 QString QtWebKitWebBackend::getEngineVersion() const
201 {
202 	return qWebKitVersion();
203 }
204 
getSslVersion() const205 QString QtWebKitWebBackend::getSslVersion() const
206 {
207 	return (QSslSocket::supportsSsl() ? QSslSocket::sslLibraryVersionString() : QString());
208 }
209 
getUserAgent(const QString & pattern) const210 QString QtWebKitWebBackend::getUserAgent(const QString &pattern) const
211 {
212 	if (!pattern.isEmpty())
213 	{
214 		if (m_userAgents.contains(pattern))
215 		{
216 			return (m_userAgents[pattern].isEmpty() ? pattern : m_userAgents[pattern]);
217 		}
218 
219 		const QString userAgent(Utils::substitutePlaceholders(pattern, m_userAgentComponents));
220 
221 		m_userAgents[pattern] = ((pattern == userAgent) ? QString() : userAgent);
222 
223 		return userAgent;
224 	}
225 
226 	const UserAgentDefinition userAgent(NetworkManagerFactory::getUserAgent(SettingsManager::getOption(SettingsManager::Network_UserAgentOption).toString()));
227 
228 	return (userAgent.value.isEmpty() ? QString() : getUserAgent(userAgent.value));
229 }
230 
getHomePage() const231 QUrl QtWebKitWebBackend::getHomePage() const
232 {
233 	return QUrl(QLatin1String("https://otter-browser.org/"));
234 }
235 
getActiveDictionary()236 QString QtWebKitWebBackend::getActiveDictionary()
237 {
238 	if (m_activeWidget && m_activeWidget->getOption(SettingsManager::Browser_EnableSpellCheckOption, m_activeWidget->getUrl()).toBool())
239 	{
240 		const QString dictionary(m_activeWidget->getOption(SettingsManager::Browser_SpellCheckDictionaryOption, m_activeWidget->getUrl()).toString());
241 
242 		return (dictionary.isEmpty() ? SpellCheckManager::getDefaultDictionary() : dictionary);
243 	}
244 
245 	return {};
246 }
247 
getCapabilities() const248 WebBackend::BackendCapabilities QtWebKitWebBackend::getCapabilities() const
249 {
250 	return (CacheManagementCapability | CookiesManagementCapability | PasswordsManagementCapability | PluginsOnDemandCapability | UserScriptsCapability | UserStyleSheetsCapability | GlobalCookiesPolicyCapability | GlobalContentFilteringCapability | GlobalDoNotTrackCapability | GlobalProxyCapability | GlobalReferrerCapability | GlobalUserAgentCapability | TabCookiesPolicyCapability | TabContentFilteringCapability | TabDoNotTrackCapability | TabProxyCapability | TabReferrerCapability | TabUserAgentCapability | FindInPageHighlightAllCapability);
251 }
252 
getOptionIdentifier(QtWebKitWebBackend::OptionIdentifier identifier)253 int QtWebKitWebBackend::getOptionIdentifier(QtWebKitWebBackend::OptionIdentifier identifier)
254 {
255 	switch (identifier)
256 	{
257 		case QtWebKitBackend_EnableMediaOption:
258 			return m_enableMediaOption;
259 		case QtWebKitBackend_EnableMediaSourceOption:
260 			return m_enableMediaSourceOption;
261 		case QtWebKitBackend_EnableWebSecurityOption:
262 			return m_enableWebSecurityOption;
263 		default:
264 			return -1;
265 	}
266 
267 	return -1;
268 }
269 
requestThumbnail(const QUrl & url,const QSize & size)270 bool QtWebKitWebBackend::requestThumbnail(const QUrl &url, const QSize &size)
271 {
272 	QtWebKitWebPageThumbnailJob *job(new QtWebKitWebPageThumbnailJob(url, size, this));
273 
274 	connect(job, &QtWebKitWebPageThumbnailJob::jobFinished, [=](bool isSuccess)
275 	{
276 		Q_UNUSED(isSuccess)
277 
278 		emit thumbnailAvailable(url, job->getThumbnail(), job->getTitle());
279 	});
280 
281 	job->start();
282 
283 	return true;
284 }
285 
QtWebKitWebPageThumbnailJob(const QUrl & url,const QSize & size,QObject * parent)286 QtWebKitWebPageThumbnailJob::QtWebKitWebPageThumbnailJob(const QUrl &url, const QSize &size, QObject *parent) : WebPageThumbnailJob(url, size, parent),
287 	m_page(nullptr),
288 	m_url(url),
289 	m_size(size)
290 {
291 }
292 
start()293 void QtWebKitWebPageThumbnailJob::start()
294 {
295 	if (!m_page)
296 	{
297 		m_page = new QtWebKitPage(m_url);
298 		m_page->setParent(this);
299 
300 		connect(m_page, &QtWebKitPage::loadFinished, this, &QtWebKitWebPageThumbnailJob::handlePageLoadFinished);
301 	}
302 }
303 
cancel()304 void QtWebKitWebPageThumbnailJob::cancel()
305 {
306 	if (m_page)
307 	{
308 		m_page->triggerAction(QWebPage::Stop);
309 	}
310 
311 	deleteLater();
312 }
313 
handlePageLoadFinished(bool result)314 void QtWebKitWebPageThumbnailJob::handlePageLoadFinished(bool result)
315 {
316 	if (!result)
317 	{
318 		deleteLater();
319 
320 		emit jobFinished(false);
321 
322 		return;
323 	}
324 
325 	QSize contentsSize(m_page->mainFrame()->contentsSize());
326 
327 	if (contentsSize.isNull())
328 	{
329 		m_page->setViewportSize(QSize(1280, 760));
330 
331 		contentsSize = m_page->mainFrame()->contentsSize();
332 	}
333 
334 	if (!m_size.isNull() && !contentsSize.isNull())
335 	{
336 		if (contentsSize.width() < m_size.width())
337 		{
338 			contentsSize.setWidth(m_size.width());
339 		}
340 		else if (contentsSize.width() > 2000)
341 		{
342 			contentsSize.setWidth(2000);
343 		}
344 
345 		contentsSize.setHeight(qRound(m_size.height() * (static_cast<qreal>(contentsSize.width()) / m_size.width())));
346 
347 		m_page->setViewportSize(contentsSize);
348 
349 		if (!contentsSize.isNull())
350 		{
351 			m_pixmap = QPixmap(contentsSize);
352 			m_pixmap.fill(Qt::white);
353 
354 			QPainter painter(&m_pixmap);
355 
356 			m_page->mainFrame()->render(&painter, QWebFrame::ContentsLayer, QRegion(QRect(QPoint(0, 0), contentsSize)));
357 
358 			painter.end();
359 
360 			if (m_pixmap.size() != m_size)
361 			{
362 				m_pixmap = m_pixmap.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
363 			}
364 		}
365 	}
366 
367 	m_title = m_page->mainFrame()->title();
368 
369 	deleteLater();
370 
371 	emit jobFinished(true);
372 }
373 
getTitle() const374 QString QtWebKitWebPageThumbnailJob::getTitle() const
375 {
376 	return m_title;
377 }
378 
getThumbnail() const379 QPixmap QtWebKitWebPageThumbnailJob::getThumbnail() const
380 {
381 	return m_pixmap;
382 }
383 
isRunning() const384 bool QtWebKitWebPageThumbnailJob::isRunning() const
385 {
386 	return (m_page != nullptr);
387 }
388 
389 }
390