From 234c07adc97c003cf8bea626ea4eb224bd7d807c Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 23 Apr 2025 11:49:53 -0400 Subject: [PATCH] LibWebView: Split about:settings into modules The about:settings page has gotten beefy enough that it is a bit of a paint to manage in a single HTML file. This patch breaks the settings into several modules to make this page easier to mantain. Modules have some nice benefits over classic scripts, such as enabling strict mode by default, and allowing each module to function without polluting the global object. --- Base/res/ladybird/about-pages/settings.html | 624 +----------------- .../about-pages/settings/languages.js | 156 ++++- .../ladybird/about-pages/settings/network.js | 90 +++ .../about-pages/settings/new-tab-page.js | 29 + .../about-pages/settings/permissions.js | 142 ++++ .../ladybird/about-pages/settings/privacy.js | 15 + .../ladybird/about-pages/settings/search.js | 249 +++++++ UI/cmake/ResourceFiles.cmake | 5 + 8 files changed, 694 insertions(+), 616 deletions(-) create mode 100644 Base/res/ladybird/about-pages/settings/network.js create mode 100644 Base/res/ladybird/about-pages/settings/new-tab-page.js create mode 100644 Base/res/ladybird/about-pages/settings/permissions.js create mode 100644 Base/res/ladybird/about-pages/settings/privacy.js create mode 100644 Base/res/ladybird/about-pages/settings/search.js diff --git a/Base/res/ladybird/about-pages/settings.html b/Base/res/ladybird/about-pages/settings.html index 0477a65fcfb..cfb223a7c2e 100644 --- a/Base/res/ladybird/about-pages/settings.html +++ b/Base/res/ladybird/about-pages/settings.html @@ -500,45 +500,7 @@ - - - newTabPageURL.addEventListener("change", () => { - newTabPageURL.classList.remove("success"); - newTabPageURL.classList.remove("error"); + + + + + + - if (!containsValidURL(newTabPageURL)) { - newTabPageURL.classList.add("error"); - return; - } - - ladybird.sendMessage("setNewTabPageURL", newTabPageURL.value); - newTabPageURL.classList.add("success"); - - setTimeout(() => { - newTabPageURL.classList.remove("success"); - }, 1000); - }); - - const languageDisplayName = language => { - const item = window.languages.find(item => item.language === language); - return item.displayName; - }; - - const saveLanguages = () => { - ladybird.sendMessage("setLanguages", window.settings.languages); - }; - - const moveLanguage = (from, to) => { - [window.settings.languages[from], window.settings.languages[to]] = [ - window.settings.languages[to], - window.settings.languages[from], - ]; - - saveLanguages(); - }; - - const removeLanguage = index => { - window.settings.languages.splice(index, 1); - saveLanguages(); - }; - - const loadLanguages = () => { - for (const language of window.languages) { - const option = document.createElement("option"); - option.text = language.displayName; - option.value = language.language; - - languagesSelect.add(option); - } - }; - - const showLanguages = () => { - languagesList.innerHTML = ""; - - window.settings.languages.forEach((language, index) => { - const name = document.createElement("span"); - name.className = "dialog-list-item-label"; - name.textContent = languageDisplayName(language); - - const moveUp = document.createElement("button"); - moveUp.className = "dialog-button"; - moveUp.innerHTML = upwardArrowSVG; - moveUp.title = "Move up"; - - if (index === 0) { - moveUp.disabled = true; - } else { - moveUp.addEventListener("click", () => { - moveLanguage(index, index - 1); - }); - } - - const moveDown = document.createElement("button"); - moveDown.className = "dialog-button"; - moveDown.innerHTML = downwardArrowSVG; - moveDown.title = "Move down"; - - if (index === window.settings.languages.length - 1) { - moveDown.disabled = true; - } else { - moveDown.addEventListener("click", () => { - moveLanguage(index, index + 1); - }); - } - - const remove = document.createElement("button"); - remove.className = "dialog-button"; - remove.innerHTML = "×"; - remove.title = "Remove"; - - if (window.settings.languages.length <= 1) { - remove.disabled = true; - } else { - remove.addEventListener("click", () => { - removeLanguage(index); - }); - } - - const controls = document.createElement("div"); - controls.className = "dialog-controls"; - controls.appendChild(moveUp); - controls.appendChild(moveDown); - controls.appendChild(remove); - - const item = document.createElement("div"); - item.className = "dialog-list-item"; - item.appendChild(name); - item.appendChild(controls); - - languagesList.appendChild(item); - }); - - for (const language of languagesSelect.options) { - language.disabled = window.settings.languages.includes(language.value); - } - - if (!languagesDialog.open) { - setTimeout(() => languagesSelect.focus()); - languagesDialog.showModal(); - } - }; - - languagesAdd.addEventListener("click", () => { - const language = languagesSelect.value; - - languagesAdd.disabled = true; - languagesSelect.selectedIndex = 0; - - if (!language || window.settings.languages.includes(language)) { - return; - } - - window.settings.languages.push(language); - saveLanguages(); - }); - - languagesClose.addEventListener("click", () => { - languagesDialog.close(); - }); - - languagesSelect.addEventListener("change", () => { - languagesAdd.disabled = !languagesSelect.value; - }); - - languagesSettings.addEventListener("click", event => { - showLanguages(); - event.stopPropagation(); - }); - - 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); - } - - if (type === Engine.search) { - window.nativeSearchEngineCount = engine.length; - engine.appendChild(document.createElement("hr")); - } - }; - - const renderEngine = type => { - const [name, toggle, engine] = engineForType(type); - - if (toggle.checked) { - engine.closest(".card-group").classList.remove("hidden"); - } else { - engine.closest(".card-group").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 loadCustomSearchEngines = () => { - while (searchEngine.length > window.nativeSearchEngineCount) { - searchEngine.remove(window.nativeSearchEngineCount); - } - - const custom = window.settings.searchEngine?.custom || []; - - custom.forEach(custom => { - const option = document.createElement("option"); - option.text = custom.name; - option.value = custom.name; - - searchEngine.add(option); - }); - }; - - const showSearchEngineSettings = () => { - searchCustomName.classList.remove("error"); - searchCustomURL.classList.remove("error"); - searchList.innerHTML = ""; - - const custom = window.settings.searchEngine?.custom || []; - - if (custom.length === 0) { - const placeholder = document.createElement("div"); - placeholder.className = "dialog-list-item-placeholder"; - placeholder.textContent = "No custom search engines added"; - - searchList.appendChild(placeholder); - } - - custom.forEach(custom => { - const name = document.createElement("span"); - name.textContent = custom.name; - - const url = document.createElement("span"); - url.className = "dialog-list-item-placeholder"; - url.style = "padding-left: 0"; - url.textContent = ` — ${custom.url}`; - - const engine = document.createElement("span"); - engine.className = "dialog-list-item-label"; - engine.appendChild(name); - engine.appendChild(url); - - const remove = document.createElement("button"); - remove.className = "dialog-button"; - remove.innerHTML = "×"; - remove.title = `Remove ${custom.name}`; - - remove.addEventListener("click", () => { - ladybird.sendMessage("removeCustomSearchEngine", custom); - }); - - const item = document.createElement("div"); - item.className = "dialog-list-item"; - item.appendChild(engine); - item.appendChild(remove); - - searchList.appendChild(item); - }); - - if (!searchDialog.open) { - setTimeout(() => searchCustomName.focus()); - searchDialog.showModal(); - } - }; - - const addCustomSearchEngine = () => { - searchCustomName.classList.remove("error"); - searchCustomURL.classList.remove("error"); - - for (const i = 0; i < searchEngine.length; ++i) { - if (searchCustomName.value === searchEngine.item(i).value) { - searchCustomName.classList.add("error"); - return; - } - } - - if (!containsValidURL(searchCustomURL)) { - searchCustomURL.classList.add("error"); - return; - } - - ladybird.sendMessage("addCustomSearchEngine", { - name: searchCustomName.value, - url: searchCustomURL.value, - }); - - searchCustomName.value = ""; - searchCustomURL.value = ""; - - setTimeout(() => searchCustomName.focus()); - }; - - searchCustomAdd.addEventListener("click", addCustomSearchEngine); - - searchCustomName.addEventListener("keydown", event => { - if (event.key === "Enter") { - addCustomSearchEngine(); - } - }); - - searchCustomURL.addEventListener("keydown", event => { - if (event.key === "Enter") { - addCustomSearchEngine(); - } - }); - - searchClose.addEventListener("click", () => { - searchDialog.close(); - }); - - searchSettings.addEventListener("click", event => { - showSearchEngineSettings(); - event.stopPropagation(); - }); - - const forciblyEnableSiteSettings = settings => { - settings.forEach(setting => { - const label = document.querySelector(`#${setting}-forcibly-enabled`); - label.classList.remove("hidden"); - - const button = document.querySelector(`#${setting}-settings`); - button.classList.add("hidden"); - }); - }; - - const currentSiteSetting = () => { - if (!siteSettings.open) { - return null; - } - - return siteSettingsTitle.innerText.toLowerCase(); - }; - - const showSiteSettings = (title, settings) => { - siteSettingsTitle.innerText = title; - siteSettingsGlobal.checked = settings.enabledGlobally; - siteSettingsList.innerHTML = ""; - - siteSettingsGlobal.onchange = () => { - ladybird.sendMessage("setSiteSettingEnabledGlobally", { - setting: currentSiteSetting(), - enabled: siteSettingsGlobal.checked, - }); - }; - - if (settings.siteFilters.length === 0) { - const placeholder = document.createElement("div"); - placeholder.className = "dialog-list-item-placeholder"; - placeholder.textContent = "No sites added"; - - siteSettingsList.appendChild(placeholder); - } - - settings.siteFilters.forEach(site => { - const filter = document.createElement("span"); - filter.className = "dialog-list-item-label"; - filter.textContent = site; - - const remove = document.createElement("button"); - remove.className = "dialog-button"; - remove.innerHTML = "×"; - remove.title = `Remove ${site}`; - - remove.addEventListener("click", () => { - ladybird.sendMessage("removeSiteSettingFilter", { - setting: currentSiteSetting(), - filter: site, - }); - }); - - const item = document.createElement("div"); - item.className = "dialog-list-item"; - item.appendChild(filter); - item.appendChild(remove); - - siteSettingsList.appendChild(item); - }); - - if (!siteSettings.open) { - setTimeout(() => siteSettingsInput.focus()); - siteSettings.showModal(); - } - }; - - const addSiteSettingFilter = () => { - ladybird.sendMessage("addSiteSettingFilter", { - setting: currentSiteSetting(), - filter: siteSettingsInput.value, - }); - - siteSettingsInput.classList.add("success"); - siteSettingsInput.value = ""; - - setTimeout(() => { - siteSettingsInput.classList.remove("success"); - }, 1000); - - setTimeout(() => siteSettingsInput.focus()); - }; - - siteSettingsAdd.addEventListener("click", addSiteSettingFilter); - - siteSettingsInput.addEventListener("keydown", event => { - if (event.key === "Enter") { - addSiteSettingFilter(); - } - }); - - siteSettingsClose.addEventListener("click", () => { - siteSettings.close(); - }); - - siteSettingsRemoveAll.addEventListener("click", () => { - ladybird.sendMessage("removeAllSiteSettingFilters", { - setting: currentSiteSetting(), - }); - }); - - // DNS settings - const loadDnsSettings = settings => { - dnsUpstream.value = settings.mode; - - if (settings.mode === "custom") { - dnsServer.value = settings.server; - dnsPort.value = settings.port; - dnsType.value = settings.type; - customDnsSettings.classList.remove("hidden"); - } else { - dnsServer.value = ""; - dnsType.value = "udp"; - dnsPort.value = "53"; - customDnsSettings.classList.add("hidden"); - } - - if (settings.forciblyEnabled) { - dnsForciblyEnabled.classList.remove("hidden"); - dnsUpstream.setAttribute("disabled", ""); - dnsServer.setAttribute("disabled", ""); - dnsPort.setAttribute("disabled", ""); - dnsType.setAttribute("disabled", ""); - } else { - dnsForciblyEnabled.classList.add("hidden"); - dnsUpstream.removeAttribute("disabled"); - dnsServer.removeAttribute("disabled"); - dnsPort.removeAttribute("disabled"); - dnsType.removeAttribute("disabled"); - } - }; - - dnsUpstream.addEventListener("change", () => { - if (dnsUpstream.value === "custom") { - customDnsSettings.classList.remove("hidden"); - if (dnsServer.value !== "" && dnsPort.value !== "") { - updateDnsSettings(); - } - } else { - customDnsSettings.classList.add("hidden"); - ladybird.sendMessage("setDNSSettings", { mode: "system" }); - } - }); - - function updateDnsSettings() { - if (dnsUpstream.value === "custom") { - dnsPort.placeholder = dnsType.value === "tls" ? "853" : "53"; - if ((dnsPort.value | 0) === 0) dnsPort.value = dnsPort.placeholder; - - const settings = { - mode: "custom", - server: dnsServer.value, - port: dnsPort.value | 0, - type: dnsType.value, - }; - ladybird.sendMessage("setDNSSettings", settings); - } - } - - dnsServer.addEventListener("change", updateDnsSettings); - dnsPort.addEventListener("change", updateDnsSettings); - dnsType.addEventListener("change", updateDnsSettings); - - autoplaySettings.addEventListener("click", event => { - showSiteSettings("Autoplay", window.settings.autoplay); - event.stopPropagation(); - }); - - doNotTrackToggle.addEventListener("change", () => { - ladybird.sendMessage("setDoNotTrack", doNotTrackToggle.checked); - }); + diff --git a/Base/res/ladybird/about-pages/settings/languages.js b/Base/res/ladybird/about-pages/settings/languages.js index a3dde2b7ea9..644406cb97b 100644 --- a/Base/res/ladybird/about-pages/settings/languages.js +++ b/Base/res/ladybird/about-pages/settings/languages.js @@ -1,9 +1,163 @@ +const languagesAdd = document.querySelector("#languages-add"); +const languagesClose = document.querySelector("#languages-close"); +const languagesDialog = document.querySelector("#languages-dialog"); +const languagesList = document.querySelector("#languages-list"); +const languagesSelect = document.querySelector("#languages-select"); +const languagesSettings = document.querySelector("#languages-settings"); + +let LANGUAGES = {}; + +function loadSettings(settings) { + LANGUAGES = settings.languages; + + if (languagesDialog.open) { + showLanguages(); + } +} + +function languageDisplayName(language) { + const item = AVAILABLE_LANGUAGES.find(item => item.language === language); + return item.displayName; +} + +function saveLanguages() { + ladybird.sendMessage("setLanguages", LANGUAGES); +} + +function moveLanguage(from, to) { + [LANGUAGES[from], LANGUAGES[to]] = [LANGUAGES[to], LANGUAGES[from]]; + saveLanguages(); +} + +function removeLanguage(index) { + LANGUAGES.splice(index, 1); + saveLanguages(); +} + +function loadLanguages() { + for (const language of AVAILABLE_LANGUAGES) { + const option = document.createElement("option"); + option.text = language.displayName; + option.value = language.language; + + languagesSelect.add(option); + } +} + +function showLanguages() { + languagesList.innerHTML = ""; + + LANGUAGES.forEach((language, index) => { + const name = document.createElement("span"); + name.className = "dialog-list-item-label"; + name.textContent = languageDisplayName(language); + + const moveUp = document.createElement("button"); + moveUp.className = "dialog-button"; + moveUp.innerHTML = upwardArrowSVG; + moveUp.title = "Move up"; + + if (index === 0) { + moveUp.disabled = true; + } else { + moveUp.addEventListener("click", () => { + moveLanguage(index, index - 1); + }); + } + + const moveDown = document.createElement("button"); + moveDown.className = "dialog-button"; + moveDown.innerHTML = downwardArrowSVG; + moveDown.title = "Move down"; + + if (index === LANGUAGES.length - 1) { + moveDown.disabled = true; + } else { + moveDown.addEventListener("click", () => { + moveLanguage(index, index + 1); + }); + } + + const remove = document.createElement("button"); + remove.className = "dialog-button"; + remove.innerHTML = "×"; + remove.title = "Remove"; + + if (LANGUAGES.length <= 1) { + remove.disabled = true; + } else { + remove.addEventListener("click", () => { + removeLanguage(index); + }); + } + + const controls = document.createElement("div"); + controls.className = "dialog-controls"; + controls.appendChild(moveUp); + controls.appendChild(moveDown); + controls.appendChild(remove); + + const item = document.createElement("div"); + item.className = "dialog-list-item"; + item.appendChild(name); + item.appendChild(controls); + + languagesList.appendChild(item); + }); + + for (const language of languagesSelect.options) { + language.disabled = LANGUAGES.includes(language.value); + } + + if (!languagesDialog.open) { + setTimeout(() => languagesSelect.focus()); + languagesDialog.showModal(); + } +} + +languagesAdd.addEventListener("click", () => { + const language = languagesSelect.value; + + languagesAdd.disabled = true; + languagesSelect.selectedIndex = 0; + + if (!language || LANGUAGES.includes(language)) { + return; + } + + LANGUAGES.push(language); + saveLanguages(); +}); + +languagesClose.addEventListener("click", () => { + languagesDialog.close(); +}); + +languagesSelect.addEventListener("change", () => { + languagesAdd.disabled = !languagesSelect.value; +}); + +languagesSettings.addEventListener("click", event => { + showLanguages(); + event.stopPropagation(); +}); + +document.addEventListener("WebUILoaded", () => { + loadLanguages(); +}); + +document.addEventListener("WebUIMessage", event => { + if (event.detail.name === "loadSettings") { + loadSettings(event.detail.data); + } +}); + // Rather than creating a list of all languages supported by ICU (of which there are on the order of a thousand), we // create a list of languages that are supported by both Chrome and Firefox. We can extend this list as needed. // // https://github.com/chromium/chromium/blob/main/ui/base/l10n/l10n_util.cc (see kAcceptLanguageList) // https://github.com/mozilla/gecko-dev/blob/master/intl/locale/language.properties -window.languages = (() => { +const AVAILABLE_LANGUAGES = (() => { const display = new Intl.DisplayNames([], { type: "language", languageDisplay: "standard" }); const language = languageID => { diff --git a/Base/res/ladybird/about-pages/settings/network.js b/Base/res/ladybird/about-pages/settings/network.js new file mode 100644 index 00000000000..00956084f66 --- /dev/null +++ b/Base/res/ladybird/about-pages/settings/network.js @@ -0,0 +1,90 @@ +const customDnsSettings = document.querySelector("#custom-dns-settings"); +const dnsForciblyEnabled = document.querySelector("#dns-forcibly-enabled"); + +const dnsUpstream = document.querySelector("#dns-upstream"); +const dnsType = document.querySelector("#dns-type"); +const dnsServer = document.querySelector("#dns-server"); +const dnsPort = document.querySelector("#dns-port"); + +let DNS_SETTINGS = {}; + +function loadSettings(settings) { + DNS_SETTINGS = settings.dnsSettings || {}; + loadDnsSettings(); +} + +function loadDnsSettings() { + dnsUpstream.value = DNS_SETTINGS.mode || "system"; + + if (dnsUpstream.value === "custom") { + dnsType.value = DNS_SETTINGS.type; + dnsServer.value = DNS_SETTINGS.server; + dnsPort.value = DNS_SETTINGS.port; + + customDnsSettings.classList.remove("hidden"); + } else { + dnsType.value = "udp"; + dnsServer.value = ""; + dnsPort.value = "53"; + + customDnsSettings.classList.add("hidden"); + } + + if (DNS_SETTINGS.forciblyEnabled) { + dnsForciblyEnabled.classList.remove("hidden"); + + dnsUpstream.disabled = true; + dnsType.disabled = true; + dnsServer.disabled = true; + dnsPort.disabled = true; + } else { + dnsForciblyEnabled.classList.add("hidden"); + + dnsUpstream.disabled = false; + dnsType.disabled = false; + dnsServer.disabled = false; + dnsPort.disabled = false; + } +} + +dnsUpstream.addEventListener("change", () => { + if (dnsUpstream.value === "custom") { + customDnsSettings.classList.remove("hidden"); + + if (dnsServer.value.length !== 0 && dnsPort.value.length !== 0) { + updateDnsSettings(); + } + } else { + customDnsSettings.classList.add("hidden"); + ladybird.sendMessage("setDNSSettings", { mode: "system" }); + } +}); + +function updateDnsSettings() { + if (dnsUpstream.value !== "custom") { + return; + } + + dnsPort.placeholder = dnsType.value === "tls" ? "853" : "53"; + + if ((dnsPort.value || 0) === 0) { + dnsPort.value = dnsPort.placeholder; + } + + ladybird.sendMessage("setDNSSettings", { + mode: "custom", + type: dnsType.value, + server: dnsServer.value, + port: dnsPort.value | 0, + }); +} + +dnsServer.addEventListener("change", updateDnsSettings); +dnsPort.addEventListener("change", updateDnsSettings); +dnsType.addEventListener("change", updateDnsSettings); + +document.addEventListener("WebUIMessage", event => { + if (event.detail.name === "loadSettings") { + loadSettings(event.detail.data); + } +}); diff --git a/Base/res/ladybird/about-pages/settings/new-tab-page.js b/Base/res/ladybird/about-pages/settings/new-tab-page.js new file mode 100644 index 00000000000..f226ef41908 --- /dev/null +++ b/Base/res/ladybird/about-pages/settings/new-tab-page.js @@ -0,0 +1,29 @@ +const newTabPageURL = document.querySelector("#new-tab-page-url"); + +const loadSettings = settings => { + newTabPageURL.classList.remove("error"); + newTabPageURL.value = settings.newTabPageURL; +}; + +newTabPageURL.addEventListener("change", () => { + newTabPageURL.classList.remove("success"); + newTabPageURL.classList.remove("error"); + + if (!containsValidURL(newTabPageURL)) { + newTabPageURL.classList.add("error"); + return; + } + + ladybird.sendMessage("setNewTabPageURL", newTabPageURL.value); + newTabPageURL.classList.add("success"); + + setTimeout(() => { + newTabPageURL.classList.remove("success"); + }, 1000); +}); + +document.addEventListener("WebUIMessage", event => { + if (event.detail.name === "loadSettings") { + loadSettings(event.detail.data); + } +}); diff --git a/Base/res/ladybird/about-pages/settings/permissions.js b/Base/res/ladybird/about-pages/settings/permissions.js new file mode 100644 index 00000000000..3f0022c04ea --- /dev/null +++ b/Base/res/ladybird/about-pages/settings/permissions.js @@ -0,0 +1,142 @@ +const siteSettings = document.querySelector("#site-settings"); +const siteSettingsAdd = document.querySelector("#site-settings-add"); +const siteSettingsClose = document.querySelector("#site-settings-close"); +const siteSettingsGlobal = document.querySelector("#site-settings-global"); +const siteSettingsList = document.querySelector("#site-settings-list"); +const siteSettingsInput = document.querySelector("#site-settings-input"); +const siteSettingsRemoveAll = document.querySelector("#site-settings-remove-all"); +const siteSettingsTitle = document.querySelector("#site-settings-title"); + +const autoplaySettings = document.querySelector("#autoplay-settings"); + +let AUTOPLAY_SETTINGS = {}; + +function loadSiteSettings(settings) { + AUTOPLAY_SETTINGS = settings.autoplay; + + const siteSetting = currentSiteSetting(); + + if (siteSetting === "autoplay") { + showSiteSettings("Autoplay", AUTOPLAY_SETTINGS); + } +} + +function forciblyEnableSiteSettings(settings) { + settings.forEach(setting => { + const label = document.querySelector(`#${setting}-forcibly-enabled`); + label.classList.remove("hidden"); + + const button = document.querySelector(`#${setting}-settings`); + button.classList.add("hidden"); + }); +} + +function currentSiteSetting() { + if (!siteSettings.open) { + return null; + } + + return siteSettingsTitle.innerText.toLowerCase(); +} + +function showSiteSettings(title, settings) { + siteSettingsTitle.innerText = title; + siteSettingsGlobal.checked = settings.enabledGlobally; + siteSettingsList.innerHTML = ""; + + siteSettingsGlobal.onchange = () => { + ladybird.sendMessage("setSiteSettingEnabledGlobally", { + setting: currentSiteSetting(), + enabled: siteSettingsGlobal.checked, + }); + }; + + if (settings.siteFilters.length === 0) { + const placeholder = document.createElement("div"); + placeholder.className = "dialog-list-item-placeholder"; + placeholder.textContent = "No sites added"; + + siteSettingsList.appendChild(placeholder); + } + + settings.siteFilters.forEach(site => { + const filter = document.createElement("span"); + filter.className = "dialog-list-item-label"; + filter.textContent = site; + + const remove = document.createElement("button"); + remove.className = "dialog-button"; + remove.innerHTML = "×"; + remove.title = `Remove ${site}`; + + remove.addEventListener("click", () => { + ladybird.sendMessage("removeSiteSettingFilter", { + setting: currentSiteSetting(), + filter: site, + }); + }); + + const item = document.createElement("div"); + item.className = "dialog-list-item"; + item.appendChild(filter); + item.appendChild(remove); + + siteSettingsList.appendChild(item); + }); + + if (!siteSettings.open) { + setTimeout(() => siteSettingsInput.focus()); + siteSettings.showModal(); + } +} + +function addSiteSettingFilter() { + ladybird.sendMessage("addSiteSettingFilter", { + setting: currentSiteSetting(), + filter: siteSettingsInput.value, + }); + + siteSettingsInput.classList.add("success"); + siteSettingsInput.value = ""; + + setTimeout(() => { + siteSettingsInput.classList.remove("success"); + }, 1000); + + setTimeout(() => siteSettingsInput.focus()); +} + +siteSettingsAdd.addEventListener("click", addSiteSettingFilter); + +siteSettingsInput.addEventListener("keydown", event => { + if (event.key === "Enter") { + addSiteSettingFilter(); + } +}); + +siteSettingsClose.addEventListener("click", () => { + siteSettings.close(); +}); + +siteSettingsRemoveAll.addEventListener("click", () => { + ladybird.sendMessage("removeAllSiteSettingFilters", { + setting: currentSiteSetting(), + }); +}); + +autoplaySettings.addEventListener("click", event => { + showSiteSettings("Autoplay", AUTOPLAY_SETTINGS); + event.stopPropagation(); +}); + +document.addEventListener("WebUILoaded", () => { + ladybird.sendMessage("loadForciblyEnabledSiteSettings"); +}); + +document.addEventListener("WebUIMessage", event => { + if (event.detail.name === "loadSettings") { + loadSiteSettings(event.detail.data); + } else if (event.detail.name === "forciblyEnableSiteSettings") { + forciblyEnableSiteSettings(event.detail.data); + } +}); diff --git a/Base/res/ladybird/about-pages/settings/privacy.js b/Base/res/ladybird/about-pages/settings/privacy.js new file mode 100644 index 00000000000..30d79649387 --- /dev/null +++ b/Base/res/ladybird/about-pages/settings/privacy.js @@ -0,0 +1,15 @@ +const doNotTrackToggle = document.querySelector("#do-not-track-toggle"); + +function loadSettings(settings) { + doNotTrackToggle.checked = settings.doNotTrack; +} + +doNotTrackToggle.addEventListener("change", () => { + ladybird.sendMessage("setDoNotTrack", doNotTrackToggle.checked); +}); + +document.addEventListener("WebUIMessage", event => { + if (event.detail.name === "loadSettings") { + loadSettings(event.detail.data); + } +}); diff --git a/Base/res/ladybird/about-pages/settings/search.js b/Base/res/ladybird/about-pages/settings/search.js new file mode 100644 index 00000000000..ca01814b78d --- /dev/null +++ b/Base/res/ladybird/about-pages/settings/search.js @@ -0,0 +1,249 @@ +const searchClose = document.querySelector("#search-close"); +const searchCustomAdd = document.querySelector("#search-custom-add"); +const searchCustomName = document.querySelector("#search-custom-name"); +const searchCustomURL = document.querySelector("#search-custom-url"); +const searchDialog = document.querySelector("#search-dialog"); +const searchEngine = document.querySelector("#search-engine"); +const searchList = document.querySelector("#search-list"); +const searchSettings = document.querySelector("#search-settings"); +const searchToggle = document.querySelector("#search-toggle"); + +const autocompleteEngine = document.querySelector("#autocomplete-engine"); +const autocompleteToggle = document.querySelector("#autocomplete-toggle"); + +let SEARCH_ENGINE = {}; +let AUTOCOMPLETE_ENGINE = {}; + +let NATIVE_SEARCH_ENGINE_COUNT = 0; + +const Engine = Object.freeze({ + search: 1, + autocomplete: 2, +}); + +function loadEngineSettings(settings) { + SEARCH_ENGINE = settings.searchEngine || {}; + AUTOCOMPLETE_ENGINE = settings.autocompleteEngine || {}; + + const renderEngineSettings = (type, setting) => { + const [name, toggle, engine] = engineForType(type); + + if (setting.name) { + toggle.checked = true; + engine.value = setting.name; + } else { + toggle.checked = false; + } + + renderEngine(type); + }; + + loadCustomSearchEngines(); + renderEngineSettings(Engine.search, SEARCH_ENGINE); + renderEngineSettings(Engine.autocomplete, AUTOCOMPLETE_ENGINE); + + if (searchDialog.open) { + showSearchEngineSettings(); + } +} + +function engineForType(engine) { + if (engine === Engine.search) { + return ["Search", searchToggle, searchEngine]; + } + if (engine === Engine.autocomplete) { + return ["Autocomplete", autocompleteToggle, autocompleteEngine]; + } + throw Error(`Unrecognized engine type ${engine}`); +} + +function 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); + } + + if (type === Engine.search) { + NATIVE_SEARCH_ENGINE_COUNT = engine.length; + engine.appendChild(document.createElement("hr")); + } +} + +function renderEngine(type) { + const [name, toggle, engine] = engineForType(type); + + if (toggle.checked) { + engine.closest(".card-group").classList.remove("hidden"); + } else { + engine.closest(".card-group").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; + } +} + +function 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); +} + +function setSaveEngineListeners(type) { + const [name, toggle, engine] = engineForType(type); + + toggle.addEventListener("change", () => { + saveEngine(type); + }); + engine.addEventListener("change", () => { + saveEngine(type); + }); +} + +setSaveEngineListeners(Engine.search); +setSaveEngineListeners(Engine.autocomplete); + +function loadCustomSearchEngines() { + while (searchEngine.length > NATIVE_SEARCH_ENGINE_COUNT) { + searchEngine.remove(NATIVE_SEARCH_ENGINE_COUNT); + } + + const custom = SEARCH_ENGINE.custom || []; + + custom.forEach(custom => { + const option = document.createElement("option"); + option.text = custom.name; + option.value = custom.name; + + searchEngine.add(option); + }); +} + +function showSearchEngineSettings() { + searchCustomName.classList.remove("error"); + searchCustomURL.classList.remove("error"); + searchList.innerHTML = ""; + + const custom = SEARCH_ENGINE.custom || []; + + if (custom.length === 0) { + const placeholder = document.createElement("div"); + placeholder.className = "dialog-list-item-placeholder"; + placeholder.textContent = "No custom search engines added"; + + searchList.appendChild(placeholder); + } + + custom.forEach(custom => { + const name = document.createElement("span"); + name.textContent = custom.name; + + const url = document.createElement("span"); + url.className = "dialog-list-item-placeholder"; + url.style = "padding-left: 0"; + url.textContent = ` — ${custom.url}`; + + const engine = document.createElement("span"); + engine.className = "dialog-list-item-label"; + engine.appendChild(name); + engine.appendChild(url); + + const remove = document.createElement("button"); + remove.className = "dialog-button"; + remove.innerHTML = "×"; + remove.title = `Remove ${custom.name}`; + + remove.addEventListener("click", () => { + ladybird.sendMessage("removeCustomSearchEngine", custom); + }); + + const item = document.createElement("div"); + item.className = "dialog-list-item"; + item.appendChild(engine); + item.appendChild(remove); + + searchList.appendChild(item); + }); + + if (!searchDialog.open) { + setTimeout(() => searchCustomName.focus()); + searchDialog.showModal(); + } +} + +function addCustomSearchEngine() { + searchCustomName.classList.remove("error"); + searchCustomURL.classList.remove("error"); + + for (const i = 0; i < searchEngine.length; ++i) { + if (searchCustomName.value === searchEngine.item(i).value) { + searchCustomName.classList.add("error"); + return; + } + } + + if (!containsValidURL(searchCustomURL)) { + searchCustomURL.classList.add("error"); + return; + } + + ladybird.sendMessage("addCustomSearchEngine", { + name: searchCustomName.value, + url: searchCustomURL.value, + }); + + searchCustomName.value = ""; + searchCustomURL.value = ""; + + setTimeout(() => searchCustomName.focus()); +} + +searchCustomAdd.addEventListener("click", addCustomSearchEngine); + +searchCustomName.addEventListener("keydown", event => { + if (event.key === "Enter") { + addCustomSearchEngine(); + } +}); + +searchCustomURL.addEventListener("keydown", event => { + if (event.key === "Enter") { + addCustomSearchEngine(); + } +}); + +searchClose.addEventListener("click", () => { + searchDialog.close(); +}); + +searchSettings.addEventListener("click", event => { + showSearchEngineSettings(); + event.stopPropagation(); +}); + +document.addEventListener("WebUILoaded", () => { + ladybird.sendMessage("loadAvailableEngines"); +}); + +document.addEventListener("WebUIMessage", event => { + if (event.detail.name === "loadSettings") { + loadEngineSettings(event.detail.data); + } else if (event.detail.name === "loadEngines") { + loadEngines(Engine.search, event.detail.data.search); + loadEngines(Engine.autocomplete, event.detail.data.autocomplete); + } +}); diff --git a/UI/cmake/ResourceFiles.cmake b/UI/cmake/ResourceFiles.cmake index 18795afce26..2aecbfc30fd 100644 --- a/UI/cmake/ResourceFiles.cmake +++ b/UI/cmake/ResourceFiles.cmake @@ -76,6 +76,11 @@ list(TRANSFORM ABOUT_PAGES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/abo set(ABOUT_SETTINGS_RESOURCES languages.js + network.js + new-tab-page.js + permissions.js + privacy.js + search.js ) list(TRANSFORM ABOUT_SETTINGS_RESOURCES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/about-pages/settings/")