From 51c24d386f65a270ef25652a43341aa02e40b72d Mon Sep 17 00:00:00 2001 From: Pavel Tumakaev Date: Mon, 21 Feb 2022 16:04:39 +0300 Subject: [PATCH 1/6] [sailfish-browser] Add useragents.json with a list of predefined user agent overrides. Contributes to JB#31240 For Chrome, Firefox, Safari and Edge two versions are defined: the latest and functionally close to gecko esr78. Each version contains user agents for desktop and mobile modes. --- data/useragents.json | 68 ++++++++++++++++++++++++++++++++++++++++++++ sailfish-browser.pro | 4 ++- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 data/useragents.json diff --git a/data/useragents.json b/data/useragents.json new file mode 100644 index 00000000000..11403c89351 --- /dev/null +++ b/data/useragents.json @@ -0,0 +1,68 @@ +[ + { + "key": "chlast", + "name": "Google Chrome 96", + "mobileUA": "Mozilla/5.0 (Linux; Android 8.1.0; Nexus 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36", + "desktopUA": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36" + }, + { + "key": "ch", + "name": "Google Chrome 91", + "mobileUA": "Mozilla/5.0 (Linux; Android 8.1.0; Nexus 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Mobile Safari/537.36", + "desktopUA": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36" + }, + { + "key": "fflast", + "name": "Mozilla Firefox 96", + "mobileUA": "Mozilla/5.0 (Android 8.1.0; Mobile; rv:96.0) Gecko/96.0 Firefox/96.0", + "desktopUA": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0" + }, + { + "key": "ff", + "name": "Mozilla Firefox 78", + "mobileUA": "Mozilla/5.0 (Android 8.1.0; Mobile; rv:78.0) Gecko/78.0 Firefox/78.0", + "desktopUA": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0" + }, + { + "key": "sflast", + "name": "Apple Safari 15", + "mobileUA": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1", + "desktopUA": "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15" + }, + { + "key": "sf", + "name": "Apple Safari 14", + "mobileUA": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1", + "desktopUA": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15" + }, + { + "key": "edgelast", + "name": "Microsoft Edge 97", + "mobileUA": "Mozilla/5.0 (Linux; Android 10; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Mobile Safari/537.36 Edg/97.0.1072.69", + "desktopUA": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/97.0.1072.69" + }, + { + "key": "edge", + "name": "Microsoft Edge 91", + "mobileUA": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Mobile Safari/537.36 Edg/91.0.864.71", + "desktopUA": "Mozilla/5.0 (X11; Linux x86_32) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.37" + }, + { + "key": "ie11", + "name": "Microsoft Internet Explorer 11", + "mobileUA": "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko", + "desktopUA": "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko" + }, + { + "key": "ie8", + "name": "Microsoft Internet Explorer 8", + "mobileUA": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)", + "desktopUA": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)" + }, + { + "key": "ie6", + "name": "Microsoft Internet Explorer 6", + "mobileUA": "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)", + "desktopUA": "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)" + } +] diff --git a/sailfish-browser.pro b/sailfish-browser.pro index 37e726a084a..ef9b550ea27 100644 --- a/sailfish-browser.pro +++ b/sailfish-browser.pro @@ -21,7 +21,9 @@ oneshots.files = oneshot.d/browser-cleanup-startup-cache \ oneshots.path = /usr/lib/oneshot.d data.files = data/prefs.js \ - data/ua-update.json.in + data/ua-update.json.in \ + data/useragents.json + data.path = /usr/share/sailfish-browser/data INSTALLS += desktop dbus_service chrome_scripts oneshots data From 9876768ebfb3a4f1593bfb587b38b91276bb3c64 Mon Sep 17 00:00:00 2001 From: Pavel Tumakaev Date: Thu, 17 Feb 2022 21:15:52 +0300 Subject: [PATCH 2/6] [sailfish-browser] Add a table and interfaces to store user agent overrides in the database. Contributes to JB#31240 --- apps/storage/dbmanager.cpp | 25 +++++++++++++++++++ apps/storage/dbmanager.h | 5 ++++ apps/storage/dbworker.cpp | 50 ++++++++++++++++++++++++++++++++++++++ apps/storage/dbworker.h | 5 ++++ 4 files changed, 85 insertions(+) diff --git a/apps/storage/dbmanager.cpp b/apps/storage/dbmanager.cpp index 3cb6a21c53c..768da04229c 100644 --- a/apps/storage/dbmanager.cpp +++ b/apps/storage/dbmanager.cpp @@ -187,3 +187,28 @@ void DBManager::deleteSetting(const QString &name) Q_ARG(QString, name)); } } + +void DBManager::setUserAgentOverride(const QString &host, const bool isKey, const QString &userAgent) +{ + QMetaObject::invokeMethod(worker, "setUserAgentOverride", Qt::QueuedConnection, + Q_ARG(QString, host), Q_ARG(bool, isKey), Q_ARG(QString, userAgent)); +} + +void DBManager::unsetUserAgentOverride(const QString &host) +{ + QMetaObject::invokeMethod(worker, "unsetUserAgentOverride", Qt::QueuedConnection, + Q_ARG(QString, host)); +} + +void DBManager::clearUserAgentOverrides() +{ + QMetaObject::invokeMethod(worker, "clearUserAgentOverrides", Qt::QueuedConnection); +} + +QVariantMap DBManager::getUserAgentOverrides() const +{ + QVariantMap userAgentOverrides; + QMetaObject::invokeMethod(worker, "getUserAgentOverrides", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariantMap, userAgentOverrides)); + return userAgentOverrides; +} diff --git a/apps/storage/dbmanager.h b/apps/storage/dbmanager.h index 087e898844e..9a476c5c9c1 100644 --- a/apps/storage/dbmanager.h +++ b/apps/storage/dbmanager.h @@ -50,6 +50,11 @@ class DBManager : public QObject QString getSetting(const QString &name); void deleteSetting(const QString &name); + void setUserAgentOverride(const QString &host, const bool isKey, const QString &userAgent); + void unsetUserAgentOverride(const QString &host); + void clearUserAgentOverrides(); + QVariantMap getUserAgentOverrides() const; + int getMaxTabId(); signals: diff --git a/apps/storage/dbworker.cpp b/apps/storage/dbworker.cpp index 5acb75b630d..921a44bf3ed 100644 --- a/apps/storage/dbworker.cpp +++ b/apps/storage/dbworker.cpp @@ -67,6 +67,13 @@ static const char * const create_table_settings = "value TEXT\n" ");\n"; +static const char * const create_table_user_agent_overrides = + "CREATE TABLE user_agent_overrides (\n" + " host TEXT NOT NULL UNIQUE PRIMARY KEY,\n" + " is_key INTEGER NOT NULL CHECK(is_key IN (0,1)),\n" + " user_agent TEXT\n" + ") WITHOUT ROWID\n"; + static const char * const set_user_version = "PRAGMA user_version=" STR(DB_USER_VERSION) ";\n"; @@ -76,6 +83,7 @@ static const char *db_schema[] = { create_table_link, create_table_browser_history, create_table_settings, + create_table_user_agent_overrides, set_user_version }; static int db_schema_count = sizeof(db_schema) / sizeof(*db_schema); @@ -813,3 +821,45 @@ void DBWorker::deleteSetting(const QString &name) query.bindValue(0, name); execute(query); } + +void DBWorker::setUserAgentOverride(const QString &host, const bool isKey, const QString &userAgent) +{ + QSqlQuery query = prepare("INSERT INTO user_agent_overrides " + "(host, is_key, user_agent) " + "VALUES (?, ?, ?) " + "ON CONFLICT(host) DO UPDATE SET " + "is_key=excluded.is_key, " + "user_agent=excluded.user_agent;"); + + query.bindValue(0, host); + query.bindValue(1, isKey); + query.bindValue(2, userAgent); + execute(query); +} + +void DBWorker::unsetUserAgentOverride(const QString &host) +{ + QSqlQuery query = prepare("DELETE FROM user_agent_overrides WHERE host = ?"); + query.bindValue(0, host); + execute(query); +} + +void DBWorker::clearUserAgentOverrides() +{ + QSqlQuery query = prepare("DELETE FROM user_agent_overrides"); + execute(query); +} + +QVariantMap DBWorker::getUserAgentOverrides() +{ + QSqlQuery query = prepare("SELECT host, is_key, user_agent FROM user_agent_overrides;"); + QVariantMap userAgentOverrides; + if (execute(query)) { + while (query.next()) { + userAgentOverrides.insert(query.value(0).toString(), + QVariantList({query.value(1).toBool(), + query.value(2).toString()})); + } + } + return userAgentOverrides; +} diff --git a/apps/storage/dbworker.h b/apps/storage/dbworker.h index 3afa37d5e75..05acc174ce7 100644 --- a/apps/storage/dbworker.h +++ b/apps/storage/dbworker.h @@ -59,6 +59,11 @@ public slots: SettingsMap getSettings(); void deleteSetting(const QString &name); + void setUserAgentOverride(const QString &host, const bool isKey, const QString &userAgent); + void unsetUserAgentOverride(const QString &host); + void clearUserAgentOverrides(); + QVariantMap getUserAgentOverrides(); + signals: void tabsAvailable(QList tabs); void thumbPathChanged(int tabId, const QString &path); From a12fa8453e0f933fd6ccd0c964642619c80e55c4 Mon Sep 17 00:00:00 2001 From: Pavel Tumakaev Date: Mon, 21 Feb 2022 16:07:24 +0300 Subject: [PATCH 3/6] [sailfish-browser] Add user agent override management models. Contributes to JB#31240 --- apps/browser/main.cpp | 4 + apps/core/browser.cpp | 3 + apps/core/core.pri | 6 + apps/core/useragentfiltermodel.cpp | 60 +++++++++ apps/core/useragentfiltermodel.h | 38 ++++++ apps/core/useragentmanager.cpp | 141 +++++++++++++++++++++ apps/core/useragentmanager.h | 41 +++++++ apps/core/useragentmodel.cpp | 189 +++++++++++++++++++++++++++++ apps/core/useragentmodel.h | 81 +++++++++++++ 9 files changed, 563 insertions(+) create mode 100644 apps/core/useragentfiltermodel.cpp create mode 100644 apps/core/useragentfiltermodel.h create mode 100644 apps/core/useragentmanager.cpp create mode 100644 apps/core/useragentmanager.h create mode 100644 apps/core/useragentmodel.cpp create mode 100644 apps/core/useragentmodel.h diff --git a/apps/browser/main.cpp b/apps/browser/main.cpp index 8cab91427a2..2d8ba7dd1de 100644 --- a/apps/browser/main.cpp +++ b/apps/browser/main.cpp @@ -40,6 +40,8 @@ #include "secureaction.h" #include "faviconmanager.h" #include "bookmarkmanager.h" +#include "useragentmodel.h" +#include "useragentfiltermodel.h" #ifdef HAS_BOOSTER #include @@ -153,6 +155,8 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) qmlRegisterType(uri, 1, 0, "LoginModel"); qmlRegisterType(uri, 1, 0, "LoginFilterModel"); qmlRegisterSingletonType(uri, 1, 0, "BookmarkManager", bookmarkmanager_factory); + qmlRegisterType(uri, 1, 0, "UserAgentModel"); + qmlRegisterType(uri, 1, 0, "UserAgentFilterModel"); } qmlRegisterSingletonType(uri, 1, 0, "FaviconManager", faviconmanager_factory); qmlRegisterUncreatableType(uri, 1, 0, "DownloadStatus", ""); diff --git a/apps/core/browser.cpp b/apps/core/browser.cpp index 083918b01d2..9d1691b54c2 100644 --- a/apps/core/browser.cpp +++ b/apps/core/browser.cpp @@ -14,6 +14,7 @@ #include "browser_p.h" #include "declarativewebutils.h" #include "downloadmanager.h" +#include "useragentmanager.h" #include "settingmanager.h" #include "browserapp.h" @@ -83,10 +84,12 @@ Browser::Browser(QQuickView *view, QObject *parent) DeclarativeWebUtils *utils = DeclarativeWebUtils::instance(); DownloadManager *downloadManager = DownloadManager::instance(); + UserAgentManager *userAgentManager = UserAgentManager::instance(); d->view->rootContext()->setContextProperty("WebUtils", utils); d->view->rootContext()->setContextProperty("Settings", SettingManager::instance()); d->view->rootContext()->setContextProperty("DownloadManager", downloadManager); + d->view->rootContext()->setContextProperty("UserAgentManager", userAgentManager); QString mainQml = BrowserApp::captivePortal() ? "captiveportal.qml" : "browser.qml"; diff --git a/apps/core/core.pri b/apps/core/core.pri index 9f6e7d35d8a..19525752077 100644 --- a/apps/core/core.pri +++ b/apps/core/core.pri @@ -20,6 +20,9 @@ SOURCES += \ $$PWD/logging.cpp \ $$PWD/secureaction.cpp \ $$PWD/settingmanager.cpp \ + $$PWD/useragentmanager.cpp \ + $$PWD/useragentmodel.cpp \ + $$PWD/useragentfiltermodel.cpp \ $$PWD/webpagequeue.cpp \ $$PWD/webpages.cpp @@ -40,5 +43,8 @@ HEADERS += \ $$PWD/logging.h \ $$PWD/secureaction.h \ $$PWD/settingmanager.h \ + $$PWD/useragentmanager.h \ + $$PWD/useragentmodel.h \ + $$PWD/useragentfiltermodel.h \ $$PWD/webpagequeue.h \ $$PWD/webpages.h diff --git a/apps/core/useragentfiltermodel.cpp b/apps/core/useragentfiltermodel.cpp new file mode 100644 index 00000000000..e088e31703f --- /dev/null +++ b/apps/core/useragentfiltermodel.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "useragentfiltermodel.h" +#include "useragentmodel.h" + +UserAgentFilterModel::UserAgentFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setDynamicSortFilter(true); +} + +int UserAgentFilterModel::getIndex(int currentIndex) +{ + QModelIndex proxyIndex = index(currentIndex, 0); + QModelIndex sourceIndex = mapToSource(proxyIndex); + return sourceIndex.row(); +} + +bool UserAgentFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + + if (sourceModel()->data(index, UserAgentModel::HostRole).toString().trimmed().contains(m_search, Qt::CaseInsensitive)) { + return true; + } + return false; +} + +void UserAgentFilterModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + if (sourceModel) { + beginResetModel(); + QSortFilterProxyModel::setSourceModel(sourceModel); + endResetModel(); + } +} + +QString UserAgentFilterModel::search() const +{ + return m_search; +} + +void UserAgentFilterModel::setSearch(const QString &search) +{ + if (m_search == search) { + return; + } + + m_search = search; + invalidateFilter(); + emit searchChanged(m_search); +} diff --git a/apps/core/useragentfiltermodel.h b/apps/core/useragentfiltermodel.h new file mode 100644 index 00000000000..2dd551e9281 --- /dev/null +++ b/apps/core/useragentfiltermodel.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef USERAGENTFILTERMODEL_H +#define USERAGENTFILTERMODEL_H + +#include + +class UserAgentFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(QString search READ search WRITE setSearch NOTIFY searchChanged) +public: + UserAgentFilterModel(QObject *parent = nullptr); + + Q_INVOKABLE int getIndex(int currentIndex); + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + void setSourceModel(QAbstractItemModel *sourceModel) override; + + QString search() const; + void setSearch(const QString &search); + +signals: + void searchChanged(QString search); + +private: + QString m_search; +}; + +#endif // USERAGENTFILTERMODEL_H diff --git a/apps/core/useragentmanager.cpp b/apps/core/useragentmanager.cpp new file mode 100644 index 00000000000..a0c0c087f69 --- /dev/null +++ b/apps/core/useragentmanager.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "useragentmanager.h" +#include +#include +#include +#include +#include +#include "qmozcontext.h" +#include "dbmanager.h" +#include "declarativewebcontainer.h" +#include "faviconmanager.h" + +const auto USER_AGENTS_CONFIG = QStringLiteral("/usr/share/sailfish-browser/data/useragents.json"); + +static UserAgentManager *gSingleton = nullptr; + +UserAgentManager::UserAgentManager(QObject *parent) + : QObject(parent) +{ + initialize(); +} + +UserAgentManager::~UserAgentManager() +{ + gSingleton = nullptr; +} + +void UserAgentManager::initialize() +{ + QScopedPointer file(new QFile(USER_AGENTS_CONFIG)); + + if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) { + return; + } + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file->readAll(), &error); + + if (doc.isNull()) { + return; + } + + if (!doc.isArray()) { + return; + } + + QJsonArray array = doc.array(); + + m_browserList.clear(); + for (const QJsonValue &value : array) { + if (value.isObject()) { + QJsonObject obj = value.toObject(); + QVariantMap val; + val["key"] = obj.value("key").toString(); + val["name"] = obj.value("name").toString(); + m_browserList.push_back(val); + } + } + + QVariant conf = doc.toVariant(); + QVariantMap overrides = getUserAgentOverrides(); + + QMozContext *mozContext = QMozContext::instance(); + if (mozContext->isInitialized()) { + SailfishOS::WebEngine::instance()->notifyObservers(QLatin1String("useragentlistchanged"), conf); + SailfishOS::WebEngine::instance()->notifyObservers(QLatin1String("useragentoverrideschanged"), overrides); + } else { + QObject *context = new QObject(this); + connect(mozContext, &QMozContext::initialized, context, [conf, overrides, context]() { + SailfishOS::WebEngine::instance()->notifyObservers(QLatin1String("useragentlistchanged"), conf); + SailfishOS::WebEngine::instance()->notifyObservers(QLatin1String("useragentoverrideschanged"), overrides); + context->deleteLater(); + }); + } +} + +QVariantMap UserAgentManager::getUserAgentOverrides() const +{ + return DBManager::instance()->getUserAgentOverrides(); +} + +void UserAgentManager::setUserAgentOverride(const QString &host, const QString &userAgent, const bool isKey) +{ + DBManager::instance()->setUserAgentOverride(host, isKey, userAgent); + SailfishOS::WebEngine::instance()->notifyObservers(QLatin1String("useragentoverrideschanged"), + QVariantMap{ + std::pair( + host, + QVariantList{isKey, userAgent} + ) + }); + + DeclarativeWebContainer *webContainer = DeclarativeWebContainer::instance(); + if (webContainer->webPage() && QUrl(webContainer->url()).host() == host) { + FaviconManager::instance()->grabIcon("userAgents", webContainer->webPage(), QSize(64, 64)); + } +} + +void UserAgentManager::unsetUserAgentOverride(const QString &host) { + DBManager::instance()->unsetUserAgentOverride(host); + qWarning() << "#### UserAgentManager::setUserAgentOverride"; + SailfishOS::WebEngine::instance()->notifyObservers(QLatin1String("useragentoverrideschanged"), + QVariantMap{ + std::pair( + host, + QVariantList{true, ""} + ) + }); + FaviconManager::instance()->remove("userAgents", "https://" + host); + FaviconManager::instance()->remove("userAgents", "http://" + host); +} + +void UserAgentManager::clearUserAgentOverrides() +{ + qWarning() << "UserAgentManager::clearUserAgentOverrides"; + DBManager::instance()->clearUserAgentOverrides(); + SailfishOS::WebEngine::instance()->notifyObservers(QLatin1String("useragentoverrideschanged"), + QVariantMap{}); +} + +QVariantList UserAgentManager::getBrowserList() const +{ + return m_browserList; +} + +UserAgentManager *UserAgentManager::instance() +{ + if (!gSingleton) { + gSingleton = new UserAgentManager(); + } + return gSingleton; +} diff --git a/apps/core/useragentmanager.h b/apps/core/useragentmanager.h new file mode 100644 index 00000000000..a38d0cf8d99 --- /dev/null +++ b/apps/core/useragentmanager.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef USERAGENTMANAGER_H +#define USERAGENTMANAGER_H + +#include +#include +#include +#include + +class UserAgentManager : public QObject +{ + Q_OBJECT + +public: + static UserAgentManager *instance(); + QVariantMap getUserAgentOverrides() const; + void setUserAgentOverride(const QString &host, const QString &userAgent, const bool isKey); + void unsetUserAgentOverride(const QString &host); + Q_INVOKABLE void clearUserAgentOverrides(); + +public slots: + QVariantList getBrowserList() const; + +private: + explicit UserAgentManager(QObject *parent = nullptr); + ~UserAgentManager(); + + void initialize(); + QVariantList m_browserList; +}; + +#endif diff --git a/apps/core/useragentmodel.cpp b/apps/core/useragentmodel.cpp new file mode 100644 index 00000000000..e7bc26847fa --- /dev/null +++ b/apps/core/useragentmodel.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "useragentmodel.h" +#include "useragentmanager.h" +#include "faviconmanager.h" + +UserAgent::UserAgent(const QString &host, const bool isKey, const QString &userAgent) + : m_host(host) + , m_isKey(isKey) + , m_userAgent(userAgent) +{ +} + +QString UserAgent::host() const +{ + return m_host; +} + +bool UserAgent::isKey() const +{ + return m_isKey; +} + +void UserAgent::setIsKey(bool isKey) +{ + m_isKey = isKey; +} + +QString UserAgent::userAgent() const +{ + return m_userAgent; +} + +void UserAgent::setUserAgent(const QString &userAgent) +{ + m_userAgent = userAgent; +} + +UserAgentModel::UserAgentModel(QObject *parent) + : QAbstractListModel(parent) +{ + QVariantMap overrides = UserAgentManager::instance()->getUserAgentOverrides(); + + beginResetModel(); + for (QVariantMap::const_iterator it = overrides.begin(); it != overrides.end(); ++it) { + m_userAgentList.append(UserAgent(it.key(), it.value().toList().at(0).toBool(), it.value().toList().at(1).toString())); + m_index.insert(it.key(), m_userAgentList.size() - 1); + } + endResetModel(); +} + +int UserAgentModel::findHostIndex(const QString &host) const +{ + QHash::const_iterator it = m_index.find(host); + return (it != m_index.end()) ? it.value() : -1; +} + +QVariantMap UserAgentModel::currentHostUserAgent() const +{ + int id = findHostIndex(m_currentHost); + return (id >= 0) ? QVariantMap{{"isKey", m_userAgentList[id].isKey()}, + {"userAgent", m_userAgentList[id].userAgent()}} : + QVariantMap{{"isKey", true}, + {"userAgent", ""}}; +} + +void UserAgentModel::setUserAgentOverride(const QString &host, const QString &userAgent, const bool isKey) +{ + UserAgentManager::instance()->setUserAgentOverride(host, userAgent, isKey); + + int id = findHostIndex(host); + if (id >= 0) { + UserAgent &ua = m_userAgentList[id]; + + QVector roles; + if (ua.isKey() != isKey) { + roles << IsKeyRole; + ua.setIsKey(isKey); + } + if (ua.userAgent() != userAgent) { + roles << UserAgentRole; + ua.setUserAgent(userAgent); + } + if (roles.count() > 0) { + emit dataChanged(index(id), index(id), roles); + if (host == m_currentHost) { + emit currentHostUserAgentChanged(); + } + } + } else { + int count = m_userAgentList.count(); + beginInsertRows(QModelIndex(), count, count); + m_userAgentList.append(UserAgent{host, isKey, userAgent}); + m_index.insert(host, m_userAgentList.size() - 1); + endInsertRows(); + + emit countChanged(); + + if (host == m_currentHost) { + emit currentHostUserAgentChanged(); + } + } +} + +void UserAgentModel::unsetUserAgentOverride(const QString &host) +{ + UserAgentManager::instance()->unsetUserAgentOverride(host); + + int id = findHostIndex(host); + if (id >= 0) { + beginRemoveRows(QModelIndex(), id, id); + m_userAgentList.removeAt(id); + endRemoveRows(); + + m_index.clear(); + for (int i = 0; i < m_userAgentList.size(); ++i) { + m_index.insert(m_userAgentList[i].host(), i); + } + + emit countChanged(); + + if (host == m_currentHost) { + emit currentHostUserAgentChanged(); + } + } +} + +QVariant UserAgentModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= m_userAgentList.count()) + return QVariant(); + + const UserAgent &ua = m_userAgentList.at(index.row()); + + switch (role) { + case HostRole: + return ua.host(); + case IsKeyRole: + return ua.isKey(); + case UserAgentRole: + return ua.userAgent(); + case FavIconRole: { + QString favIcon = FaviconManager::instance()->get("userAgents", "https://" + ua.host()); + if (favIcon.isEmpty()) favIcon = FaviconManager::instance()->get("userAgents", "http://" + ua.host()); + return favIcon; + } + default: + return QVariant(); + } +} + +int UserAgentModel::rowCount(const QModelIndex&) const +{ + return m_userAgentList.count(); +} + +QHash UserAgentModel::roleNames() const +{ + QHash roles; + roles[HostRole] = "host"; + roles[IsKeyRole] = "isKey"; + roles[UserAgentRole] = "userAgent"; + roles[FavIconRole] = "favicon"; + return roles; +} + +QString UserAgentModel::currentHost() const +{ + return m_currentHost; +} + +void UserAgentModel::setCurrentHost(const QString &host) +{ + if (m_currentHost == host) { + return; + } + + m_currentHost = host; + emit currentHostChanged(); + emit currentHostUserAgentChanged(); +} diff --git a/apps/core/useragentmodel.h b/apps/core/useragentmodel.h new file mode 100644 index 00000000000..aabbdebb8f2 --- /dev/null +++ b/apps/core/useragentmodel.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef USERAGENTMODEL_H +#define USERAGENTMODEL_H + +#include +#include +#include + +class UserAgent +{ +public: + UserAgent(const QString &host, + const bool isKey, + const QString &userAgent); + + QString host() const; + bool isKey() const; + void setIsKey(bool isKey); + QString userAgent() const; + void setUserAgent(const QString &userAgent); + +private: + QString m_host; + bool m_isKey; + QString m_userAgent; +}; + +class UserAgentModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(QVariantMap currentHostUserAgent READ currentHostUserAgent NOTIFY currentHostUserAgentChanged FINAL) + Q_PROPERTY(QString currentHost READ currentHost WRITE setCurrentHost NOTIFY currentHostChanged) + +public: + enum Roles { + HostRole = Qt::UserRole, + IsKeyRole, + UserAgentRole, + FavIconRole + }; + + UserAgentModel(QObject *parent = nullptr); + + Q_INVOKABLE void setUserAgentOverride(const QString &host, const QString &userAgent, const bool isKey); + Q_INVOKABLE void unsetUserAgentOverride(const QString &host); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; + + QString currentHost() const; + void setCurrentHost(const QString &host); + +signals: + void countChanged(); + void currentHostUserAgentChanged(); + void currentHostChanged(); + +private: + QVariantMap currentHostUserAgent() const; + int findHostIndex(const QString &host) const; + +private: + // + QHash m_index; + QList m_userAgentList; + QString m_currentHost; +}; + +#endif // USERAGENTMODEL_H From 2b31b2d54e9485f3bd54f3f1a42cc7d951d82f04 Mon Sep 17 00:00:00 2001 From: Pavel Tumakaev Date: Mon, 21 Feb 2022 16:34:00 +0300 Subject: [PATCH 4/6] [sailfish-browser] Provide an option in site info panel to set user agent override. Contributes to JB#31240 --- apps/browser/qml/pages/SiteUserAgentPage.qml | 104 ++++++++++++++++++ .../qml/pages/components/CertificateInfo.qml | 49 ++++++++- apps/browser/qml/pages/useragenthelper.js | 30 +++++ apps/core/declarativewebutils.cpp | 5 + apps/core/declarativewebutils.h | 1 + 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 apps/browser/qml/pages/SiteUserAgentPage.qml create mode 100755 apps/browser/qml/pages/useragenthelper.js diff --git a/apps/browser/qml/pages/SiteUserAgentPage.qml b/apps/browser/qml/pages/SiteUserAgentPage.qml new file mode 100644 index 00000000000..0c11606a53b --- /dev/null +++ b/apps/browser/qml/pages/SiteUserAgentPage.qml @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC. +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import QtQuick 2.1 +import Sailfish.Silica 1.0 +import Sailfish.Browser 1.0 +import "useragenthelper.js" as UserAgentHelper + +Page { + id: page + + property UserAgentModel userAgentModel: UserAgentModel { + currentHost: WebUtils.host(webView.url) + } + property string hostname: userAgentModel.currentHost + property bool isKey: userAgentModel.currentHostUserAgent.isKey + property string userAgent: userAgentModel.currentHostUserAgent.userAgent + property WebPage webPage: null + + SilicaListView { + anchors.fill: parent + model: UserAgentHelper.model + + header: PageHeader { + //% "User agent override" + title: qsTrId("sailfish_browser-he-user_agent_override") + description: page.hostname + } + + delegate: BackgroundItem { + id: delegateItem + + onClicked: { + if ("custom" in modelData) { + pageStack.animatorPush(customUserAgentDialog, + { + userAgentModel: userAgentModel, + userAgent: page.isKey ? "" : page.userAgent, + hostname: page.hostname + }) + } else { + userAgentModel.setUserAgentOverride(page.hostname, modelData.key, true) + pageStack.pop() + } + } + + Label { + width: parent.width + leftPadding: Theme.horizontalPageMargin + rightPadding: Theme.horizontalPageMargin + text: modelData.name + (!page.isKey && "custom" in modelData ? ": " + userAgent : "") + elide: Text.ElideRight + highlighted: page.isKey ? modelData.key === page.userAgent : "custom" in modelData + } + } + + VerticalScrollDecorator {} + } + + Component { + id: customUserAgentDialog + + Dialog { + id: dialog + + property UserAgentModel userAgentModel + property string userAgent + property string hostname + + canAccept: textField.text !== "" + onAccepted: { + userAgentModel.setUserAgentOverride(hostname, textField.text.trim(), false) + } + + DialogHeader { + id: header + //: Accept button text for adding a home page adress + //% "OK" + acceptText: qsTrId("sailfish_browser-he-ok") + } + + TextField { + id: textField + + anchors.top: header.bottom + focus: true + text: userAgent + //% "User agent to use when loading %1." + description: qsTrId("sailfish_browser-he-user_agent_for_site").arg(hostname) + label: placeholderText + inputMethodHints: Qt.ImhNoPredictiveText + EnterKey.onClicked: dialog.accept() + } + } + } +} + diff --git a/apps/browser/qml/pages/components/CertificateInfo.qml b/apps/browser/qml/pages/components/CertificateInfo.qml index a9cee600100..846ace9f58a 100644 --- a/apps/browser/qml/pages/components/CertificateInfo.qml +++ b/apps/browser/qml/pages/components/CertificateInfo.qml @@ -13,6 +13,7 @@ import Sailfish.Browser 1.0 import Qt5Mozilla 1.0 import "." as Browser import Sailfish.WebView.Controls 1.0 +import "../useragenthelper.js" as UserAgentHelper SilicaFlickable { id: root @@ -33,6 +34,10 @@ SilicaFlickable { }) } + function openSiteUseragentSettings() { + pageStack.push("../SiteUserAgentPage.qml") + } + onSecurityChanged: { // Jump back to the top contentY = originY @@ -43,6 +48,11 @@ SilicaFlickable { host: toolBarRow.url } + UserAgentModel { + id: userAgentModel + currentHost: WebUtils.host(webView.url) + } + VerticalScrollDecorator {} MouseArea { @@ -116,7 +126,6 @@ SilicaFlickable { onClicked: showCertDetail() } - Loader { height: Theme.fontSizeMedium + Theme.iconSizeMedium + Theme.paddingMedium width: permissionIndicationModel.count === 0 ? implicitWidth : parent.width @@ -124,6 +133,44 @@ SilicaFlickable { sourceComponent: permissionIndicationModel.count === 0 ? permissionButtonComponent : permissionComponent } + MouseArea { + id: userAgentArea + + width: parent.width + height: userAgentColumn.height + onClicked: openSiteUseragentSettings() + + Column { + id: userAgentColumn + + width: parent.width + spacing: Theme.paddingMedium + anchors.horizontalCenter: parent.horizontalCenter + + Label { + width: parent.width - 2 * Theme.horizontalPageMargin + x: Theme.horizontalPageMargin + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + color: Theme.highlightColor + //% "Current user agent" + text: qsTrId("sailfish_browser-sh-current_user_agent") + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width - 2 * Theme.horizontalPageMargin + horizontalAlignment: Text.AlignHCenter + color: Theme.primaryColor + font.pixelSize: Theme.fontSizeSmall + textFormat: Text.PlainText + wrapMode: Text.Wrap + text: UserAgentHelper.getUserAgentString(userAgentModel.currentHostUserAgent.userAgent, + userAgentModel.currentHostUserAgent.isKey) + } + } + } + Component { id: permissionButtonComponent diff --git a/apps/browser/qml/pages/useragenthelper.js b/apps/browser/qml/pages/useragenthelper.js new file mode 100755 index 00000000000..dfc84041da3 --- /dev/null +++ b/apps/browser/qml/pages/useragenthelper.js @@ -0,0 +1,30 @@ +var model = [].concat( + [{ + key: "", + //: Identifier of the current browser + //% "Gecko-based browser (Default)" + name: qsTrId("sailfish_browser-la-browser_list_current_browser") + }], + UserAgentManager.getBrowserList(), + [{ + custom: true, + //: A user-defined user agent + //% "Custom" + name: qsTrId("sailfish_browser-la-browser_list_custom") + }] + ) + +function getUserAgentString(userAgent, isKey) +{ + if (!isKey) { + return userAgent + } + + for (var i = 0; i < model.length; i++) { + if (i in model && model[i].key === userAgent) { + return model[i].name + } + } + + return model[0].name +} diff --git a/apps/core/declarativewebutils.cpp b/apps/core/declarativewebutils.cpp index 5f13b490708..2799d9b4c5c 100644 --- a/apps/core/declarativewebutils.cpp +++ b/apps/core/declarativewebutils.cpp @@ -202,6 +202,11 @@ QString DeclarativeWebUtils::displayableUrl(const QString &fullUrl) const return !returnUrl.isEmpty() ? returnUrl : fullUrl; } +QString DeclarativeWebUtils::host(const QString &fullUrl) const +{ + return QUrl(fullUrl).host(); +} + QString DeclarativeWebUtils::pageName(const QString &fullUrl) const { QUrl url(fullUrl); diff --git a/apps/core/declarativewebutils.h b/apps/core/declarativewebutils.h index 8068f0fa9a6..5857944e45a 100644 --- a/apps/core/declarativewebutils.h +++ b/apps/core/declarativewebutils.h @@ -36,6 +36,7 @@ class DeclarativeWebUtils : public QObject Q_INVOKABLE int getLightness(const QColor &color) const; Q_INVOKABLE QString displayableUrl(const QString &fullUrl) const; + Q_INVOKABLE QString host(const QString &fullUrl) const; Q_INVOKABLE QString pageName(const QString &fullUrl) const; public slots: From 44261b4f75d9ce46ab32d97803b3b816756a3652 Mon Sep 17 00:00:00 2001 From: Pavel Tumakaev Date: Mon, 21 Feb 2022 16:34:36 +0300 Subject: [PATCH 5/6] [sailfish-browser] Add user agent override management page. Contributes to JB#31240 --- apps/browser/qml/pages/SettingsPage.qml | 26 +++++ apps/browser/qml/pages/UserAgentPage.qml | 135 +++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 apps/browser/qml/pages/UserAgentPage.qml diff --git a/apps/browser/qml/pages/SettingsPage.qml b/apps/browser/qml/pages/SettingsPage.qml index b83f2f1098b..a33735d11f0 100644 --- a/apps/browser/qml/pages/SettingsPage.qml +++ b/apps/browser/qml/pages/SettingsPage.qml @@ -232,6 +232,32 @@ Page { onClicked: pageStack.push("PermissionPage.qml") } + BackgroundItem { + width: parent.width + contentHeight: Theme.itemSizeMedium + Row { + width: parent.width - 2*Theme.horizontalPageMargin + x: Theme.horizontalPageMargin + spacing: Theme.paddingMedium + anchors.verticalCenter: parent.verticalCenter + + Icon { + id: userAgentsIcon + source: "image://theme/icon-m-browser-user-agents" + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium + } + Label { + width: parent.width - parent.spacing - userAgentsIcon.width + //: The label for the button for accessing user agent overrides management + //% "User agent overrides" + text: qsTrId("settings_browser-la-user_agent_overrides") + anchors.verticalCenter: userAgentsIcon.verticalCenter + } + } + onClicked: pageStack.push("UserAgentPage.qml") + } + BackgroundItem { width: parent.width contentHeight: Theme.itemSizeMedium diff --git a/apps/browser/qml/pages/UserAgentPage.qml b/apps/browser/qml/pages/UserAgentPage.qml new file mode 100644 index 00000000000..5be442b445f --- /dev/null +++ b/apps/browser/qml/pages/UserAgentPage.qml @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (c) 2022 Open Mobile Platform LLC. +** +****************************************************************************/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import QtQuick 2.5 +import Sailfish.Silica 1.0 +import Sailfish.Browser 1.0 +import "useragenthelper.js" as UserAgentHelper +import "components" + +Page { + UserAgentFilterModel { + id: userAgentFilterModel + sourceModel: UserAgentModel { + id: userAgentModel + } + } + + SilicaListView { + id: view + + anchors.fill: parent + model: userAgentFilterModel + currentIndex: -1 + header: Column { + width: parent.width + PageHeader { + //% "User agent overrides" + title: qsTrId("sailfish_browser-he-user_agent_overrides") + } + SearchField { + width: parent.width + //% "Search" + placeholderText: qsTrId("sailfish_browser-ph-usera_gent_override_search") + EnterKey.onClicked: focus = false + onTextChanged: userAgentFilterModel.search = text + inputMethodHints: Qt.ImhPreferLowercase | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText + visible: userAgentModel.count > 0 + } + } + + delegate: ListItem { + id: listItem + + width: parent.width + contentHeight: Theme.itemSizeMedium + ListView.onAdd: AddAnimation { target: listItem } + ListView.onRemove: animateRemoval() + + function remove(host) { + remorseDelete(function() { + userAgentModel.unsetUserAgentOverride(host) + }) + } + + onClicked: openMenu() + + Row { + width: parent.width - 2 * Theme.horizontalPageMargin + x: Theme.horizontalPageMargin + spacing: Theme.paddingMedium + anchors.verticalCenter: parent.verticalCenter + + FavoriteIcon { + id: loginsIcon + + anchors.verticalCenter: parent.verticalCenter + icon: model.favicon + sourceSize.width: Theme.iconSizeMedium + sourceSize.height: Theme.iconSizeMedium + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium + } + + Column { + anchors.verticalCenter: parent.verticalCenter + width: parent.width - parent.spacing - loginsIcon.width + Label { + width: parent.width + text: Theme.highlightText(model.host, + userAgentFilterModel.search, + Theme.highlightColor) + textFormat: Text.StyledText + } + + Label { + width: parent.width + text: UserAgentHelper.getUserAgentString(model.userAgent, model.isKey) + font.pixelSize: Theme.fontSizeExtraSmall + elide: Text.ElideRight + color: Theme.secondaryColor + } + } + } + + menu: Component { + ContextMenu { + MenuItem { + //% "Edit" + text: qsTrId("sailfish_browser-me-user_agent_override_edit") + onClicked: { + userAgentModel.currentHost = model.host + pageStack.animatorPush("SiteUserAgentPage.qml", + { + userAgentModel: userAgentModel + }) + } + } + MenuItem { + //% "Delete" + text: qsTrId("sailfish_browser-me-user_agent_override_delete") + onClicked: listItem.remove(host) + } + } + } + } + + ViewPlaceholder { + //% "Your saved user agent overrides show up here" + text: qsTrId("sailfish_browser-la-user_agents_none") + enabled: userAgentModel.count === 0 + } + + VerticalScrollDecorator { + parent: view + flickable: view + } + } +} From 1e9b5edb5d81a34b067eaca11cb5e221650b2554 Mon Sep 17 00:00:00 2001 From: Pavel Tumakaev Date: Mon, 21 Feb 2022 16:35:31 +0300 Subject: [PATCH 6/6] [sailfish-browser] Add an option to PrivacySettingsPage to clear user agent overrides. Contributes to JB#31240 --- apps/browser/qml/pages/PrivacySettingsPage.qml | 10 ++++++++++ .../pages/components/PrivacySettingsConfirmDialog.qml | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/apps/browser/qml/pages/PrivacySettingsPage.qml b/apps/browser/qml/pages/PrivacySettingsPage.qml index dabaafb59c0..595b181870c 100644 --- a/apps/browser/qml/pages/PrivacySettingsPage.qml +++ b/apps/browser/qml/pages/PrivacySettingsPage.qml @@ -123,6 +123,14 @@ Page { checked: true } + TextSwitch { + id: clearUserAgentOverrides + + //% "User аgent overrides" + text: qsTrId("settings_browser-la-user_аgent_overrides") + checked: true + } + // Spacer between Button and switches Item { width: parent.width @@ -140,6 +148,7 @@ Page { || clearCache.checked || clearBookmarks.checked || clearSitePermissions.checked + || clearUserAgentOverrides.checked onClicked: { var page = pageStack.push(Qt.resolvedUrl("components/PrivacySettingsConfirmDialog.qml"), { @@ -149,6 +158,7 @@ Page { cacheEnabled: clearCache.checked, bookmarksEnabled: clearBookmarks.checked, sitePermissionsEnabled: clearSitePermissions.checked, + userAgentOverridesEnabled: clearUserAgentOverrides.checked, historyPeriod: historyErasingComboBox.currentItem.period, acceptDestination: previousPage }) diff --git a/apps/browser/qml/pages/components/PrivacySettingsConfirmDialog.qml b/apps/browser/qml/pages/components/PrivacySettingsConfirmDialog.qml index 2cfe431cfd0..00f6cf0ef1a 100644 --- a/apps/browser/qml/pages/components/PrivacySettingsConfirmDialog.qml +++ b/apps/browser/qml/pages/components/PrivacySettingsConfirmDialog.qml @@ -20,6 +20,7 @@ Dialog { property alias cacheEnabled: cacheItem.visible property alias bookmarksEnabled: bookmarksItem.visible property alias sitePermissionsEnabled: sitePermissionsItem.visible + property alias userAgentOverridesEnabled: userAgentOverridesItem.visible property int historyPeriod @@ -118,6 +119,13 @@ Dialog { //% "Site permissions" text: qsTrId("sailfish_browser-la-site_permissions"); } + + BrowserDataItem { + id: userAgentOverridesItem + + //% "User agent overrides" + text: qsTrId("sailfish_browser-la-user_agent_overrides"); + } } } @@ -140,6 +148,9 @@ Dialog { if (sitePermissionsEnabled) { Settings.clearSitePermissions() } + if (userAgentOverridesEnabled) { + UserAgentManager.clearUserAgentOverrides() + } } Component.onCompleted: {