File indexing completed on 2024-05-12 04:58:31

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "webtab.h"
0019 #include "browserwindow.h"
0020 #include "tabbedwebview.h"
0021 #include "webinspector.h"
0022 #include "webpage.h"
0023 #include "tabbar.h"
0024 #include "tabicon.h"
0025 #include "tabwidget.h"
0026 #include "locationbar.h"
0027 #include "qztools.h"
0028 #include "qzsettings.h"
0029 #include "mainapplication.h"
0030 #include "iconprovider.h"
0031 #include "searchtoolbar.h"
0032 
0033 #include <QVBoxLayout>
0034 #include <QWebEngineHistory>
0035 #include <QLabel>
0036 #include <QTimer>
0037 #include <QSplitter>
0038 
0039 static const int savedTabVersion = 6;
0040 
0041 WebTab::SavedTab::SavedTab()
0042     : isPinned(false)
0043     , zoomLevel(qzSettings->defaultZoomLevel)
0044     , parentTab(-1)
0045 {
0046 }
0047 
0048 WebTab::SavedTab::SavedTab(WebTab* webTab)
0049 {
0050     title = webTab->title();
0051     url = webTab->url();
0052     icon = webTab->icon(true);
0053     history = webTab->historyData();
0054     isPinned = webTab->isPinned();
0055     zoomLevel = webTab->zoomLevel();
0056     parentTab = webTab->parentTab() ? webTab->parentTab()->tabIndex() : -1;
0057 
0058     const auto children = webTab->childTabs();
0059     childTabs.reserve(children.count());
0060     for (WebTab *child : children) {
0061         childTabs.append(child->tabIndex());
0062     }
0063 
0064     sessionData = webTab->sessionData();
0065 }
0066 
0067 bool WebTab::SavedTab::isValid() const
0068 {
0069     return !url.isEmpty() || !history.isEmpty();
0070 }
0071 
0072 void WebTab::SavedTab::clear()
0073 {
0074     title.clear();
0075     url.clear();
0076     icon = QIcon();
0077     history.clear();
0078     isPinned = false;
0079     zoomLevel = qzSettings->defaultZoomLevel;
0080     parentTab = -1;
0081     childTabs.clear();
0082     sessionData.clear();
0083 }
0084 
0085 QDataStream &operator <<(QDataStream &stream, const WebTab::SavedTab &tab)
0086 {
0087     stream << savedTabVersion;
0088     stream << tab.title;
0089     stream << tab.url;
0090     stream << tab.icon.pixmap(16);
0091     stream << tab.history;
0092     stream << tab.isPinned;
0093     stream << tab.zoomLevel;
0094     stream << tab.parentTab;
0095     stream << tab.childTabs;
0096     stream << tab.sessionData;
0097 
0098     return stream;
0099 }
0100 
0101 QDataStream &operator >>(QDataStream &stream, WebTab::SavedTab &tab)
0102 {
0103     int version;
0104     stream >> version;
0105 
0106     if (version < 1)
0107         return stream;
0108 
0109     QPixmap pixmap;
0110     stream >> tab.title;
0111     stream >> tab.url;
0112     stream >> pixmap;
0113     stream >> tab.history;
0114 
0115     if (version >= 2)
0116         stream >> tab.isPinned;
0117 
0118     if (version >= 3)
0119         stream >> tab.zoomLevel;
0120 
0121     if (version >= 4)
0122         stream >> tab.parentTab;
0123 
0124     if (version >= 5)
0125         stream >> tab.childTabs;
0126 
0127     if (version >= 6)
0128         stream >> tab.sessionData;
0129 
0130     tab.icon = QIcon(pixmap);
0131 
0132     return stream;
0133 }
0134 
0135 WebTab::WebTab(QWidget *parent)
0136     : QWidget(parent)
0137 {
0138     setObjectName(QSL("webtab"));
0139 
0140     m_webView = new TabbedWebView(this);
0141     m_webView->setPage(new WebPage);
0142     m_webView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
0143     setFocusProxy(m_webView);
0144 
0145     m_locationBar = new LocationBar(this);
0146     m_locationBar->setWebView(m_webView);
0147 
0148     m_tabIcon = new TabIcon(this);
0149     m_tabIcon->setWebTab(this);
0150 
0151     m_layout = new QVBoxLayout(this);
0152     m_layout->setContentsMargins(0, 0, 0, 0);
0153     m_layout->setSpacing(0);
0154     m_layout->addWidget(m_webView);
0155 
0156     auto *viewWidget = new QWidget(this);
0157     viewWidget->setLayout(m_layout);
0158 
0159     m_splitter = new QSplitter(Qt::Vertical, this);
0160     m_splitter->setChildrenCollapsible(false);
0161     m_splitter->addWidget(viewWidget);
0162 
0163     auto *layout = new QVBoxLayout(this);
0164     layout->setContentsMargins(0, 0, 0, 0);
0165     layout->setSpacing(0);
0166     layout->addWidget(m_splitter);
0167     setLayout(layout);
0168 
0169     m_notificationWidget = new QWidget(this);
0170     m_notificationWidget->setAutoFillBackground(true);
0171     QPalette pal = m_notificationWidget->palette();
0172     pal.setColor(QPalette::Window, pal.window().color().darker(110));
0173     m_notificationWidget->setPalette(pal);
0174 
0175     auto *nlayout = new QVBoxLayout(m_notificationWidget);
0176     nlayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
0177     nlayout->setContentsMargins(0, 0, 0, 0);
0178     nlayout->setSpacing(1);
0179 
0180     connect(m_webView, &WebView::showNotification, this, &WebTab::showNotification);
0181     connect(m_webView, &QWebEngineView::loadFinished, this, &WebTab::loadFinished);
0182     connect(m_webView, &TabbedWebView::titleChanged, this, &WebTab::titleWasChanged);
0183     connect(m_webView, &TabbedWebView::titleChanged, this, &WebTab::titleChanged);
0184     connect(m_webView, &TabbedWebView::iconChanged, this, &WebTab::iconChanged);
0185     connect(m_webView, &TabbedWebView::backgroundActivityChanged, this, &WebTab::backgroundActivityChanged);
0186     connect(m_webView, &TabbedWebView::loadStarted, this, std::bind(&WebTab::loadingChanged, this, true));
0187     connect(m_webView, &TabbedWebView::loadFinished, this, std::bind(&WebTab::loadingChanged, this, false));
0188 
0189     auto pageChanged = [this](WebPage *page) {
0190         connect(page, &WebPage::audioMutedChanged, this, &WebTab::playingChanged);
0191         connect(page, &WebPage::recentlyAudibleChanged, this, &WebTab::mutedChanged);
0192     };
0193     pageChanged(m_webView->page());
0194     connect(m_webView, &TabbedWebView::pageChanged, this, pageChanged);
0195 
0196     // Workaround QTabBar not immediately noticing resizing of tab buttons
0197     connect(m_tabIcon, &TabIcon::resized, this, [this]() {
0198         if (m_tabBar) {
0199             m_tabBar->update();
0200         }
0201     });
0202 }
0203 
0204 BrowserWindow *WebTab::browserWindow() const
0205 {
0206     return m_window;
0207 }
0208 
0209 TabbedWebView* WebTab::webView() const
0210 {
0211     return m_webView;
0212 }
0213 
0214 bool WebTab::haveInspector() const
0215 {
0216     return m_splitter->count() > 1 && m_splitter->widget(1)->inherits("WebInspector");
0217 }
0218 
0219 void WebTab::showWebInspector(bool inspectElement)
0220 {
0221     if (!WebInspector::isEnabled() || haveInspector())
0222         return;
0223 
0224     auto *inspector = new WebInspector(this);
0225     inspector->setView(m_webView);
0226     if (inspectElement)
0227         inspector->inspectElement();
0228 
0229     const int height = inspector->sizeHint().height();
0230     m_splitter->addWidget(inspector);
0231     m_splitter->setSizes({m_splitter->height() - height, height});
0232 }
0233 
0234 void WebTab::toggleWebInspector()
0235 {
0236     if (!haveInspector())
0237         showWebInspector();
0238     else
0239         delete m_splitter->widget(1);
0240 }
0241 
0242 void WebTab::showSearchToolBar(const QString &searchText)
0243 {
0244     const int index = 1;
0245 
0246     SearchToolBar *toolBar = nullptr;
0247 
0248     if (m_layout->count() == 1) {
0249         toolBar = new SearchToolBar(m_webView, this);
0250         m_layout->insertWidget(index, toolBar);
0251     } else if (m_layout->count() == 2) {
0252         Q_ASSERT(qobject_cast<SearchToolBar*>(m_layout->itemAt(index)->widget()));
0253         toolBar = static_cast<SearchToolBar*>(m_layout->itemAt(index)->widget());
0254     }
0255 
0256     Q_ASSERT(toolBar);
0257     if (!searchText.isEmpty()) {
0258         toolBar->setText(searchText);
0259     }
0260     toolBar->focusSearchLine();
0261 }
0262 
0263 QUrl WebTab::url() const
0264 {
0265     if (isRestored()) {
0266         if (m_webView->url().isEmpty() && m_webView->isLoading()) {
0267             return m_webView->page()->requestedUrl();
0268         }
0269         return m_webView->url();
0270     }
0271     else {
0272         return m_savedTab.url;
0273     }
0274 }
0275 
0276 QString WebTab::title(bool allowEmpty) const
0277 {
0278     if (isRestored()) {
0279         return m_webView->title(allowEmpty);
0280     }
0281     else {
0282         return m_savedTab.title;
0283     }
0284 }
0285 
0286 QIcon WebTab::icon(bool allowNull) const
0287 {
0288     if (isRestored()) {
0289         return m_webView->icon(allowNull);
0290     }
0291 
0292     if (allowNull || !m_savedTab.icon.isNull()) {
0293         return m_savedTab.icon;
0294     }
0295 
0296     return IconProvider::emptyWebIcon();
0297 }
0298 
0299 QWebEngineHistory* WebTab::history() const
0300 {
0301     return m_webView->history();
0302 }
0303 
0304 int WebTab::zoomLevel() const
0305 {
0306     return m_webView->zoomLevel();
0307 }
0308 
0309 void WebTab::setZoomLevel(int level)
0310 {
0311     m_webView->setZoomLevel(level);
0312 }
0313 
0314 void WebTab::detach()
0315 {
0316     Q_ASSERT(m_window);
0317     Q_ASSERT(m_tabBar);
0318 
0319     // Remove from tab tree
0320     removeFromTabTree();
0321 
0322     // Remove icon from tab
0323     m_tabBar->setTabButton(tabIndex(), m_tabBar->iconButtonPosition(), nullptr);
0324     m_tabIcon->setParent(this);
0325 
0326     // Remove the tab from tabbar
0327     m_window->tabWidget()->removeTab(tabIndex());
0328     setParent(nullptr);
0329     // Remove the locationbar from window
0330     m_locationBar->setParent(this);
0331     // Detach TabbedWebView
0332     m_webView->setBrowserWindow(nullptr);
0333 
0334     if (m_isCurrentTab) {
0335         m_isCurrentTab = false;
0336         Q_EMIT currentTabChanged(m_isCurrentTab);
0337     }
0338     m_tabBar->disconnect(this);
0339 
0340     // WebTab is now standalone widget
0341     m_window = nullptr;
0342     m_tabBar = nullptr;
0343 }
0344 
0345 void WebTab::attach(BrowserWindow* window)
0346 {
0347     m_window = window;
0348     m_tabBar = m_window->tabWidget()->tabBar();
0349 
0350     m_webView->setBrowserWindow(m_window);
0351     m_locationBar->setBrowserWindow(m_window);
0352     m_tabBar->setTabText(tabIndex(), title());
0353     m_tabBar->setTabButton(tabIndex(), m_tabBar->iconButtonPosition(), m_tabIcon);
0354     QTimer::singleShot(0, m_tabIcon, &TabIcon::updateIcon);
0355 
0356     auto currentChanged = [this](int index) {
0357         const bool wasCurrent = m_isCurrentTab;
0358         m_isCurrentTab = index == tabIndex();
0359         if (wasCurrent != m_isCurrentTab) {
0360             Q_EMIT currentTabChanged(m_isCurrentTab);
0361         }
0362     };
0363 
0364     currentChanged(m_tabBar->currentIndex());
0365     connect(m_tabBar, &TabBar::currentChanged, this, currentChanged);
0366 }
0367 
0368 QByteArray WebTab::historyData() const
0369 {
0370     if (isRestored()) {
0371         QByteArray historyArray;
0372         QDataStream historyStream(&historyArray, QIODevice::WriteOnly);
0373         historyStream << *m_webView->history();
0374         return historyArray;
0375     }
0376     else {
0377         return m_savedTab.history;
0378     }
0379 }
0380 
0381 void WebTab::stop()
0382 {
0383     m_webView->stop();
0384 }
0385 
0386 void WebTab::reload()
0387 {
0388     m_webView->reload();
0389 }
0390 
0391 void WebTab::load(const LoadRequest &request)
0392 {
0393     if (!isRestored()) {
0394         tabActivated();
0395         QTimer::singleShot(0, this, std::bind(&WebTab::load, this, request));
0396     } else {
0397         m_webView->load(request);
0398     }
0399 }
0400 
0401 void WebTab::unload()
0402 {
0403     m_savedTab = SavedTab(this);
0404     Q_EMIT restoredChanged(isRestored());
0405     m_webView->setPage(new WebPage);
0406     m_webView->setFocus();
0407 }
0408 
0409 bool WebTab::isLoading() const
0410 {
0411     return m_webView->isLoading();
0412 }
0413 
0414 bool WebTab::isPinned() const
0415 {
0416     return m_isPinned;
0417 }
0418 
0419 void WebTab::setPinned(bool state)
0420 {
0421     if (m_isPinned == state) {
0422         return;
0423     }
0424 
0425     if (state) {
0426         removeFromTabTree();
0427     }
0428 
0429     m_isPinned = state;
0430     Q_EMIT pinnedChanged(m_isPinned);
0431 }
0432 
0433 bool WebTab::isMuted() const
0434 {
0435     return m_webView->page()->isAudioMuted();
0436 }
0437 
0438 bool WebTab::isPlaying() const
0439 {
0440     return m_webView->page()->recentlyAudible();
0441 }
0442 
0443 void WebTab::setMuted(bool muted)
0444 {
0445     m_webView->page()->setAudioMuted(muted);
0446 }
0447 
0448 void WebTab::toggleMuted()
0449 {
0450     bool muted = isMuted();
0451     setMuted(!muted);
0452 }
0453 
0454 bool WebTab::backgroundActivity() const
0455 {
0456     return m_webView->backgroundActivity();
0457 }
0458 
0459 LocationBar* WebTab::locationBar() const
0460 {
0461     return m_locationBar;
0462 }
0463 
0464 TabIcon* WebTab::tabIcon() const
0465 {
0466     return m_tabIcon;
0467 }
0468 
0469 WebTab *WebTab::parentTab() const
0470 {
0471     return m_parentTab;
0472 }
0473 
0474 void WebTab::setParentTab(WebTab *tab)
0475 {
0476     if (m_isPinned || m_parentTab == tab) {
0477         return;
0478     }
0479 
0480     if (tab && tab->isPinned()) {
0481         return;
0482     }
0483 
0484     if (m_parentTab) {
0485         const int index = m_parentTab->m_childTabs.indexOf(this);
0486         if (index >= 0) {
0487             m_parentTab->m_childTabs.removeAt(index);
0488             Q_EMIT m_parentTab->childTabRemoved(this, index);
0489         }
0490     }
0491 
0492     m_parentTab = tab;
0493 
0494     if (tab) {
0495         m_parentTab = nullptr;
0496         tab->addChildTab(this);
0497     } else {
0498         Q_EMIT parentTabChanged(m_parentTab);
0499     }
0500 }
0501 
0502 void WebTab::addChildTab(WebTab *tab, int index)
0503 {
0504     if (m_isPinned || !tab || tab->isPinned()) {
0505         return;
0506     }
0507 
0508     WebTab *oldParent = tab->m_parentTab;
0509     tab->m_parentTab = this;
0510     if (oldParent) {
0511         const int index = oldParent->m_childTabs.indexOf(tab);
0512         if (index >= 0) {
0513             oldParent->m_childTabs.removeAt(index);
0514             Q_EMIT oldParent->childTabRemoved(tab, index);
0515         }
0516     }
0517 
0518     if (index < 0 || index > m_childTabs.size()) {
0519         index = 0;
0520         if (addChildBehavior() == AppendChild) {
0521             index = m_childTabs.size();
0522         } else if (addChildBehavior() == PrependChild) {
0523             index = 0;
0524         }
0525     }
0526 
0527     m_childTabs.insert(index, tab);
0528     Q_EMIT childTabAdded(tab, index);
0529 
0530     Q_EMIT tab->parentTabChanged(this);
0531 }
0532 
0533 QVector<WebTab*> WebTab::childTabs() const
0534 {
0535     return m_childTabs;
0536 }
0537 
0538 QHash<QString, QVariant> WebTab::sessionData() const
0539 {
0540     return m_sessionData;
0541 }
0542 
0543 void WebTab::setSessionData(const QString &key, const QVariant &value)
0544 {
0545     m_sessionData[key] = value;
0546 }
0547 
0548 bool WebTab::isRestored() const
0549 {
0550     return !m_savedTab.isValid();
0551 }
0552 
0553 void WebTab::restoreTab(const WebTab::SavedTab &tab)
0554 {
0555     Q_ASSERT(m_tabBar);
0556 
0557     setPinned(tab.isPinned);
0558     m_sessionData = tab.sessionData;
0559 
0560     if (!isPinned() && qzSettings->loadTabsOnActivation) {
0561         m_savedTab = tab;
0562         Q_EMIT restoredChanged(isRestored());
0563         int index = tabIndex();
0564 
0565         m_tabBar->setTabText(index, tab.title);
0566         m_locationBar->showUrl(tab.url);
0567         m_tabIcon->updateIcon();
0568     }
0569     else {
0570         // This is called only on restore session and restoring tabs immediately
0571         // crashes QtWebEngine, waiting after initialization is complete fixes it
0572         QTimer::singleShot(1000, this, [=]() {
0573             p_restoreTab(tab);
0574         });
0575     }
0576 }
0577 
0578 void WebTab::p_restoreTab(const QUrl &url, const QByteArray &history, int zoomLevel)
0579 {
0580     m_webView->load(url);
0581 
0582     // Restoring history of internal pages crashes QtWebEngine 5.8
0583     static const QStringList blacklistedSchemes = {
0584         QSL("view-source"),
0585         QSL("chrome")
0586     };
0587 
0588     if (!blacklistedSchemes.contains(url.scheme())) {
0589         QDataStream stream(history);
0590         stream >> *m_webView->history();
0591     }
0592 
0593     m_webView->setZoomLevel(zoomLevel);
0594     m_webView->setFocus();
0595 }
0596 
0597 void WebTab::p_restoreTab(const WebTab::SavedTab &tab)
0598 {
0599     p_restoreTab(tab.url, tab.history, tab.zoomLevel);
0600 }
0601 
0602 void WebTab::showNotification(QWidget* notif)
0603 {
0604     m_notificationWidget->setParent(this);
0605     m_notificationWidget->raise();
0606     m_notificationWidget->setFixedWidth(width());
0607     m_notificationWidget->layout()->addWidget(notif);
0608     m_notificationWidget->show();
0609     notif->show();
0610 }
0611 
0612 void WebTab::loadFinished()
0613 {
0614     titleWasChanged(m_webView->title());
0615 }
0616 
0617 void WebTab::titleWasChanged(const QString &title)
0618 {
0619     if (!m_tabBar || !m_window || title.isEmpty()) {
0620         return;
0621     }
0622 
0623     if (m_isCurrentTab) {
0624         m_window->setWindowTitle(tr("%1 - Falkon").arg(title));
0625     }
0626 
0627     m_tabBar->setTabText(tabIndex(), title);
0628 }
0629 
0630 void WebTab::tabActivated()
0631 {
0632     if (isRestored()) {
0633         return;
0634     }
0635 
0636     QTimer::singleShot(0, this, [this]() {
0637         if (isRestored()) {
0638             return;
0639         }
0640         p_restoreTab(m_savedTab);
0641         m_savedTab.clear();
0642         Q_EMIT restoredChanged(isRestored());
0643     });
0644 }
0645 
0646 static WebTab::AddChildBehavior s_addChildBehavior = WebTab::AppendChild;
0647 
0648 // static
0649 WebTab::AddChildBehavior WebTab::addChildBehavior()
0650 {
0651     return s_addChildBehavior;
0652 }
0653 
0654 // static
0655 void WebTab::setAddChildBehavior(AddChildBehavior behavior)
0656 {
0657     s_addChildBehavior = behavior;
0658 }
0659 
0660 void WebTab::resizeEvent(QResizeEvent *event)
0661 {
0662     QWidget::resizeEvent(event);
0663 
0664     m_notificationWidget->setFixedWidth(width());
0665 }
0666 
0667 void WebTab::removeFromTabTree()
0668 {
0669     WebTab *parentTab = m_parentTab;
0670     const int parentIndex = parentTab ? parentTab->m_childTabs.indexOf(this) : -1;
0671 
0672     setParentTab(nullptr);
0673 
0674     int i = 0;
0675     while (!m_childTabs.isEmpty()) {
0676         WebTab *child = m_childTabs.at(0);
0677         child->setParentTab(nullptr);
0678         if (parentTab) {
0679             parentTab->addChildTab(child, parentIndex + i++);
0680         }
0681     }
0682 }
0683 
0684 bool WebTab::isCurrentTab() const
0685 {
0686     return m_isCurrentTab;
0687 }
0688 
0689 void WebTab::makeCurrentTab()
0690 {
0691     if (m_tabBar) {
0692         m_tabBar->tabWidget()->setCurrentIndex(tabIndex());
0693     }
0694 }
0695 
0696 void WebTab::closeTab()
0697 {
0698     if (m_tabBar) {
0699         m_tabBar->tabWidget()->closeTab(tabIndex());
0700     }
0701 }
0702 
0703 void WebTab::moveTab(int to)
0704 {
0705     if (m_tabBar) {
0706         m_tabBar->tabWidget()->moveTab(tabIndex(), to);
0707     }
0708 }
0709 
0710 int WebTab::tabIndex() const
0711 {
0712     return m_tabBar ? m_tabBar->tabWidget()->indexOf(const_cast<WebTab*>(this)) : -1;
0713 }
0714 
0715 void WebTab::togglePinned()
0716 {
0717     Q_ASSERT(m_tabBar);
0718     Q_ASSERT(m_window);
0719 
0720     setPinned(!isPinned());
0721     m_window->tabWidget()->pinUnPinTab(tabIndex(), title());
0722 }