mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 04:25:13 +00:00
LibWebView: Add autocomplete settings to about:settings
This implements an autocomplete engine inside LibWebView, to replace the engine currently used by Qt. Whereas Qt uses the Qt Network framework to perform autocomplete requests, LibWebView uses RequestServer. This moves downloading this untrusted data out of the browser process. This patch only implements the persisted settings and their UI. It does not integrate this engine into the browser UI.
This commit is contained in:
parent
0cb506277f
commit
127a3d6f79
9 changed files with 432 additions and 66 deletions
|
@ -52,6 +52,12 @@
|
|||
float: left;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-background-color);
|
||||
|
||||
|
@ -72,6 +78,10 @@
|
|||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-body > hr {
|
||||
margin: 0 8px 16px 8px;
|
||||
}
|
||||
|
||||
.card-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
@ -165,10 +175,6 @@
|
|||
}
|
||||
|
||||
dialog .dialog-body hr {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
border: none;
|
||||
|
||||
margin: 8px 4px 10px 4px;
|
||||
}
|
||||
|
||||
|
@ -283,17 +289,36 @@
|
|||
<div class="card-body">
|
||||
<div class="card-group">
|
||||
<div class="toggle-container">
|
||||
<label for="search-enabled">Enable Search</label>
|
||||
<label for="search-toggle">Enable Search</label>
|
||||
<label class="toggle">
|
||||
<input id="search-enabled" type="checkbox" />
|
||||
<input id="search-toggle" type="checkbox" />
|
||||
<span class="toggle-button"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="search-engine-list" class="card-group hidden">
|
||||
<div class="card-group hidden">
|
||||
<label for="search-engine">Default Search Engine</label>
|
||||
<select id="search-engine">
|
||||
<option value="">Please Select a Search Engine</option>
|
||||
<option value="">Please select a search engine</option>
|
||||
<hr />
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="card-group">
|
||||
<div class="toggle-container">
|
||||
<label for="autocomplete-toggle">Enable Autocomplete</label>
|
||||
<label class="toggle">
|
||||
<input id="autocomplete-toggle" type="checkbox" />
|
||||
<span class="toggle-button"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-group hidden">
|
||||
<label for="autocomplete-engine">Default Autocomplete Engine</label>
|
||||
<select id="autocomplete-engine">
|
||||
<option value="">Please select an autocomplete engine</option>
|
||||
<hr />
|
||||
</select>
|
||||
</div>
|
||||
|
@ -347,9 +372,10 @@
|
|||
|
||||
<script>
|
||||
const newTabPageURL = document.querySelector("#new-tab-page-url");
|
||||
const searchEngineList = document.querySelector("#search-engine-list");
|
||||
const searchEnabled = document.querySelector("#search-enabled");
|
||||
const searchToggle = document.querySelector("#search-toggle");
|
||||
const searchEngine = document.querySelector("#search-engine");
|
||||
const autocompleteToggle = document.querySelector("#autocomplete-toggle");
|
||||
const autocompleteEngine = document.querySelector("#autocomplete-engine");
|
||||
const autoplaySettings = document.querySelector("#autoplay-settings");
|
||||
const siteSettings = document.querySelector("#site-settings");
|
||||
const siteSettingsAdd = document.querySelector("#site-settings-add");
|
||||
|
@ -372,16 +398,21 @@
|
|||
newTabPageURL.classList.remove("error");
|
||||
newTabPageURL.value = window.settings.newTabPageURL;
|
||||
|
||||
const searchEngineName = window.settings.searchEngine?.name;
|
||||
const renderEngineSettings = (type, setting) => {
|
||||
const [name, toggle, engine] = engineForType(type);
|
||||
|
||||
if (searchEngineName) {
|
||||
searchEnabled.checked = true;
|
||||
searchEngine.value = searchEngineName;
|
||||
} else {
|
||||
searchEnabled.checked = false;
|
||||
}
|
||||
if (setting?.name) {
|
||||
toggle.checked = true;
|
||||
engine.value = setting?.name;
|
||||
} else {
|
||||
toggle.checked = false;
|
||||
}
|
||||
|
||||
renderSearchEngine();
|
||||
renderEngine(type);
|
||||
};
|
||||
|
||||
renderEngineSettings(Engine.search, window.settings.searchEngine);
|
||||
renderEngineSettings(Engine.autocomplete, window.settings.autocompleteEngine);
|
||||
|
||||
const siteSetting = currentSiteSetting();
|
||||
|
||||
|
@ -390,41 +421,6 @@
|
|||
}
|
||||
};
|
||||
|
||||
const loadSearchEngines = engines => {
|
||||
for (const engine of engines) {
|
||||
const option = document.createElement("option");
|
||||
option.text = engine;
|
||||
option.value = engine;
|
||||
|
||||
searchEngine.add(option);
|
||||
}
|
||||
};
|
||||
|
||||
const renderSearchEngine = () => {
|
||||
if (searchEnabled.checked) {
|
||||
searchEngineList.classList.remove("hidden");
|
||||
} else {
|
||||
searchEngineList.classList.add("hidden");
|
||||
}
|
||||
|
||||
if (searchEnabled.checked && searchEngine.selectedIndex !== 0) {
|
||||
searchEngine.item(0).disabled = true;
|
||||
} else if (!searchEnabled.checked) {
|
||||
searchEngine.item(0).disabled = false;
|
||||
searchEngine.selectedIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const saveSearchEngine = () => {
|
||||
if (searchEnabled.checked && searchEngine.selectedIndex !== 0) {
|
||||
ladybird.sendMessage("setSearchEngine", searchEngine.value);
|
||||
} else if (!searchEnabled.checked) {
|
||||
ladybird.sendMessage("setSearchEngine", null);
|
||||
}
|
||||
|
||||
renderSearchEngine();
|
||||
};
|
||||
|
||||
newTabPageURL.addEventListener("change", () => {
|
||||
newTabPageURL.classList.remove("success");
|
||||
newTabPageURL.classList.remove("error");
|
||||
|
@ -442,8 +438,75 @@
|
|||
}, 1000);
|
||||
});
|
||||
|
||||
searchEnabled.addEventListener("change", saveSearchEngine);
|
||||
searchEngine.addEventListener("change", saveSearchEngine);
|
||||
const Engine = Object.freeze({
|
||||
search: 1,
|
||||
autocomplete: 2,
|
||||
});
|
||||
|
||||
const engineForType = engine => {
|
||||
if (engine === Engine.search) {
|
||||
return ["Search", searchToggle, searchEngine];
|
||||
}
|
||||
if (engine === Engine.autocomplete) {
|
||||
return ["Autocomplete", autocompleteToggle, autocompleteEngine];
|
||||
}
|
||||
throw Error(`Unrecognized engine type ${engine}`);
|
||||
};
|
||||
|
||||
const loadEngines = (type, engines) => {
|
||||
const [name, toggle, engine] = engineForType(type);
|
||||
|
||||
for (const engineName of engines) {
|
||||
const option = document.createElement("option");
|
||||
option.text = engineName;
|
||||
option.value = engineName;
|
||||
|
||||
engine.add(option);
|
||||
}
|
||||
};
|
||||
|
||||
const renderEngine = type => {
|
||||
const [name, toggle, engine] = engineForType(type);
|
||||
|
||||
if (toggle.checked) {
|
||||
engine.parentElement.classList.remove("hidden");
|
||||
} else {
|
||||
engine.parentElement.classList.add("hidden");
|
||||
}
|
||||
|
||||
if (toggle.checked && engine.selectedIndex !== 0) {
|
||||
engine.item(0).disabled = true;
|
||||
} else if (!toggle.checked) {
|
||||
engine.item(0).disabled = false;
|
||||
engine.selectedIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const saveEngine = type => {
|
||||
const [name, toggle, engine] = engineForType(type);
|
||||
|
||||
if (toggle.checked && engine.selectedIndex !== 0) {
|
||||
ladybird.sendMessage(`set${name}Engine`, engine.value);
|
||||
} else if (!toggle.checked) {
|
||||
ladybird.sendMessage(`set${name}Engine`, null);
|
||||
}
|
||||
|
||||
renderEngine(type);
|
||||
};
|
||||
|
||||
const setSaveEngineListeners = type => {
|
||||
const [name, toggle, engine] = engineForType(type);
|
||||
|
||||
toggle.addEventListener("change", () => {
|
||||
saveEngine(type);
|
||||
});
|
||||
engine.addEventListener("change", () => {
|
||||
saveEngine(type);
|
||||
});
|
||||
};
|
||||
|
||||
setSaveEngineListeners(Engine.search);
|
||||
setSaveEngineListeners(Engine.autocomplete);
|
||||
|
||||
const forciblyEnableSiteSettings = settings => {
|
||||
settings.forEach(setting => {
|
||||
|
@ -576,7 +639,7 @@
|
|||
});
|
||||
|
||||
document.addEventListener("WebUILoaded", () => {
|
||||
ladybird.sendMessage("loadAvailableSearchEngines");
|
||||
ladybird.sendMessage("loadAvailableEngines");
|
||||
ladybird.sendMessage("loadCurrentSettings");
|
||||
ladybird.sendMessage("loadForciblyEnabledSiteSettings");
|
||||
});
|
||||
|
@ -584,8 +647,9 @@
|
|||
document.addEventListener("WebUIMessage", event => {
|
||||
if (event.detail.name === "loadSettings") {
|
||||
loadSettings(event.detail.data);
|
||||
} else if (event.detail.name === "loadSearchEngines") {
|
||||
loadSearchEngines(event.detail.data);
|
||||
} else if (event.detail.name === "loadEngines") {
|
||||
loadEngines(Engine.search, event.detail.data.search);
|
||||
loadEngines(Engine.autocomplete, event.detail.data.autocomplete);
|
||||
} else if (event.detail.name === "forciblyEnableSiteSettings") {
|
||||
forciblyEnableSiteSettings(event.detail.data);
|
||||
}
|
||||
|
|
194
Libraries/LibWebView/Autocomplete.cpp
Normal file
194
Libraries/LibWebView/Autocomplete.cpp
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Find.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibRequests/Request.h>
|
||||
#include <LibRequests/RequestClient.h>
|
||||
#include <LibURL/Parser.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/Autocomplete.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
static constexpr auto builtin_autocomplete_engines = to_array<AutocompleteEngine>({
|
||||
{ "DuckDuckGo"sv, "https://duckduckgo.com/ac/?q={}"sv },
|
||||
{ "Google"sv, "https://www.google.com/complete/search?client=chrome&q={}"sv },
|
||||
{ "Yahoo"sv, "https://search.yahoo.com/sugg/gossip/gossip-us-ura/?output=sd1&command={}"sv },
|
||||
});
|
||||
|
||||
ReadonlySpan<AutocompleteEngine> autocomplete_engines()
|
||||
{
|
||||
return builtin_autocomplete_engines;
|
||||
}
|
||||
|
||||
Optional<AutocompleteEngine const&> find_autocomplete_engine_by_name(StringView name)
|
||||
{
|
||||
auto it = AK::find_if(builtin_autocomplete_engines.begin(), builtin_autocomplete_engines.end(),
|
||||
[&](auto const& engine) {
|
||||
return engine.name == name;
|
||||
});
|
||||
|
||||
if (it == builtin_autocomplete_engines.end())
|
||||
return {};
|
||||
return *it;
|
||||
}
|
||||
|
||||
Autocomplete::Autocomplete() = default;
|
||||
Autocomplete::~Autocomplete() = default;
|
||||
|
||||
void Autocomplete::query_autocomplete_engine(String query)
|
||||
{
|
||||
if (m_request) {
|
||||
m_request->stop();
|
||||
m_request.clear();
|
||||
}
|
||||
|
||||
if (query.bytes_as_string_view().trim_whitespace().is_empty()) {
|
||||
invoke_autocomplete_query_complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
auto engine = Application::settings().autocomplete_engine();
|
||||
if (!engine.has_value()) {
|
||||
invoke_autocomplete_query_complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
auto url_string = MUST(String::formatted(engine->query_url, URL::percent_encode(query)));
|
||||
auto url = URL::Parser::basic_parse(url_string);
|
||||
|
||||
if (!url.has_value()) {
|
||||
invoke_autocomplete_query_complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
m_request = Application::request_server_client().start_request("GET"sv, *url);
|
||||
m_query = move(query);
|
||||
|
||||
m_request->set_buffered_request_finished_callback(
|
||||
[this, engine = engine.release_value()](u64, Requests::RequestTimingInfo const&, Optional<Requests::NetworkError> const& network_error, HTTP::HeaderMap const&, Optional<u32> response_code, Optional<String> const& reason_phrase, ReadonlyBytes payload) {
|
||||
Core::deferred_invoke([this]() { m_request.clear(); });
|
||||
|
||||
if (network_error.has_value()) {
|
||||
warnln("Unable to fetch autocomplete suggestions: {}", Requests::network_error_to_string(*network_error));
|
||||
invoke_autocomplete_query_complete({});
|
||||
return;
|
||||
}
|
||||
if (response_code.has_value() && *response_code >= 400) {
|
||||
warnln("Received error response code {} from autocomplete engine: {}", *response_code, reason_phrase);
|
||||
invoke_autocomplete_query_complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto result = received_autocomplete_respsonse(engine, payload); result.is_error()) {
|
||||
warnln("Unable to handle autocomplete response: {}", result.error());
|
||||
invoke_autocomplete_query_complete({});
|
||||
} else {
|
||||
invoke_autocomplete_query_complete(result.release_value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static ErrorOr<Vector<String>> parse_duckduckgo_autocomplete(JsonValue const& json)
|
||||
{
|
||||
if (!json.is_array())
|
||||
return Error::from_string_literal("Expected DuckDuckGo autocomplete response to be a JSON array");
|
||||
|
||||
Vector<String> results;
|
||||
results.ensure_capacity(json.as_array().size());
|
||||
|
||||
TRY(json.as_array().try_for_each([&](JsonValue const& suggestion) -> ErrorOr<void> {
|
||||
if (!suggestion.is_object())
|
||||
return Error::from_string_literal("Invalid DuckDuckGo autocomplete response, expected value to be an object");
|
||||
|
||||
if (auto value = suggestion.as_object().get_string("phrase"sv); value.has_value())
|
||||
results.unchecked_append(*value);
|
||||
|
||||
return {};
|
||||
}));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
static ErrorOr<Vector<String>> parse_google_autocomplete(JsonValue const& json)
|
||||
{
|
||||
if (!json.is_array())
|
||||
return Error::from_string_literal("Expected Google autocomplete response to be a JSON array");
|
||||
|
||||
auto const& values = json.as_array();
|
||||
|
||||
if (values.size() != 5)
|
||||
return Error::from_string_literal("Invalid Google autocomplete response, expected 5 elements in array");
|
||||
if (!values[1].is_array())
|
||||
return Error::from_string_literal("Invalid Google autocomplete response, expected second element to be an array");
|
||||
|
||||
auto const& suggestions = values[1].as_array();
|
||||
|
||||
Vector<String> results;
|
||||
results.ensure_capacity(suggestions.size());
|
||||
|
||||
TRY(suggestions.try_for_each([&](JsonValue const& suggestion) -> ErrorOr<void> {
|
||||
if (!suggestion.is_string())
|
||||
return Error::from_string_literal("Invalid Google autocomplete response, expected value to be a string");
|
||||
|
||||
results.unchecked_append(suggestion.as_string());
|
||||
return {};
|
||||
}));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
static ErrorOr<Vector<String>> parse_yahoo_autocomplete(JsonValue const& json)
|
||||
{
|
||||
if (!json.is_object())
|
||||
return Error::from_string_literal("Expected Yahoo autocomplete response to be a JSON array");
|
||||
|
||||
auto suggestions = json.as_object().get_array("r"sv);
|
||||
if (!suggestions.has_value())
|
||||
return Error::from_string_literal("Invalid Yahoo autocomplete response, expected \"r\" to be an object");
|
||||
|
||||
Vector<String> results;
|
||||
results.ensure_capacity(suggestions->size());
|
||||
|
||||
TRY(suggestions->try_for_each([&](JsonValue const& suggestion) -> ErrorOr<void> {
|
||||
if (!suggestion.is_object())
|
||||
return Error::from_string_literal("Invalid Yahoo autocomplete response, expected value to be an object");
|
||||
|
||||
auto result = suggestion.as_object().get_string("k"sv);
|
||||
if (!result.has_value())
|
||||
return Error::from_string_literal("Invalid Yahoo autocomplete response, expected \"k\" to be a string");
|
||||
|
||||
results.unchecked_append(*result);
|
||||
return {};
|
||||
}));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
ErrorOr<Vector<String>> Autocomplete::received_autocomplete_respsonse(AutocompleteEngine const& engine, StringView response)
|
||||
{
|
||||
auto json = TRY(JsonValue::from_string(response));
|
||||
|
||||
if (engine.name == "DuckDuckGo")
|
||||
return parse_duckduckgo_autocomplete(json);
|
||||
if (engine.name == "Google")
|
||||
return parse_google_autocomplete(json);
|
||||
if (engine.name == "Yahoo")
|
||||
return parse_yahoo_autocomplete(json);
|
||||
|
||||
return Error::from_string_literal("Invalid engine name");
|
||||
}
|
||||
|
||||
void Autocomplete::invoke_autocomplete_query_complete(Vector<String> suggestions) const
|
||||
{
|
||||
if (on_autocomplete_query_complete)
|
||||
on_autocomplete_query_complete(move(suggestions));
|
||||
}
|
||||
|
||||
}
|
44
Libraries/LibWebView/Autocomplete.h
Normal file
44
Libraries/LibWebView/Autocomplete.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibRequests/Forward.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
struct AutocompleteEngine {
|
||||
StringView name;
|
||||
StringView query_url;
|
||||
};
|
||||
|
||||
ReadonlySpan<AutocompleteEngine> autocomplete_engines();
|
||||
Optional<AutocompleteEngine const&> find_autocomplete_engine_by_name(StringView name);
|
||||
|
||||
class Autocomplete {
|
||||
public:
|
||||
Autocomplete();
|
||||
~Autocomplete();
|
||||
|
||||
Function<void(Vector<String>)> on_autocomplete_query_complete;
|
||||
|
||||
void query_autocomplete_engine(String);
|
||||
|
||||
private:
|
||||
static ErrorOr<Vector<String>> received_autocomplete_respsonse(AutocompleteEngine const&, StringView response);
|
||||
void invoke_autocomplete_query_complete(Vector<String> suggestions) const;
|
||||
|
||||
String m_query;
|
||||
RefPtr<Requests::Request> m_request;
|
||||
};
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@ include(fontconfig)
|
|||
set(SOURCES
|
||||
Application.cpp
|
||||
Attribute.cpp
|
||||
Autocomplete.cpp
|
||||
BrowserProcess.cpp
|
||||
ConsoleOutput.cpp
|
||||
CookieJar.cpp
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
namespace WebView {
|
||||
|
||||
class Application;
|
||||
class Autocomplete;
|
||||
class CookieJar;
|
||||
class Database;
|
||||
class OutOfProcessWebView;
|
||||
|
@ -21,6 +22,7 @@ class WebContentClient;
|
|||
class WebUI;
|
||||
|
||||
struct Attribute;
|
||||
struct AutocompleteEngine;
|
||||
struct ConsoleOutput;
|
||||
struct CookieStorageKey;
|
||||
struct DOMNodeProperties;
|
||||
|
|
|
@ -24,6 +24,9 @@ static constexpr auto new_tab_page_url_key = "newTabPageURL"sv;
|
|||
static constexpr auto search_engine_key = "searchEngine"sv;
|
||||
static constexpr auto search_engine_name_key = "name"sv;
|
||||
|
||||
static constexpr auto autocomplete_engine_key = "autocompleteEngine"sv;
|
||||
static constexpr auto autocomplete_engine_name_key = "name"sv;
|
||||
|
||||
static constexpr auto site_setting_enabled_globally_key = "enabledGlobally"sv;
|
||||
static constexpr auto site_setting_site_filters_key = "siteFilters"sv;
|
||||
|
||||
|
@ -81,6 +84,11 @@ Settings Settings::create(Badge<Application>)
|
|||
settings.m_search_engine = find_search_engine_by_name(*search_engine_name);
|
||||
}
|
||||
|
||||
if (auto autocomplete_engine = settings_json.value().get_object(autocomplete_engine_key); autocomplete_engine.has_value()) {
|
||||
if (auto autocomplete_engine_name = autocomplete_engine->get_string(autocomplete_engine_name_key); autocomplete_engine_name.has_value())
|
||||
settings.m_autocomplete_engine = find_autocomplete_engine_by_name(*autocomplete_engine_name);
|
||||
}
|
||||
|
||||
auto load_site_setting = [&](SiteSetting& site_setting, StringView key) {
|
||||
auto saved_settings = settings_json.value().get_object(key);
|
||||
if (!saved_settings.has_value())
|
||||
|
@ -122,6 +130,13 @@ JsonValue Settings::serialize_json() const
|
|||
settings.set(search_engine_key, move(search_engine));
|
||||
}
|
||||
|
||||
if (m_autocomplete_engine.has_value()) {
|
||||
JsonObject autocomplete_engine;
|
||||
autocomplete_engine.set(autocomplete_engine_name_key, m_autocomplete_engine->name);
|
||||
|
||||
settings.set(autocomplete_engine_key, move(autocomplete_engine));
|
||||
}
|
||||
|
||||
auto save_site_setting = [&](SiteSetting const& site_setting, StringView key) {
|
||||
JsonArray site_filters;
|
||||
site_filters.ensure_capacity(site_setting.site_filters.size());
|
||||
|
@ -145,6 +160,7 @@ void Settings::restore_defaults()
|
|||
{
|
||||
m_new_tab_page_url = URL::about_newtab();
|
||||
m_search_engine.clear();
|
||||
m_autocomplete_engine.clear();
|
||||
m_autoplay = SiteSetting {};
|
||||
|
||||
persist_settings();
|
||||
|
@ -175,6 +191,19 @@ void Settings::set_search_engine(Optional<StringView> search_engine_name)
|
|||
observer.search_engine_changed();
|
||||
}
|
||||
|
||||
void Settings::set_autocomplete_engine(Optional<StringView> autocomplete_engine_name)
|
||||
{
|
||||
if (autocomplete_engine_name.has_value())
|
||||
m_autocomplete_engine = find_autocomplete_engine_by_name(*autocomplete_engine_name);
|
||||
else
|
||||
m_autocomplete_engine.clear();
|
||||
|
||||
persist_settings();
|
||||
|
||||
for (auto& observer : m_observers)
|
||||
observer.autocomplete_engine_changed();
|
||||
}
|
||||
|
||||
void Settings::set_autoplay_enabled_globally(bool enabled_globally)
|
||||
{
|
||||
m_autoplay.enabled_globally = enabled_globally;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/JsonValue.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWebView/Autocomplete.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
#include <LibWebView/SearchEngine.h>
|
||||
|
||||
|
@ -30,6 +31,7 @@ public:
|
|||
|
||||
virtual void new_tab_page_url_changed() { }
|
||||
virtual void search_engine_changed() { }
|
||||
virtual void autocomplete_engine_changed() { }
|
||||
virtual void autoplay_settings_changed() { }
|
||||
};
|
||||
|
||||
|
@ -47,6 +49,9 @@ public:
|
|||
Optional<SearchEngine> const& search_engine() const { return m_search_engine; }
|
||||
void set_search_engine(Optional<StringView> search_engine_name);
|
||||
|
||||
Optional<AutocompleteEngine> const& autocomplete_engine() const { return m_autocomplete_engine; }
|
||||
void set_autocomplete_engine(Optional<StringView> autocomplete_engine_name);
|
||||
|
||||
SiteSetting const& autoplay_settings() const { return m_autoplay; }
|
||||
void set_autoplay_enabled_globally(bool);
|
||||
void add_autoplay_site_filter(String const&);
|
||||
|
@ -65,6 +70,7 @@ private:
|
|||
|
||||
URL::URL m_new_tab_page_url;
|
||||
Optional<SearchEngine> m_search_engine;
|
||||
Optional<AutocompleteEngine> m_autocomplete_engine;
|
||||
SiteSetting m_autoplay;
|
||||
|
||||
Vector<SettingsObserver&> m_observers;
|
||||
|
|
|
@ -20,15 +20,21 @@ void SettingsUI::register_interfaces()
|
|||
register_interface("restoreDefaultSettings"sv, [this](auto const&) {
|
||||
restore_default_settings();
|
||||
});
|
||||
|
||||
register_interface("setNewTabPageURL"sv, [this](auto const& data) {
|
||||
set_new_tab_page_url(data);
|
||||
});
|
||||
register_interface("loadAvailableSearchEngines"sv, [this](auto const&) {
|
||||
load_available_search_engines();
|
||||
|
||||
register_interface("loadAvailableEngines"sv, [this](auto const&) {
|
||||
load_available_engines();
|
||||
});
|
||||
register_interface("setSearchEngine"sv, [this](auto const& data) {
|
||||
set_search_engine(data);
|
||||
});
|
||||
register_interface("setAutocompleteEngine"sv, [this](auto const& data) {
|
||||
set_autocomplete_engine(data);
|
||||
});
|
||||
|
||||
register_interface("loadForciblyEnabledSiteSettings"sv, [this](auto const&) {
|
||||
load_forcibly_enabled_site_settings();
|
||||
});
|
||||
|
@ -70,13 +76,21 @@ void SettingsUI::set_new_tab_page_url(JsonValue const& new_tab_page_url)
|
|||
WebView::Application::settings().set_new_tab_page_url(parsed_new_tab_page_url.release_value());
|
||||
}
|
||||
|
||||
void SettingsUI::load_available_search_engines()
|
||||
void SettingsUI::load_available_engines()
|
||||
{
|
||||
JsonArray engines;
|
||||
for (auto const& engine : search_engines())
|
||||
engines.must_append(engine.name);
|
||||
JsonArray search_engines;
|
||||
for (auto const& engine : WebView::search_engines())
|
||||
search_engines.must_append(engine.name);
|
||||
|
||||
async_send_message("loadSearchEngines"sv, move(engines));
|
||||
JsonArray autocomplete_engines;
|
||||
for (auto const& engine : WebView::autocomplete_engines())
|
||||
autocomplete_engines.must_append(engine.name);
|
||||
|
||||
JsonObject engines;
|
||||
engines.set("search"sv, move(search_engines));
|
||||
engines.set("autocomplete"sv, move(autocomplete_engines));
|
||||
|
||||
async_send_message("loadEngines"sv, move(engines));
|
||||
}
|
||||
|
||||
void SettingsUI::set_search_engine(JsonValue const& search_engine)
|
||||
|
@ -87,6 +101,14 @@ void SettingsUI::set_search_engine(JsonValue const& search_engine)
|
|||
WebView::Application::settings().set_search_engine(search_engine.as_string());
|
||||
}
|
||||
|
||||
void SettingsUI::set_autocomplete_engine(JsonValue const& autocomplete_engine)
|
||||
{
|
||||
if (autocomplete_engine.is_null())
|
||||
WebView::Application::settings().set_autocomplete_engine({});
|
||||
else if (autocomplete_engine.is_string())
|
||||
WebView::Application::settings().set_autocomplete_engine(autocomplete_engine.as_string());
|
||||
}
|
||||
|
||||
enum class SiteSettingType {
|
||||
Autoplay,
|
||||
};
|
||||
|
|
|
@ -18,9 +18,13 @@ private:
|
|||
|
||||
void load_current_settings();
|
||||
void restore_default_settings();
|
||||
|
||||
void set_new_tab_page_url(JsonValue const&);
|
||||
void load_available_search_engines();
|
||||
|
||||
void load_available_engines();
|
||||
void set_search_engine(JsonValue const&);
|
||||
void set_autocomplete_engine(JsonValue const&);
|
||||
|
||||
void load_forcibly_enabled_site_settings();
|
||||
void set_site_setting_enabled_globally(JsonValue const&);
|
||||
void add_site_setting_filter(JsonValue const&);
|
||||
|
|
Loading…
Add table
Reference in a new issue