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 }