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) 2015 - 2016 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 "QtWebKitWebWidget.h"
22 #include "QtWebKitInspector.h"
23 #include "QtWebKitNetworkManager.h"
24 #include "QtWebKitPage.h"
25 #include "QtWebKitPluginWidget.h"
26 #include "QtWebKitWebBackend.h"
27 #include "../../../../core/Application.h"
28 #include "../../../../core/BookmarksManager.h"
29 #include "../../../../core/Console.h"
30 #include "../../../../core/CookieJar.h"
31 #include "../../../../core/ContentFiltersManager.h"
32 #include "../../../../core/GesturesManager.h"
33 #include "../../../../core/HistoryManager.h"
34 #include "../../../../core/JsonSettings.h"
35 #include "../../../../core/NetworkCache.h"
36 #include "../../../../core/NetworkManager.h"
37 #include "../../../../core/NetworkManagerFactory.h"
38 #include "../../../../core/NotesManager.h"
39 #include "../../../../core/SearchEnginesManager.h"
40 #include "../../../../core/SessionsManager.h"
41 #include "../../../../core/SettingsManager.h"
42 #include "../../../../core/ThemesManager.h"
43 #include "../../../../core/TransfersManager.h"
44 #include "../../../../core/Utils.h"
45 #include "../../../../ui/ContentsDialog.h"
46 #include "../../../../ui/ContentsWidget.h"
47 #include "../../../../ui/ImagePropertiesDialog.h"
48 #include "../../../../ui/MainWindow.h"
49 #include "../../../../ui/SearchEnginePropertiesDialog.h"
50 #include "../../../../ui/SourceViewerWebWidget.h"
51 #include "../../../../ui/WebsitePreferencesDialog.h"
52
53 #include <QtCore/QDataStream>
54 #include <QtCore/QFileInfo>
55 #include <QtCore/QJsonArray>
56 #include <QtCore/QJsonDocument>
57 #include <QtCore/QJsonObject>
58 #include <QtCore/QMimeData>
59 #include <QtCore/QTimer>
60 #include <QtCore/QUuid>
61 #include <QtGui/QClipboard>
62 #include <QtGui/QImageWriter>
63 #include <QtGui/QMouseEvent>
64 #include <QtPrintSupport/QPrintPreviewDialog>
65 #include <QtWebKit/QWebFullScreenRequest>
66 #include <QtWebKit/QWebElement>
67 #include <QtWebKit/QWebHistory>
68 #include <QtWebKitWidgets/QWebFrame>
69 #include <QtWidgets/QMenu>
70 #include <QtWidgets/QMessageBox>
71 #include <QtWidgets/QShortcut>
72 #include <QtWidgets/QUndoStack>
73 #include <QtWidgets/QVBoxLayout>
74
75 namespace Otter
76 {
77
QtWebKitWebWidget(const QVariantMap & parameters,WebBackend * backend,QtWebKitNetworkManager * networkManager,ContentsWidget * parent)78 QtWebKitWebWidget::QtWebKitWebWidget(const QVariantMap ¶meters, WebBackend *backend, QtWebKitNetworkManager *networkManager, ContentsWidget *parent) : WebWidget(parameters, backend, parent),
79 m_webView(new QWebView(this)),
80 m_page(nullptr),
81 m_inspector(nullptr),
82 m_networkManager(networkManager),
83 m_loadingState(FinishedLoadingState),
84 m_amountOfDeferredPlugins(0),
85 m_transfersTimer(0),
86 m_canLoadPlugins(false),
87 m_isAudioMuted(false),
88 m_isFullScreen(false),
89 m_isTyped(false),
90 m_isNavigating(false)
91 {
92 const bool isPrivate(SessionsManager::calculateOpenHints(parameters).testFlag(SessionsManager::PrivateOpen));
93 QVBoxLayout *layout(new QVBoxLayout(this));
94 layout->addWidget(m_webView);
95 layout->setContentsMargins(0, 0, 0, 0);
96
97 setLayout(layout);
98 setFocusPolicy(Qt::StrongFocus);
99
100 if (m_networkManager)
101 {
102 m_networkManager->setWidget(this);
103 }
104 else
105 {
106 m_networkManager = new QtWebKitNetworkManager(isPrivate, nullptr, this);
107 }
108
109 m_page = new QtWebKitPage(m_networkManager, this);
110 m_page->settings()->setAttribute(QWebSettings::PrivateBrowsingEnabled, isPrivate);
111 m_page->setParent(m_webView);
112 m_page->setVisibilityState(isVisible() ? QWebPage::VisibilityStateVisible : QWebPage::VisibilityStateHidden);
113
114 m_webView->setPage(m_page);
115 m_webView->setContextMenuPolicy(Qt::CustomContextMenu);
116 m_webView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
117 m_webView->installEventFilter(this);
118
119 if (parameters.contains(QLatin1String("size")))
120 {
121 m_page->setViewportSize(parameters[QLatin1String("size")].toSize());
122 }
123
124 handleOptionChanged(SettingsManager::Permissions_ScriptsCanShowStatusMessagesOption, SettingsManager::getOption(SettingsManager::Permissions_ScriptsCanShowStatusMessagesOption));
125 handleOptionChanged(SettingsManager::Content_BackgroundColorOption, SettingsManager::getOption(SettingsManager::Content_BackgroundColorOption));
126 handleOptionChanged(SettingsManager::History_BrowsingLimitAmountWindowOption, SettingsManager::getOption(SettingsManager::History_BrowsingLimitAmountWindowOption));
127 setZoom(SettingsManager::getOption(SettingsManager::Content_DefaultZoomOption).toInt());
128
129 connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &QtWebKitWebWidget::handleOptionChanged);
130 connect(m_page, &QtWebKitPage::requestedNewWindow, this, &QtWebKitWebWidget::requestedNewWindow);
131 connect(m_page, &QtWebKitPage::requestedPopupWindow, this, &QtWebKitWebWidget::requestedPopupWindow);
132 connect(m_page, &QtWebKitPage::saveFrameStateRequested, this, &QtWebKitWebWidget::saveState);
133 connect(m_page, &QtWebKitPage::restoreFrameStateRequested, this, &QtWebKitWebWidget::restoreState);
134 connect(m_page, &QtWebKitPage::downloadRequested, this, &QtWebKitWebWidget::handleDownloadRequested);
135 connect(m_page, &QtWebKitPage::unsupportedContent, this, &QtWebKitWebWidget::handleUnsupportedContent);
136 connect(m_page, &QtWebKitPage::linkHovered, this, &QtWebKitWebWidget::setStatusMessageOverride);
137 connect(m_page, &QtWebKitPage::microFocusChanged, [&]()
138 {
139 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::EditingCategory});
140 });
141 connect(m_page, &QtWebKitPage::printRequested, this, &QtWebKitWebWidget::handlePrintRequest);
142 connect(m_page, &QtWebKitPage::windowCloseRequested, this, &QtWebKitWebWidget::handleWindowCloseRequest);
143 connect(m_page, &QtWebKitPage::fullScreenRequested, this, &QtWebKitWebWidget::handleFullScreenRequest);
144 connect(m_page, &QtWebKitPage::featurePermissionRequested, this, &QtWebKitWebWidget::handlePermissionRequest);
145 connect(m_page, &QtWebKitPage::featurePermissionRequestCanceled, this, &QtWebKitWebWidget::handlePermissionCancel);
146 connect(m_page, &QtWebKitPage::loadStarted, this, &QtWebKitWebWidget::handleLoadStarted);
147 connect(m_page, &QtWebKitPage::loadProgress, this, &QtWebKitWebWidget::handleLoadProgress);
148 connect(m_page, &QtWebKitPage::loadFinished, this, &QtWebKitWebWidget::handleLoadFinished);
149 connect(m_page, &QtWebKitPage::recentlyAudibleChanged, this, &QtWebKitWebWidget::isAudibleChanged);
150 connect(m_page->mainFrame(), &QWebFrame::titleChanged, this, &QtWebKitWebWidget::notifyTitleChanged);
151 connect(m_page->mainFrame(), &QWebFrame::urlChanged, this, &QtWebKitWebWidget::notifyUrlChanged);
152 connect(m_page->mainFrame(), &QWebFrame::iconChanged, this, &QtWebKitWebWidget::notifyIconChanged);
153 connect(m_page->mainFrame(), &QWebFrame::contentsSizeChanged, this, &QtWebKitWebWidget::geometryChanged);
154 connect(m_page->mainFrame(), &QWebFrame::initialLayoutCompleted, this, &QtWebKitWebWidget::geometryChanged);
155 connect(m_page->undoStack(), &QUndoStack::canRedoChanged, this, &QtWebKitWebWidget::notifyRedoActionStateChanged);
156 connect(m_page->undoStack(), &QUndoStack::redoTextChanged, this, &QtWebKitWebWidget::notifyRedoActionStateChanged);
157 connect(m_page->undoStack(), &QUndoStack::canUndoChanged, this, &QtWebKitWebWidget::notifyUndoActionStateChanged);
158 connect(m_page->undoStack(), &QUndoStack::undoTextChanged, this, &QtWebKitWebWidget::notifyUndoActionStateChanged);
159 connect(m_networkManager, &QtWebKitNetworkManager::pageInformationChanged, this, &QtWebKitWebWidget::pageInformationChanged);
160 connect(m_networkManager, &QtWebKitNetworkManager::requestBlocked, this, &QtWebKitWebWidget::requestBlocked);
161 connect(m_networkManager, &QtWebKitNetworkManager::contentStateChanged, this, [&]()
162 {
163 emit contentStateChanged(getContentState());
164 });
165 connect(new QShortcut(QKeySequence(QKeySequence::SelectAll), this, nullptr, nullptr, Qt::WidgetWithChildrenShortcut), &QShortcut::activated, [&]()
166 {
167 triggerAction(ActionsManager::SelectAllAction);
168 });
169 }
170
~QtWebKitWebWidget()171 QtWebKitWebWidget::~QtWebKitWebWidget()
172 {
173 m_networkManager->blockSignals(true);
174
175 m_page->undoStack()->blockSignals(true);
176 m_page->blockSignals(true);
177 m_page->triggerAction(QWebPage::Stop);
178 m_page->settings()->setAttribute(QWebSettings::JavascriptEnabled, false);
179 }
180
timerEvent(QTimerEvent * event)181 void QtWebKitWebWidget::timerEvent(QTimerEvent *event)
182 {
183 if (event->timerId() == m_transfersTimer)
184 {
185 killTimer(m_transfersTimer);
186
187 Transfer *transfer(m_transfers.dequeue());
188
189 if (transfer)
190 {
191 startTransfer(transfer);
192 }
193
194 if (m_transfers.isEmpty())
195 {
196 m_transfersTimer = 0;
197 }
198 else
199 {
200 m_transfersTimer = startTimer(250);
201 }
202 }
203 else
204 {
205 WebWidget::timerEvent(event);
206 }
207 }
208
showEvent(QShowEvent * event)209 void QtWebKitWebWidget::showEvent(QShowEvent *event)
210 {
211 WebWidget::showEvent(event);
212
213 m_page->setVisibilityState(QWebPage::VisibilityStateVisible);
214 }
215
hideEvent(QHideEvent * event)216 void QtWebKitWebWidget::hideEvent(QHideEvent *event)
217 {
218 WebWidget::hideEvent(event);
219
220 m_page->setVisibilityState(QWebPage::VisibilityStateHidden);
221 }
222
focusInEvent(QFocusEvent * event)223 void QtWebKitWebWidget::focusInEvent(QFocusEvent *event)
224 {
225 WebWidget::focusInEvent(event);
226
227 m_webView->setFocus();
228
229 emit widgetActivated(this);
230 }
231
search(const QString & query,const QString & searchEngine)232 void QtWebKitWebWidget::search(const QString &query, const QString &searchEngine)
233 {
234 QNetworkRequest request;
235 QNetworkAccessManager::Operation method;
236 QByteArray body;
237
238 if (SearchEnginesManager::setupSearchQuery(query, searchEngine, &request, &method, &body))
239 {
240 setRequestedUrl(request.url(), false, true);
241 updateOptions(request.url());
242
243 m_page->mainFrame()->load(request, method, body);
244 }
245 }
246
print(QPrinter * printer)247 void QtWebKitWebWidget::print(QPrinter *printer)
248 {
249 m_page->mainFrame()->print(printer);
250 }
251
saveState(QWebFrame * frame,QWebHistoryItem * item)252 void QtWebKitWebWidget::saveState(QWebFrame *frame, QWebHistoryItem *item)
253 {
254 if (frame == m_page->mainFrame())
255 {
256 QVariantList state(m_page->history()->currentItem().userData().toList());
257
258 if (state.isEmpty() || state.count() < 4)
259 {
260 state = {0, getZoom(), m_page->mainFrame()->scrollPosition(), QDateTime::currentDateTimeUtc()};
261 }
262 else
263 {
264 state[ZoomEntryData] = getZoom();
265 state[PositionEntryData] = m_page->mainFrame()->scrollPosition();
266 }
267
268 item->setUserData(state);
269 }
270 }
271
restoreState(QWebFrame * frame)272 void QtWebKitWebWidget::restoreState(QWebFrame *frame)
273 {
274 if (frame == m_page->mainFrame())
275 {
276 const QVariantList state(m_page->history()->currentItem().userData().toList());
277
278 setZoom(state.value(ZoomEntryData, getZoom()).toInt());
279
280 if (m_page->mainFrame()->scrollPosition().isNull())
281 {
282 m_page->mainFrame()->setScrollPosition(state.value(PositionEntryData).toPoint());
283 }
284 }
285 }
286
clearPluginToken()287 void QtWebKitWebWidget::clearPluginToken()
288 {
289 QList<QWebFrame*> frames({m_page->mainFrame()});
290
291 while (!frames.isEmpty())
292 {
293 const QWebFrame *frame(frames.takeFirst());
294 QWebElement element(frame->documentElement().findFirst(QStringLiteral("object[data-otter-browser='%1'], embed[data-otter-browser='%1']").arg(m_pluginToken)));
295
296 if (!element.isNull())
297 {
298 element.removeAttribute(QLatin1String("data-otter-browser"));
299
300 break;
301 }
302
303 frames.append(frame->childFrames());
304 }
305
306 emit arbitraryActionsStateChanged({ActionsManager::LoadPluginsAction});
307
308 m_pluginToken.clear();
309 }
310
resetSpellCheck(QWebElement element)311 void QtWebKitWebWidget::resetSpellCheck(QWebElement element)
312 {
313 if (element.isNull())
314 {
315 element = m_page->mainFrame()->findFirstElement(QLatin1String("*:focus"));
316 }
317
318 if (!element.isNull())
319 {
320 m_page->runScript(QLatin1String("resetSpellCheck"), element);
321 }
322 }
323
muteAudio(QWebFrame * frame,bool isMuted)324 void QtWebKitWebWidget::muteAudio(QWebFrame *frame, bool isMuted)
325 {
326 if (!frame)
327 {
328 return;
329 }
330
331 const QString script(QLatin1String("this.muted = ") + (isMuted ? QLatin1String("true") : QLatin1String("false")));
332 const QWebElementCollection elements(frame->findAllElements(QLatin1String("audio, video")));
333
334 for (int i = 0; i < elements.count(); ++i)
335 {
336 elements.at(i).evaluateJavaScript(script);
337 }
338
339 const QList<QWebFrame*> frames(frame->childFrames());
340
341 for (int i = 0; i < frames.count(); ++i)
342 {
343 muteAudio(frames.at(i), isMuted);
344 }
345 }
346
openRequest(const QNetworkRequest & request,QNetworkAccessManager::Operation operation,QIODevice * outgoingData)347 void QtWebKitWebWidget::openRequest(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, QIODevice *outgoingData)
348 {
349 m_formRequest = request;
350 m_formRequestOperation = operation;
351 m_formRequestBody = (outgoingData ? outgoingData->readAll() : QByteArray());
352
353 if (outgoingData)
354 {
355 outgoingData->close();
356 outgoingData->deleteLater();
357 }
358
359 setRequestedUrl(m_formRequest.url(), false, true);
360 updateOptions(m_formRequest.url());
361
362 QTimer::singleShot(50, this, [&]()
363 {
364 m_page->mainFrame()->load(m_formRequest, m_formRequestOperation, m_formRequestBody);
365
366 m_formRequest = QNetworkRequest();
367 m_formRequestBody = QByteArray();
368 });
369 }
370
openFormRequest(const QNetworkRequest & request,QNetworkAccessManager::Operation operation,QIODevice * outgoingData)371 void QtWebKitWebWidget::openFormRequest(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, QIODevice *outgoingData)
372 {
373 m_page->triggerAction(QWebPage::Stop);
374
375 QtWebKitWebWidget *widget(qobject_cast<QtWebKitWebWidget*>(clone(false)));
376 widget->openRequest(request, operation, outgoingData);
377
378 emit requestedNewWindow(widget, SessionsManager::calculateOpenHints(SessionsManager::NewTabOpen), {});
379 }
380
startDelayedTransfer(Transfer * transfer)381 void QtWebKitWebWidget::startDelayedTransfer(Transfer *transfer)
382 {
383 m_transfers.enqueue(transfer);
384
385 if (m_transfersTimer == 0)
386 {
387 m_transfersTimer = startTimer(250);
388 }
389 }
390
handleDownloadRequested(const QNetworkRequest & request)391 void QtWebKitWebWidget::handleDownloadRequested(const QNetworkRequest &request)
392 {
393 if ((!getCurrentHitTestResult().imageUrl.isEmpty() && request.url() == getCurrentHitTestResult().imageUrl) || (!getCurrentHitTestResult().mediaUrl.isEmpty() && request.url() == getCurrentHitTestResult().mediaUrl))
394 {
395 NetworkCache *cache(NetworkManagerFactory::getCache());
396
397 if (cache && cache->metaData(request.url()).isValid())
398 {
399 QIODevice *device(cache->data(request.url()));
400
401 if (device && device->size() > 0)
402 {
403 const QString path(Utils::getSavePath(request.url().fileName()).path);
404
405 if (path.isEmpty())
406 {
407 device->deleteLater();
408
409 return;
410 }
411
412 QFile file(path);
413
414 if (!file.open(QIODevice::WriteOnly))
415 {
416 QMessageBox::critical(this, tr("Error"), tr("Failed to open file for writing."), QMessageBox::Close);
417 }
418
419 file.write(device->readAll());
420 file.close();
421
422 device->deleteLater();
423
424 return;
425 }
426
427 if (device)
428 {
429 device->deleteLater();
430 }
431 }
432 else if (!getCurrentHitTestResult().imageUrl.isEmpty() && getCurrentHitTestResult().imageUrl.url().contains(QLatin1String(";base64,")))
433 {
434 const QString imageUrl(getCurrentHitTestResult().imageUrl.url());
435 const QString imageType(imageUrl.mid(11, (imageUrl.indexOf(QLatin1Char(';')) - 11)));
436 const QString path(Utils::getSavePath(tr("file") + QLatin1Char('.') + imageType).path);
437
438 if (!path.isEmpty())
439 {
440 QImageWriter writer(path);
441
442 if (!writer.write(QImage::fromData(QByteArray::fromBase64(imageUrl.mid(imageUrl.indexOf(QLatin1String(";base64,")) + 7).toUtf8()), imageType.toStdString().c_str())))
443 {
444 Console::addMessage(tr("Failed to save image: %1").arg(writer.errorString()), Console::OtherCategory, Console::ErrorLevel, path, -1, getWindowIdentifier());
445 }
446 }
447
448 return;
449 }
450
451 QNetworkRequest mutableRequest(request);
452 mutableRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
453
454 new Transfer(m_networkManager->get(mutableRequest), {}, (Transfer::CanAskForPathOption | Transfer::CanAutoDeleteOption | Transfer::IsPrivateOption));
455 }
456 else
457 {
458 startDelayedTransfer(new Transfer(request, {}, (Transfer::CanNotifyOption | (isPrivate() ? Transfer::IsPrivateOption : Transfer::NoOption))));
459 }
460 }
461
handleUnsupportedContent(QNetworkReply * reply)462 void QtWebKitWebWidget::handleUnsupportedContent(QNetworkReply *reply)
463 {
464 m_networkManager->registerTransfer(reply);
465
466 startDelayedTransfer(new Transfer(reply, {}, (Transfer::CanNotifyOption | (isPrivate() ? Transfer::IsPrivateOption : Transfer::NoOption))));
467 }
468
handleOptionChanged(int identifier,const QVariant & value)469 void QtWebKitWebWidget::handleOptionChanged(int identifier, const QVariant &value)
470 {
471 switch (identifier)
472 {
473 case SettingsManager::Content_BackgroundColorOption:
474 {
475 QPalette palette(m_page->palette());
476 palette.setColor(QPalette::Base, QColor(value.toString()));
477
478 m_page->setPalette(palette);
479 }
480
481 break;
482 case SettingsManager::History_BrowsingLimitAmountWindowOption:
483 m_page->history()->setMaximumItemCount(value.toInt());
484
485 break;
486 case SettingsManager::Permissions_ScriptsCanShowStatusMessagesOption:
487 disconnect(m_page, &QtWebKitPage::statusBarMessage, this, &QtWebKitWebWidget::setStatusMessage);
488
489 if (value.toBool() || SettingsManager::getOption(identifier, Utils::extractHost(getUrl())).toBool())
490 {
491 connect(m_page, &QtWebKitPage::statusBarMessage, this, &QtWebKitWebWidget::setStatusMessage);
492 }
493 else
494 {
495 setStatusMessage({});
496 }
497
498 break;
499 default:
500 break;
501 }
502 }
503
handleLoadStarted()504 void QtWebKitWebWidget::handleLoadStarted()
505 {
506 if (m_loadingState == OngoingLoadingState)
507 {
508 return;
509 }
510
511 m_thumbnail = {};
512 m_messageToken = QUuid::createUuid().toString();
513 m_canLoadPlugins = (getOption(SettingsManager::Permissions_EnablePluginsOption, getUrl()).toString() == QLatin1String("enabled"));
514 m_loadingState = OngoingLoadingState;
515
516 setStatusMessage({});
517 setStatusMessageOverride({});
518
519 emit geometryChanged();
520 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::NavigationCategory});
521 emit loadingStateChanged(OngoingLoadingState);
522 }
523
handleLoadProgress(int progress)524 void QtWebKitWebWidget::handleLoadProgress(int progress)
525 {
526 m_networkManager->setPageInformation(TotalLoadingProgressInformation, progress);
527 }
528
handleLoadFinished(bool result)529 void QtWebKitWebWidget::handleLoadFinished(bool result)
530 {
531 if (m_isAudioMuted)
532 {
533 muteAudio(m_page->mainFrame(), true);
534 }
535
536 if (m_loadingState != OngoingLoadingState)
537 {
538 return;
539 }
540
541 m_networkManager->handleLoadFinished(result);
542
543 m_thumbnail = {};
544 m_loadingState = FinishedLoadingState;
545
546 updateAmountOfDeferredPlugins();
547 handleHistory();
548 startReloadTimer();
549
550 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::NavigationCategory});
551 emit contentStateChanged(getContentState());
552 emit loadingStateChanged(FinishedLoadingState);
553 emit watchedDataChanged(FeedsWatcher);
554 emit watchedDataChanged(LinksWatcher);
555 emit watchedDataChanged(MetaDataWatcher);
556 emit watchedDataChanged(SearchEnginesWatcher);
557 emit watchedDataChanged(StylesheetsWatcher);
558 }
559
handleViewSourceReplyFinished()560 void QtWebKitWebWidget::handleViewSourceReplyFinished()
561 {
562 QNetworkReply *reply(qobject_cast<QNetworkReply*>(sender()));
563
564 if (reply)
565 {
566 if (reply->error() == QNetworkReply::NoError && m_viewSourceReplies.contains(reply) && m_viewSourceReplies[reply])
567 {
568 m_viewSourceReplies[reply]->setContents(reply->readAll(), reply->header(QNetworkRequest::ContentTypeHeader).toString());
569 }
570
571 m_viewSourceReplies.remove(reply);
572
573 reply->deleteLater();
574 }
575 }
576
handlePrintRequest(QWebFrame * frame)577 void QtWebKitWebWidget::handlePrintRequest(QWebFrame *frame)
578 {
579 QPrintPreviewDialog printPreviewDialog(this);
580 printPreviewDialog.setWindowFlags(printPreviewDialog.windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint);
581 printPreviewDialog.setWindowTitle(tr("Print Preview"));
582
583 if (Application::getActiveWindow())
584 {
585 printPreviewDialog.resize(Application::getActiveWindow()->size());
586 }
587
588 connect(&printPreviewDialog, &QPrintPreviewDialog::paintRequested, frame, &QWebFrame::print);
589
590 printPreviewDialog.exec();
591 }
592
handleHistory()593 void QtWebKitWebWidget::handleHistory()
594 {
595 if (isPrivate() || m_page->history()->count() == 0)
596 {
597 return;
598 }
599
600 const QUrl url(m_page->history()->currentItem().url());
601 const QVariant state(m_page->history()->currentItem().userData());
602
603 if (state.isValid())
604 {
605 const quint64 identifier(state.toList().value(IdentifierEntryData).toULongLong());
606
607 if (identifier > 0)
608 {
609 HistoryManager::updateEntry(identifier, url, getTitle(), m_page->mainFrame()->icon());
610 }
611 }
612 else
613 {
614 m_page->history()->currentItem().setUserData(QVariantList({(Utils::isUrlEmpty(url) ? 0 : HistoryManager::addEntry(url, getTitle(), m_page->mainFrame()->icon(), m_isTyped)), getZoom(), QPoint(0, 0), QDateTime::currentDateTimeUtc()}));
615
616 if (m_isTyped)
617 {
618 m_isTyped = false;
619 }
620
621 SessionsManager::markSessionAsModified();
622 BookmarksManager::updateVisits(url.toString());
623 }
624 }
625
handleNavigationRequest(const QUrl & url,QWebPage::NavigationType type)626 void QtWebKitWebWidget::handleNavigationRequest(const QUrl &url, QWebPage::NavigationType type)
627 {
628 if (type != QWebPage::NavigationTypeBackOrForward && (type != QWebPage::NavigationTypeLinkClicked || !getUrl().matches(url, QUrl::RemoveFragment)))
629 {
630 handleLoadStarted();
631 handleHistory();
632
633 m_networkManager->resetStatistics();
634 }
635
636 updateOptions(url);
637
638 m_isNavigating = true;
639
640 emit aboutToNavigate();
641 }
642
handleFullScreenRequest(QWebFullScreenRequest request)643 void QtWebKitWebWidget::handleFullScreenRequest(QWebFullScreenRequest request)
644 {
645 request.accept();
646
647 if (request.toggleOn())
648 {
649 const QString value(SettingsManager::getOption(SettingsManager::Permissions_EnableFullScreenOption, Utils::extractHost(request.origin())).toString());
650
651 if (value == QLatin1String("allow"))
652 {
653 MainWindow *mainWindow(MainWindow::findMainWindow(this));
654
655 if (mainWindow && !mainWindow->isFullScreen())
656 {
657 mainWindow->triggerAction(ActionsManager::FullScreenAction);
658 }
659 }
660 else if (value == QLatin1String("ask"))
661 {
662 emit requestedPermission(FullScreenFeature, request.origin(), false);
663 }
664 }
665 else
666 {
667 MainWindow *mainWindow(MainWindow::findMainWindow(this));
668
669 if (mainWindow && mainWindow->isFullScreen())
670 {
671 mainWindow->triggerAction(ActionsManager::FullScreenAction);
672 }
673
674 emit requestedPermission(FullScreenFeature, request.origin(), true);
675 }
676
677 m_isFullScreen = request.toggleOn();
678
679 emit isFullScreenChanged(m_isFullScreen);
680 }
681
handlePermissionRequest(QWebFrame * frame,QWebPage::Feature feature)682 void QtWebKitWebWidget::handlePermissionRequest(QWebFrame *frame, QWebPage::Feature feature)
683 {
684 notifyPermissionRequested(frame, feature, false);
685 }
686
handlePermissionCancel(QWebFrame * frame,QWebPage::Feature feature)687 void QtWebKitWebWidget::handlePermissionCancel(QWebFrame *frame, QWebPage::Feature feature)
688 {
689 notifyPermissionRequested(frame, feature, true);
690 }
691
notifyTitleChanged()692 void QtWebKitWebWidget::notifyTitleChanged()
693 {
694 emit titleChanged(getTitle());
695
696 handleHistory();
697 }
698
notifyUrlChanged(const QUrl & url)699 void QtWebKitWebWidget::notifyUrlChanged(const QUrl &url)
700 {
701 m_isNavigating = false;
702
703 updateOptions(url);
704
705 emit urlChanged(url);
706 emit arbitraryActionsStateChanged({ActionsManager::InspectPageAction, ActionsManager::InspectElementAction});
707 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::NavigationCategory, ActionsManager::ActionDefinition::PageCategory});
708
709 SessionsManager::markSessionAsModified();
710 }
711
notifyIconChanged()712 void QtWebKitWebWidget::notifyIconChanged()
713 {
714 emit iconChanged(getIcon());
715 }
716
notifyPermissionRequested(QWebFrame * frame,QWebPage::Feature nativeFeature,bool cancel)717 void QtWebKitWebWidget::notifyPermissionRequested(QWebFrame *frame, QWebPage::Feature nativeFeature, bool cancel)
718 {
719 FeaturePermission feature(UnknownFeature);
720
721 switch (nativeFeature)
722 {
723 case QWebPage::Geolocation:
724 feature = GeolocationFeature;
725
726 break;
727 case QWebPage::Notifications:
728 feature = NotificationsFeature;
729
730 break;
731 default:
732 return;
733 }
734
735 const QUrl url(frame->url().isValid() ? frame->url() : frame->requestedUrl());
736
737 if (cancel)
738 {
739 emit requestedPermission(feature, url, true);
740 }
741 else
742 {
743 switch (getPermission(feature, url))
744 {
745 case GrantedPermission:
746 m_page->setFeaturePermission(frame, nativeFeature, QWebPage::PermissionGrantedByUser);
747
748 break;
749 case DeniedPermission:
750 m_page->setFeaturePermission(frame, nativeFeature, QWebPage::PermissionDeniedByUser);
751
752 break;
753 default:
754 emit requestedPermission(feature, url, false);
755
756 break;
757 }
758 }
759 }
760
notifySavePasswordRequested(const PasswordsManager::PasswordInformation & password,bool isUpdate)761 void QtWebKitWebWidget::notifySavePasswordRequested(const PasswordsManager::PasswordInformation &password, bool isUpdate)
762 {
763 emit requestedSavePassword(password, isUpdate);
764 }
765
updateAmountOfDeferredPlugins()766 void QtWebKitWebWidget::updateAmountOfDeferredPlugins()
767 {
768 const int amountOfDeferredPlugins(m_canLoadPlugins ? 0 : findChildren<QtWebKitPluginWidget*>().count());
769
770 if (amountOfDeferredPlugins != m_amountOfDeferredPlugins)
771 {
772 const bool needsActionUpdate(amountOfDeferredPlugins == 0 || m_amountOfDeferredPlugins == 0);
773
774 m_amountOfDeferredPlugins = amountOfDeferredPlugins;
775
776 if (needsActionUpdate)
777 {
778 emit arbitraryActionsStateChanged({ActionsManager::LoadPluginsAction});
779 }
780 }
781 }
782
updateOptions(const QUrl & url)783 void QtWebKitWebWidget::updateOptions(const QUrl &url)
784 {
785 const QString encoding(getOption(SettingsManager::Content_DefaultCharacterEncodingOption, url).toString());
786 const bool arePluginsEnabled(getOption(SettingsManager::Permissions_EnablePluginsOption, url).toString() != QLatin1String("disabled"));
787 QWebSettings *settings(m_page->settings());
788 settings->setAttribute(QWebSettings::AutoLoadImages, (getOption(SettingsManager::Permissions_EnableImagesOption, url).toString() != QLatin1String("onlyCached")));
789 settings->setAttribute(QWebSettings::PluginsEnabled, arePluginsEnabled);
790 settings->setAttribute(QWebSettings::JavaEnabled, arePluginsEnabled);
791 settings->setAttribute(QWebSettings::JavascriptEnabled, (m_page->isDisplayingErrorPage() || m_page->isViewingMedia() || getOption(SettingsManager::Permissions_EnableJavaScriptOption, url).toBool()));
792 settings->setAttribute(QWebSettings::JavascriptCanAccessClipboard, getOption(SettingsManager::Permissions_ScriptsCanAccessClipboardOption, url).toBool());
793 settings->setAttribute(QWebSettings::JavascriptCanCloseWindows, getOption(SettingsManager::Permissions_ScriptsCanCloseWindowsOption, url).toBool());
794 settings->setAttribute(QWebSettings::JavascriptCanOpenWindows, (getOption(SettingsManager::Permissions_ScriptsCanOpenWindowsOption, url).toString() != QLatin1String("blockAll")));
795 settings->setAttribute(QWebSettings::WebGLEnabled, getOption(SettingsManager::Permissions_EnableWebglOption, url).toBool());
796 settings->setAttribute(QWebSettings::LocalStorageEnabled, getOption(SettingsManager::Permissions_EnableLocalStorageOption, url).toBool());
797 settings->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, getOption(SettingsManager::Permissions_EnableOfflineStorageDatabaseOption, url).toBool());
798 settings->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, getOption(SettingsManager::Permissions_EnableOfflineWebApplicationCacheOption, url).toBool());
799 settings->setAttribute(QWebSettings::AllowRunningInsecureContent, getOption(SettingsManager::Security_AllowMixedContentOption, url).toBool());
800 settings->setAttribute(QWebSettings::MediaEnabled, getOption(QtWebKitWebBackend::getOptionIdentifier(QtWebKitWebBackend::QtWebKitBackend_EnableMediaOption), url).toBool());
801 settings->setAttribute(QWebSettings::MediaSourceEnabled, getOption(QtWebKitWebBackend::getOptionIdentifier(QtWebKitWebBackend::QtWebKitBackend_EnableMediaSourceOption), url).toBool());
802 settings->setAttribute(QWebSettings::WebSecurityEnabled, getOption(QtWebKitWebBackend::getOptionIdentifier(QtWebKitWebBackend::QtWebKitBackend_EnableWebSecurityOption), url).toBool());
803 settings->setDefaultTextEncoding((encoding == QLatin1String("auto")) ? QString() : encoding);
804
805 disconnect(m_page, &QtWebKitPage::geometryChangeRequested, this, &QtWebKitWebWidget::requestedGeometryChange);
806 disconnect(m_page, &QtWebKitPage::statusBarMessage, this, &QtWebKitWebWidget::setStatusMessage);
807
808 if (getOption(SettingsManager::Permissions_ScriptsCanChangeWindowGeometryOption, url).toBool())
809 {
810 connect(m_page, &QtWebKitPage::geometryChangeRequested, this, &QtWebKitWebWidget::requestedGeometryChange);
811 }
812
813 if (getOption(SettingsManager::Permissions_ScriptsCanShowStatusMessagesOption, url).toBool())
814 {
815 connect(m_page, &QtWebKitPage::statusBarMessage, this, &QtWebKitWebWidget::setStatusMessage);
816 }
817 else
818 {
819 setStatusMessage({});
820 }
821
822 m_page->updateStyleSheets(url);
823
824 m_networkManager->updateOptions(url);
825
826 m_canLoadPlugins = (getOption(SettingsManager::Permissions_EnablePluginsOption, url).toString() == QLatin1String("enabled"));
827 }
828
clearOptions()829 void QtWebKitWebWidget::clearOptions()
830 {
831 WebWidget::clearOptions();
832
833 updateOptions(getUrl());
834 }
835
fillPassword(const PasswordsManager::PasswordInformation & password)836 void QtWebKitWebWidget::fillPassword(const PasswordsManager::PasswordInformation &password)
837 {
838 QFile file(QLatin1String(":/modules/backends/web/qtwebkit/resources/formFiller.js"));
839
840 if (!file.open(QIODevice::ReadOnly))
841 {
842 return;
843 }
844
845 QJsonArray fieldsArray;
846
847 for (int i = 0; i < password.fields.count(); ++i)
848 {
849 fieldsArray.append(QJsonObject({{QLatin1String("name"), password.fields.at(i).name}, {QLatin1String("value"), password.fields.at(i).value}, {QLatin1String("type"), ((password.fields.at(i).type == PasswordsManager::PasswordField) ? QLatin1String("password") : QLatin1String("text"))}}));
850 }
851
852 const QString script(QString(file.readAll()).arg(QString(QJsonDocument(fieldsArray).toJson(QJsonDocument::Indented))));
853
854 file.close();
855
856 QList<QWebFrame*> frames({m_page->mainFrame()});
857
858 while (!frames.isEmpty())
859 {
860 const QWebFrame *frame(frames.takeFirst());
861 frame->documentElement().evaluateJavaScript(script);
862
863 frames.append(frame->childFrames());
864 }
865 }
866
triggerAction(int identifier,const QVariantMap & parameters,ActionsManager::TriggerType trigger)867 void QtWebKitWebWidget::triggerAction(int identifier, const QVariantMap ¶meters, ActionsManager::TriggerType trigger)
868 {
869 switch (identifier)
870 {
871 case ActionsManager::SaveAction:
872 if (m_page->isViewingMedia())
873 {
874 const SaveInformation information(Utils::getSavePath(suggestSaveFileName(SingleFileSaveFormat)));
875
876 if (information.canSave)
877 {
878 QNetworkRequest request(getUrl());
879 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
880
881 new Transfer(m_networkManager->get(request), information.path, (Transfer::CanAskForPathOption | Transfer::CanAutoDeleteOption | Transfer::CanOverwriteOption | Transfer::IsPrivateOption));
882 }
883 }
884 else
885 {
886 SaveFormat format(UnknownSaveFormat);
887 const QString path(getSavePath({SingleFileSaveFormat, PdfSaveFormat}, &format));
888
889 if (!path.isEmpty())
890 {
891 switch (format)
892 {
893 case PdfSaveFormat:
894 {
895 QPrinter printer;
896 printer.setOutputFormat(QPrinter::PdfFormat);
897 printer.setOutputFileName(path);
898 printer.setCreator(QStringLiteral("Otter Browser %1").arg(Application::getFullVersion()));
899 printer.setDocName(getTitle());
900
901 m_page->mainFrame()->print(&printer);
902 }
903
904 break;
905 default:
906 {
907 QNetworkRequest request(getUrl());
908 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
909
910 new Transfer(m_networkManager->get(request), path, (Transfer::CanAskForPathOption | Transfer::CanAutoDeleteOption | Transfer::CanOverwriteOption | Transfer::IsPrivateOption));
911 }
912
913 break;
914 }
915 }
916 }
917
918 break;
919 case ActionsManager::ClearTabHistoryAction:
920 if (parameters.value(QLatin1String("clearGlobalHistory")).toBool())
921 {
922 for (int i = 0; i < m_page->history()->count(); ++i)
923 {
924 const quint64 historyIdentifier(m_page->history()->itemAt(i).userData().toList().value(IdentifierEntryData).toULongLong());
925
926 if (historyIdentifier > 0)
927 {
928 HistoryManager::removeEntry(historyIdentifier);
929 }
930 }
931 }
932
933 setUrl(QUrl(QLatin1String("about:blank")));
934
935 m_page->history()->clear();
936
937 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::NavigationCategory});
938
939 break;
940 case ActionsManager::PurgeTabHistoryAction:
941 triggerAction(ActionsManager::ClearTabHistoryAction, {{QLatin1String("clearGlobalHistory"), true}}, trigger);
942
943 break;
944 case ActionsManager::MuteTabMediaAction:
945 m_isAudioMuted = !m_isAudioMuted;
946
947 muteAudio(m_page->mainFrame(), m_isAudioMuted);
948
949 emit arbitraryActionsStateChanged({ActionsManager::MuteTabMediaAction});
950
951 break;
952 case ActionsManager::OpenLinkAction:
953 {
954 const SessionsManager::OpenHints hints(SessionsManager::calculateOpenHints(parameters));
955
956 if (hints == SessionsManager::DefaultOpen && !getCurrentHitTestResult().flags.testFlag(HitTestResult::IsLinkFromSelectionTest))
957 {
958 m_page->triggerAction(QWebPage::OpenLink);
959
960 setClickPosition({});
961 }
962 else if (getCurrentHitTestResult().linkUrl.isValid())
963 {
964 openUrl(getCurrentHitTestResult().linkUrl, hints);
965 }
966 }
967
968 break;
969 case ActionsManager::OpenLinkInCurrentTabAction:
970 if (getCurrentHitTestResult().linkUrl.isValid())
971 {
972 openUrl(getCurrentHitTestResult().linkUrl, SessionsManager::CurrentTabOpen);
973 }
974
975 break;
976 case ActionsManager::OpenLinkInNewTabAction:
977 if (getCurrentHitTestResult().linkUrl.isValid())
978 {
979 openUrl(getCurrentHitTestResult().linkUrl, SessionsManager::calculateOpenHints(SessionsManager::NewTabOpen));
980 }
981
982 break;
983 case ActionsManager::OpenLinkInNewTabBackgroundAction:
984 if (getCurrentHitTestResult().linkUrl.isValid())
985 {
986 openUrl(getCurrentHitTestResult().linkUrl, (SessionsManager::NewTabOpen | SessionsManager::BackgroundOpen));
987 }
988
989 break;
990 case ActionsManager::OpenLinkInNewWindowAction:
991 if (getCurrentHitTestResult().linkUrl.isValid())
992 {
993 openUrl(getCurrentHitTestResult().linkUrl, SessionsManager::calculateOpenHints(SessionsManager::NewWindowOpen));
994 }
995
996 break;
997 case ActionsManager::OpenLinkInNewWindowBackgroundAction:
998 if (getCurrentHitTestResult().linkUrl.isValid())
999 {
1000 openUrl(getCurrentHitTestResult().linkUrl, (SessionsManager::NewWindowOpen | SessionsManager::BackgroundOpen));
1001 }
1002
1003 break;
1004 case ActionsManager::OpenLinkInNewPrivateTabAction:
1005 if (getCurrentHitTestResult().linkUrl.isValid())
1006 {
1007 openUrl(getCurrentHitTestResult().linkUrl, (SessionsManager::NewTabOpen | SessionsManager::PrivateOpen));
1008 }
1009
1010 break;
1011 case ActionsManager::OpenLinkInNewPrivateTabBackgroundAction:
1012 if (getCurrentHitTestResult().linkUrl.isValid())
1013 {
1014 openUrl(getCurrentHitTestResult().linkUrl, (SessionsManager::NewTabOpen | SessionsManager::BackgroundOpen | SessionsManager::PrivateOpen));
1015 }
1016
1017 break;
1018 case ActionsManager::OpenLinkInNewPrivateWindowAction:
1019 if (getCurrentHitTestResult().linkUrl.isValid())
1020 {
1021 openUrl(getCurrentHitTestResult().linkUrl, (SessionsManager::NewWindowOpen | SessionsManager::PrivateOpen));
1022 }
1023
1024 break;
1025 case ActionsManager::OpenLinkInNewPrivateWindowBackgroundAction:
1026 if (getCurrentHitTestResult().linkUrl.isValid())
1027 {
1028 openUrl(getCurrentHitTestResult().linkUrl, (SessionsManager::NewWindowOpen | SessionsManager::BackgroundOpen | SessionsManager::PrivateOpen));
1029 }
1030
1031 break;
1032 case ActionsManager::CopyLinkToClipboardAction:
1033 if (!getCurrentHitTestResult().linkUrl.isEmpty())
1034 {
1035 Application::clipboard()->setText(getCurrentHitTestResult().linkUrl.toString(QUrl::EncodeReserved | QUrl::EncodeSpaces));
1036 }
1037
1038 break;
1039 case ActionsManager::BookmarkLinkAction:
1040 if (getCurrentHitTestResult().linkUrl.isValid())
1041 {
1042 const QString title(getCurrentHitTestResult().title);
1043
1044 Application::triggerAction(ActionsManager::BookmarkPageAction, {{QLatin1String("url"), getCurrentHitTestResult().linkUrl}, {QLatin1String("title"), (title.isEmpty() ? m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition).element().toPlainText() : title)}}, parentWidget(), trigger);
1045 }
1046
1047 break;
1048 case ActionsManager::SaveLinkToDiskAction:
1049 if (getCurrentHitTestResult().linkUrl.isValid())
1050 {
1051 handleDownloadRequested(QNetworkRequest(getCurrentHitTestResult().linkUrl));
1052 }
1053 else
1054 {
1055 m_page->triggerAction(QWebPage::DownloadLinkToDisk);
1056 }
1057
1058 break;
1059 case ActionsManager::SaveLinkToDownloadsAction:
1060 TransfersManager::addTransfer(new Transfer(getCurrentHitTestResult().linkUrl.toString(), {}, (Transfer::CanNotifyOption | Transfer::CanAskForPathOption | Transfer::IsQuickTransferOption | (isPrivate() ? Transfer::IsPrivateOption : Transfer::NoOption))));
1061
1062 break;
1063 case ActionsManager::OpenFrameAction:
1064 if (getCurrentHitTestResult().frameUrl.isValid())
1065 {
1066 openUrl(getCurrentHitTestResult().frameUrl, SessionsManager::calculateOpenHints(parameters));
1067 }
1068
1069 break;
1070 case ActionsManager::OpenFrameInCurrentTabAction:
1071 if (getCurrentHitTestResult().frameUrl.isValid())
1072 {
1073 setUrl(getCurrentHitTestResult().frameUrl, false);
1074 }
1075
1076 break;
1077 case ActionsManager::OpenFrameInNewTabAction:
1078 if (getCurrentHitTestResult().frameUrl.isValid())
1079 {
1080 openUrl(getCurrentHitTestResult().frameUrl, SessionsManager::calculateOpenHints(SessionsManager::NewTabOpen));
1081 }
1082
1083 break;
1084 case ActionsManager::OpenFrameInNewTabBackgroundAction:
1085 if (getCurrentHitTestResult().frameUrl.isValid())
1086 {
1087 openUrl(getCurrentHitTestResult().frameUrl, (SessionsManager::NewTabOpen | SessionsManager::BackgroundOpen));
1088 }
1089
1090 break;
1091 case ActionsManager::CopyFrameLinkToClipboardAction:
1092 if (getCurrentHitTestResult().frameUrl.isValid())
1093 {
1094 Application::clipboard()->setText(getCurrentHitTestResult().frameUrl.toString(QUrl::EncodeReserved | QUrl::EncodeSpaces));
1095 }
1096
1097 break;
1098 case ActionsManager::ReloadFrameAction:
1099 if (getCurrentHitTestResult().frameUrl.isValid())
1100 {
1101 const QUrl url(getCurrentHitTestResult().frameUrl);
1102 QWebHitTestResult hitResult(m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition));
1103
1104 if (hitResult.frame())
1105 {
1106 m_networkManager->addContentBlockingException(url, NetworkManager::SubFrameType);
1107
1108 hitResult.frame()->setUrl({});
1109 hitResult.frame()->setUrl(url);
1110 }
1111 }
1112
1113 break;
1114 case ActionsManager::ViewFrameSourceAction:
1115 if (getCurrentHitTestResult().frameUrl.isValid())
1116 {
1117 const QString defaultEncoding(m_page->settings()->defaultTextEncoding());
1118 QNetworkRequest request(getCurrentHitTestResult().frameUrl);
1119 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
1120
1121 QNetworkReply *reply(m_networkManager->get(request));
1122 SourceViewerWebWidget *sourceViewer(new SourceViewerWebWidget(isPrivate()));
1123 sourceViewer->setRequestedUrl(QUrl(QLatin1String("view-source:") + getCurrentHitTestResult().frameUrl.toString()), false);
1124
1125 if (!defaultEncoding.isEmpty())
1126 {
1127 sourceViewer->setOption(SettingsManager::Content_DefaultCharacterEncodingOption, defaultEncoding);
1128 }
1129
1130 m_viewSourceReplies[reply] = sourceViewer;
1131
1132 connect(reply, &QNetworkReply::finished, this, &QtWebKitWebWidget::handleViewSourceReplyFinished);
1133
1134 emit requestedNewWindow(sourceViewer, SessionsManager::DefaultOpen, {});
1135 }
1136
1137 break;
1138 case ActionsManager::OpenImageAction:
1139 if (getCurrentHitTestResult().imageUrl.isValid())
1140 {
1141 openUrl(getCurrentHitTestResult().imageUrl, SessionsManager::calculateOpenHints(parameters));
1142 }
1143
1144 break;
1145 case ActionsManager::OpenImageInNewTabAction:
1146 if (getCurrentHitTestResult().imageUrl.isValid())
1147 {
1148 openUrl(getCurrentHitTestResult().imageUrl, SessionsManager::calculateOpenHints(SessionsManager::NewTabOpen));
1149 }
1150
1151 break;
1152 case ActionsManager::OpenImageInNewTabBackgroundAction:
1153 if (getCurrentHitTestResult().imageUrl.isValid())
1154 {
1155 openUrl(getCurrentHitTestResult().imageUrl, (SessionsManager::NewTabOpen | SessionsManager::BackgroundOpen));
1156 }
1157
1158 break;
1159 case ActionsManager::SaveImageToDiskAction:
1160 if (getCurrentHitTestResult().imageUrl.isValid())
1161 {
1162 handleDownloadRequested(QNetworkRequest(getCurrentHitTestResult().imageUrl));
1163 }
1164
1165 break;
1166 case ActionsManager::CopyImageToClipboardAction:
1167 {
1168 const QPixmap pixmap(m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition).pixmap());
1169
1170 if (!pixmap.isNull())
1171 {
1172 Application::clipboard()->setPixmap(pixmap);
1173 }
1174 else
1175 {
1176 m_page->triggerAction(QWebPage::CopyImageToClipboard);
1177 }
1178 }
1179
1180 break;
1181 case ActionsManager::CopyImageUrlToClipboardAction:
1182 if (!getCurrentHitTestResult().imageUrl.isEmpty())
1183 {
1184 Application::clipboard()->setText(getCurrentHitTestResult().imageUrl.toString(QUrl::EncodeReserved | QUrl::EncodeSpaces));
1185 }
1186
1187 break;
1188 case ActionsManager::ReloadImageAction:
1189 if (!getCurrentHitTestResult().imageUrl.isEmpty())
1190 {
1191 m_networkManager->addContentBlockingException(getCurrentHitTestResult().imageUrl, NetworkManager::ImageType);
1192
1193 m_page->settings()->setAttribute(QWebSettings::AutoLoadImages, true);
1194
1195 if (getUrl().matches(getCurrentHitTestResult().imageUrl, (QUrl::NormalizePathSegments | QUrl::RemoveFragment | QUrl::StripTrailingSlash)))
1196 {
1197 triggerAction(ActionsManager::ReloadAndBypassCacheAction, {}, trigger);
1198 }
1199 else
1200 {
1201 const QWebHitTestResult hitResult(m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition));
1202 const QUrl url(getCurrentHitTestResult().imageUrl);
1203 const QString src(hitResult.element().attribute(QLatin1String("src")));
1204 NetworkCache *cache(NetworkManagerFactory::getCache());
1205
1206 hitResult.element().setAttribute(QLatin1String("src"), {});
1207
1208 if (cache)
1209 {
1210 cache->remove(url);
1211 }
1212
1213 hitResult.element().setAttribute(QLatin1String("src"), src);
1214
1215 m_page->mainFrame()->documentElement().evaluateJavaScript(QStringLiteral("var images = document.querySelectorAll('img[src=\"%1\"]'); for (var i = 0; i < images.length; ++i) { images[i].src = ''; images[i].src = '%1'; }").arg(src));
1216 }
1217 }
1218
1219 break;
1220 case ActionsManager::ImagePropertiesAction:
1221 {
1222 QVariantMap properties({{QLatin1String("alternativeText"), getCurrentHitTestResult().alternateText}, {QLatin1String("longDescription"), getCurrentHitTestResult().longDescription}});
1223 const QWebHitTestResult hitResult(m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition));
1224
1225 if (!hitResult.pixmap().isNull())
1226 {
1227 properties[QLatin1String("width")] = hitResult.pixmap().width();
1228 properties[QLatin1String("height")] = hitResult.pixmap().height();
1229 properties[QLatin1String("depth")] = hitResult.pixmap().depth();
1230 }
1231
1232 ImagePropertiesDialog *imagePropertiesDialog(new ImagePropertiesDialog(getCurrentHitTestResult().imageUrl, properties, (m_networkManager->cache() ? m_networkManager->cache()->data(getCurrentHitTestResult().imageUrl) : nullptr), this));
1233 imagePropertiesDialog->setButtonsVisible(false);
1234
1235 ContentsDialog *dialog(new ContentsDialog(ThemesManager::createIcon(QLatin1String("dialog-information")), imagePropertiesDialog->windowTitle(), {}, {}, QDialogButtonBox::Close, imagePropertiesDialog, this));
1236
1237 connect(this, &QtWebKitWebWidget::aboutToReload, dialog, &ContentsDialog::close);
1238 connect(imagePropertiesDialog, &ImagePropertiesDialog::finished, dialog, &ContentsDialog::close);
1239
1240 showDialog(dialog, false);
1241 }
1242
1243 break;
1244 case ActionsManager::SaveMediaToDiskAction:
1245 if (getCurrentHitTestResult().mediaUrl.isValid())
1246 {
1247 handleDownloadRequested(QNetworkRequest(getCurrentHitTestResult().mediaUrl));
1248 }
1249
1250 break;
1251 case ActionsManager::CopyMediaUrlToClipboardAction:
1252 if (!getCurrentHitTestResult().mediaUrl.isEmpty())
1253 {
1254 Application::clipboard()->setText(getCurrentHitTestResult().mediaUrl.toString(QUrl::EncodeReserved | QUrl::EncodeSpaces));
1255 }
1256
1257 break;
1258 case ActionsManager::MediaControlsAction:
1259 m_page->triggerAction(QWebPage::ToggleMediaControls, parameters.value(QLatin1String("isChecked"), !getActionState(identifier, parameters).isChecked).toBool());
1260
1261 break;
1262 case ActionsManager::MediaLoopAction:
1263 m_page->triggerAction(QWebPage::ToggleMediaLoop, parameters.value(QLatin1String("isChecked"), !getActionState(identifier, parameters).isChecked).toBool());
1264
1265 break;
1266 case ActionsManager::MediaPlayPauseAction:
1267 m_page->triggerAction(QWebPage::ToggleMediaPlayPause);
1268
1269 break;
1270 case ActionsManager::MediaMuteAction:
1271 m_page->triggerAction(QWebPage::ToggleMediaMute);
1272
1273 break;
1274 case ActionsManager::MediaPlaybackRateAction:
1275 m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition).element().evaluateJavaScript(QStringLiteral("this.playbackRate = %1").arg(parameters.value(QLatin1String("rate"), 1).toReal()));
1276
1277 break;
1278 case ActionsManager::GoBackAction:
1279 m_page->triggerAction(QWebPage::Back);
1280
1281 break;
1282 case ActionsManager::GoForwardAction:
1283 m_page->triggerAction(QWebPage::Forward);
1284
1285 break;
1286 case ActionsManager::GoToHistoryIndexAction:
1287 if (parameters.contains(QLatin1String("index")))
1288 {
1289 const int index(parameters[QLatin1String("index")].toInt());
1290
1291 if (index >= 0 && index < m_page->history()->count())
1292 {
1293 m_page->history()->goToItem(m_page->history()->itemAt(index));
1294 }
1295 }
1296
1297 break;
1298 case ActionsManager::RewindAction:
1299 m_page->history()->goToItem(m_page->history()->itemAt(0));
1300
1301 break;
1302 case ActionsManager::FastForwardAction:
1303 {
1304 const QUrl url(m_page->mainFrame()->evaluateJavaScript(getFastForwardScript(true)).toUrl());
1305
1306 if (url.isValid())
1307 {
1308 setUrl(url);
1309 }
1310 else if (canGoForward())
1311 {
1312 m_page->triggerAction(QWebPage::Forward);
1313 }
1314 }
1315
1316 break;
1317 case ActionsManager::RemoveHistoryIndexAction:
1318 if (parameters.contains(QLatin1String("index")))
1319 {
1320 const int index(parameters[QLatin1String("index")].toInt());
1321
1322 if (index >= 0 && index < m_page->history()->count())
1323 {
1324 if (parameters.value(QLatin1String("clearGlobalHistory"), false).toBool())
1325 {
1326 const quint64 entryIdentifier(m_page->history()->itemAt(index).userData().toList().value(IdentifierEntryData).toULongLong());
1327
1328 if (entryIdentifier > 0)
1329 {
1330 HistoryManager::removeEntry(entryIdentifier);
1331 }
1332 }
1333
1334 WindowHistoryInformation history(getHistory());
1335 history.entries.removeAt(index);
1336
1337 if (history.index >= index)
1338 {
1339 history.index = (history.index - 1);
1340 }
1341
1342 setHistory(history);
1343 }
1344 }
1345
1346 break;
1347 case ActionsManager::StopAction:
1348 m_page->triggerAction(QWebPage::Stop);
1349
1350 break;
1351 case ActionsManager::StopScheduledReloadAction:
1352 m_page->triggerAction(QWebPage::StopScheduledPageRefresh);
1353
1354 break;
1355 case ActionsManager::ReloadAction:
1356 emit aboutToReload();
1357
1358 m_page->triggerAction(QWebPage::Stop);
1359 m_page->triggerAction(QWebPage::Reload);
1360
1361 break;
1362 case ActionsManager::ReloadOrStopAction:
1363 if (m_loadingState == OngoingLoadingState)
1364 {
1365 triggerAction(ActionsManager::StopAction, {}, trigger);
1366 }
1367 else
1368 {
1369 triggerAction(ActionsManager::ReloadAction, {}, trigger);
1370 }
1371
1372 break;
1373 case ActionsManager::ReloadAndBypassCacheAction:
1374 m_page->triggerAction(QWebPage::ReloadAndBypassCache);
1375
1376 break;
1377 case ActionsManager::ContextMenuAction:
1378 if (parameters.contains(QLatin1String("context")) && parameters[QLatin1String("context")].toInt() == QContextMenuEvent::Keyboard)
1379 {
1380 const QWebElement element(m_page->mainFrame()->findFirstElement(QLatin1String(":focus")));
1381
1382 if (element.isNull())
1383 {
1384 setClickPosition(m_webView->mapFromGlobal(QCursor::pos()));
1385 }
1386 else
1387 {
1388 QPoint clickPosition(element.geometry().center());
1389 QWebFrame *frame(element.webFrame());
1390
1391 while (frame)
1392 {
1393 clickPosition -= frame->scrollPosition();
1394
1395 frame = frame->parentFrame();
1396 }
1397
1398 setClickPosition(clickPosition);
1399 }
1400 }
1401 else
1402 {
1403 setClickPosition(m_webView->mapFromGlobal(QCursor::pos()));
1404 }
1405
1406 showContextMenu(getClickPosition());
1407
1408 break;
1409 case ActionsManager::UndoAction:
1410 m_page->triggerAction(QWebPage::Undo);
1411
1412 break;
1413 case ActionsManager::RedoAction:
1414 m_page->triggerAction(QWebPage::Redo);
1415
1416 break;
1417 case ActionsManager::CutAction:
1418 m_page->triggerAction(QWebPage::Cut);
1419
1420 break;
1421 case ActionsManager::CopyAction:
1422 if (parameters.value(QLatin1String("mode")) == QLatin1String("plainText"))
1423 {
1424 const QString text(getSelectedText());
1425
1426 if (!text.isEmpty())
1427 {
1428 Application::clipboard()->setText(text);
1429 }
1430 }
1431 else
1432 {
1433 m_page->triggerAction(QWebPage::Copy);
1434 }
1435
1436 break;
1437 case ActionsManager::CopyPlainTextAction:
1438 triggerAction(ActionsManager::CopyAction, {{QLatin1String("mode"), QLatin1String("plainText")}}, trigger);
1439
1440 break;
1441 case ActionsManager::CopyAddressAction:
1442 Application::clipboard()->setText(getUrl().toString());
1443
1444 break;
1445 case ActionsManager::CopyToNoteAction:
1446 NotesManager::addNote(BookmarksModel::UrlBookmark, {{BookmarksModel::UrlRole, getUrl()}, {BookmarksModel::DescriptionRole, getSelectedText()}});
1447
1448 break;
1449 case ActionsManager::PasteAction:
1450 if (parameters.contains(QLatin1String("note")))
1451 {
1452 const BookmarksModel::Bookmark *bookmark(NotesManager::getModel()->getBookmark(parameters[QLatin1String("note")].toULongLong()));
1453
1454 if (bookmark)
1455 {
1456 triggerAction(ActionsManager::PasteAction, {{QLatin1String("text"), bookmark->getDescription()}}, trigger);
1457 }
1458 }
1459 else if (parameters.contains(QLatin1String("text")))
1460 {
1461 QMimeData *mimeData(new QMimeData());
1462 const QStringList mimeTypes(Application::clipboard()->mimeData()->formats());
1463
1464 for (int i = 0; i < mimeTypes.count(); ++i)
1465 {
1466 mimeData->setData(mimeTypes.at(i), Application::clipboard()->mimeData()->data(mimeTypes.at(i)));
1467 }
1468
1469 Application::clipboard()->setText(parameters[QLatin1String("text")].toString());
1470
1471 m_page->triggerAction(QWebPage::Paste);
1472
1473 Application::clipboard()->setMimeData(mimeData);
1474 }
1475 else
1476 {
1477 m_page->triggerAction(QWebPage::Paste);
1478 }
1479
1480 break;
1481 case ActionsManager::DeleteAction:
1482 m_page->triggerAction(QWebPage::DeleteEndOfWord);
1483
1484 break;
1485 case ActionsManager::SelectAllAction:
1486 m_page->triggerAction(QWebPage::SelectAll);
1487
1488 break;
1489 case ActionsManager::UnselectAction:
1490 m_page->triggerAction(QWebPage::Unselect);
1491
1492 break;
1493 case ActionsManager::ClearAllAction:
1494 m_page->triggerAction(QWebPage::SelectAll);
1495 m_page->triggerAction(QWebPage::DeleteEndOfWord);
1496
1497 break;
1498 case ActionsManager::CheckSpellingAction:
1499 {
1500 QWebElement element(m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition).element());
1501 element.evaluateJavaScript(QStringLiteral("this.spellcheck = %1").arg(parameters.value(QLatin1String("isChecked"), !getActionState(identifier, parameters).isChecked).toBool() ? QLatin1String("true") : QLatin1String("false")));
1502
1503 if (parameters.contains(QLatin1String("dictionary")))
1504 {
1505 setOption(SettingsManager::Browser_SpellCheckDictionaryOption, parameters.value(QLatin1String("dictionary")));
1506 }
1507 else
1508 {
1509 resetSpellCheck(element);
1510 }
1511
1512 emit arbitraryActionsStateChanged({ActionsManager::CheckSpellingAction});
1513 }
1514
1515 break;
1516 case ActionsManager::CreateSearchAction:
1517 {
1518 const QWebHitTestResult hitResult(m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition));
1519 QWebElement parentElement(hitResult.element().parent());
1520
1521 while (!parentElement.isNull() && parentElement.tagName().toLower() != QLatin1String("form"))
1522 {
1523 parentElement = parentElement.parent();
1524 }
1525
1526 if (!parentElement.hasAttribute(QLatin1String("action")))
1527 {
1528 break;
1529 }
1530
1531 QList<QWebElement> inputElements(parentElement.findAll(QLatin1String("button:not([disabled])[name][type='submit'], input:not([disabled])[name], select:not([disabled])[name], textarea:not([disabled])[name]")).toList());
1532
1533 if (inputElements.isEmpty())
1534 {
1535 break;
1536 }
1537
1538 QWebElement searchTermsElement;
1539 const QString tagName(hitResult.element().tagName().toLower());
1540 const QUrl url(parentElement.attribute(QLatin1String("action")));
1541 const QIcon icon(m_page->mainFrame()->icon());
1542 SearchEnginesManager::SearchEngineDefinition searchEngine;
1543 searchEngine.title = getTitle();
1544 searchEngine.formUrl = getUrl();
1545 searchEngine.icon = (icon.isNull() ? ThemesManager::createIcon(QLatin1String("edit-find")) : icon);
1546 searchEngine.resultsUrl.url = (url.isEmpty() ? getUrl() : resolveUrl(parentElement.webFrame(), url)).toString();
1547 searchEngine.resultsUrl.enctype = parentElement.attribute(QLatin1String("enctype"));
1548 searchEngine.resultsUrl.method = ((parentElement.attribute(QLatin1String("method"), QLatin1String("get")).toLower() == QLatin1String("post")) ? QLatin1String("post") : QLatin1String("get"));
1549
1550 if (tagName != QLatin1String("select"))
1551 {
1552 const QString type(hitResult.element().attribute(QLatin1String("type")));
1553
1554 if (!inputElements.contains(hitResult.element()) && (type == QLatin1String("image") || type == QLatin1String("submit")))
1555 {
1556 inputElements.append(hitResult.element());
1557 }
1558
1559 if (tagName == QLatin1String("textarea") || type == QLatin1String("email") || type == QLatin1String("password") || type == QLatin1String("search") || type == QLatin1String("tel") || type == QLatin1String("text") || type == QLatin1String("url"))
1560 {
1561 searchTermsElement = hitResult.element();
1562 }
1563 }
1564
1565 if (searchTermsElement.isNull())
1566 {
1567 searchTermsElement = parentElement.findFirst(QLatin1String("input:not([disabled])[name][type='search']"));
1568 }
1569
1570 for (int i = 0; i < inputElements.count(); ++i)
1571 {
1572 const QString name(inputElements.at(i).attribute(QLatin1String("name")));
1573
1574 if (inputElements.at(i).tagName().toLower() != QLatin1String("select"))
1575 {
1576 const QString type(inputElements.at(i).attribute(QLatin1String("type")));
1577 const bool isSubmit(type == QLatin1String("image") || type == QLatin1String("submit"));
1578
1579 if ((isSubmit && inputElements.at(i) != hitResult.element()) || ((type == QLatin1String("checkbox") || type == QLatin1String("radio")) && !inputElements[i].evaluateJavaScript(QLatin1String("this.checked")).toBool()))
1580 {
1581 continue;
1582 }
1583
1584 if (isSubmit && inputElements.at(i) == hitResult.element())
1585 {
1586 if (inputElements.at(i).hasAttribute(QLatin1String("formaction")))
1587 {
1588 searchEngine.resultsUrl.url = resolveUrl(parentElement.webFrame(), inputElements.at(i).attribute(QLatin1String("formaction"))).toString();
1589 }
1590
1591 if (inputElements.at(i).hasAttribute(QLatin1String("formenctype")))
1592 {
1593 searchEngine.resultsUrl.enctype = inputElements.at(i).attribute(QLatin1String("formenctype"));
1594 }
1595
1596 if (inputElements.at(i).hasAttribute(QLatin1String("formmethod")))
1597 {
1598 searchEngine.resultsUrl.method = inputElements.at(i).attribute(QLatin1String("formmethod"));
1599 }
1600 }
1601
1602 if (!isSubmit && searchTermsElement.isNull() && type != QLatin1String("hidden"))
1603 {
1604 searchTermsElement = inputElements.at(i);
1605 }
1606
1607 if (!name.isEmpty())
1608 {
1609 searchEngine.resultsUrl.parameters.addQueryItem(name, ((inputElements.at(i) == searchTermsElement) ? QLatin1String("{searchTerms}") : inputElements[i].evaluateJavaScript((inputElements.at(i).tagName().toLower() == QLatin1String("button")) ? QLatin1String("this.innerHTML") : QLatin1String("this.value")).toString()));
1610 }
1611 }
1612 else if (!name.isEmpty())
1613 {
1614 const QWebElementCollection optionElements(inputElements.at(i).findAll(QLatin1String("option:checked")));
1615
1616 for (int j = 0; j < optionElements.count(); ++j)
1617 {
1618 searchEngine.resultsUrl.parameters.addQueryItem(name, optionElements.at(j).evaluateJavaScript(QLatin1String("this.value")).toString());
1619 }
1620 }
1621 }
1622
1623 SearchEnginePropertiesDialog dialog(searchEngine, SearchEnginesManager::getSearchKeywords(), this);
1624
1625 if (dialog.exec() == QDialog::Accepted)
1626 {
1627 SearchEnginesManager::addSearchEngine(dialog.getSearchEngine());
1628 }
1629 }
1630
1631 break;
1632 case ActionsManager::ScrollToStartAction:
1633 m_page->mainFrame()->setScrollPosition(QPoint(m_page->mainFrame()->scrollPosition().x(), 0));
1634
1635 break;
1636 case ActionsManager::ScrollToEndAction:
1637 m_page->mainFrame()->setScrollPosition(QPoint(m_page->mainFrame()->scrollPosition().x(), m_page->mainFrame()->scrollBarMaximum(Qt::Vertical)));
1638
1639 break;
1640 case ActionsManager::ScrollPageUpAction:
1641 m_page->mainFrame()->setScrollPosition(QPoint(m_page->mainFrame()->scrollPosition().x(), qMax(0, (m_page->mainFrame()->scrollPosition().y() - m_webView->height()))));
1642
1643 break;
1644 case ActionsManager::ScrollPageDownAction:
1645 m_page->mainFrame()->setScrollPosition(QPoint(m_page->mainFrame()->scrollPosition().x(), qMin(m_page->mainFrame()->scrollBarMaximum(Qt::Vertical), (m_page->mainFrame()->scrollPosition().y() + m_webView->height()))));
1646
1647 break;
1648 case ActionsManager::ScrollPageLeftAction:
1649 m_page->mainFrame()->setScrollPosition(QPoint(qMax(0, (m_page->mainFrame()->scrollPosition().x() - m_webView->width())), m_page->mainFrame()->scrollPosition().y()));
1650
1651 break;
1652 case ActionsManager::ScrollPageRightAction:
1653 m_page->mainFrame()->setScrollPosition(QPoint(qMin(m_page->mainFrame()->scrollBarMaximum(Qt::Horizontal), (m_page->mainFrame()->scrollPosition().x() + m_webView->width())), m_page->mainFrame()->scrollPosition().y()));
1654
1655 break;
1656 case ActionsManager::TakeScreenshotAction:
1657 {
1658 const QString mode(parameters.value(QLatin1String("mode"), QLatin1String("viewport")).toString());
1659 const QSize viewportSize(m_page->viewportSize());
1660 const QSize contentsSize((mode == QLatin1String("viewport")) ? viewportSize : m_page->mainFrame()->contentsSize());
1661 const QRect rectangle((mode == QLatin1String("area")) ? JsonSettings::readRectangle(parameters.value(QLatin1String("geometry"))) : QRect());
1662 QPixmap pixmap(rectangle.isValid() ? rectangle.size() : contentsSize);
1663 QPainter painter(&pixmap);
1664
1665 m_page->setViewportSize(contentsSize);
1666
1667 if (rectangle.isValid())
1668 {
1669 painter.translate(-rectangle.topLeft());
1670
1671 m_page->mainFrame()->render(&painter, {rectangle});
1672 }
1673 else
1674 {
1675 m_page->mainFrame()->render(&painter);
1676 }
1677
1678 m_page->setViewportSize(viewportSize);
1679
1680 const QStringList filters({tr("PNG image (*.png)"), tr("JPEG image (*.jpg *.jpeg)")});
1681 const SaveInformation result(Utils::getSavePath(suggestSaveFileName(QLatin1String(".png")), {}, filters));
1682
1683 if (result.canSave)
1684 {
1685 pixmap.save(result.path, ((filters.indexOf(result.filter) == 0) ? "PNG" : "JPEG"));
1686 }
1687 }
1688
1689 break;
1690 case ActionsManager::ActivateContentAction:
1691 {
1692 m_webView->setFocus();
1693
1694 m_page->mainFrame()->setFocus();
1695
1696 const QString tagName(m_page->mainFrame()->findFirstElement(QLatin1String(":focus")).tagName().toLower());
1697
1698 if (tagName == QLatin1String("textarea") || tagName == QLatin1String("input"))
1699 {
1700 m_page->mainFrame()->documentElement().evaluateJavaScript(QLatin1String("document.activeElement.blur()"));
1701 }
1702 }
1703
1704 break;
1705 case ActionsManager::LoadPluginsAction:
1706 {
1707 m_canLoadPlugins = true;
1708
1709 QList<QWebFrame*> frames({m_page->mainFrame()});
1710
1711 while (!frames.isEmpty())
1712 {
1713 const QWebFrame *frame(frames.takeFirst());
1714 const QWebElementCollection elements(frame->documentElement().findAll(QLatin1String("object, embed")));
1715
1716 for (int i = 0; i < elements.count(); ++i)
1717 {
1718 elements.at(i).replace(elements.at(i).clone());
1719 }
1720
1721 frames.append(frame->childFrames());
1722 }
1723
1724 m_amountOfDeferredPlugins = 0;
1725
1726 emit arbitraryActionsStateChanged({ActionsManager::LoadPluginsAction});
1727 }
1728
1729 break;
1730 case ActionsManager::ViewSourceAction:
1731 if (canViewSource())
1732 {
1733 const QString defaultEncoding(m_page->settings()->defaultTextEncoding());
1734 QNetworkRequest request(getUrl());
1735 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
1736
1737 QNetworkReply *reply(m_networkManager->get(request));
1738 SourceViewerWebWidget *sourceViewer(new SourceViewerWebWidget(isPrivate()));
1739 sourceViewer->setRequestedUrl(QUrl(QLatin1String("view-source:") + getUrl().toString()), false);
1740
1741 if (!defaultEncoding.isEmpty())
1742 {
1743 sourceViewer->setOption(SettingsManager::Content_DefaultCharacterEncodingOption, defaultEncoding);
1744 }
1745
1746 m_viewSourceReplies[reply] = sourceViewer;
1747
1748 connect(reply, &QNetworkReply::finished, this, &QtWebKitWebWidget::handleViewSourceReplyFinished);
1749
1750 emit requestedNewWindow(sourceViewer, SessionsManager::DefaultOpen, {});
1751 }
1752
1753 break;
1754 case ActionsManager::InspectPageAction:
1755 {
1756 const bool showInspector(parameters.value(QLatin1String("isChecked"), !getActionState(identifier, parameters).isChecked).toBool());
1757
1758 if (showInspector && !m_inspector)
1759 {
1760 getInspector();
1761 }
1762
1763 emit requestedInspectorVisibilityChange(showInspector);
1764 emit arbitraryActionsStateChanged({ActionsManager::InspectPageAction});
1765 }
1766
1767 break;
1768 case ActionsManager::InspectElementAction:
1769 triggerAction(ActionsManager::InspectPageAction, {{QLatin1String("isChecked"), true}}, trigger);
1770
1771 m_page->triggerAction(QWebPage::InspectElement);
1772
1773 break;
1774 case ActionsManager::FullScreenAction:
1775 {
1776 const MainWindow *mainWindow(MainWindow::findMainWindow(this));
1777
1778 if (mainWindow && !mainWindow->isFullScreen())
1779 {
1780 m_page->mainFrame()->evaluateJavaScript(QLatin1String("document.webkitExitFullscreen()"));
1781 }
1782 }
1783
1784 break;
1785 case ActionsManager::WebsitePreferencesAction:
1786 {
1787 const QUrl url(getUrl());
1788 CookieJar *cookieJar(m_networkManager->getCookieJar());
1789 WebsitePreferencesDialog dialog(Utils::extractHost(url), (url.host().isEmpty() ? QVector<QNetworkCookie>() : cookieJar->getCookies(url.host())), this);
1790
1791 if (dialog.exec() == QDialog::Accepted)
1792 {
1793 updateOptions(url);
1794
1795 const QVector<QNetworkCookie> cookiesToDelete(dialog.getCookiesToDelete());
1796 const QVector<QNetworkCookie> cookiesToInsert(dialog.getCookiesToInsert());
1797
1798 for (int i = 0; i < cookiesToDelete.count(); ++i)
1799 {
1800 cookieJar->forceDeleteCookie(cookiesToDelete.at(i));
1801 }
1802
1803 for (int i = 0; i < cookiesToInsert.count(); ++i)
1804 {
1805 cookieJar->forceInsertCookie(cookiesToInsert.at(i));
1806 }
1807 }
1808 }
1809
1810 break;
1811 default:
1812 break;
1813 }
1814 }
1815
setActiveStyleSheet(const QString & styleSheet)1816 void QtWebKitWebWidget::setActiveStyleSheet(const QString &styleSheet)
1817 {
1818 const QWebElementCollection elements(m_page->mainFrame()->findAllElements(QLatin1String("link[rel='alternate stylesheet']")));
1819
1820 for (int i = 0; i < elements.count(); ++i)
1821 {
1822 elements.at(i).evaluateJavaScript(QLatin1String("this.disabled = ") + ((elements.at(i).attribute(QLatin1String("title")) == styleSheet) ? QLatin1String("false") : QLatin1String("true")));
1823 }
1824 }
1825
setHistory(const WindowHistoryInformation & history)1826 void QtWebKitWebWidget::setHistory(const WindowHistoryInformation &history)
1827 {
1828 if (history.entries.isEmpty())
1829 {
1830 m_page->history()->clear();
1831
1832 updateOptions({});
1833
1834 if (m_page->getMainFrame())
1835 {
1836 m_page->getMainFrame()->runUserScripts(QUrl(QLatin1String("about:blank")));
1837 }
1838
1839 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::NavigationCategory, ActionsManager::ActionDefinition::PageCategory});
1840
1841 return;
1842 }
1843
1844 const int index(qMin(history.index, (m_page->history()->maximumItemCount() - 1)));
1845 QVariantList entries;
1846 entries.reserve(history.entries.count());
1847
1848 for (int i = 0; i < history.entries.count(); ++i)
1849 {
1850 QVariantMap entry;
1851 entry[QLatin1String("pageScaleFactor")] = 0;
1852 entry[QLatin1String("title")] = history.entries.at(i).title;
1853 entry[QLatin1String("urlString")] = QString(QUrl::fromUserInput(history.entries.at(i).url).toEncoded());
1854 entry[QLatin1String("scrollPosition")] = QVariantMap({{QLatin1String("x"), history.entries.at(i).position.x()}, {QLatin1String("y"), history.entries.at(i).position.y()}});
1855
1856 entries.append(entry);
1857 }
1858
1859 m_page->history()->loadFromMap({{QLatin1String("currentItemIndex"), index}, {QLatin1String("history"), entries}});
1860
1861 for (int i = 0; i < history.entries.count(); ++i)
1862 {
1863 m_page->history()->itemAt(i).setUserData(QVariantList({-1, history.entries.at(i).zoom, history.entries.at(i).position, history.entries.at(i).time}));
1864 }
1865
1866 m_page->history()->goToItem(m_page->history()->itemAt(index));
1867
1868 const QUrl url(m_page->history()->itemAt(index).url());
1869
1870 setRequestedUrl(url, false, true);
1871 updateOptions(url);
1872
1873 m_page->triggerAction(QWebPage::Reload);
1874
1875 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::PageCategory});
1876 }
1877
setHistory(const QVariantMap & history)1878 void QtWebKitWebWidget::setHistory(const QVariantMap &history)
1879 {
1880 m_page->history()->loadFromMap(history);
1881
1882 const QUrl url(m_page->history()->currentItem().url());
1883
1884 setRequestedUrl(url, false, true);
1885 updateOptions(url);
1886
1887 m_page->triggerAction(QWebPage::Reload);
1888
1889 emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::PageCategory});
1890 }
1891
setZoom(int zoom)1892 void QtWebKitWebWidget::setZoom(int zoom)
1893 {
1894 if (zoom != getZoom())
1895 {
1896 m_page->mainFrame()->setZoomFactor(qBound(0.1, (static_cast<qreal>(zoom) / 100), static_cast<qreal>(100)));
1897
1898 SessionsManager::markSessionAsModified();
1899
1900 emit zoomChanged(zoom);
1901 emit geometryChanged();
1902 }
1903 }
1904
setUrl(const QUrl & url,bool isTyped)1905 void QtWebKitWebWidget::setUrl(const QUrl &url, bool isTyped)
1906 {
1907 if (url.scheme() == QLatin1String("javascript"))
1908 {
1909 m_page->mainFrame()->documentElement().evaluateJavaScript(url.toDisplayString(QUrl::RemoveScheme | QUrl::DecodeReserved));
1910 }
1911 else if (!url.fragment().isEmpty() && url.matches(getUrl(), (QUrl::RemoveFragment | QUrl::StripTrailingSlash | QUrl::NormalizePathSegments)))
1912 {
1913 m_page->mainFrame()->scrollToAnchor(url.fragment());
1914 }
1915 else
1916 {
1917 m_isTyped = isTyped;
1918
1919 const QUrl targetUrl(Utils::expandUrl(url));
1920
1921 updateOptions(targetUrl);
1922
1923 m_page->mainFrame()->load(targetUrl);
1924
1925 notifyTitleChanged();
1926 notifyIconChanged();
1927 }
1928 }
1929
setPermission(FeaturePermission feature,const QUrl & url,PermissionPolicies policies)1930 void QtWebKitWebWidget::setPermission(FeaturePermission feature, const QUrl &url, PermissionPolicies policies)
1931 {
1932 WebWidget::setPermission(feature, url, policies);
1933
1934 if (feature == FullScreenFeature)
1935 {
1936 if (policies.testFlag(GrantedPermission))
1937 {
1938 MainWindow *mainWindow(MainWindow::findMainWindow(this));
1939
1940 if (mainWindow && !mainWindow->isFullScreen())
1941 {
1942 mainWindow->triggerAction(ActionsManager::FullScreenAction);
1943 }
1944 }
1945
1946 return;
1947 }
1948
1949 QWebPage::Feature nativeFeature(QWebPage::Geolocation);
1950
1951 switch (feature)
1952 {
1953 case GeolocationFeature:
1954 nativeFeature = QWebPage::Geolocation;
1955
1956 break;
1957 case NotificationsFeature:
1958 nativeFeature = QWebPage::Notifications;
1959
1960 break;
1961 default:
1962 return;
1963 }
1964
1965 QList<QWebFrame*> frames({m_page->mainFrame()});
1966
1967 while (!frames.isEmpty())
1968 {
1969 QWebFrame *frame(frames.takeFirst());
1970
1971 if (frame->requestedUrl() == url)
1972 {
1973 m_page->setFeaturePermission(frame, nativeFeature, (policies.testFlag(GrantedPermission) ? QWebPage::PermissionGrantedByUser : QWebPage::PermissionDeniedByUser));
1974 }
1975
1976 frames.append(frame->childFrames());
1977 }
1978 }
1979
setOption(int identifier,const QVariant & value)1980 void QtWebKitWebWidget::setOption(int identifier, const QVariant &value)
1981 {
1982 WebWidget::setOption(identifier, value);
1983
1984 updateOptions(getUrl());
1985
1986 switch (identifier)
1987 {
1988 case SettingsManager::Content_DefaultCharacterEncodingOption:
1989 m_page->triggerAction(QWebPage::Reload);
1990
1991 break;
1992 case SettingsManager::Browser_SpellCheckDictionaryOption:
1993 emit widgetActivated(this);
1994
1995 resetSpellCheck(m_page->mainFrame()->hitTestContent(getCurrentHitTestResult().hitPosition).element());
1996
1997 break;
1998 default:
1999 break;
2000 }
2001 }
2002
setOptions(const QHash<int,QVariant> & options,const QStringList & excludedOptions)2003 void QtWebKitWebWidget::setOptions(const QHash<int, QVariant> &options, const QStringList &excludedOptions)
2004 {
2005 WebWidget::setOptions(options, excludedOptions);
2006
2007 updateOptions(getUrl());
2008 }
2009
setScrollPosition(const QPoint & position)2010 void QtWebKitWebWidget::setScrollPosition(const QPoint &position)
2011 {
2012 m_page->mainFrame()->setScrollPosition(position);
2013 }
2014
clone(bool cloneHistory,bool isPrivate,const QStringList & excludedOptions) const2015 WebWidget* QtWebKitWebWidget::clone(bool cloneHistory, bool isPrivate, const QStringList &excludedOptions) const
2016 {
2017 QtWebKitNetworkManager *networkManager((this->isPrivate() != isPrivate) ? nullptr : m_networkManager->clone());
2018 QtWebKitWebWidget *widget((this->isPrivate() || isPrivate) ? new QtWebKitWebWidget({{QLatin1String("hints"), SessionsManager::PrivateOpen}}, getBackend(), networkManager, nullptr) : new QtWebKitWebWidget({}, getBackend(), networkManager, nullptr));
2019 widget->getPage()->setViewportSize(m_page->viewportSize());
2020 widget->setOptions(getOptions(), excludedOptions);
2021
2022 if (cloneHistory)
2023 {
2024 widget->setHistory(m_page->history()->toMap());
2025 }
2026
2027 widget->setZoom(getZoom());
2028
2029 return widget;
2030 }
2031
getInspector()2032 QWidget* QtWebKitWebWidget::getInspector()
2033 {
2034 if (!m_inspector)
2035 {
2036 m_page->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
2037
2038 m_inspector = new QtWebKitInspector(this);
2039 m_inspector->setPage(m_page);
2040 }
2041
2042 return m_inspector;
2043 }
2044
getViewport()2045 QWidget* QtWebKitWebWidget::getViewport()
2046 {
2047 return m_webView;
2048 }
2049
getPage() const2050 QtWebKitPage* QtWebKitWebWidget::getPage() const
2051 {
2052 return m_page;
2053 }
2054
getTitle() const2055 QString QtWebKitWebWidget::getTitle() const
2056 {
2057 const QString title(m_page->mainFrame()->title());
2058
2059 if (!title.isEmpty())
2060 {
2061 return title;
2062 }
2063
2064 const QUrl url(getUrl());
2065
2066 if (Utils::isUrlEmpty(url))
2067 {
2068 return tr("Blank Page");
2069 }
2070
2071 if (url.isLocalFile())
2072 {
2073 return QFileInfo(url.toLocalFile()).canonicalFilePath();
2074 }
2075
2076 if (!url.isEmpty())
2077 {
2078 return url.toString();
2079 }
2080
2081 return tr("(Untitled)");
2082 }
2083
getDescription() const2084 QString QtWebKitWebWidget::getDescription() const
2085 {
2086 const QString description(m_page->mainFrame()->findFirstElement(QLatin1String("[name='description']")).attribute(QLatin1String("content")));
2087
2088 return (description.isEmpty() ? m_page->mainFrame()->findFirstElement(QLatin1String("[name='og:description']")).attribute(QLatin1String("property")) : description);
2089 }
2090
getActiveStyleSheet() const2091 QString QtWebKitWebWidget::getActiveStyleSheet() const
2092 {
2093 if (m_page->mainFrame()->documentElement().evaluateJavaScript(QLatin1String("var isDefault = true; for (var i = 0; i < document.styleSheets.length; ++i) { if (document.styleSheets[i].ownerNode.rel.indexOf('alt') >= 0) { isDefault = false; break; } } isDefault")).toBool())
2094 {
2095 return {};
2096 }
2097
2098 return m_page->mainFrame()->findFirstElement(QLatin1String("link[rel='alternate stylesheet']:not([disabled])")).attribute(QLatin1String("title"));
2099 }
2100
getSelectedText() const2101 QString QtWebKitWebWidget::getSelectedText() const
2102 {
2103 return m_page->selectedText();
2104 }
2105
getMessageToken() const2106 QString QtWebKitWebWidget::getMessageToken() const
2107 {
2108 return m_messageToken;
2109 }
2110
getPluginToken() const2111 QString QtWebKitWebWidget::getPluginToken() const
2112 {
2113 return m_pluginToken;
2114 }
2115
getPageInformation(PageInformation key) const2116 QVariant QtWebKitWebWidget::getPageInformation(PageInformation key) const
2117 {
2118 if (key == LoadingTimeInformation)
2119 {
2120 return WebWidget::getPageInformation(LoadingTimeInformation);
2121 }
2122
2123 return m_networkManager->getPageInformation(key);
2124 }
2125
resolveUrl(QWebFrame * frame,const QUrl & url) const2126 QUrl QtWebKitWebWidget::resolveUrl(QWebFrame *frame, const QUrl &url) const
2127 {
2128 if (url.isRelative())
2129 {
2130 return (frame ? frame->baseUrl() : getUrl()).resolved(url);
2131 }
2132
2133 return url;
2134 }
2135
getUrl() const2136 QUrl QtWebKitWebWidget::getUrl() const
2137 {
2138 const QUrl url(m_page->mainFrame()->url());
2139
2140 return (Utils::isUrlEmpty(url) ? m_page->mainFrame()->requestedUrl() : url);
2141 }
2142
getIcon() const2143 QIcon QtWebKitWebWidget::getIcon() const
2144 {
2145 if (isPrivate())
2146 {
2147 return ThemesManager::createIcon(QLatin1String("tab-private"));
2148 }
2149
2150 const QIcon icon(m_page->mainFrame()->icon());
2151
2152 return (icon.isNull() ? ThemesManager::createIcon(QLatin1String("tab")) : icon);
2153 }
2154
createThumbnail(const QSize & size)2155 QPixmap QtWebKitWebWidget::createThumbnail(const QSize &size)
2156 {
2157 if ((size.isNull() || size == m_thumbnail.size()) && ((!m_thumbnail.isNull() && qFuzzyCompare(m_thumbnail.devicePixelRatio(), devicePixelRatio())) || m_loadingState == OngoingLoadingState))
2158 {
2159 return m_thumbnail;
2160 }
2161
2162 const QSize thumbnailSize(size.isValid() ? size : QSize(260, 170));
2163 const QSize oldViewportSize(m_page->viewportSize());
2164 const QPoint position(m_page->mainFrame()->scrollPosition());
2165 const qreal zoom(m_page->mainFrame()->zoomFactor());
2166 QSize contentsSize(m_page->mainFrame()->contentsSize());
2167 QWidget *newView(new QWidget());
2168 QWidget *oldView(m_page->view());
2169
2170 m_page->setView(newView);
2171 m_page->setViewportSize(contentsSize);
2172 m_page->mainFrame()->setZoomFactor(1);
2173
2174 if (contentsSize.width() > 2000)
2175 {
2176 contentsSize.setWidth(2000);
2177 }
2178
2179 contentsSize.setHeight(qRound(thumbnailSize.height() * (static_cast<qreal>(contentsSize.width()) / thumbnailSize.width())));
2180
2181 m_page->setViewportSize(contentsSize);
2182
2183 QPixmap pixmap(contentsSize);
2184 pixmap.fill(Qt::white);
2185
2186 QPainter painter(&pixmap);
2187
2188 m_page->mainFrame()->render(&painter, QWebFrame::ContentsLayer, QRegion(QRect(QPoint(0, 0), contentsSize)));
2189 m_page->mainFrame()->setZoomFactor(zoom);
2190 m_page->setView(oldView);
2191 m_page->setViewportSize(oldViewportSize);
2192 m_page->mainFrame()->setScrollPosition(position);
2193
2194 painter.end();
2195
2196 pixmap = pixmap.scaled((thumbnailSize * devicePixelRatio()), Qt::KeepAspectRatio, Qt::SmoothTransformation);
2197 pixmap.setDevicePixelRatio(devicePixelRatio());
2198
2199 newView->deleteLater();
2200
2201 if (size.isNull())
2202 {
2203 m_thumbnail = pixmap;
2204 }
2205
2206 return pixmap;
2207 }
2208
getScrollPosition() const2209 QPoint QtWebKitWebWidget::getScrollPosition() const
2210 {
2211 return m_page->mainFrame()->scrollPosition();
2212 }
2213
getGeometry(bool excludeScrollBars) const2214 QRect QtWebKitWebWidget::getGeometry(bool excludeScrollBars) const
2215 {
2216 if (!excludeScrollBars)
2217 {
2218 return geometry();
2219 }
2220
2221 QRect geometry(this->geometry());
2222 const QRect horizontalScrollBar(m_page->mainFrame()->scrollBarGeometry(Qt::Horizontal));
2223 const QRect verticalScrollBar(m_page->mainFrame()->scrollBarGeometry(Qt::Vertical));
2224
2225 if (horizontalScrollBar.height() > 0 && geometry.intersects(horizontalScrollBar))
2226 {
2227 geometry.moveTop(m_webView->height() - horizontalScrollBar.height());
2228 }
2229
2230 if (verticalScrollBar.width() > 0 && geometry.intersects(verticalScrollBar))
2231 {
2232 if (verticalScrollBar.left() == 0)
2233 {
2234 geometry.setLeft(verticalScrollBar.right());
2235 }
2236 else
2237 {
2238 geometry.setRight(verticalScrollBar.left());
2239 }
2240 }
2241
2242 return geometry;
2243 }
2244
getActiveFrame() const2245 WebWidget::LinkUrl QtWebKitWebWidget::getActiveFrame() const
2246 {
2247 const QWebFrame *frame(m_page->frameAt(getClickPosition()));
2248 LinkUrl link;
2249
2250 if (frame && frame != m_page->mainFrame())
2251 {
2252 link.title = frame->title();
2253 link.url = (frame->url().isValid() ? frame->url() : frame->requestedUrl());
2254 }
2255
2256 return link;
2257 }
2258
getActiveImage() const2259 WebWidget::LinkUrl QtWebKitWebWidget::getActiveImage() const
2260 {
2261 const QWebHitTestResult result(m_page->mainFrame()->hitTestContent(getClickPosition()));
2262 LinkUrl link;
2263
2264 if (!result.imageUrl().isEmpty())
2265 {
2266 link.title = result.alternateText();
2267 link.url = result.imageUrl();
2268 }
2269
2270 return link;
2271 }
2272
getActiveLink() const2273 WebWidget::LinkUrl QtWebKitWebWidget::getActiveLink() const
2274 {
2275 const QWebHitTestResult result(m_page->mainFrame()->hitTestContent(getClickPosition()));
2276 LinkUrl link;
2277
2278 if (!result.linkElement().isNull())
2279 {
2280 link.title = result.linkElement().toPlainText();
2281 link.url = result.linkUrl();
2282 }
2283
2284 return link;
2285 }
2286
getActiveMedia() const2287 WebWidget::LinkUrl QtWebKitWebWidget::getActiveMedia() const
2288 {
2289 const QWebHitTestResult result(m_page->mainFrame()->hitTestContent(getClickPosition()));
2290 LinkUrl link;
2291
2292 if (!result.mediaUrl().isEmpty())
2293 {
2294 link.title = result.title();
2295 link.url = result.mediaUrl();
2296 }
2297
2298 return link;
2299 }
2300
getSslInformation() const2301 WebWidget::SslInformation QtWebKitWebWidget::getSslInformation() const
2302 {
2303 return m_networkManager->getSslInformation();
2304 }
2305
getHistory() const2306 WindowHistoryInformation QtWebKitWebWidget::getHistory() const
2307 {
2308 QVariantList state(m_page->history()->currentItem().userData().toList());
2309
2310 if (state.isEmpty() || state.count() < 4)
2311 {
2312 state = {0, getZoom(), m_page->mainFrame()->scrollPosition(), QDateTime::currentDateTimeUtc()};
2313 }
2314 else
2315 {
2316 state[ZoomEntryData] = getZoom();
2317 state[PositionEntryData] = m_page->mainFrame()->scrollPosition();
2318 }
2319
2320 m_page->history()->currentItem().setUserData(state);
2321
2322 const QWebHistory *history(m_page->history());
2323 const QUrl requestedUrl(m_page->mainFrame()->requestedUrl());
2324 const int historyCount(history->count());
2325 WindowHistoryInformation information;
2326 information.entries.reserve(historyCount);
2327 information.index = history->currentItemIndex();
2328
2329 for (int i = 0; i < historyCount; ++i)
2330 {
2331 const QWebHistoryItem item(history->itemAt(i));
2332 const QVariantList itemState(item.userData().toList());
2333 WindowHistoryEntry entry;
2334 entry.url = item.url().toString();
2335 entry.title = item.title();
2336 entry.time = itemState.value(VisitTimeEntryData).toDateTime();
2337 entry.position = itemState.value(PositionEntryData, QPoint(0, 0)).toPoint();
2338 entry.zoom = itemState.value(ZoomEntryData).toInt();
2339
2340 const quint64 identifier(itemState.value(IdentifierEntryData).toULongLong());
2341
2342 if (identifier > 0)
2343 {
2344 const HistoryModel::Entry *globalEntry(HistoryManager::getEntry(identifier));
2345
2346 if (globalEntry)
2347 {
2348 entry.icon = globalEntry->icon();
2349 }
2350 }
2351
2352 information.entries.append(entry);
2353 }
2354
2355 if (m_loadingState == OngoingLoadingState && requestedUrl != history->itemAt(history->currentItemIndex()).url())
2356 {
2357 WindowHistoryEntry entry;
2358 entry.url = requestedUrl.toString();
2359 entry.title = getTitle();
2360 entry.position = state.value(PositionEntryData, QPoint(0, 0)).toPoint();
2361 entry.zoom = state.value(ZoomEntryData).toInt();
2362
2363 information.index = historyCount;
2364 information.entries.append(entry);
2365 }
2366
2367 return information;
2368 }
2369
getHitTestResult(const QPoint & position)2370 WebWidget::HitTestResult QtWebKitWebWidget::getHitTestResult(const QPoint &position)
2371 {
2372 const QWebFrame *frame(m_page->frameAt(position));
2373
2374 if (!frame)
2375 {
2376 return {};
2377 }
2378
2379 const QWebHitTestResult nativeResult(m_page->mainFrame()->hitTestContent(position));
2380 HitTestResult result;
2381 result.title = nativeResult.title();
2382 result.tagName = nativeResult.element().tagName().toLower();
2383 result.alternateText = nativeResult.element().attribute(QLatin1String("alt"));
2384 result.longDescription = nativeResult.element().attribute(QLatin1String("longDesc"));
2385 result.frameUrl = ((nativeResult.frame() && nativeResult.frame() != m_page->mainFrame()) ? (nativeResult.frame()->url().isValid() ? nativeResult.frame()->url() : nativeResult.frame()->requestedUrl()) : QUrl());
2386 result.imageUrl = nativeResult.imageUrl();
2387 result.linkUrl = nativeResult.linkUrl();
2388 result.mediaUrl = nativeResult.mediaUrl();
2389 result.hitPosition = position;
2390 result.elementGeometry = nativeResult.element().geometry();
2391
2392 const QString type(nativeResult.element().attribute(QLatin1String("type")).toLower());
2393 const bool isSubmit((result.tagName == QLatin1String("button") || result.tagName == QLatin1String("input")) && (type == QLatin1String("image") || type == QLatin1String("submit")));
2394
2395 if (isSubmit || result.tagName == QLatin1String("button") || result.tagName == QLatin1String("input") || result.tagName == QLatin1String("select") || result.tagName == QLatin1String("textarea"))
2396 {
2397 QWebElement parentElement(nativeResult.element().parent());
2398
2399 while (!parentElement.isNull() && parentElement.tagName().toLower() != QLatin1String("form"))
2400 {
2401 parentElement = parentElement.parent();
2402 }
2403
2404 if (!parentElement.isNull() && parentElement.hasAttribute(QLatin1String("action")))
2405 {
2406 if (isSubmit)
2407 {
2408 const QUrl url(nativeResult.element().hasAttribute(QLatin1String("formaction")) ? nativeResult.element().attribute(QLatin1String("formaction")) : parentElement.attribute(QLatin1String("action")));
2409
2410 result.formUrl = (url.isEmpty() ? getUrl() : resolveUrl(parentElement.webFrame(), url)).toString();
2411 }
2412
2413 if (parentElement.findAll(QLatin1String("input:not([disabled])[name], select:not([disabled])[name], textarea:not([disabled])[name]")).count() > 0)
2414 {
2415 result.flags |= HitTestResult::IsFormTest;
2416 }
2417 }
2418 }
2419
2420 if (result.tagName == QLatin1String("textarea") || (result.tagName == QLatin1String("input") && (type.isEmpty() || type == QLatin1String("text") || type == QLatin1String("password") || type == QLatin1String("search"))))
2421 {
2422 if (!nativeResult.element().hasAttribute(QLatin1String("disabled")) && !nativeResult.element().hasAttribute(QLatin1String("readonly")))
2423 {
2424 result.flags |= HitTestResult::IsContentEditableTest;
2425 }
2426
2427 if (nativeResult.element().evaluateJavaScript(QLatin1String("this.value")).toString().isEmpty())
2428 {
2429 result.flags |= HitTestResult::IsEmptyTest;
2430 }
2431 }
2432 else if (nativeResult.isContentEditable())
2433 {
2434 result.flags |= HitTestResult::IsContentEditableTest;
2435 }
2436
2437 if (nativeResult.isContentSelected())
2438 {
2439 result.flags |= HitTestResult::IsSelectedTest;
2440 }
2441
2442 if (nativeResult.element().evaluateJavaScript(QLatin1String("this.spellcheck")).toBool())
2443 {
2444 result.flags |= HitTestResult::IsSpellCheckEnabled;
2445 }
2446
2447 if (result.mediaUrl.isValid())
2448 {
2449 if (nativeResult.element().evaluateJavaScript(QLatin1String("this.controls")).toBool())
2450 {
2451 result.flags |= HitTestResult::MediaHasControlsTest;
2452 }
2453
2454 if (nativeResult.element().evaluateJavaScript(QLatin1String("this.looped")).toBool())
2455 {
2456 result.flags |= HitTestResult::MediaIsLoopedTest;
2457 }
2458
2459 if (nativeResult.element().evaluateJavaScript(QLatin1String("this.muted")).toBool())
2460 {
2461 result.flags |= HitTestResult::MediaIsMutedTest;
2462 }
2463
2464 if (nativeResult.element().evaluateJavaScript(QLatin1String("this.paused")).toBool())
2465 {
2466 result.flags |= HitTestResult::MediaIsPausedTest;
2467 }
2468
2469 result.playbackRate = nativeResult.element().evaluateJavaScript(QLatin1String("this.playbackRate")).toReal();
2470 }
2471
2472 if (result.flags.testFlag(HitTestResult::IsSelectedTest) && !result.linkUrl.isValid() && Utils::isUrl(m_page->selectedText()))
2473 {
2474 result.flags |= HitTestResult::IsLinkFromSelectionTest;
2475 result.linkUrl = QUrl::fromUserInput(m_page->selectedText());
2476 }
2477
2478 return result;
2479 }
2480
getBlockedElements() const2481 QStringList QtWebKitWebWidget::getBlockedElements() const
2482 {
2483 return m_networkManager->getBlockedElements();
2484 }
2485
getStyleSheets() const2486 QStringList QtWebKitWebWidget::getStyleSheets() const
2487 {
2488 const QWebElementCollection elements(m_page->mainFrame()->findAllElements(QLatin1String("link[rel='alternate stylesheet']")));
2489 QStringList titles;
2490 titles.reserve(elements.count());
2491
2492 for (int i = 0; i < elements.count(); ++i)
2493 {
2494 const QString title(elements.at(i).attribute(QLatin1String("title")));
2495
2496 if (!title.isEmpty() && !titles.contains(title))
2497 {
2498 titles.append(title);
2499 }
2500 }
2501
2502 return titles;
2503 }
2504
getFeeds() const2505 QVector<WebWidget::LinkUrl> QtWebKitWebWidget::getFeeds() const
2506 {
2507 return getLinks(QLatin1String("a[type='application/atom+xml'], a[type='application/rss+xml'], link[type='application/atom+xml'], link[type='application/rss+xml']"));
2508 }
2509
getLinks(const QString & query) const2510 QVector<WebWidget::LinkUrl> QtWebKitWebWidget::getLinks(const QString &query) const
2511 {
2512 const QWebElementCollection elements(m_page->mainFrame()->findAllElements(query));
2513 QSet<QUrl> urls;
2514 urls.reserve(elements.count());
2515
2516 QVector<LinkUrl> links;
2517 links.reserve(elements.count());
2518
2519 for (int i = 0; i < elements.count(); ++i)
2520 {
2521 const QUrl url(resolveUrl(m_page->mainFrame(), QUrl(elements.at(i).attribute(QLatin1String("href")))));
2522
2523 if (urls.contains(url))
2524 {
2525 continue;
2526 }
2527
2528 urls.insert(url);
2529
2530 LinkUrl link;
2531 link.title = elements.at(i).attribute(QLatin1String("title"));
2532 link.mimeType = elements.at(i).attribute(QLatin1String("type"));
2533 link.url = url;
2534
2535 if (link.title.isEmpty())
2536 {
2537 link.title = elements.at(i).toPlainText().simplified();
2538 }
2539
2540 if (link.title.isEmpty())
2541 {
2542 const QWebElement imageElement(elements.at(i).findFirst(QLatin1String("img[alt]:not([alt=''])")));
2543
2544 if (!imageElement.isNull())
2545 {
2546 link.title = imageElement.attribute(QLatin1String("alt"));
2547 }
2548 }
2549
2550 links.append(link);
2551 }
2552
2553 links.squeeze();
2554
2555 return links;
2556 }
2557
getLinks() const2558 QVector<WebWidget::LinkUrl> QtWebKitWebWidget::getLinks() const
2559 {
2560 return getLinks(QLatin1String("a[href]"));
2561 }
2562
getSearchEngines() const2563 QVector<WebWidget::LinkUrl> QtWebKitWebWidget::getSearchEngines() const
2564 {
2565 return getLinks(QLatin1String("link[type='application/opensearchdescription+xml']"));
2566 }
2567
getBlockedRequests() const2568 QVector<NetworkManager::ResourceInformation> QtWebKitWebWidget::getBlockedRequests() const
2569 {
2570 return m_networkManager->getBlockedRequests();
2571 }
2572
getHeaders() const2573 QMap<QByteArray, QByteArray> QtWebKitWebWidget::getHeaders() const
2574 {
2575 return m_networkManager->getHeaders();
2576 }
2577
getMetaData() const2578 QMultiMap<QString, QString> QtWebKitWebWidget::getMetaData() const
2579 {
2580 return m_page->mainFrame()->metaData();
2581 }
2582
getContentState() const2583 WebWidget::ContentStates QtWebKitWebWidget::getContentState() const
2584 {
2585 const QUrl url(getUrl());
2586
2587 if (url.isEmpty() || url.scheme() == QLatin1String("about"))
2588 {
2589 return ApplicationContentState;
2590 }
2591
2592 if (url.scheme() == QLatin1String("file"))
2593 {
2594 return LocalContentState;
2595 }
2596
2597 ContentStates state(m_networkManager->getContentState() | RemoteContentState);
2598
2599 if (getOption(SettingsManager::Security_EnableFraudCheckingOption, url).toBool() && ContentFiltersManager::isFraud(url))
2600 {
2601 state |= FraudContentState;
2602 }
2603
2604 return state;
2605 }
2606
getLoadingState() const2607 WebWidget::LoadingState QtWebKitWebWidget::getLoadingState() const
2608 {
2609 return m_loadingState;
2610 }
2611
getZoom() const2612 int QtWebKitWebWidget::getZoom() const
2613 {
2614 return static_cast<int>(m_page->mainFrame()->zoomFactor() * 100);
2615 }
2616
getAmountOfDeferredPlugins() const2617 int QtWebKitWebWidget::getAmountOfDeferredPlugins() const
2618 {
2619 return m_amountOfDeferredPlugins;
2620 }
2621
findInPage(const QString & text,FindFlags flags)2622 int QtWebKitWebWidget::findInPage(const QString &text, FindFlags flags)
2623 {
2624 QWebPage::FindFlags nativeFlags(QWebPage::FindWrapsAroundDocument | QWebPage::FindBeginsInSelection);
2625
2626 if (flags.testFlag(BackwardFind))
2627 {
2628 nativeFlags |= QWebPage::FindBackward;
2629 }
2630
2631 if (flags.testFlag(CaseSensitiveFind))
2632 {
2633 nativeFlags |= QWebPage::FindCaseSensitively;
2634 }
2635
2636 if (flags.testFlag(HighlightAllFind) || text.isEmpty())
2637 {
2638 m_page->findText({}, QWebPage::HighlightAllOccurrences);
2639 m_page->findText(text, (nativeFlags | QWebPage::HighlightAllOccurrences));
2640 }
2641
2642 return (m_page->findText(text, nativeFlags) ? -1 : 0);
2643 }
2644
canLoadPlugins() const2645 bool QtWebKitWebWidget::canLoadPlugins() const
2646 {
2647 return m_canLoadPlugins;
2648 }
2649
canGoBack() const2650 bool QtWebKitWebWidget::canGoBack() const
2651 {
2652 return m_page->history()->canGoBack();
2653 }
2654
canGoForward() const2655 bool QtWebKitWebWidget::canGoForward() const
2656 {
2657 return m_page->history()->canGoForward();
2658 }
2659
canFastForward() const2660 bool QtWebKitWebWidget::canFastForward() const
2661 {
2662 if (canGoForward())
2663 {
2664 return true;
2665 }
2666
2667 if (Utils::isUrlEmpty(getUrl()))
2668 {
2669 return false;
2670 }
2671
2672 return m_page->mainFrame()->evaluateJavaScript(getFastForwardScript(false)).toBool();
2673 }
2674
canInspect() const2675 bool QtWebKitWebWidget::canInspect() const
2676 {
2677 return !Utils::isUrlEmpty(getUrl());
2678 }
2679
canTakeScreenshot() const2680 bool QtWebKitWebWidget::canTakeScreenshot() const
2681 {
2682 return true;
2683 }
2684
canRedo() const2685 bool QtWebKitWebWidget::canRedo() const
2686 {
2687 return m_page->undoStack()->canRedo();
2688 }
2689
canUndo() const2690 bool QtWebKitWebWidget::canUndo() const
2691 {
2692 return m_page->undoStack()->canUndo();
2693 }
2694
canShowContextMenu(const QPoint & position) const2695 bool QtWebKitWebWidget::canShowContextMenu(const QPoint &position) const
2696 {
2697 if (!getOption(SettingsManager::Permissions_ScriptsCanReceiveRightClicksOption).toBool())
2698 {
2699 return true;
2700 }
2701
2702 QContextMenuEvent menuEvent(QContextMenuEvent::Other, position, m_webView->mapToGlobal(position), Qt::NoModifier);
2703
2704 return !m_page->swallowContextMenuEvent(&menuEvent);
2705 }
2706
canViewSource() const2707 bool QtWebKitWebWidget::canViewSource() const
2708 {
2709 return (!m_page->isDisplayingErrorPage() && !m_page->isViewingMedia() && !Utils::isUrlEmpty(getUrl()));
2710 }
2711
hasSelection() const2712 bool QtWebKitWebWidget::hasSelection() const
2713 {
2714 return (m_page->hasSelection() && !m_page->selectedText().isEmpty());
2715 }
2716
hasWatchedChanges(ChangeWatcher watcher) const2717 bool QtWebKitWebWidget::hasWatchedChanges(ChangeWatcher watcher) const
2718 {
2719 Q_UNUSED(watcher)
2720
2721 return true;
2722 }
2723
isAudible() const2724 bool QtWebKitWebWidget::isAudible() const
2725 {
2726 return m_page->recentlyAudible();
2727 }
2728
isAudioMuted() const2729 bool QtWebKitWebWidget::isAudioMuted() const
2730 {
2731 return m_isAudioMuted;
2732 }
2733
isFullScreen() const2734 bool QtWebKitWebWidget::isFullScreen() const
2735 {
2736 return m_isFullScreen;
2737 }
2738
isInspecting() const2739 bool QtWebKitWebWidget::isInspecting() const
2740 {
2741 return (m_inspector && m_inspector->isVisible());
2742 }
2743
isNavigating() const2744 bool QtWebKitWebWidget::isNavigating() const
2745 {
2746 return m_isNavigating;
2747 }
2748
isPopup() const2749 bool QtWebKitWebWidget::isPopup() const
2750 {
2751 return m_page->isPopup();
2752 }
2753
isPrivate() const2754 bool QtWebKitWebWidget::isPrivate() const
2755 {
2756 return m_page->settings()->testAttribute(QWebSettings::PrivateBrowsingEnabled);
2757 }
2758
isScrollBar(const QPoint & position) const2759 bool QtWebKitWebWidget::isScrollBar(const QPoint &position) const
2760 {
2761 return (m_page->mainFrame()->scrollBarGeometry(Qt::Horizontal).contains(position) || m_page->mainFrame()->scrollBarGeometry(Qt::Vertical).contains(position));
2762 }
2763
eventFilter(QObject * object,QEvent * event)2764 bool QtWebKitWebWidget::eventFilter(QObject *object, QEvent *event)
2765 {
2766 if (object == m_webView)
2767 {
2768 const QMouseEvent *mouseEvent(static_cast<QMouseEvent*>(event));
2769
2770 if (event->type() == QEvent::MouseButtonPress && mouseEvent && mouseEvent->button() == Qt::LeftButton && SettingsManager::getOption(SettingsManager::Permissions_EnablePluginsOption, Utils::extractHost(getUrl())).toString() == QLatin1String("onDemand"))
2771 {
2772 const QWidget *widget(childAt(mouseEvent->pos()));
2773
2774 if (widget && widget->metaObject()->className() == QLatin1String("Otter::QtWebKitPluginWidget"))
2775 {
2776 QWebElement element(m_page->mainFrame()->hitTestContent(mouseEvent->pos()).element());
2777 const QString tagName(element.tagName().toLower());
2778
2779 if (tagName == QLatin1String("object") || tagName == QLatin1String("embed"))
2780 {
2781 m_pluginToken = QUuid::createUuid().toString();
2782
2783 QWebElement clonedElement(element.clone());
2784 clonedElement.setAttribute(QLatin1String("data-otter-browser"), m_pluginToken);
2785
2786 element.replace(clonedElement);
2787
2788 return true;
2789 }
2790 }
2791 }
2792
2793 switch (event->type())
2794 {
2795 case QEvent::ChildAdded:
2796 case QEvent::ChildRemoved:
2797 if (!m_canLoadPlugins && m_loadingState == FinishedLoadingState)
2798 {
2799 updateAmountOfDeferredPlugins();
2800 }
2801
2802 break;
2803 case QEvent::ContextMenu:
2804 {
2805 const QContextMenuEvent *contextMenuEvent(static_cast<QContextMenuEvent*>(event));
2806
2807 if (contextMenuEvent && contextMenuEvent->reason() != QContextMenuEvent::Mouse)
2808 {
2809 triggerAction(ActionsManager::ContextMenuAction, {{QLatin1String("context"), contextMenuEvent->reason()}});
2810 }
2811
2812 if (!getOption(SettingsManager::Permissions_ScriptsCanReceiveRightClicksOption).toBool())
2813 {
2814 return true;
2815 }
2816 }
2817
2818 break;
2819 case QEvent::KeyPress:
2820 {
2821 const QKeyEvent *keyEvent(static_cast<QKeyEvent*>(event));
2822
2823 if (keyEvent->key() == Qt::Key_Escape && m_page->hasSelection())
2824 {
2825 event->ignore();
2826
2827 return true;
2828 }
2829 }
2830
2831 break;
2832 case QEvent::MouseButtonDblClick:
2833 case QEvent::MouseButtonPress:
2834 case QEvent::Wheel:
2835 {
2836 if (mouseEvent)
2837 {
2838 setClickPosition(mouseEvent->pos());
2839 updateHitTestResult(mouseEvent->pos());
2840 }
2841
2842 QVector<GesturesManager::GesturesContext> contexts;
2843
2844 if (getCurrentHitTestResult().flags.testFlag(HitTestResult::IsContentEditableTest))
2845 {
2846 contexts.append(GesturesManager::ContentEditableContext);
2847 }
2848
2849 if (getCurrentHitTestResult().linkUrl.isValid())
2850 {
2851 contexts.append(GesturesManager::LinkContext);
2852 }
2853
2854 contexts.append(GesturesManager::GenericContext);
2855
2856 if ((!mouseEvent || !isScrollBar(mouseEvent->pos())) && GesturesManager::startGesture(object, event, contexts))
2857 {
2858 return true;
2859 }
2860
2861 if (event->type() == QEvent::MouseButtonDblClick && mouseEvent->button() == Qt::LeftButton && SettingsManager::getOption(SettingsManager::Browser_ShowSelectionContextMenuOnDoubleClickOption).toBool())
2862 {
2863 const HitTestResult hitResult(getHitTestResult(mouseEvent->pos()));
2864
2865 if (!hitResult.flags.testFlag(HitTestResult::IsContentEditableTest) && hitResult.tagName != QLatin1String("textarea") && hitResult.tagName!= QLatin1String("select") && hitResult.tagName != QLatin1String("input"))
2866 {
2867 setClickPosition(mouseEvent->pos());
2868
2869 QTimer::singleShot(250, this, [&]()
2870 {
2871 showContextMenu();
2872 });
2873 }
2874 }
2875 }
2876
2877 break;
2878 case QEvent::MouseButtonRelease:
2879 if (mouseEvent && mouseEvent->button() == Qt::MiddleButton && !isScrollBar(mouseEvent->pos()) && !getCurrentHitTestResult().flags.testFlag(HitTestResult::IsContentEditableTest))
2880 {
2881 return true;
2882 }
2883
2884 break;
2885 case QEvent::MouseMove:
2886 event->ignore();
2887
2888 return QObject::eventFilter(object, event);
2889 case QEvent::Move:
2890 case QEvent::Resize:
2891 emit geometryChanged();
2892
2893 break;
2894 case QEvent::ShortcutOverride:
2895 {
2896 const QString tagName(m_page->mainFrame()->findFirstElement(QLatin1String("*:focus")).tagName().toLower());
2897
2898 if (tagName == QLatin1String("object") || tagName == QLatin1String("embed"))
2899 {
2900 event->accept();
2901
2902 return true;
2903 }
2904
2905 const QKeyEvent *keyEvent(static_cast<QKeyEvent*>(event));
2906
2907 if (keyEvent->modifiers() == Qt::ControlModifier)
2908 {
2909 switch (keyEvent->key())
2910 {
2911 case Qt::Key_Backspace:
2912 case Qt::Key_Left:
2913 case Qt::Key_Right:
2914 if (m_page->currentFrame()->hitTestContent(m_page->inputMethodQuery(Qt::ImCursorRectangle).toRect().center()).isContentEditable())
2915 {
2916 event->accept();
2917 }
2918
2919 break;
2920 default:
2921 break;
2922 }
2923
2924 return true;
2925 }
2926
2927 if ((keyEvent->modifiers().testFlag(Qt::AltModifier) || keyEvent->modifiers().testFlag(Qt::GroupSwitchModifier)) && m_page->currentFrame()->hitTestContent(m_page->inputMethodQuery(Qt::ImCursorRectangle).toRect().center()).isContentEditable())
2928 {
2929 event->accept();
2930
2931 return true;
2932 }
2933 }
2934
2935 break;
2936 case QEvent::ToolTip:
2937 handleToolTipEvent(static_cast<QHelpEvent*>(event), m_webView);
2938
2939 return true;
2940 default:
2941 break;
2942 }
2943 }
2944
2945 return QObject::eventFilter(object, event);
2946 }
2947
2948 }
2949