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();
+});