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 ¶meters, 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