Skip to content

Implement HTML bookmark import for QtWebEngine #1614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
<file>resources/hideBlockedRequests.js</file>
<file>resources/hitTest.js</file>
<file>resources/imageViewer.js</file>
<file>resources/importBookmarks.js</file>
</qresource>
</RCC>
206 changes: 206 additions & 0 deletions src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -37,6 +38,7 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QRegularExpression>
#include <QtWebChannel/QtWebChannel>
#include <QtWebEngineWidgets/QWebEngineProfile>
#include <QtWebEngineWidgets/QWebEngineSettings>

Expand Down Expand Up @@ -276,6 +278,11 @@ WebWidget* QtWebEngineWebBackend::createWidget(const QVariantMap &parameters, 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");
Expand Down Expand Up @@ -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<int, QVariant> 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<int, QVariant> 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;
Expand Down
29 changes: 29 additions & 0 deletions src/modules/backends/web/qtwebengine/QtWebEngineWebBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class QtWebEngineWebBackend final : public WebBackend
explicit QtWebEngineWebBackend(QObject *parent = nullptr);

WebWidget* createWidget(const QVariantMap &parameters, 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;
Expand Down Expand Up @@ -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
64 changes: 64 additions & 0 deletions src/modules/backends/web/qtwebengine/resources/importBookmarks.js
Original file line number Diff line number Diff line change
@@ -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();
});