From f4ff60c85e0ac3686538cae26fdf9b25cb418916 Mon Sep 17 00:00:00 2001 From: Stefan Comanescu Date: Sun, 22 Dec 2019 01:41:01 +0200 Subject: [PATCH] Implement QtWebEngine HTML bookmark import --- CMakeLists.txt | 2 +- .../web/qtwebengine/QtWebEngineResources.qrc | 1 + .../web/qtwebengine/QtWebEngineWebBackend.cpp | 206 ++++++++++++++++++ .../web/qtwebengine/QtWebEngineWebBackend.h | 29 +++ .../qtwebengine/resources/importBookmarks.js | 64 ++++++ 5 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 src/modules/backends/web/qtwebengine/resources/importBookmarks.js diff --git a/CMakeLists.txt b/CMakeLists.txt index bfcedaa269..9d80b8ae11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -529,7 +529,7 @@ add_executable(otter-browser WIN32 MACOSX_BUNDLE ) if (Qt5WebEngineWidgets_FOUND AND ENABLE_QTWEBENGINE) - target_link_libraries(otter-browser Qt5::WebEngineCore Qt5::WebEngineWidgets) + target_link_libraries(otter-browser Qt5::WebEngineCore Qt5::WebEngineWidgets Qt5::WebChannel) endif () if (Qt5WebKitWidgets_FOUND AND ENABLE_QTWEBKIT) diff --git a/src/modules/backends/web/qtwebengine/QtWebEngineResources.qrc b/src/modules/backends/web/qtwebengine/QtWebEngineResources.qrc index cc31d302c8..bb5f022f71 100644 --- a/src/modules/backends/web/qtwebengine/QtWebEngineResources.qrc +++ b/src/modules/backends/web/qtwebengine/QtWebEngineResources.qrc @@ -8,5 +8,6 @@ resources/hideBlockedRequests.js resources/hitTest.js resources/imageViewer.js + resources/importBookmarks.js diff --git a/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.cpp b/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.cpp index b1eb2857aa..2129212314 100644 --- a/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.cpp +++ b/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.cpp @@ -23,6 +23,7 @@ #include "QtWebEngineTransfer.h" #include "QtWebEngineUrlRequestInterceptor.h" #include "QtWebEngineWebWidget.h" +#include "../../../../core/BookmarksManager.h" #include "../../../../core/ContentFiltersManager.h" #include "../../../../core/HandlersManager.h" #include "../../../../core/NetworkManagerFactory.h" @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -276,6 +278,11 @@ WebWidget* QtWebEngineWebBackend::createWidget(const QVariantMap ¶meters, Co return new QtWebEngineWebWidget(parameters, this, parent); } +BookmarksImportJob *QtWebEngineWebBackend::createBookmarksImportJob(BookmarksModel::Bookmark *folder, const QString &path, bool areDuplicatesAllowed) +{ + return new QtWebEngineBookmarksImportJob(folder, path, areDuplicatesAllowed, this); +} + QString QtWebEngineWebBackend::getName() const { return QLatin1String("qtwebengine"); @@ -344,6 +351,205 @@ WebBackend::BackendCapabilities QtWebEngineWebBackend::getCapabilities() const return (UserScriptsCapability | GlobalCookiesPolicyCapability | GlobalContentFilteringCapability | GlobalDoNotTrackCapability | GlobalProxyCapability | GlobalReferrerCapability | GlobalUserAgentCapability); } +QtWebEngineBookmarksImportJob::QtWebEngineBookmarksImportJob(BookmarksModel::Bookmark *folder, const QString &path, bool areDuplicatesAllowed, QObject *parent) : BookmarksImportJob(folder, areDuplicatesAllowed, parent), + m_path(path), + m_currentAmount(0), + m_totalAmount(-1), + m_isRunning(false) +{ +} + +void QtWebEngineBookmarksImportJob::start() +{ + QFile file(m_path); + + if (!file.open(QIODevice::ReadOnly)) + { + endImport(); + return; + } + + m_isRunning = true; + + QWebEnginePage *page = new QWebEnginePage(this); + QWebChannel *webChannel = new QWebChannel(this); + + page->settings()->setAttribute(QWebEngineSettings::AutoLoadImages, false); + page->setWebChannel(webChannel); + + webChannel->registerObject("bookmarkImporter", this); + + connect(page, &QWebEnginePage::loadFinished, + [this, page]() { + QFile qWebChannelFile(":/qtwebchannel/qwebchannel.js"); + + if (!qWebChannelFile.open(QIODevice::ReadOnly)) + { + endImport(); + return; + } + + page->runJavaScript(QString::fromLatin1(qWebChannelFile.readAll())); + + QFile importBookmarksScriptFile(QLatin1String(":/modules/backends/web/qtwebengine/resources/importBookmarks.js")); + + if (!importBookmarksScriptFile.open(QIODevice::ReadOnly)) + { + endImport(); + return; + } + + page->runJavaScript(QString::fromLatin1(importBookmarksScriptFile.readAll())); + }); + + page->setHtml(QString::fromLatin1(file.readAll())); + + file.close(); +} + +void QtWebEngineBookmarksImportJob::cancel() +{ +} + +QDateTime QtWebEngineBookmarksImportJob::getDateTime(const QVariant &attribute) +{ +#if QT_VERSION < 0x050800 + const uint seconds(attribute.toUInt()); + + return ((seconds > 0) ? QDateTime::fromTime_t(seconds) : QDateTime()); +#else + const qint64 seconds(attribute.toLongLong()); + + return ((seconds != 0) ? QDateTime::fromSecsSinceEpoch(seconds) : QDateTime()); +#endif +} + +bool QtWebEngineBookmarksImportJob::isRunning() const +{ + return m_isRunning; +} + +void QtWebEngineBookmarksImportJob::beginImport(const int totalAmount, const int estimatedUrlsCount, const int estimatedKeywordsCount) +{ + m_totalAmount = totalAmount; + + emit importStarted(Importer::BookmarksImport, m_totalAmount); + + if (m_totalAmount == 0) + { + endImport(); + return; + } + + BookmarksManager::getModel()->beginImport(getImportFolder(), estimatedUrlsCount, estimatedKeywordsCount); +} + +void QtWebEngineBookmarksImportJob::endImport() +{ + m_isRunning = false; + + BookmarksManager::getModel()->endImport(); + + if (m_totalAmount < 0 || m_currentAmount < m_totalAmount) + { + emit importFinished(Importer::BookmarksImport, Importer::FailedImport, m_totalAmount); + emit jobFinished(false); + } + else + { + emit importFinished(Importer::BookmarksImport, Importer::SuccessfullImport, m_totalAmount); + emit jobFinished(true); + } + + deleteLater(); +} + +void QtWebEngineBookmarksImportJob::beginFolder(QVariant itemVariant) +{ + ++m_currentAmount; + emit importProgress(Importer::BookmarksImport, m_totalAmount, m_currentAmount); + + const QVariantMap itemMap = itemVariant.toMap(); + const QString title = itemMap["title"].toString(); + + QMap metaData({{BookmarksModel::TitleRole, title}}); + + const QDateTime dateAdded(getDateTime(itemMap["dateAdded"])); + const QDateTime dateModified(getDateTime(itemMap["dateModified"])); + + if (dateAdded.isValid()) + { + metaData[BookmarksModel::TimeAddedRole] = dateAdded; + } + + if (dateModified.isValid()) + { + metaData[BookmarksModel::TimeAddedRole] = dateModified; + } + + setCurrentFolder(BookmarksManager::addBookmark(BookmarksModel::FolderBookmark, metaData, getCurrentFolder())); +} + +void QtWebEngineBookmarksImportJob::endFolder() +{ + goToParent(); +} + +void QtWebEngineBookmarksImportJob::addBookmark(QVariant itemVariant) +{ + ++m_currentAmount; + emit importProgress(Importer::BookmarksImport, m_totalAmount, m_currentAmount); + + const QVariantMap itemMap = itemVariant.toMap(); + const QString typeString = itemMap["type"].toString(); + const QString title = itemMap["title"].toString(); + + BookmarksModel::BookmarkType type(BookmarksModel::UnknownBookmark); + QMap metaData({{BookmarksModel::TitleRole, title}}); + + if (typeString == "anchor" || typeString == "feed") + { + const QDateTime dateAdded(getDateTime(itemMap["dateAdded"])); + const QDateTime dateModified(getDateTime(itemMap["dateModified"])); + + if (dateAdded.isValid()) + { + metaData[BookmarksModel::TimeAddedRole] = dateAdded; + } + + if (dateModified.isValid()) + { + metaData[BookmarksModel::TimeAddedRole] = dateModified; + } + + type = (typeString == "anchor" ? BookmarksModel::UrlBookmark : BookmarksModel::FeedBookmark); + + const QString url = itemMap["url"].toString(); + + if (!areDuplicatesAllowed() && BookmarksManager::hasBookmark(url)) + { + return; + } + + const QVariant shortcutUrl = itemMap["keyword"]; + if (!shortcutUrl.isNull()) + { + metaData[BookmarksModel::KeywordRole] = shortcutUrl.toString(); + } + + metaData[BookmarksModel::UrlRole] = url; + } + else if (typeString == "separator") + { + type = BookmarksModel::SeparatorBookmark; + } + + if (type != BookmarksModel::UnknownBookmark) + { + BookmarksManager::addBookmark(type, metaData, getCurrentFolder()); + } +} + bool QtWebEngineWebBackend::hasSslSupport() const { return true; diff --git a/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.h b/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.h index 5e8386552b..95ed50dfeb 100644 --- a/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.h +++ b/src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.h @@ -45,6 +45,7 @@ class QtWebEngineWebBackend final : public WebBackend explicit QtWebEngineWebBackend(QObject *parent = nullptr); WebWidget* createWidget(const QVariantMap ¶meters, ContentsWidget *parent = nullptr) override; + BookmarksImportJob* createBookmarksImportJob(BookmarksModel::Bookmark *folder, const QString &path, bool areDuplicatesAllowed) override; QString getName() const override; QString getTitle() const override; QString getDescription() const override; @@ -81,6 +82,34 @@ protected slots: friend class QtWebEnginePage; }; +class QtWebEngineBookmarksImportJob final : public BookmarksImportJob +{ + Q_OBJECT + +public: + explicit QtWebEngineBookmarksImportJob(BookmarksModel::Bookmark *folder, const QString &path, bool areDuplicatesAllowed, QObject *parent = nullptr); + bool isRunning() const override; + + Q_INVOKABLE void beginImport(const int totalAmount, const int estimatedUrlsCount, const int estimatedKeywordsCount); + Q_INVOKABLE void endImport(); + Q_INVOKABLE void beginFolder(QVariant itemVariant); + Q_INVOKABLE void endFolder(); + Q_INVOKABLE void addBookmark(QVariant itemVariant); + +public slots: + void start() override; + void cancel() override; + +protected: + static QDateTime getDateTime(const QVariant &attribute); + +private: + QString m_path; + int m_currentAmount; + int m_totalAmount; + bool m_isRunning; +}; + } #endif diff --git a/src/modules/backends/web/qtwebengine/resources/importBookmarks.js b/src/modules/backends/web/qtwebengine/resources/importBookmarks.js new file mode 100644 index 0000000000..ea2853d1ef --- /dev/null +++ b/src/modules/backends/web/qtwebengine/resources/importBookmarks.js @@ -0,0 +1,64 @@ +var importBookmarksObj; + +function processBookmarkItems(items) { + var currentIndex = 0; + + while (currentIndex < items.length) { + var node = items[currentIndex]; + currentIndex += 1; + + if (node.tagName.toUpperCase() === 'A') + { + if (node.innerHTML === '---' && node.getAttribute('href') === 'http://bookmark.placeholder.url/') + { + // vivaldi markup for separators + importBookmarksObj.addBookmark({'type':'separator'}); + } + else + { + importBookmarksObj.addBookmark({ + 'type': (node.hasAttribute('FEEDURL') ? 'feed' : 'anchor'), + 'title': node.innerHTML, + 'url': node.getAttribute('href'), + 'dateAdded': node.getAttribute('add_date'), + 'dateModified': node.getAttribute('last_modified'), + 'keyword': node.getAttribute('shortcuturl') + }); + } + } + else if (node.tagName.toUpperCase() === 'HR') + { + importBookmarksObj.addBookmark({'type':'separator'}); + } + else if (node.tagName.toUpperCase() === 'H3') + { + directChildren = node.parentNode.querySelectorAll(':scope > DL > DT > *, :scope > DL > HR'); + + importBookmarksObj.beginFolder({ + type: 'folder', + title: node.innerHTML, + dateAdded: node.getAttribute('add_date'), + dateModified: node.getAttribute('last_modified') + }); + processBookmarkItems(directChildren); + importBookmarksObj.endFolder(); + } + } +} + +new QWebChannel(qt.webChannelTransport, function(webChannel) { + importBookmarksObj = webChannel.objects.bookmarkImporter; + + var totalAmount = document.querySelectorAll('DT, HR').length; + + var estimatedUrls = document.querySelectorAll('A[href]').length; + var estimatedKeywords = document.querySelectorAll('A[shortcuturl]').length; + + importBookmarksObj.beginImport(totalAmount, estimatedUrls, estimatedKeywords); + + var rootNode = document.querySelector('dl'); + var directChildren = rootNode.querySelectorAll(':scope > DT > *, :scope > HR'); + + processBookmarkItems(directChildren); + importBookmarksObj.endImport(); +});