diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
commit | 40a355a42d4a9444dc753c04c6608dade2f06a23 (patch) | |
tree | 871fc667d2de662f171103ce5ec067014ef85e61 /toolkit/content | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.tar.xz firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/content')
71 files changed, 1625 insertions, 394 deletions
diff --git a/toolkit/content/aboutLogging.js b/toolkit/content/aboutLogging.js index 5aaf0f9ecc..daff10fbec 100644 --- a/toolkit/content/aboutLogging.js +++ b/toolkit/content/aboutLogging.js @@ -50,7 +50,7 @@ function moduleEnvVarPresent() { * as markers. * * [1]: The keys of the `presets` object defined in - * https://searchfox.org/mozilla-central/source/devtools/client/performance-new/shared/background.jsm.js + * https://searchfox.org/mozilla-central/source/devtools/client/performance-new/shared/background.sys.mjs */ const gOsSpecificLoggingPresets = (() => { @@ -74,7 +74,7 @@ const gOsSpecificLoggingPresets = (() => { const gLoggingPresets = { networking: { modules: - "timestamp,sync,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5", + "timestamp,sync,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5,EarlyHint:5", l10nIds: { label: "about-logging-preset-networking-label", description: "about-logging-preset-networking-description", @@ -228,7 +228,6 @@ function populatePresets() { $("#log-modules").value = gLoggingPresets[dropdown.value].modules; } setPresetAndDescription(dropdown.value); - setLogModules(); Services.prefs.setCharPref("logging.config.preset", dropdown.value); }; @@ -376,7 +375,6 @@ function parseURL() { $("#set-log-modules-button").disabled = true; $("#logging-preset-dropdown").disabled = true; someElementsDisabled = true; - setLogModules(); updateLogModules(); } if (outputTypeOverriden) { diff --git a/toolkit/content/aboutNetError.mjs b/toolkit/content/aboutNetError.mjs index 83f40fc479..554553fd62 100644 --- a/toolkit/content/aboutNetError.mjs +++ b/toolkit/content/aboutNetError.mjs @@ -430,11 +430,21 @@ function initPage() { tryAgain.hidden = true; break; - // Pinning errors are of type nssFailure2 + // TLS errors and non-overridable certificate errors (e.g. pinning + // failures) are of type nssFailure2. case "nssFailure2": { learnMore.hidden = false; const errorCode = document.getNetErrorInfo().errorCodeString; + RPMRecordTelemetryEvent( + "security.ui.tlserror", + "load", + "abouttlserror", + errorCode, + { + is_frame: (window.parent != window).toString(), + } + ); switch (errorCode) { case "SSL_ERROR_UNSUPPORTED_VERSION": case "SSL_ERROR_PROTOCOL_VERSION_ALERT": { diff --git a/toolkit/content/aboutSupport.js b/toolkit/content/aboutSupport.js index f668fd671f..f9f35e7e76 100644 --- a/toolkit/content/aboutSupport.js +++ b/toolkit/content/aboutSupport.js @@ -1179,7 +1179,7 @@ var snapshotFormatters = { $.new("td", cdmInfo.keySystemName), $.new("td", getVideoRobustness(rvArray)), $.new("td", getAudioRobustness(rvArray)), - $.new("td", getCapabilities(rvArray)), + $.new("td", getCapabilities(rvArray), null, { colspan: "4" }), $.new("td", cdmInfo.clearlead ? "Yes" : "No"), $.new("td", cdmInfo.isHDCP22Compatible ? "Yes" : "No"), ]); diff --git a/toolkit/content/aboutSupport.xhtml b/toolkit/content/aboutSupport.xhtml index d3de7d0019..d19fb64d56 100644 --- a/toolkit/content/aboutSupport.xhtml +++ b/toolkit/content/aboutSupport.xhtml @@ -567,13 +567,13 @@ <tbody id="media-content-decryption-modules-tbody"> <tr> - <th colspan="6" class="title-column" data-l10n-id="media-content-decryption-modules-title"/> + <th colspan="9" class="title-column" data-l10n-id="media-content-decryption-modules-title"/> </tr> <tr> <th data-l10n-id="media-key-system-name"/> <th data-l10n-id="media-video-robustness"/> <th data-l10n-id="media-audio-robustness"/> - <th data-l10n-id="media-cdm-capabilities"/> + <th colspan="4" data-l10n-id="media-cdm-capabilities"/> <th data-l10n-id="media-cdm-clear-lead"/> <th data-l10n-id="media-hdcp-22-compatible"/> </tr> diff --git a/toolkit/content/aboutwebrtc/aboutWebrtc.mjs b/toolkit/content/aboutwebrtc/aboutWebrtc.mjs index 3c41a4aa66..7e2c92a4bd 100644 --- a/toolkit/content/aboutwebrtc/aboutWebrtc.mjs +++ b/toolkit/content/aboutwebrtc/aboutWebrtc.mjs @@ -230,7 +230,7 @@ class SavePage extends Control { ]); let FilePicker = makeFilePickerService(); const lazyFileUtils = lazy.FileUtils; - FilePicker.init(window, dialogTitle, FilePicker.modeSave); + FilePicker.init(window.browsingContext, dialogTitle, FilePicker.modeSave); FilePicker.defaultString = LOGFILE_NAME_DEFAULT; const rv = await new Promise(r => FilePicker.open(r)); if (rv != FilePicker.returnOK && rv != FilePicker.returnReplace) { diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js index d9ee83026e..983fd9890d 100644 --- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -11,7 +11,6 @@ var { XPCOMUtils } = ChromeUtils.importESModule( ChromeUtils.defineESModuleGetters(this, { BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs", - Deprecated: "resource://gre/modules/Deprecated.sys.mjs", DownloadLastDir: "resource://gre/modules/DownloadLastDir.sys.mjs", DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs", Downloads: "resource://gre/modules/Downloads.sys.mjs", @@ -689,7 +688,7 @@ function promiseTargetFile( let fp = makeFilePicker(); let titleKey = aFpP.fpTitleKey || "SaveLinkTitle"; fp.init( - window, + window.browsingContext, ContentAreaUtils.stringBundle.GetStringFromName(titleKey), Ci.nsIFilePicker.modeSave ); diff --git a/toolkit/content/customElements.js b/toolkit/content/customElements.js index 30958a3a31..b0a8f33fe6 100644 --- a/toolkit/content/customElements.js +++ b/toolkit/content/customElements.js @@ -818,6 +818,8 @@ // like the previous Services.scriptloader.loadSubscript() function function importCustomElementFromESModule(name) { switch (name) { + case "moz-button": + return import("chrome://global/content/elements/moz-button.mjs"); case "moz-button-group": return import( "chrome://global/content/elements/moz-button-group.mjs" diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 8b18c94525..d08037a84a 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -91,10 +91,15 @@ toolkit.jar: content/global/elements/message-bar.js (widgets/message-bar.js) content/global/elements/menu.js (widgets/menu.js) content/global/elements/menupopup.js (widgets/menupopup.js) + content/global/elements/moz-button.css (widgets/moz-button/moz-button.css) + content/global/elements/moz-button.mjs (widgets/moz-button/moz-button.mjs) content/global/elements/moz-button-group.css (widgets/moz-button-group/moz-button-group.css) content/global/elements/moz-button-group.mjs (widgets/moz-button-group/moz-button-group.mjs) content/global/elements/moz-card.css (widgets/moz-card/moz-card.css) content/global/elements/moz-card.mjs (widgets/moz-card/moz-card.mjs) + content/global/elements/moz-page-nav.css (widgets/moz-page-nav/moz-page-nav.css) + content/global/elements/moz-page-nav-button.css (widgets/moz-page-nav/moz-page-nav-button.css) + content/global/elements/moz-page-nav.mjs (widgets/moz-page-nav/moz-page-nav.mjs) content/global/elements/moz-five-star.css (widgets/moz-five-star/moz-five-star.css) content/global/elements/moz-five-star.mjs (widgets/moz-five-star/moz-five-star.mjs) content/global/elements/moz-input-box.js (widgets/moz-input-box.js) diff --git a/toolkit/content/license.html b/toolkit/content/license.html index 9e0721906b..e9d2642354 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -104,7 +104,6 @@ <li><a href="about:license#jquery">jQuery License</a></li> <li><a href="about:license#k_exp">k_exp License</a></li> <li><a href="about:license#khronos">Khronos group License</a></li> - <li><a href="about:license#kiss_fft">Kiss FFT License</a></li> #ifdef MOZ_USE_LIBCXX <li><a href="about:license#libc++">libc++ License</a></li> #endif @@ -154,9 +153,6 @@ <li><a href="about:license#validator">Validator License</a></li> <li><a href="about:license#vtune">VTune License</a></li> <li><a href="about:license#webrtc">WebRTC License</a></li> -#ifdef MOZ_DEFAULT_BROWSER_AGENT - <li><a href="about:license#wintoast">WinToast License</a></li> -#endif <li><a href="about:license#x264">x264 License</a></li> <li><a href="about:license#xiph">Xiph.org Foundation License</a></li> </ul> @@ -2041,7 +2037,6 @@ WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. <li><code>gfx/ots/</code></li> <li><code>gfx/ycbcr/</code></li> <li><code>ipc/chromium/</code></li> - <li><code>media/openmax_dl/</code></li> <li><code>toolkit/components/reputationservice/</code></li> <li><code>toolkit/components/url-classifier/chromium/</code></li> <li><code>tools/profiler/</code></li> @@ -3116,80 +3111,6 @@ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. </pre> - - <hr> - - <h1><a id="khronos"></a>Khronos group License</h1> - - <p>This license applies to the following files:</p> - - <ul> - <li><code>media/openmax_dl/dl/api/omxtypes.h</code></li> - <li><code>media/openmax_dl/dl/sp/api/omxSP.h</code></li> - </ul> - -<pre> -Copyright 2005-2008 The Khronos Group Inc. All Rights Reserved. - -These materials are protected by copyright laws and contain material -proprietary to the Khronos Group, Inc. You may use these materials -for implementing Khronos specifications, without altering or removing -any trademark, copyright or other notice from the specification. - -Khronos Group makes no, and expressly disclaims any, representations -or warranties, express or implied, regarding these materials, including, -without limitation, any implied warranties of merchantability or fitness -for a particular purpose or non-infringement of any intellectual property. -Khronos Group makes no, and expressly disclaims any, warranties, express -or implied, regarding the correctness, accuracy, completeness, timeliness, -and reliability of these materials. - -Under no circumstances will the Khronos Group, or any of its Promoters, -Contributors or Members or their respective partners, officers, directors, -employees, agents or representatives be liable for any damages, whether -direct, indirect, special or consequential damages for lost revenues, -lost profits, or otherwise, arising from or in connection with these -materials. - -Khronos and OpenMAX are trademarks of the Khronos Group Inc. -</pre> - - <hr> - - <h1><a id="kiss_fft"></a>Kiss FFT License</h1> - - <p>This license applies to files in the directory - <code>media/kiss_fft/</code>.</p> - -<pre> -Copyright (c) 2003-2010 Mark Borgerding - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the author nor the names of any contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -</pre> - <hr> #ifdef MOZ_USE_LIBCXX @@ -3695,9 +3616,6 @@ SOFTWARE. <li><code>third_party/rust/synstructure</code></li> <li><code>third_party/rust/void</code></li> <li><code>js/src/zydis</code> (unless otherwise specified)</li> -#ifdef MOZ_DEFAULT_BROWSER_AGENT - <li><code>third_party/WinToast</code> unless otherwise specified</li> -#endif </ul> See the individual LICENSE files or headers for copyright owners.</p> @@ -5645,6 +5563,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. they are referred to below as "Distributable Code": <ul> <li><var>msvc*.dll</var> (C and C++ runtime libraries)</li> + <li><var>vcruntime*.dll</var> (Visual C++ Runtime)</li> </ul> </p> diff --git a/toolkit/content/tests/browser/browser_about_logging.js b/toolkit/content/tests/browser/browser_about_logging.js index f458b36e0d..fdf8eab57b 100644 --- a/toolkit/content/tests/browser/browser_about_logging.js +++ b/toolkit/content/tests/browser/browser_about_logging.js @@ -110,15 +110,12 @@ add_task(async function testURLParameters() { !$("#some-elements-unavailable").hidden, "If modules are selected via URL, a warning should be displayed." ); - var inPageSorted = $("#current-log-modules") - .innerText.split(",") - .sort() - .join(","); - var inURLSorted = modulesInURL.split(",").sort().join(","); + var inInputSorted = $("#log-modules").value.split(",").sort().join(","); + var modulesSorted = modulesInURL.split(",").sort().join(","); Assert.equal( - inPageSorted, - inURLSorted, - "When selecting modules via URL params, the same modules are reflected in the page." + modulesSorted, + inInputSorted, + "When selecting modules via URL params, the log modules aren't immediately set" ); }); } @@ -135,19 +132,16 @@ add_task(async function testURLParameters() { !$("#some-elements-unavailable").hidden, "If a preset is selected via URL, a warning should be displayed." ); - var inPageSorted = $("#current-log-modules") - .innerText.split(",") - .sort() - .join(","); + var inInputSorted = $("#log-modules").value.split(",").sort().join(","); var presetSorted = content .presets() [presetInURL].modules.split(",") .sort() .join(","); Assert.equal( - inPageSorted, + inInputSorted, presetSorted, - "When selecting a preset via URL params, the correct log modules are reflected in the page." + "When selecting a preset via URL params, the correct log modules are reflected in the input." ); }); } diff --git a/toolkit/content/tests/browser/browser_default_audio_filename.js b/toolkit/content/tests/browser/browser_default_audio_filename.js index c32dda6878..2732c0e434 100644 --- a/toolkit/content/tests/browser/browser_default_audio_filename.js +++ b/toolkit/content/tests/browser/browser_default_audio_filename.js @@ -2,7 +2,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); registerCleanupFunction(function () { MockFilePicker.cleanup(); }); diff --git a/toolkit/content/tests/browser/browser_default_image_filename.js b/toolkit/content/tests/browser/browser_default_image_filename.js index 9add704664..0f7847020c 100644 --- a/toolkit/content/tests/browser/browser_default_image_filename.js +++ b/toolkit/content/tests/browser/browser_default_image_filename.js @@ -2,7 +2,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); const DATA_IMAGE_GIF_URL = "data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7"; registerCleanupFunction(function () { diff --git a/toolkit/content/tests/browser/browser_default_image_filename_redirect.js b/toolkit/content/tests/browser/browser_default_image_filename_redirect.js index a3fdd2d19e..82926b3d44 100644 --- a/toolkit/content/tests/browser/browser_default_image_filename_redirect.js +++ b/toolkit/content/tests/browser/browser_default_image_filename_redirect.js @@ -7,7 +7,7 @@ */ let MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); add_task(async function () { // This URL will redirect to doggy.png. const URL_FIREBIRD = diff --git a/toolkit/content/tests/browser/browser_saveImageURL.js b/toolkit/content/tests/browser/browser_saveImageURL.js index c936b8ef84..0f7bf4b117 100644 --- a/toolkit/content/tests/browser/browser_saveImageURL.js +++ b/toolkit/content/tests/browser/browser_saveImageURL.js @@ -5,7 +5,7 @@ const IMAGE_PAGE = var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); MockFilePicker.returnValue = MockFilePicker.returnCancel; registerCleanupFunction(function () { diff --git a/toolkit/content/tests/browser/browser_save_folder_standalone_image.js b/toolkit/content/tests/browser/browser_save_folder_standalone_image.js index ce45d04fdc..073e71a88b 100644 --- a/toolkit/content/tests/browser/browser_save_folder_standalone_image.js +++ b/toolkit/content/tests/browser/browser_save_folder_standalone_image.js @@ -43,7 +43,7 @@ async function clearHistoryAndWait() { */ let MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); add_task(async function () { const IMAGE_URL = diff --git a/toolkit/content/tests/browser/browser_save_resend_postdata.js b/toolkit/content/tests/browser/browser_save_resend_postdata.js index 5eb1b1c904..3f3e729dab 100644 --- a/toolkit/content/tests/browser/browser_save_resend_postdata.js +++ b/toolkit/content/tests/browser/browser_save_resend_postdata.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); /** * Test for bug 471962 <https://bugzilla.mozilla.org/show_bug.cgi?id=471962>: diff --git a/toolkit/content/tests/browser/datetime/browser.toml b/toolkit/content/tests/browser/datetime/browser.toml index 6e8580ddc4..747014f386 100644 --- a/toolkit/content/tests/browser/datetime/browser.toml +++ b/toolkit/content/tests/browser/datetime/browser.toml @@ -8,8 +8,9 @@ skip-if = [ "os == 'linux' && fission && socketprocess_networking && !debug", # high frequency intermittent, Bug 1673140 ] +["browser_datetime_change_event.js"] + ["browser_datetime_datepicker.js"] -fail-if = ["a11y_checks"] # Bug 1854538 clicked td.outside may not be accessible # This file was skipped before new tests were written based on it in Bug 1676068 skip-if = [ "tsan", # Frequently times out on TSan @@ -46,7 +47,6 @@ skip-if = [ ] ["browser_datetime_datepicker_min_max.js"] -fail-if = ["a11y_checks"] # Bug 1854538 clicked TD may not be accessible skip-if = [ "tsan", # Frequently times out on TSan "os == 'win' && asan", # fails on asan @@ -61,7 +61,6 @@ skip-if = [ ] ["browser_datetime_datepicker_mousenav.js"] -fail-if = ["a11y_checks"] # Bug 1854538 clicked td.weekend.outside may not be accessible skip-if = [ "tsan", # Frequently times out on TSan "os == 'win' && asan", # fails on asan diff --git a/toolkit/content/tests/browser/datetime/browser_datetime_change_event.js b/toolkit/content/tests/browser/datetime/browser_datetime_change_event.js new file mode 100644 index 0000000000..920653778a --- /dev/null +++ b/toolkit/content/tests/browser/datetime/browser_datetime_change_event.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function open_change_and_expect_one_change_event(page) { + await helper.openPicker(page); + + let changeEventPromise = helper.promiseChange(); + + // Click the first item (top-left corner) of the calendar + helper.click(helper.getElement(DAYS_VIEW).children[0]); + await changeEventPromise; + + await helper.closePicker(); + + let changeEvents = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + function () { + return content.wrappedJSObject.changeEventCount; + } + ); + is(changeEvents, 1, "Should've got one change event"); + await helper.tearDown(); +} + +add_task(async function test_change_event_simple() { + await open_change_and_expect_one_change_event(`data:text/html, + <!doctype html> + <script> + var changeEventCount = 0; + </script> + <input type="date" id="date" onchange="changeEventCount++"> + `); +}); + +add_task(async function test_change_event_with_mutation() { + await open_change_and_expect_one_change_event(`data:text/html, + <!doctype html> + <script> + var changeEventCount = 0; + </script> + <input type="date" id="date" onchange="this.value = ''; changeEventCount++"> + `); +}); diff --git a/toolkit/content/tests/browser/datetime/head.js b/toolkit/content/tests/browser/datetime/head.js index bbef72873c..46e2c78af5 100644 --- a/toolkit/content/tests/browser/datetime/head.js +++ b/toolkit/content/tests/browser/datetime/head.js @@ -113,15 +113,19 @@ class DateTimeTestHelper { EventUtils.synthesizeMouseAtCenter(element, {}, this.frame.contentWindow); } - /** - * Close the panel and the tab - */ - async tearDown() { + async closePicker() { if (this.panel.state != "closed") { let pickerClosePromise = this.promisePickerClosed(); this.panel.hidePopup(); await pickerClosePromise; } + } + + /** + * Close the panel and the tab + */ + async tearDown() { + await this.closePicker(); BrowserTestUtils.removeTab(this.tab); this.tab = null; } diff --git a/toolkit/content/tests/chrome/chrome.toml b/toolkit/content/tests/chrome/chrome.toml index 70fa12c4b6..3391a2923d 100644 --- a/toolkit/content/tests/chrome/chrome.toml +++ b/toolkit/content/tests/chrome/chrome.toml @@ -224,6 +224,8 @@ support-files = [ ["test_menulist_in_popup.xhtml"] +["test_menulist_initial_selection.xhtml"] + ["test_menulist_keynav.xhtml"] ["test_menulist_null_value.xhtml"] diff --git a/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml index b49f8a1d5e..005c6ebffe 100644 --- a/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml +++ b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml @@ -57,9 +57,9 @@ function checkKeyCaretTest(key, expectedStart, expectedEnd, result, testid) keypressFired = true; } } - SpecialPowers.addSystemEventListener(window, "keypress", listener, false); + SpecialPowers.wrap(window).addEventListener("keypress", listener, { mozSystemGroup: true }); synthesizeKey(key, {}); - SpecialPowers.removeSystemEventListener(window, "keypress", listener, false); + SpecialPowers.wrap(window).removeEventListener("keypress", listener, { mozSystemGroup: true }); is(keypressFired, result, `${testid} keypress event should${result ? "" : " not"} be fired`); is(autocomplete.selectionStart, expectedStart, testid + " selectionStart"); is(autocomplete.selectionEnd, expectedEnd, testid + " selectionEnd"); diff --git a/toolkit/content/tests/chrome/test_menulist_initial_selection.xhtml b/toolkit/content/tests/chrome/test_menulist_initial_selection.xhtml new file mode 100644 index 0000000000..19e9beae67 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_initial_selection.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist Initial Selection Test" + onload="setTimeout(runTest, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +async function runTest() { + const panel = document.querySelector("panel"); + const menulist1 = document.getElementById("menulist1"); + const menulist2 = document.getElementById("menulist2"); + + const panelShown = new Promise(r => panel.addEventListener("popupshown", r, { once: true })); + info("opening panel"); + panel.openPopup(null, { x: 0, y: 0 }); + await panelShown; + info("panel opened"); + + is(menulist1.value, "1", "menulist1 should have the first menuitem's value"); + is(menulist1.label, "One", "menulist1 should have the first menuitem's label"); + + is(menulist2.value, "", "menulist2 should not be selected to the first item's value"); + is(menulist2.label, "None", "menulist2 should not be selected to the first item's value"); + + SimpleTest.finish(); +} + +]]> +</script> + +<panel> + <menulist id="menulist1" value="" label="None"> + <menupopup id="menulistpopup"> + <menuitem value="1" label="One"/> + <menuitem value="2" label="Two"/> + <menuitem value="3" label="Three"/> + </menupopup> + </menulist> + <menulist id="menulist2" value="" label="None" noinitialselection="true"> + <menupopup id="menulistpopup"> + <menuitem value="1" label="One"/> + <menuitem value="2" label="Two"/> + <menuitem value="3" label="Three"/> + </menupopup> + </menulist> +</panel> + +</window> diff --git a/toolkit/content/tests/chrome/window_tooltip.xhtml b/toolkit/content/tests/chrome/window_tooltip.xhtml index 6a573f0bd9..b78075de45 100644 --- a/toolkit/content/tests/chrome/window_tooltip.xhtml +++ b/toolkit/content/tests/chrome/window_tooltip.xhtml @@ -15,12 +15,12 @@ <box id="parent" tooltiptext="Box Tooltip" style="margin: 10px"> <button id="withtext" label="Tooltip Text" tooltiptext="Button Tooltip" - style="-moz-appearance: none; padding: 0;"/> - <button id="without" label="No Tooltip" style="-moz-appearance: none; padding: 0;"/> + style="appearance: none; padding: 0;"/> + <button id="without" label="No Tooltip" style="appearance: none; padding: 0;"/> <!-- remove the native theme and borders to avoid some platform specific sizing differences --> <button id="withtooltip" label="Tooltip Element" tooltip="thetooltip" - class="plain" style="-moz-appearance: none; padding: 0;"/> + class="plain" style="appearance: none; padding: 0;"/> </box> <script class="testbody" type="application/javascript"> diff --git a/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html b/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html index fe4a6ee67c..271cf50a79 100644 --- a/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html +++ b/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html @@ -77,7 +77,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=998893 resolve(); } - SpecialPowers.addSystemEventListener(field, "keypress", handleEnter, true); + SpecialPowers.wrap(field).addEventListener("keypress", handleEnter, { capture: true, mozSystemGroup: true }); }); field.focus(); diff --git a/toolkit/content/tests/widgets/chrome.toml b/toolkit/content/tests/widgets/chrome.toml index af2c778947..18fe0d153a 100644 --- a/toolkit/content/tests/widgets/chrome.toml +++ b/toolkit/content/tests/widgets/chrome.toml @@ -22,6 +22,8 @@ skip-if = ["os == 'linux'"] # Bug 1116215 ["test_menubar.xhtml"] skip-if = ["os == 'mac'"] +["test_moz_button.html"] + ["test_moz_button_group.html"] ["test_moz_card.html"] @@ -32,6 +34,8 @@ skip-if = ["os == 'mac'"] ["test_moz_message_bar.html"] +["test_moz_page_nav.html"] + ["test_moz_support_link.html"] ["test_moz_toggle.html"] @@ -65,4 +69,5 @@ skip-if = [ "os == 'android'", "os == 'linux' && debug", # Bug 1765783 ] + ["test_videocontrols_onclickplay.html"] diff --git a/toolkit/content/tests/widgets/test_moz_button.html b/toolkit/content/tests/widgets/test_moz_button.html new file mode 100644 index 0000000000..473b2d1a1c --- /dev/null +++ b/toolkit/content/tests/widgets/test_moz_button.html @@ -0,0 +1,158 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>MozButton Tests</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="module" src="chrome://global/content/elements/moz-button.mjs"></script> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <link rel="stylesheet" href="chrome://global/skin/design-system/tokens-brand.css"> + <link rel="stylesheet" href="chrome://global/skin/design-system/text-and-typography.css"> +<style> +.four::part(button), +.five::part(button), +.six::part(button) { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16' fill='context-fill' fill-opacity='context-fill-opacity'%3E%3Cpath d='M3 7 1.5 7l-.5.5L1 9l.5.5 1.5 0 .5-.5 0-1.5z'/%3E%3Cpath d='m8.75 7-1.5 0-.5.5 0 1.5.5.5 1.5 0 .5-.5 0-1.5z'/%3E%3Cpath d='M14.5 7 13 7l-.5.5 0 1.5.5.5 1.5 0L15 9l0-1.5z'/%3E%3C/svg%3E"); +} +</style> + <script> + function normalizeColor(val, computedStyles) { + if (val.includes("currentColor")) { + val = val.replaceAll("currentColor", computedStyles.color); + } + if (val.startsWith("light-dark")) { + let [, light, dark] = val.match(/light-dark\(([^,]+),\s*([^)]+)\)/); + if (light && dark) { + val = window.matchMedia("(prefers-color-scheme: dark)").matches ? dark : light; + } + } + try { + let { r, g, b, a } = InspectorUtils.colorToRGBA(val); + return `rgba(${r}, ${g}, ${b}, ${a})`; + } catch (e) { + info(val); + throw e; + } + } + + function assertButtonPropertiesMatch(el, propertyToCssVar) { + let elStyles = getComputedStyle(el.buttonEl); + for (let [property, cssVar] of Object.entries(propertyToCssVar)) { + let propertyVal = elStyles[property]; + let cssVarVal = cssVar.startsWith("--") ? elStyles.getPropertyValue(cssVar) : cssVar; + if (propertyVal.startsWith("rgb") || propertyVal.startsWith("#") || propertyVal.startsWith("color")) { + propertyVal = normalizeColor(propertyVal, elStyles); + cssVarVal = normalizeColor(cssVarVal, elStyles); + } + info(`${propertyVal} == ${cssVarVal}`); + is(propertyVal, cssVarVal, `${property} should be ${cssVar}`); + } + } + + add_task(async function testButtonTypes() { + let [...buttons] = document.querySelectorAll("moz-button"); + let [one, two, three, four, five, six] = buttons; + + await Promise.all(buttons.map(btn => btn.updateComplete)); + + is(one.textContent, "Test button", "Text is set"); + is(two.buttonEl.textContent.trim(), "Test button", "Text is set"); + is(three.textContent, "Test button", "Text is set"); + + assertButtonPropertiesMatch(one, { + backgroundColor: "--button-background-color", + color: "--button-text-color", + height: "--button-min-height", + }); + assertButtonPropertiesMatch(two, { + backgroundColor: "--button-background-color", + color: "--button-text-color", + height: "--button-min-height", + }); + assertButtonPropertiesMatch(three, { + backgroundColor: "--button-background-color-primary", + color: "--button-text-color-primary", + height: "--button-min-height", + }); + + assertButtonPropertiesMatch(four, { + width: "--button-size-icon", + height: "--button-size-icon", + backgroundColor: "--button-background-color", + fill: "--button-text-color", + }); + assertButtonPropertiesMatch(five, { + width: "--button-size-icon", + height: "--button-size-icon", + backgroundColor: "transparent", + fill: "--button-text-color", + }); + assertButtonPropertiesMatch(six, { + width: "--button-size-icon", + height: "--button-size-icon", + backgroundColor: "transparent", + fill: "--button-text-color", + }); + + buttons.forEach(btn => (btn.size = "small")); + + await Promise.all(buttons.map(btn => btn.updateComplete)); + + assertButtonPropertiesMatch(one, { + height: "--button-min-height-small", + }); + assertButtonPropertiesMatch(two, { + height: "--button-min-height-small", + }); + assertButtonPropertiesMatch(three, { + height: "--button-min-height-small", + }); + assertButtonPropertiesMatch(four, { + width: "--button-size-icon-small", + height: "--button-size-icon-small", + }); + assertButtonPropertiesMatch(five, { + width: "--button-size-icon-small", + height: "--button-size-icon-small", + }); + assertButtonPropertiesMatch(six, { + width: "--button-size-icon-small", + height: "--button-size-icon-small", + }); + }); + + add_task(async function testA11yAttributes() { + let button = document.querySelector("moz-button"); + + async function testProperty(propName, jsPropName = propName) { + let propValue = `${propName} value`; + ok(!button.buttonEl.hasAttribute(propName), `No ${propName} on inner button`); + button.setAttribute(propName, propValue); + + await button.updateComplete; + + ok(!button.hasAttribute(propName), `moz-button ${propName} cleared`); + is(button.buttonEl.getAttribute(propName), propValue, `${propName} added to inner button`); + + button[jsPropName] = null; + await button.updateComplete; + + ok(!button.buttonEl.hasAttribute(propName), `${propName} cleared by setting property`); + } + + await testProperty("title"); + await testProperty("aria-label", "ariaLabel"); + }); + + </script> +</head> +<body> + <moz-button class="one">Test button</moz-button> + <moz-button class="two" label="Test button"></moz-button> + <moz-button class="three" type="primary">Test button</moz-button> + <moz-button class="four" type="icon"></moz-button> + <moz-button class="five" type="icon ghost"></moz-button> + <moz-button class="six" type="ghost icon"></moz-button> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_moz_page_nav.html b/toolkit/content/tests/widgets/test_moz_page_nav.html new file mode 100644 index 0000000000..604df7c024 --- /dev/null +++ b/toolkit/content/tests/widgets/test_moz_page_nav.html @@ -0,0 +1,306 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>MozPageNav Tests</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"> + <script type="module" src="chrome://global/content/elements/moz-page-nav.mjs"></script> +</head> +<style> +body { + display: flex; +} +#navigation { + width: var(--page-nav-width); +} +</style> +<body> + <p id="display"></p> + <div id="content"> + <div id="navigation"> + <moz-page-nav heading="Heading"> + <moz-page-nav-button view="view-one" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg"> + <span class="view-name">View 1</span> + </moz-page-nav-button> + <moz-page-nav-button view="view-two" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg"> + <span class="view-name">View 2</span> + </moz-page-nav-button> + <moz-page-nav-button view="view-three" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg"> + <span class="view-name">View 3</span> + </moz-page-nav-button> + <moz-page-nav-button view="view-four" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg"> + <span class="view-name">View 4</span> + </moz-page-nav-button> + <moz-page-nav-button view="view-five" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg"> + <span class="view-name">View 5</span> + </moz-page-nav-button> + </moz-page-nav> + </div> + </div> +<pre id="test"></pre> +<script> + Services.scriptloader.loadSubScript( + "chrome://browser/content/utilityOverlay.js", + this + ); + const { BrowserTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + +const mozPageNav = document.querySelector("moz-page-nav"); + +function isActiveElement(expectedActiveEl) { + return expectedActiveEl.getRootNode().activeElement == expectedActiveEl; + } + + /** + * Tests that the first page nav button is selected by default + */ + add_task(async function test_first_item_selected_by_default() { + is( + mozPageNav.pageNavButtons.length, + 5, + "Five page nav buttons are in the navigation" + ); + + ok( + mozPageNav.pageNavButtons[0].view === mozPageNav.currentView, + "The first page nav button is selected by default" + ) + }); + + /** + * Tests that views are selected when clicked + */ + add_task(async function test_select_view() { + let gBrowser = BrowserWindowTracker.getTopWindow().top.gBrowser; + let secondViewButton = mozPageNav.pageNavButtons[1]; + let viewChanged = BrowserTestUtils.waitForEvent( + gBrowser, + "change-view" + ); + + secondViewButton.buttonEl.click(); + await viewChanged; + + ok( + secondViewButton.view === mozPageNav.currentView, + "The second page nav button is selected" + ) + + let thirdPageNavButton = mozPageNav.pageNavButtons[2]; + viewChanged = BrowserTestUtils.waitForEvent( + gBrowser, + "change-view" + ); + + thirdPageNavButton.buttonEl.click(); + await viewChanged; + + ok( + thirdPageNavButton.view === mozPageNav.currentView, + "The third page nav button is selected" + ) + + let firstPageNavButton = mozPageNav.pageNavButtons[0]; + viewChanged = BrowserTestUtils.waitForEvent( + gBrowser, + "change-view" + ); + + firstPageNavButton.buttonEl.click(); + await viewChanged; + + ok( + firstPageNavButton.view === mozPageNav.currentView, + "The first page nav button is selected" + ) + }); + + /** + * Tests that categories are keyboard-navigable + */ + add_task(async function test_keyboard_navigation() { + const arrowDown = async () => { + info("Arrow down"); + synthesizeKey("KEY_ArrowDown", {}); + await mozPageNav.updateComplete; + }; + const arrowUp = async () => { + info("Arrow up"); + synthesizeKey("KEY_ArrowUp", {}); + await mozPageNav.updateComplete; + }; + const arrowLeft = async () => { + info("Arrow left"); + synthesizeKey("KEY_ArrowLeft", {}); + await mozPageNav.updateComplete; + }; + const arrowRight = async () => { + info("Arrow right"); + synthesizeKey("KEY_ArrowRight", {}); + await mozPageNav.updateComplete; + }; + + // Setting this pref allows the test to run as expected with a keyboard on MacOS + await SpecialPowers.pushPrefEnv({ + set: [["accessibility.tabfocus", 7]], + }); + + let firstPageNavButton = mozPageNav.pageNavButtons[0]; + let secondPageNavButton = mozPageNav.pageNavButtons[1]; + let thirdPageNavButton = mozPageNav.pageNavButtons[2]; + let fourthPageNavButton = mozPageNav.pageNavButtons[3]; + let fifthPageNavButton = mozPageNav.pageNavButtons[4]; + + is( + firstPageNavButton.view, + mozPageNav.currentView, + "The first page nav button is selected" + ) + firstPageNavButton.buttonEl.focus(); + await arrowDown(); + ok( + isActiveElement(secondPageNavButton), + "The second page nav button is the active element after first arrow down" + ); + is( + secondPageNavButton.view, + mozPageNav.currentView, + "The second page nav button is selected" + ) + await arrowDown(); + is( + thirdPageNavButton.view, + mozPageNav.currentView, + "The third page nav button is selected" + ) + await arrowDown(); + is( + fourthPageNavButton.view, + mozPageNav.currentView, + "The fourth page nav button is selected" + ) + await arrowDown(); + is( + fifthPageNavButton.view, + mozPageNav.currentView, + "The fifth page nav button is selected" + ) + await arrowDown(); + is( + fifthPageNavButton.view, + mozPageNav.currentView, + "The fifth page nav button is still selected" + ) + await arrowUp(); + is( + fourthPageNavButton.view, + mozPageNav.currentView, + "The fourth page nav button is selected" + ) + await arrowUp(); + is( + thirdPageNavButton.view, + mozPageNav.currentView, + "The third page nav button is selected" + ) + await arrowUp(); + is( + secondPageNavButton.view, + mozPageNav.currentView, + "The second page nav button is selected" + ) + await arrowUp(); + is( + firstPageNavButton.view, + mozPageNav.currentView, + "The first page nav button is selected" + ) + await arrowUp(); + is( + firstPageNavButton.view, + mozPageNav.currentView, + "The first page nav button is still selected" + ) + + // Test navigation with arrow left/right keys + is( + firstPageNavButton.view, + mozPageNav.currentView, + "The first page nav button is selected" + ) + firstPageNavButton.buttonEl.focus(); + await arrowRight(); + ok( + isActiveElement(secondPageNavButton), + "The second page nav button is the active element after first arrow right" + ); + is( + secondPageNavButton.view, + mozPageNav.currentView, + "The second page nav button is selected" + ) + await arrowRight(); + is( + thirdPageNavButton.view, + mozPageNav.currentView, + "The third page nav button is selected" + ) + await arrowRight(); + is( + fourthPageNavButton.view, + mozPageNav.currentView, + "The fourth page nav button is selected" + ) + await arrowRight(); + is( + fifthPageNavButton.view, + mozPageNav.currentView, + "The fifth page nav button is selected" + ) + await arrowRight(); + is( + fifthPageNavButton.view, + mozPageNav.currentView, + "The fifth page nav button is still selected" + ) + await arrowLeft(); + is( + fourthPageNavButton.view, + mozPageNav.currentView, + "The fourth page nav button is selected" + ) + await arrowLeft(); + is( + thirdPageNavButton.view, + mozPageNav.currentView, + "The third page nav button is selected" + ) + await arrowLeft(); + is( + secondPageNavButton.view, + mozPageNav.currentView, + "The second page nav button is selected" + ) + await arrowLeft(); + is( + firstPageNavButton.view, + mozPageNav.currentView, + "The first page nav button is selected" + ) + await arrowLeft(); + is( + firstPageNavButton.view, + mozPageNav.currentView, + "The first page nav button is still selected" + ) + + await SpecialPowers.popPrefEnv(); + }); +</script> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols.html b/toolkit/content/tests/widgets/test_videocontrols.html index 32cd23df6a..076b4350fd 100644 --- a/toolkit/content/tests/widgets/test_videocontrols.html +++ b/toolkit/content/tests/widgets/test_videocontrols.html @@ -67,7 +67,7 @@ let expectingEventPromise; async function isMuteButtonMuted() { const muteButton = getElementWithinVideo(video, "muteButton"); await new Promise(SimpleTest.executeSoon); - return muteButton.getAttribute("muted") === "true"; + return muteButton.hasAttribute("muted"); } async function isVolumeSliderShowingCorrectVolume(expectedVolume) { diff --git a/toolkit/content/widgets/arrowscrollbox.js b/toolkit/content/widgets/arrowscrollbox.js index 28de96c8d7..7109891faf 100644 --- a/toolkit/content/widgets/arrowscrollbox.js +++ b/toolkit/content/widgets/arrowscrollbox.js @@ -21,7 +21,7 @@ return ` <html:link rel="stylesheet" href="chrome://global/skin/toolbarbutton.css"/> <html:link rel="stylesheet" href="chrome://global/skin/arrowscrollbox.css"/> - <toolbarbutton id="scrollbutton-up" part="scrollbutton-up" keyNav="false"/> + <toolbarbutton id="scrollbutton-up" part="scrollbutton-up" keyNav="false" data-l10n-id="overflow-scroll-button-up"/> <spacer part="overflow-start-indicator"/> <box class="scrollbox-clip" part="scrollbox-clip" flex="1"> <scrollbox part="scrollbox" flex="1"> @@ -29,7 +29,7 @@ </scrollbox> </box> <spacer part="overflow-end-indicator"/> - <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false"/> + <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false" data-l10n-id="overflow-scroll-button-down"/> `; } @@ -43,6 +43,8 @@ this._scrollButtonDown = this.shadowRoot.getElementById("scrollbutton-down"); + MozXULElement.insertFTLIfNeeded("toolkit/global/arrowscrollbox.ftl"); + this._arrowScrollAnim = { scrollbox: this, requestHandle: 0, @@ -134,6 +136,8 @@ } this.hasConnected = true; + document.l10n.connectRoot(this.shadowRoot); + if (!this.hasAttribute("smoothscroll")) { this.smoothScroll = Services.prefs.getBoolPref( "toolkit.scrollbox.smoothScroll", @@ -639,6 +643,7 @@ this._scrollTimer.cancel(); this._scrollTimer = null; } + document.l10n.disconnectRoot(this.shadowRoot); } on_wheel(event) { @@ -749,7 +754,7 @@ } } - on_touchend(event) { + on_touchend() { this._touchStart = -1; } @@ -804,12 +809,12 @@ this._updateScrollButtonsDisabledState(); } - on_scroll(event) { + on_scroll() { this._isScrolling = true; this._updateScrollButtonsDisabledState(); } - on_scrollend(event) { + on_scrollend() { this._isScrolling = false; this._destination = 0; this._direction = 0; diff --git a/toolkit/content/widgets/autocomplete-input.js b/toolkit/content/widgets/autocomplete-input.js index 36105ba4d7..a6fb4b5067 100644 --- a/toolkit/content/widgets/autocomplete-input.js +++ b/toolkit/content/widgets/autocomplete-input.js @@ -40,7 +40,7 @@ this.addEventListener( "compositionstart", - event => { + () => { if ( this.mController.input.wrappedJSObject == this.nsIAutocompleteInput ) { @@ -52,7 +52,7 @@ this.addEventListener( "compositionend", - event => { + () => { if ( this.mController.input.wrappedJSObject == this.nsIAutocompleteInput ) { @@ -64,7 +64,7 @@ this.addEventListener( "focus", - event => { + () => { this.attachController(); if ( window.gBrowser && @@ -82,7 +82,7 @@ this.addEventListener( "blur", - event => { + () => { if (!this._dontBlur) { if (this.forceComplete && this.mController.matchCount >= 1) { // If forceComplete is requested, we need to call the enter processing @@ -625,7 +625,7 @@ return value; } - onInput(aEvent) { + onInput() { if ( !this.mIgnoreInput && this.mController.input.wrappedJSObject == this.nsIAutocompleteInput diff --git a/toolkit/content/widgets/autocomplete-popup.js b/toolkit/content/widgets/autocomplete-popup.js index f033511e07..a13ba1bc62 100644 --- a/toolkit/content/widgets/autocomplete-popup.js +++ b/toolkit/content/widgets/autocomplete-popup.js @@ -572,7 +572,7 @@ } setListeners() { - this.addEventListener("popupshowing", event => { + this.addEventListener("popupshowing", () => { // If normalMaxRows wasn't already set by the input, then set it here // so that we restore the correct number when the popup is hidden. @@ -584,14 +584,14 @@ this.mPopupOpen = true; }); - this.addEventListener("popupshown", event => { + this.addEventListener("popupshown", () => { if (this._adjustHeightOnPopupShown) { this._adjustHeightOnPopupShown = false; this.adjustHeight(); } }); - this.addEventListener("popuphiding", event => { + this.addEventListener("popuphiding", () => { var isListActive = true; if (this.selectedIndex == -1) { isListActive = false; diff --git a/toolkit/content/widgets/autocomplete-richlistitem.js b/toolkit/content/widgets/autocomplete-richlistitem.js index ccbd37e132..fddd5b4029 100644 --- a/toolkit/content/widgets/autocomplete-richlistitem.js +++ b/toolkit/content/widgets/autocomplete-richlistitem.js @@ -21,7 +21,7 @@ * This overrides listitem's mousedown handler because we want to set the * selected item even when the shift or accel keys are pressed. */ - this.addEventListener("mousedown", event => { + this.addEventListener("mousedown", () => { // Call this.control only once since it's not a simple getter. let control = this.control; if (!control || control.disabled) { @@ -587,7 +587,7 @@ /** * Override _getSearchTokens to have the Learn More text emphasized */ - _getSearchTokens(aSearch) { + _getSearchTokens() { return [this._learnMoreString.toLowerCase()]; } } diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js index e9b29034fa..887f59c742 100644 --- a/toolkit/content/widgets/browser-custom-element.js +++ b/toolkit/content/widgets/browser-custom-element.js @@ -872,7 +872,7 @@ this.webProgress.removeProgressListener(aListener); } - onPageHide(aEvent) { + onPageHide() { // If we're browsing from the tab crashed UI to a URI that keeps // this browser non-remote, we'll handle that here. lazy.SessionStore?.maybeExitCrashedState(this); @@ -1919,7 +1919,7 @@ // Called immediately after changing remoteness. If this method returns // `true`, Gecko will assume frontend handled resuming the load, and will // not attempt to resume the load itself. - afterChangeRemoteness(browser, redirectLoadSwitchId) { + afterChangeRemoteness() { /* no-op unless replaced */ return false; } diff --git a/toolkit/content/widgets/datetimebox.js b/toolkit/content/widgets/datetimebox.js index 28b32fddfa..04ed398bd7 100644 --- a/toolkit/content/widgets/datetimebox.js +++ b/toolkit/content/widgets/datetimebox.js @@ -5,7 +5,7 @@ "use strict"; // This is a UA widget. It runs in per-origin UA widget scope, -// to be loaded by UAWidgetsChild.jsm. +// to be loaded by UAWidgetsChild.sys.mjs. /* * This is the class of entry. It will construct the actual implementation diff --git a/toolkit/content/widgets/dialog.js b/toolkit/content/widgets/dialog.js index 52eb2168f8..c4b25c2f48 100644 --- a/toolkit/content/widgets/dialog.js +++ b/toolkit/content/widgets/dialog.js @@ -146,7 +146,7 @@ if (document.readyState == "complete") { this._postLoadInit(); } else { - window.addEventListener("load", event => this._postLoadInit()); + window.addEventListener("load", () => this._postLoadInit()); } } @@ -521,7 +521,7 @@ } var btn = this.getButton(this.defaultButton); - if (btn) { + if (btn && !btn.hidden) { this._doButtonCommand(this.defaultButton); } } diff --git a/toolkit/content/widgets/editor.js b/toolkit/content/widgets/editor.js index 9e5ffb542e..8e014f77af 100644 --- a/toolkit/content/widgets/editor.js +++ b/toolkit/content/widgets/editor.js @@ -16,13 +16,13 @@ "nsIURIContentListener", "nsISupportsWeakReference", ]), - doContent(contentType, isContentPreferred, request, contentHandler) { + doContent() { return false; }, - isPreferred(contentType, desiredContentType) { + isPreferred() { return false; }, - canHandleContent(contentType, isContentPreferred, desiredContentType) { + canHandleContent() { return false; }, loadCookie: null, diff --git a/toolkit/content/widgets/findbar.js b/toolkit/content/widgets/findbar.js index 3cbce11771..cdfbd315a7 100644 --- a/toolkit/content/widgets/findbar.js +++ b/toolkit/content/widgets/findbar.js @@ -164,7 +164,7 @@ window.addEventListener("unload", this.destroy); - this._findField.addEventListener("input", event => { + this._findField.addEventListener("input", () => { // We should do nothing during composition. E.g., composing string // before converting may matches a forward word of expected word. // After that, even if user converts the composition string to the @@ -230,17 +230,17 @@ } }); - this._findField.addEventListener("blur", event => { + this._findField.addEventListener("blur", () => { // Note: This code used to remove the selection // if it matched an editable. this.browser.finder.enableSelection(); }); - this._findField.addEventListener("focus", event => { + this._findField.addEventListener("focus", () => { this._updateBrowserWithState(); }); - this._findField.addEventListener("compositionstart", event => { + this._findField.addEventListener("compositionstart", () => { // Don't close the find toolbar while IME is composing. let findbar = this; findbar._isIMEComposing = true; @@ -251,7 +251,7 @@ } }); - this._findField.addEventListener("compositionend", event => { + this._findField.addEventListener("compositionend", () => { this._isIMEComposing = false; if (this.findMode != this.FIND_NORMAL) { this._setFindCloseTimeout(); @@ -1307,7 +1307,7 @@ } } - onHighlightFinished(result) { + onHighlightFinished() { // Noop. } diff --git a/toolkit/content/widgets/menu.js b/toolkit/content/widgets/menu.js index f787747a01..1a55d799b6 100644 --- a/toolkit/content/widgets/menu.js +++ b/toolkit/content/widgets/menu.js @@ -129,7 +129,7 @@ }; // The <menucaption> element is used for rendering <html:optgroup> inside of <html:select>, - // See SelectParentHelper.jsm. + // See SelectParentHelper.sys.mjs. class MozMenuCaption extends MozMenuBaseMixin(MozXULElement) { static get inheritedAttributes() { return { diff --git a/toolkit/content/widgets/menulist.js b/toolkit/content/widgets/menulist.js index 4e66c030f3..3672d4ccf1 100644 --- a/toolkit/content/widgets/menulist.js +++ b/toolkit/content/widgets/menulist.js @@ -289,6 +289,9 @@ } setInitialSelection() { + if (this.getAttribute("noinitialselection") === "true") { + return; + } var popup = this.menupopup; if (popup) { var arr = popup.getElementsByAttribute("selected", "true"); diff --git a/toolkit/content/widgets/menupopup.js b/toolkit/content/widgets/menupopup.js index 31801d6a33..c7448d4f05 100644 --- a/toolkit/content/widgets/menupopup.js +++ b/toolkit/content/widgets/menupopup.js @@ -11,16 +11,12 @@ "resource://gre/modules/AppConstants.sys.mjs" ); - // For the non-native context menu styling, we need to know if we need - // a gutter for checkboxes. To do this, check whether there are any - // radio/checkbox type menuitems in a menupopup when showing it. We use a - // system bubbling event listener to ensure we run *after* the "normal" - // popupshowing listeners, so (visibility) changes they make to their items - // take effect first, before we check for checkable menuitems. - Services.els.addSystemEventListener( - document, + document.addEventListener( "popupshowing", function (e) { + // For the non-native context menu styling, we need to know if we need + // a gutter for checkboxes. To do this, check whether there are any + // radio/checkbox type menuitems in a menupopup when showing it. if (e.target.nodeName == "menupopup") { let haveCheckableChild = e.target.querySelector( `:scope > menuitem:not([hidden]):is([type=checkbox],[type=radio]${ @@ -33,7 +29,10 @@ e.target.toggleAttribute("needsgutter", haveCheckableChild); } }, - false + // we use a system bubbling event listener to ensure we run *after* the + // "normal" popupshowing listeners, so (visibility) changes they make to + // their items take effect first, before we check for checkable menuitems. + { mozSystemGroup: true } ); class MozMenuPopup extends MozElements.MozElementMixin(XULPopupElement) { @@ -74,13 +73,13 @@ initShadowDOM() { // Retarget events from shadow DOM arrowscrollbox to the host. - this.scrollBox.addEventListener("scroll", ev => + this.scrollBox.addEventListener("scroll", () => this.dispatchEvent(new Event("scroll")) ); - this.scrollBox.addEventListener("overflow", ev => + this.scrollBox.addEventListener("overflow", () => this.dispatchEvent(new Event("overflow")) ); - this.scrollBox.addEventListener("underflow", ev => + this.scrollBox.addEventListener("underflow", () => this.dispatchEvent(new Event("underflow")) ); } diff --git a/toolkit/content/widgets/moz-button-group/moz-button-group.mjs b/toolkit/content/widgets/moz-button-group/moz-button-group.mjs index 0706f94762..8bf553c23d 100644 --- a/toolkit/content/widgets/moz-button-group/moz-button-group.mjs +++ b/toolkit/content/widgets/moz-button-group/moz-button-group.mjs @@ -44,7 +44,7 @@ export default class MozButtonGroup extends MozLitElement { } } - onSlotchange(e) { + onSlotchange() { for (let child of this.defaultSlotEl.assignedNodes()) { if (!(child instanceof Element)) { // Text nodes won't support classList or getAttribute. diff --git a/toolkit/content/widgets/moz-button/moz-button.css b/toolkit/content/widgets/moz-button/moz-button.css new file mode 100644 index 0000000000..47567df41d --- /dev/null +++ b/toolkit/content/widgets/moz-button/moz-button.css @@ -0,0 +1,142 @@ +/* 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/. */ + +:host { + display: inline-block; +} + +button { + appearance: none; + min-height: var(--button-min-height); + color: var(--button-text-color); + border: var(--button-border); + border-radius: var(--button-border-radius); + background-color: var(--button-background-color); + padding: var(--button-padding); + /* HTML button gets `font: -moz-button` from UA styles, + * but we want it to match the root font styling. */ + font: inherit; + font-weight: var(--button-font-weight); + /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */ + font-size: var(--button-font-size); + width: 100%; + + &[size=small] { + min-height: var(--button-min-height-small); + font-size: var(--button-font-size-small); + } + + &:hover { + background-color: var(--button-background-color-hover); + border-color: var(--button-border-color-hover); + color: var(--button-text-color-hover); + } + + &:hover:active { + background-color: var(--button-background-color-active); + border-color: var(--button-border-color-active); + color: var(--button-text-color-active); + } + + &:disabled { + background-color: var(--button-background-color-disabled); + border-color: var(--button-border-color-disabled); + color: var(--button-text-color-disabled); + opacity: var(--button-opacity-disabled); + } + + &:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-offset); + } + + &[type="primary"] { + background-color: var(--button-background-color-primary); + border-color: var(--button-border-color-primary); + color: var(--button-text-color-primary); + + &:hover { + background-color: var(--button-background-color-primary-hover); + border-color: var(--button-border-color-primary-hover); + color: var(--button-text-color-primary-hover); + } + + &:hover:active { + background-color: var(--button-background-color-primary-active); + border-color: var(--button-border-color-primary-active); + color: var(--button-text-color-primary-active); + } + + &:disabled { + background-color: var(--button-background-color-primary-disabled); + border-color: var(--button-border-color-primary-disabled); + color: var(--button-text-color-primary-disabled); + } + } + + &[type="destructive"] { + background-color: var(--button-background-color-destructive); + border-color: var(--button-border-color-destructive); + color: var(--button-text-color-destructive); + + &:hover { + background-color: var(--button-background-color-destructive-hover); + border-color: var(--button-border-color-destructive-hover); + color: var(--button-text-color-destructive-hover); + } + + &:hover:active { + background-color: var(--button-background-color-destructive-active); + border-color: var(--button-border-color-destructive-active); + color: var(--button-text-color-destructive-active); + } + + &:disabled { + background-color: var(--button-background-color-destructive-disabled); + border-color: var(--button-border-color-destructive-disabled); + color: var(--button-text-color-destructive-disabled); + } + } + + &[type~=ghost] { + background-color: var(--button-background-color-ghost); + border-color: var(--button-border-color-ghost); + color: var(--button-text-color-ghost); + + &:hover { + background-color: var(--button-background-color-ghost-hover); + border-color: var(--button-border-color-ghost-hover); + color: var(--button-text-color-ghost-hover); + } + + &:hover:active { + background-color: var(--button-background-color-ghost-active); + border-color: var(--button-border-color-ghost-active); + color: var(--button-text-color-ghost-active); + } + + &:disabled { + background-color: var(--button-background-color-ghost-disabled); + border-color: var(--button-border-color-ghost-disabled); + color: var(--button-text-color-ghost-disabled); + } + } + + &[type~=icon] { + background-size: var(--icon-size-default); + background-position: center; + background-repeat: no-repeat; + -moz-context-properties: fill, stroke; + fill: currentColor; + stroke: currentColor; + width: var(--button-size-icon); + height: var(--button-size-icon); + padding: var(--button-padding-icon); + + &[size=small] { + width: var(--button-size-icon-small); + height: var(--button-size-icon-small); + } + } +} diff --git a/toolkit/content/widgets/moz-button/moz-button.mjs b/toolkit/content/widgets/moz-button/moz-button.mjs new file mode 100644 index 0000000000..3e7c151e61 --- /dev/null +++ b/toolkit/content/widgets/moz-button/moz-button.mjs @@ -0,0 +1,90 @@ +/* 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 { html, ifDefined } from "../vendor/lit.all.mjs"; +import { MozLitElement } from "../lit-utils.mjs"; + +/** + * A button with multiple types and two sizes. + * + * @tagname moz-button + * @property {string} label - The button's label, will be overridden by slotted content. + * @property {string} type - The button type. + * Options: default, primary, destructive, icon, icon ghost, ghost. + * @property {string} size - The button size. + * Options: default, small. + * @property {boolean} disabled - The disabled state. + * @property {string} title - The button's title attribute, used in shadow DOM and therefore not as an attribute on moz-button. + * @property {string} titleAttribute - Internal, map title attribute to the title JS property. + * @property {string} tooltipText - Set the title property, the title attribute will be used first. + * @property {string} ariaLabel - The button's arial-label attribute, used in shadow DOM and therefore not as an attribute on moz-button. + * @property {string} ariaLabelAttribute - Internal, map aria-label attribute to the ariaLabel JS property. + * @property {HTMLButtonElement} buttonEl - The internal button element in the shadow DOM. + * @slot default - The button's content, overrides label property. + * @fires click - The click event. + */ +export default class MozButton extends MozLitElement { + static shadowRootOptions = { + ...MozLitElement.shadowRootOptions, + delegatesFocus: true, + }; + + static properties = { + label: { type: String, reflect: true }, + type: { type: String, reflect: true }, + size: { type: String, reflect: true }, + disabled: { type: Boolean, reflect: true }, + title: { type: String, state: true }, + titleAttribute: { type: String, attribute: "title", reflect: true }, + tooltipText: { type: String }, + ariaLabelAttribute: { + type: String, + attribute: "aria-label", + reflect: true, + }, + ariaLabel: { type: String, state: true }, + }; + + static queries = { + buttonEl: "button", + }; + + constructor() { + super(); + this.type = "default"; + this.size = "default"; + this.disabled = false; + } + + willUpdate(changes) { + if (changes.has("titleAttribute")) { + this.title = this.titleAttribute; + this.titleAttribute = null; + } + if (changes.has("ariaLabelAttribute")) { + this.ariaLabel = this.ariaLabelAttribute; + this.ariaLabelAttribute = null; + } + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://global/content/elements/moz-button.css" + /> + <button + type=${this.type} + size=${this.size} + ?disabled=${this.disabled} + title=${ifDefined(this.title || this.tooltipText)} + aria-label=${ifDefined(this.ariaLabel)} + part="button" + > + <slot>${this.label}</slot> + </button> + `; + } +} +customElements.define("moz-button", MozButton); diff --git a/toolkit/content/widgets/moz-button/moz-button.stories.mjs b/toolkit/content/widgets/moz-button/moz-button.stories.mjs new file mode 100644 index 0000000000..52a459e807 --- /dev/null +++ b/toolkit/content/widgets/moz-button/moz-button.stories.mjs @@ -0,0 +1,100 @@ +/* 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 { html } from "../vendor/lit.all.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "./moz-button.mjs"; + +export default { + title: "UI Widgets/Moz Button", + component: "moz-button", + argTypes: { + l10nId: { + options: [ + "moz-button-labelled", + "moz-button-titled", + "moz-button-aria-labelled", + ], + control: { type: "select" }, + }, + size: { + options: ["default", "small"], + control: { type: "radio" }, + }, + }, + parameters: { + actions: { + handles: ["click"], + }, + status: "in-development", + fluent: ` +moz-button-labelled = Button +moz-button-primary = Primary +moz-button-destructive = Destructive +moz-button-titled = + .title = View logins +moz-button-aria-labelled = + .aria-label = View logins +`, + }, +}; + +const Template = ({ type, size, l10nId, iconUrl, disabled }) => html` + <style> + moz-button[type~="icon"]::part(button) { + background-image: url("${iconUrl}"); + } + </style> + <moz-button + data-l10n-id=${l10nId} + type=${type} + size=${size} + ?disabled=${disabled} + ></moz-button> +`; + +export const Default = Template.bind({}); +Default.args = { + type: "default", + size: "default", + l10nId: "moz-button-labelled", + iconUrl: "chrome://global/skin/icons/more.svg", + disabled: false, +}; +export const DefaultSmall = Template.bind({}); +DefaultSmall.args = { + type: "default", + size: "small", + l10nId: "moz-button-labelled", + iconUrl: "chrome://global/skin/icons/more.svg", + disabled: false, +}; +export const Primary = Template.bind({}); +Primary.args = { + ...Default.args, + type: "primary", + l10nId: "moz-button-primary", +}; +export const Destructive = Template.bind({}); +Destructive.args = { + ...Default.args, + type: "destructive", + l10nId: "moz-button-destructive", +}; +export const Icon = Template.bind({}); +Icon.args = { + ...Default.args, + type: "icon", + l10nId: "moz-button-titled", +}; +export const IconSmall = Template.bind({}); +IconSmall.args = { + ...Icon.args, + size: "small", +}; +export const IconGhost = Template.bind({}); +IconGhost.args = { + ...Icon.args, + type: "icon ghost", +}; diff --git a/toolkit/content/widgets/moz-input-box.js b/toolkit/content/widgets/moz-input-box.js index 4704db6dc5..6e7b7b3f29 100644 --- a/toolkit/content/widgets/moz-input-box.js +++ b/toolkit/content/widgets/moz-input-box.js @@ -92,7 +92,7 @@ }); if (this.spellcheck) { - this.menupopup.addEventListener("popuphiding", event => { + this.menupopup.addEventListener("popuphiding", () => { if (this.spellCheckerUI) { this.spellCheckerUI.clearSuggestionsFromMenu(); this.spellCheckerUI.clearDictionaryListFromMenu(); diff --git a/toolkit/content/widgets/moz-label/README.stories.md b/toolkit/content/widgets/moz-label/README.stories.md index a3492ebefa..f5e4e2dd14 100644 --- a/toolkit/content/widgets/moz-label/README.stories.md +++ b/toolkit/content/widgets/moz-label/README.stories.md @@ -3,10 +3,10 @@ `moz-label` is an extension of the built-in `HTMLLabelElement` that provides accesskey styling and formatting as well as some click handling logic. ```html story -<label is="moz-label" accesskey="c" for="check"> +<label is="moz-label" accesskey="c" for="check" style={{ display: "inline-block" }}> This is a label with an accesskey: </label> -<input id="check" type="checkbox" defaultChecked /> +<input id="check" type="checkbox" defaultChecked style={{ display: "inline-block" }} /> ``` Accesskey underlining is enabled by default on Windows and Linux. It is also enabled in Storybook on Mac for demonstrative purposes, but is usually controlled by the `ui.key.menuAccessKey` preference. diff --git a/toolkit/content/widgets/moz-label/moz-label.mjs b/toolkit/content/widgets/moz-label/moz-label.mjs index 7812436ecd..cd9144e7cc 100644 --- a/toolkit/content/widgets/moz-label/moz-label.mjs +++ b/toolkit/content/widgets/moz-label/moz-label.mjs @@ -103,7 +103,7 @@ class MozTextLabel extends HTMLLabelElement { this.formatAccessKey(); } - _onClick(event) { + _onClick() { let controlElement = this.labeledControlElement; if (!controlElement || this.disabled) { return; diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs index 58f41c28e4..d83de5d29f 100644 --- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs +++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs @@ -69,7 +69,7 @@ export default class MozMessageBar extends MozLitElement { this.dismissable = false; } - onSlotchange(e) { + onSlotchange() { let actions = this.actionsSlotEl.assignedNodes(); this.actionsEl.classList.toggle("active", actions.length); } diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs b/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs index 65803eed9f..6f19c45aee 100644 --- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs +++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs @@ -121,3 +121,9 @@ WithSupportLink.args = { hasSupportLink: true, hasActionButton: false, }; + +export const WithHeading = Template.bind({}); +WithHeading.args = { + ...Default.args, + l10nId: "moz-message-bar-message-heading", +}; diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css new file mode 100644 index 0000000000..2975bb1a7c --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css @@ -0,0 +1,123 @@ +/* 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/. */ + +:host { + border-radius: var(--border-radius-small); + &:focus-visible { + outline-offset: var(--page-nav-focus-outline-inset); + } +} + +button { + background-color: var(--page-nav-button-background-color); + border: 1px solid var(--page-nav-border-color); + -moz-context-properties: fill, fill-opacity; + fill: currentColor; + display: grid; + grid-template-columns: min-content 1fr; + gap: 12px; + align-items: center; + font-size: inherit; + width: 100%; + font-weight: normal; + border-radius: var(--page-nav-button-border-radius); + color: var(--page-nav-button-text-color); + text-align: start; + transition: background-color 150ms; + padding: var(--page-nav-button-padding); +} + +button:hover { + cursor: pointer; +} + +@media not (prefers-contrast) { + button { + border-inline-start: 2px solid transparent; + border-inline-end: none; + border-block: none; + } + + button:hover, + button[selected]:hover { + background-color: var(--page-nav-button-background-color-hover); + } + + button[selected]:hover { + border-inline-start-color: inherit; + } + + button[selected], + button[selected]:hover { + border-inline-start: 2px solid; + } + + button[selected]:not(:focus-visible) { + border-start-start-radius: 0; + border-end-start-radius: 0; + } + + button[selected]:not(:hover) { + color: var(--color-accent-primary); + background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent); + border-inline-start-color: var(--color-accent-primary); + } +} + +@media (prefers-color-scheme: dark) { + button[selected] { + background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 12%, transparent); + } +} + +button:focus-visible, +button[selected]:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-offset); + border-radius: var(--border-radius-small); +} + +.page-nav-icon { + height: 20px; + width: 20px; + -moz-context-properties: fill; + fill: currentColor; +} + +@media (prefers-contrast) { + button { + transition: none; + border-color: ButtonText; + background-color: var(--button-background-color); + } + + button:hover { + color: SelectedItem; + } + + button[selected] { + color: SelectedItemText; + background-color: SelectedItem; + border-color: SelectedItem; + } +} + +slot { + font-size: var(--font-size-large); + margin: 0; + padding-inline-start: 0; + user-select: none; +} + +@media (max-width: 52rem) { + button { + grid-template-columns: min-content; + justify-content: center; + margin-inline: 0; + } + + slot { + display: none; + } +} diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css new file mode 100644 index 0000000000..49000f622d --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css @@ -0,0 +1,76 @@ +/* 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/. */ + +:host { + --page-nav-button-border-radius: var(--button-border-radius); + --page-nav-button-text-color: var(--button-text-color); + --page-nav-button-background-color: transparent; + --page-nav-button-background-color-hover: var(--button-background-color-hover); + --page-nav-button-background-color-selected: var(--color-accent-primary); + --page-nav-button-padding: var(--space-small); + --page-nav-margin-top: 72px; + --page-nav-margin-bottom: 36px; + --page-nav-gap: 25px; + --page-nav-button-gap: var(--space-xsmall); + --page-nav-border-color: var(--border-color); + --page-nav-focus-outline-inset: var(--focus-outline-inset); + --page-nav-width: 240px; + margin-inline-start: 42px; + position: sticky; + top: 0; + height: 100vh; + width: var(--page-nav-width); + + @media (prefers-reduced-motion) { + /* (See Bug 1610081) Setting border-inline-end to add clear differentiation between side navigation and main content area */ + border-inline-end: 1px solid var(--page-nav-border-color); + } + + @media (max-width: 52rem) { + grid-template-rows: 1fr auto; + --page-nav-width: 118px; + } +} + +nav { + display: grid; + grid-template-rows: min-content 1fr auto; + gap: var(--page-nav-gap); + margin-block: var(--page-nav-margin-top) var(--page-nav-margin-bottom); + height: calc(100vh - var(--page-nav-margin-top) - var(--page-nav-margin-bottom)); + @media (max-width: 52rem) { + grid-template-rows: 1fr auto; + } +} + +.page-nav-header { + /* Align the header text/icon with the page nav button icons */ + margin-inline-start: var(--page-nav-button-padding); + font-size: var(--font-size-xlarge); + font-weight: var(--font-weight-bold); + margin-block: 0; + + @media (max-width: 52rem) { + display: none; + } +} + +.primary-nav-group, +#secondary-nav-group { + display: grid; + grid-template-columns: 1fr; + grid-auto-rows: min-content; + gap: var(--page-nav-button-gap); + + @media (max-width: 52rem) { + justify-content: center; + grid-template-columns: min-content; + } +} + +@media (prefers-contrast) { + .primary-nav-group { + gap: var(--space-small); + } +} diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs new file mode 100644 index 0000000000..f998ee735f --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs @@ -0,0 +1,170 @@ +/* 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 { html } from "chrome://global/content/vendor/lit.all.mjs"; +import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; + +/** + * A grouping of navigation buttons that is displayed at the page level, + * intended to change the selected view, provide a heading, and have links + * to external resources. + * + * @tagname moz-page-nav + * @property {string} currentView - The currently selected view. + * @property {string} heading - A heading to be displayed at the top of the navigation. + * @slot [default] - Used to append moz-page-nav-button elements to the navigation. + */ +export default class MozPageNav extends MozLitElement { + static properties = { + currentView: { type: String }, + heading: { type: String }, + }; + + static queries = { + headingEl: "#page-nav-header", + primaryNavGroupSlot: ".primary-nav-group slot", + secondaryNavGroupSlot: "#secondary-nav-group slot", + }; + + get pageNavButtons() { + return this.primaryNavGroupSlot + .assignedNodes() + .filter( + node => node?.localName === "moz-page-nav-button" && !node.hidden + ); + } + + onChangeView(e) { + this.currentView = e.target.view; + } + + handleFocus(e) { + if (e.key == "ArrowDown" || e.key == "ArrowRight") { + e.preventDefault(); + this.focusNextView(); + } else if (e.key == "ArrowUp" || e.key == "ArrowLeft") { + e.preventDefault(); + this.focusPreviousView(); + } + } + + focusPreviousView() { + let pageNavButtons = this.pageNavButtons; + let currentIndex = pageNavButtons.findIndex(b => b.selected); + let prev = pageNavButtons[currentIndex - 1]; + if (prev) { + prev.activate(); + prev.buttonEl.focus(); + } + } + + focusNextView() { + let pageNavButtons = this.pageNavButtons; + let currentIndex = pageNavButtons.findIndex(b => b.selected); + let next = pageNavButtons[currentIndex + 1]; + if (next) { + next.activate(); + next.buttonEl.focus(); + } + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://global/content/elements/moz-page-nav.css" + /> + <nav> + <h1 class="page-nav-header" id="page-nav-header">${this.heading}</h1> + <div + class="primary-nav-group" + role="tablist" + aria-orientation="vertical" + aria-labelledby="page-nav-header" + > + <slot + @change-view=${this.onChangeView} + @keydown=${this.handleFocus} + ></slot> + </div> + <div id="secondary-nav-group" role="group"> + <slot name="secondary-nav" @keydown=${this.handleFocus}></slot> + </div> + </nav> + `; + } + + updated() { + let isViewSelected = false; + let assignedPageNavButtons = this.pageNavButtons; + for (let button of assignedPageNavButtons) { + button.selected = button.view == this.currentView; + isViewSelected = isViewSelected || button.selected; + } + if (!isViewSelected && assignedPageNavButtons.length) { + // Current page nav has no matching view, reset to the first view. + assignedPageNavButtons[0].activate(); + } + } +} +customElements.define("moz-page-nav", MozPageNav); + +/** + * A navigation button intended to change the selected view within a page. + * + * @tagname moz-page-nav-button + * @property {string} iconSrc - The chrome:// url for the icon used for the button. + * @property {string} l10nId - The fluent ID for the button's text + * @property {boolean} selected - Whether or not the button is currently selected. + * @slot [default] - Used to append the l10n string to the button. + */ +export class MozPageNavButton extends MozLitElement { + static properties = { + iconSrc: { type: String }, + l10nId: { type: String }, + selected: { type: Boolean }, + }; + + connectedCallback() { + super.connectedCallback(); + this.setAttribute("role", "none"); + } + + static queries = { + buttonEl: "button", + }; + + get view() { + return this.getAttribute("view"); + } + + activate() { + this.dispatchEvent( + new CustomEvent("change-view", { + bubbles: true, + composed: true, + }) + ); + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://global/content/elements/moz-page-nav-button.css" + /> + <button + aria-selected=${this.selected} + tabindex=${this.selected ? 0 : -1} + role="tab" + ?selected=${this.selected} + @click=${this.activate} + > + <img class="page-nav-icon" src=${this.iconSrc} /> + <slot></slot> + </button> + `; + } +} +customElements.define("moz-page-nav-button", MozPageNavButton); diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs new file mode 100644 index 0000000000..4ac7b455cf --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs @@ -0,0 +1,77 @@ +/* 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 { html } from "../vendor/lit.all.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "./moz-page-nav.mjs"; + +export default { + title: "UI Widgets/Page Nav", + component: "moz-page-nav", + parameters: { + status: "in-development", + actions: { + handles: ["change-view"], + }, + fluent: ` +moz-page-nav-button-one = View 1 + .title = View 1 +moz-page-nav-button-two = View 2 + .title = View 2 +moz-page-nav-button-three = View 3 + .title = View 3 +moz-page-link-one = Support Page + .title = Support Page +moz-page-nav-heading = + .heading = Heading + `, + }, +}; + +const Template = () => html` + <style> + #page { + height: 100%; + display: flex; + + @media (max-width: 52rem) { + grid-template-columns: 82px 1fr; + } + } + moz-page-nav { + margin-inline-start: 10px; + --page-nav-margin-top: 10px; + + @media (max-width: 52rem) { + margin-inline-start: 0; + } + } + </style> + <div id="page"> + <moz-page-nav data-l10n-id="moz-page-nav-heading" data-l10n-attrs="heading"> + <moz-page-nav-button + view="view-one" + data-l10n-id="moz-page-nav-button-one" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + > + </moz-page-nav-button> + <moz-page-nav-button + view="view-two" + data-l10n-id="moz-page-nav-button-two" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + > + </moz-page-nav-button> + <moz-page-nav-button + view="view-three" + data-l10n-id="moz-page-nav-button-three" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + > + </moz-page-nav-button> + </moz-page-nav> + <main></main> + </div> +`; + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/toolkit/content/widgets/moz-support-link/moz-support-link.mjs b/toolkit/content/widgets/moz-support-link/moz-support-link.mjs index 23f18ac434..9d2d6ffac2 100644 --- a/toolkit/content/widgets/moz-support-link/moz-support-link.mjs +++ b/toolkit/content/widgets/moz-support-link/moz-support-link.mjs @@ -82,7 +82,7 @@ export default class MozSupportLink extends HTMLAnchorElement { } } - attributeChangedCallback(attrName, oldVal, newVal) { + attributeChangedCallback(attrName) { if (attrName === "support-page" || attrName === "utm-content") { this.#setHref(); } diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.css b/toolkit/content/widgets/moz-toggle/moz-toggle.css index 8b67a81878..2005544181 100644 --- a/toolkit/content/widgets/moz-toggle/moz-toggle.css +++ b/toolkit/content/widgets/moz-toggle/moz-toggle.css @@ -38,11 +38,15 @@ --toggle-background-color-pressed-hover: var(--color-accent-primary-hover); --toggle-background-color-pressed-active: var(--color-accent-primary-active); --toggle-border-color: var(--border-interactive-color); + --toggle-border-color-hover: var(--toggle-border-color); + --toggle-border-color-active: var(--toggle-border-color); --toggle-border-radius: var(--border-radius-circle); --toggle-border-width: var(--border-width); --toggle-height: var(--size-item-small); --toggle-width: var(--size-item-large); --toggle-dot-background-color: var(--toggle-border-color); + --toggle-dot-background-color-hover: var(--toggle-dot-background-color); + --toggle-dot-background-color-active: var(--toggle-dot-background-color); --toggle-dot-background-color-on-pressed: var(--color-canvas); --toggle-dot-margin: 1px; --toggle-dot-height: calc(var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * var(--toggle-border-width)); @@ -141,17 +145,6 @@ background-color: var(--toggle-background-color-disabled); } - .toggle-button { - --toggle-dot-background-color: var(--color-accent-primary); - --toggle-dot-background-color-hover: var(--color-accent-primary-hover); - --toggle-dot-background-color-active: var(--color-accent-primary-active); - --toggle-dot-background-color-on-pressed: var(--button-background-color); - --toggle-background-color-disabled: var(--button-background-color-disabled); - --toggle-border-color-hover: var(--border-interactive-color-hover); - --toggle-border-color-active: var(--border-interactive-color-active); - --toggle-border-color-disabled: var(--border-interactive-color-disabled); - } - .toggle-button:enabled:hover { border-color: var(--toggle-border-color-hover); } @@ -175,6 +168,24 @@ border-color: var(--toggle-dot-background-color-hover); } + .toggle-button:hover::before, + .toggle-button:active::before { + background-color: var(--toggle-dot-background-color-hover); + } +} + +@media (forced-colors) { + .toggle-button { + --toggle-dot-background-color: var(--color-accent-primary); + --toggle-dot-background-color-hover: var(--color-accent-primary-hover); + --toggle-dot-background-color-active: var(--color-accent-primary-active); + --toggle-dot-background-color-on-pressed: var(--button-background-color); + --toggle-background-color-disabled: var(--button-background-color-disabled); + --toggle-border-color-hover: var(--border-interactive-color-hover); + --toggle-border-color-active: var(--border-interactive-color-active); + --toggle-border-color-disabled: var(--border-interactive-color-disabled); + } + .toggle-button[aria-pressed="true"]:enabled::after { border: 1px solid var(--button-background-color); content: ''; @@ -189,10 +200,4 @@ .toggle-button[aria-pressed="true"]:enabled:active::after { border-color: var(--toggle-border-color-active); } - - .toggle-button:hover::before, - .toggle-button:hover:active::before, - .toggle-button:active::before { - background-color: var(--toggle-dot-background-color-hover); - } } diff --git a/toolkit/content/widgets/named-deck.js b/toolkit/content/widgets/named-deck.js index 6b3b7a8835..42c96e278d 100644 --- a/toolkit/content/widgets/named-deck.js +++ b/toolkit/content/widgets/named-deck.js @@ -156,7 +156,7 @@ this.getRootNode().removeEventListener("keypress", this); } - attributeChangedCallback(name, oldVal, newVal) { + attributeChangedCallback(name) { if (name == "orientation") { if (this.isVertical) { this.setAttribute("aria-orientation", this.orientation); diff --git a/toolkit/content/widgets/notificationbox.js b/toolkit/content/widgets/notificationbox.js index f23fb03a74..0161588853 100644 --- a/toolkit/content/widgets/notificationbox.js +++ b/toolkit/content/widgets/notificationbox.js @@ -622,7 +622,7 @@ async function createNotificationMessageElement() { await window.ensureCustomElements("moz-message-bar"); - let MozMessageBar = customElements.get("moz-message-bar"); + let MozMessageBar = await customElements.whenDefined("moz-message-bar"); class NotificationMessage extends MozMessageBar { static queries = { ...MozMessageBar.queries, diff --git a/toolkit/content/widgets/panel-list/README.stories.md b/toolkit/content/widgets/panel-list/README.stories.md index b8800e2b5f..3e8617958e 100644 --- a/toolkit/content/widgets/panel-list/README.stories.md +++ b/toolkit/content/widgets/panel-list/README.stories.md @@ -6,7 +6,7 @@ children and optional `hr` elements as separators. The `panel-list` will anchor itself to the target of the initiating event when opened with `panelList.toggle(event)`. -Note: Nested menus are not currently supported. XUL is currently required to +Note: XUL is currently required to support accesskey underlining (although using `moz-label` could change that). Shortcuts are not displayed automatically in the `panel-item`. @@ -229,3 +229,23 @@ grow larger than its containing window if needed. </html:panel-list> </panel> ``` + +### Submenus + +`panel-list` supports nested submenus. Submenus can be created by nesting a second `panel-list` in a `panel-item`'s `submenu` slot and specifying a `submenu` attribute on that `panel-item` that points to the nested list's ID. For example: + +```html +<panel-list> + <panel-item>No submenu</panel-item> + <panel-item>No submenu</panel-item> + <panel-item submenu="example-submenu"> + Has a submenu + <panel-list slot="submenu" id="example-submenu"> + <panel-item>I'm a submenu item!</panel-item> + <panel-item>I'm also a submenu item!</panel-item> + </panel-list> + </panel-item> +</panel-list> +``` + +As of February 2024 submenus are only in use in Firefox View and support for nesting beyond one submenu may be limited. diff --git a/toolkit/content/widgets/panel-list/panel-list.css b/toolkit/content/widgets/panel-list/panel-list.css index 4358fc0cf8..619e6919a3 100644 --- a/toolkit/content/widgets/panel-list/panel-list.css +++ b/toolkit/content/widgets/panel-list/panel-list.css @@ -26,6 +26,10 @@ box-sizing: border-box; } +:host([has-submenu]) { + overflow-y: visible; +} + :host(:not([slot=submenu])) { max-height: 100%; } diff --git a/toolkit/content/widgets/panel-list/panel-list.js b/toolkit/content/widgets/panel-list/panel-list.js index 1cc1f865c3..2e93b4ddc3 100644 --- a/toolkit/content/widgets/panel-list/panel-list.js +++ b/toolkit/content/widgets/panel-list/panel-list.js @@ -308,7 +308,7 @@ } addHideListeners() { - if (this.hasAttribute("stay-open") && !this.lastAnchorNode.hasSubmenu) { + if (this.hasAttribute("stay-open") && !this.lastAnchorNode?.hasSubmenu) { // This is intended for inspection in Storybook. return; } @@ -631,31 +631,12 @@ this.#defaultSlot = document.createElement("slot"); this.#defaultSlot.style.display = "none"; - if (this.hasSubmenu) { - this.icon = document.createElement("div"); - this.icon.setAttribute("class", "submenu-icon"); - this.label.setAttribute("class", "submenu-label"); - - this.button.setAttribute("class", "submenu-container"); - this.button.appendChild(this.icon); - - this.submenuSlot = document.createElement("slot"); - this.submenuSlot.name = "submenu"; - - this.shadowRoot.append( - style, - this.button, - this.#defaultSlot, - this.submenuSlot - ); - } else { - this.shadowRoot.append( - style, - this.button, - supportLinkSlot, - this.#defaultSlot - ); - } + this.shadowRoot.append( + style, + this.button, + supportLinkSlot, + this.#defaultSlot + ); } connectedCallback() { @@ -664,6 +645,10 @@ this._l10nRootConnected = true; } + this.panel = + this.getRootNode()?.host?.closest("panel-list") || + this.closest("panel-list"); + if (!this.#initialized) { this.#initialized = true; // When click listeners are added to the panel-item it creates a node in @@ -683,18 +668,28 @@ }); if (this.hasSubmenu) { + this.panel.setAttribute("has-submenu", ""); + this.icon = document.createElement("div"); + this.icon.setAttribute("class", "submenu-icon"); + this.label.setAttribute("class", "submenu-label"); + + this.button.setAttribute("class", "submenu-container"); + this.button.appendChild(this.icon); + + this.submenuSlot = document.createElement("slot"); + this.submenuSlot.name = "submenu"; + + this.shadowRoot.append(this.submenuSlot); + this.setSubmenuContents(); } } - this.panel = - this.getRootNode()?.host?.closest("panel-list") || - this.closest("panel-list"); - if (this.panel) { this.panel.addEventListener("hidden", this); this.panel.addEventListener("shown", this); } + if (this.hasSubmenu) { this.addEventListener("mouseenter", this); this.addEventListener("mouseleave", this); @@ -762,7 +757,9 @@ setSubmenuContents() { this.submenuPanel = this.submenuSlot.assignedNodes()[0]; - this.shadowRoot.append(this.submenuPanel); + if (this.submenuPanel) { + this.shadowRoot.append(this.submenuPanel); + } } get disabled() { diff --git a/toolkit/content/widgets/panel-list/panel-list.stories.mjs b/toolkit/content/widgets/panel-list/panel-list.stories.mjs index 9c5a4cbe1f..db0ab7597c 100644 --- a/toolkit/content/widgets/panel-list/panel-list.stories.mjs +++ b/toolkit/content/widgets/panel-list/panel-list.stories.mjs @@ -22,6 +22,9 @@ panel-list-checked = Checked panel-list-badged = Badged, look at me panel-list-passwords = Passwords panel-list-settings = Settings +submenu-item-one = Submenu Item One +submenu-item-two = Submenu Item Two +submenu-item-three = Submenu Item Three `, }, }; @@ -36,7 +39,7 @@ function openMenu(event) { } } -const Template = ({ isOpen, items, wideAnchor }) => +const Template = ({ isOpen, items, wideAnchor, hasSubMenu }) => html` <style> panel-item[icon="passwords"]::part(button) { @@ -93,22 +96,36 @@ const Template = ({ isOpen, items, wideAnchor }) => ?open=${isOpen} ?min-width-from-anchor=${wideAnchor} > - ${items.map(i => - i == "<hr>" + ${items.map((item, index) => { + // Always showing submenu on the first item for simplicity. + let showSubMenu = hasSubMenu && index == 0; + let subMenuId = showSubMenu ? "example-sub-menu" : undefined; + return item == "<hr>" ? html` <hr /> ` : html` <panel-item - icon=${i.icon ?? ""} - ?checked=${i.checked} - ?badged=${i.badged} - accesskey=${ifDefined(i.accesskey)} - data-l10n-id=${i.l10nId ?? i} - ></panel-item> - ` - )} + icon=${item.icon ?? ""} + ?checked=${item.checked} + ?badged=${item.badged} + accesskey=${ifDefined(item.accesskey)} + data-l10n-id=${item.l10nId ?? item} + submenu=${ifDefined(subMenuId)} + > + ${showSubMenu ? subMenuTemplate() : ""} + </panel-item> + `; + })} </panel-list> `; +const subMenuTemplate = () => html` + <panel-list slot="submenu" id="example-sub-menu"> + <panel-item data-l10n-id="submenu-item-one"></panel-item> + <panel-item data-l10n-id="submenu-item-two"></panel-item> + <panel-item data-l10n-id="submenu-item-three"></panel-item> + </panel-list> +`; + export const Simple = Template.bind({}); Simple.args = { isOpen: false, @@ -145,3 +162,9 @@ Wide.args = { ...Simple.args, wideAnchor: true, }; + +export const SubMenu = Template.bind({}); +SubMenu.args = { + ...Simple.args, + hasSubMenu: true, +}; diff --git a/toolkit/content/widgets/radio.js b/toolkit/content/widgets/radio.js index 482323acb9..41e8a945ba 100644 --- a/toolkit/content/widgets/radio.js +++ b/toolkit/content/widgets/radio.js @@ -197,7 +197,7 @@ * @param {DOMNode} child * The <radio> element that got removed */ - radioUnattached(child) { + radioUnattached() { // Just invalidate the cache, next time it's fetched it'll get rebuilt. this._radioChildren = null; } @@ -481,13 +481,13 @@ constructor() { super(); - this.addEventListener("click", event => { + this.addEventListener("click", () => { if (!this.disabled) { this.control.selectedItem = this; } }); - this.addEventListener("mousedown", event => { + this.addEventListener("mousedown", () => { if (!this.disabled) { this.control.focusedItem = this; } diff --git a/toolkit/content/widgets/richlistbox.js b/toolkit/content/widgets/richlistbox.js index 904ef9ceec..01d970e6ed 100644 --- a/toolkit/content/widgets/richlistbox.js +++ b/toolkit/content/widgets/richlistbox.js @@ -126,7 +126,7 @@ } }); - this.addEventListener("focus", event => { + this.addEventListener("focus", () => { if (this.getRowCount() > 0) { if (this.currentIndex == -1) { this.currentIndex = this.getIndexOfFirstVisibleRow(); diff --git a/toolkit/content/widgets/search-textbox.js b/toolkit/content/widgets/search-textbox.js index abdcfa2999..b254b2796d 100644 --- a/toolkit/content/widgets/search-textbox.js +++ b/toolkit/content/widgets/search-textbox.js @@ -214,7 +214,7 @@ } } - on_mousedown(event) { + on_mousedown() { if (!this.hasAttribute("focused")) { this.setSelectionRange(0, 0); this.focus(); diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js index 997e8413f2..b1b2ddecce 100644 --- a/toolkit/content/widgets/tabbox.js +++ b/toolkit/content/widgets/tabbox.js @@ -24,15 +24,15 @@ } connectedCallback() { - Services.els.addSystemEventListener(document, "keydown", this, false); + document.addEventListener("keydown", this, { mozSystemGroup: true }); window.addEventListener("unload", this.disconnectedCallback, { once: true, }); } disconnectedCallback() { + document.removeEventListener("keydown", this, { mozSystemGroup: true }); window.removeEventListener("unload", this.disconnectedCallback); - Services.els.removeSystemEventListener(document, "keydown", this, false); } set handleCtrlTab(val) { @@ -729,7 +729,7 @@ direction = 1, wrap = false, startWithAdjacent = true, - filter = tab => true, + filter = () => true, } = opts; let tab = startTab; @@ -804,7 +804,7 @@ } } - _canAdvanceToTab(aTab) { + _canAdvanceToTab() { return true; } diff --git a/toolkit/content/widgets/text.js b/toolkit/content/widgets/text.js index ca10f1489e..7bbf6db4cc 100644 --- a/toolkit/content/widgets/text.js +++ b/toolkit/content/widgets/text.js @@ -67,7 +67,7 @@ this.formatAccessKey(); } - _onClick(event) { + _onClick() { let controlElement = this.labeledControlElement; if (!controlElement || this.disabled) { return; diff --git a/toolkit/content/widgets/textrecognition.js b/toolkit/content/widgets/textrecognition.js index 887d576770..c517f7bfb1 100644 --- a/toolkit/content/widgets/textrecognition.js +++ b/toolkit/content/widgets/textrecognition.js @@ -4,7 +4,7 @@ "use strict"; // This is a UA widget. It runs in per-origin UA widget scope, -// to be loaded by UAWidgetsChild.jsm. +// to be loaded by UAWidgetsChild.sys.mjs. this.TextRecognitionWidget = class { /** diff --git a/toolkit/content/widgets/tree.js b/toolkit/content/widgets/tree.js index 322e42586e..4993bef0c2 100644 --- a/toolkit/content/widgets/tree.js +++ b/toolkit/content/widgets/tree.js @@ -515,7 +515,7 @@ } } - _onDragMouseUp(aEvent) { + _onDragMouseUp() { var col = document.treecolDragging; if (!col) { return; @@ -786,7 +786,7 @@ } }); - this.addEventListener("touchend", event => { + this.addEventListener("touchend", () => { this._touchY = -1; }); @@ -840,7 +840,7 @@ } }); - this.addEventListener("focus", event => { + this.addEventListener("focus", () => { this.focused = true; if (this.currentIndex == -1 && this.view.rowCount > 0) { this.currentIndex = this.getFirstVisibleRow(); @@ -1651,7 +1651,7 @@ this.ensureRowIsVisible(edge); } - _handleEnter(event) { + _handleEnter() { if (this._editingColumn) { this.stopEditing(true); this.focus(); diff --git a/toolkit/content/widgets/videocontrols.js b/toolkit/content/widgets/videocontrols.js index 21c8946e60..73a32164aa 100644 --- a/toolkit/content/widgets/videocontrols.js +++ b/toolkit/content/widgets/videocontrols.js @@ -5,7 +5,7 @@ "use strict"; // This is a UA widget. It runs in per-origin UA widget scope, -// to be loaded by UAWidgetsChild.jsm. +// to be loaded by UAWidgetsChild.sys.mjs. /* * This is the class of entry. It will construct the actual implementation @@ -64,11 +64,8 @@ this.VideoControlsWidget = class { // the underlying element state hasn't changed in ways that we // care about. This can happen if the property is set again // without a value change. - if ( - this.impl && - this.impl.constructor == newImpl && - this.impl.elementStateMatches(this.element) - ) { + if (this.impl && this.impl.constructor == newImpl) { + this.impl.onchange(); return; } if (this.impl) { @@ -458,10 +455,10 @@ this.VideoControlsImplWidget = class { this.statusIcon.setAttribute("type", "error"); this.updateErrorText(); this.setupStatusFader(true); - } else if (VideoControlsWidget.isPictureInPictureVideo(this.video)) { - this.setShowPictureInPictureMessage(true); } + this.updatePictureInPictureMessage(); + if (this.video.readyState >= this.video.HAVE_METADATA) { // According to the spec[1], at the HAVE_METADATA (or later) state, we know // the video duration and dimensions, which means we can calculate whether or @@ -934,6 +931,8 @@ this.VideoControlsImplWidget = class { // Since this event come from the layout, this is the only place // we are sure of that probing into layout won't trigger or force // reflow. + // FIXME(emilio): We should rewrite this to just use + // ResizeObserver, probably. this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = true; this.updateReflowedDimensions(); this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = false; @@ -1095,7 +1094,10 @@ this.VideoControlsImplWidget = class { ); }, - setShowPictureInPictureMessage(showMessage) { + updatePictureInPictureMessage() { + let showMessage = + !this.hasError() && + VideoControlsWidget.isPictureInPictureVideo(this.video); this.pictureInPictureOverlay.hidden = !showMessage; this.isShowingPictureInPictureMessage = showMessage; }, @@ -1188,7 +1190,7 @@ this.VideoControlsImplWidget = class { } }, - onScrubberInput(e) { + onScrubberInput() { const duration = Math.round(this.video.duration * 1000); // in ms let time = this.scrubber.value; @@ -1200,7 +1202,7 @@ this.VideoControlsImplWidget = class { this.pauseVideoDuringDragging(); }, - onScrubberChange(e) { + onScrubberChange() { this.scrubber.isDragging = false; if (this.isPausedByDragging) { @@ -1815,12 +1817,7 @@ this.VideoControlsImplWidget = class { updateMuteButtonState() { var muted = this.isEffectivelyMuted; - - if (muted) { - this.muteButton.setAttribute("muted", "true"); - } else { - this.muteButton.removeAttribute("muted"); - } + this.muteButton.toggleAttribute("muted", muted); var id = muted ? "videocontrols-unmute-button" @@ -2026,12 +2023,7 @@ this.VideoControlsImplWidget = class { }, setCastingButtonState() { - if (this.isCastingOn) { - this.castingButton.setAttribute("enabled", "true"); - } else { - this.castingButton.removeAttribute("enabled"); - } - + this.castingButton.toggleAttribute("enabled", this.isCastingOn); this.adjustControlSize(); }, @@ -2058,22 +2050,15 @@ this.VideoControlsImplWidget = class { }, setClosedCaptionButtonState() { - if (this.isClosedCaptionOn) { - this.closedCaptionButton.setAttribute("enabled", "true"); - } else { - this.closedCaptionButton.removeAttribute("enabled"); - } - + this.closedCaptionButton.toggleAttribute( + "enabled", + this.isClosedCaptionOn + ); let ttItems = this.textTrackList.childNodes; for (let tti of ttItems) { const idx = +tti.getAttribute("index"); - - if (idx == this.currentTextTrackIndex) { - tti.setAttribute("aria-checked", "true"); - } else { - tti.setAttribute("aria-checked", "false"); - } + tti.setAttribute("aria-checked", idx == this.currentTextTrackIndex); } this.adjustControlSize(); @@ -2804,10 +2789,6 @@ this.VideoControlsImplWidget = class { if (this.Utils.isTouchControls) { this.TouchUtils.init(this.shadowRoot, this.Utils); } - this.shadowRoot.firstChild.dispatchEvent( - new this.window.CustomEvent("VideoBindingAttached") - ); - this._setupEventListeners(); } @@ -2920,9 +2901,9 @@ this.VideoControlsImplWidget = class { this.l10n.translateRoots(); } - elementStateMatches(element) { - let elementInPiP = VideoControlsWidget.isPictureInPictureVideo(element); - return this.isShowingPictureInPictureMessage == elementInPiP; + onchange() { + this.Utils.updatePictureInPictureMessage(); + this.shadowRoot.firstChild.removeAttribute("flipped"); } teardown() { @@ -3080,14 +3061,9 @@ this.NoControlsMobileImplWidget = class { }, }; this.Utils.init(this.shadowRoot); - this.Utils.video.dispatchEvent( - new this.window.CustomEvent("MozNoControlsVideoBindingAttached") - ); } - elementStateMatches(element) { - return true; - } + onchange() {} teardown() { this.Utils.terminate(); @@ -3135,9 +3111,7 @@ this.NoControlsPictureInPictureImplWidget = class { this.shadowRoot.firstElementChild.setAttribute("localedir", direction); } - elementStateMatches(element) { - return true; - } + onchange() {} teardown() {} @@ -3312,9 +3286,7 @@ this.NoControlsDesktopImplWidget = class { this.Utils.init(this.shadowRoot, this.prefs); } - elementStateMatches(element) { - return true; - } + onchange() {} teardown() { this.Utils.terminate(); diff --git a/toolkit/content/widgets/wizard.js b/toolkit/content/widgets/wizard.js index 6eb4bcb517..c4285fada5 100644 --- a/toolkit/content/widgets/wizard.js +++ b/toolkit/content/widgets/wizard.js @@ -359,7 +359,7 @@ this._wizardButtons.onPageChange(); } - _advanceFocusToPage(aPage) { + _advanceFocusToPage() { if (!this._hasLoaded) { return; } diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css index e8635d4525..ead9997295 100644 --- a/toolkit/content/xul.css +++ b/toolkit/content/xul.css @@ -359,17 +359,15 @@ menubar > menu:empty { /********* menupopup, panel, & tooltip ***********/ menupopup, -panel { - flex-direction: column; -} - -menupopup, panel, tooltip { position: fixed; -moz-top-layer: top; width: fit-content; height: fit-content; + /* Make sure that popups are interactable when shown, since they escape the + * usual layering rules */ + -moz-inert: none; /* Popups can't have overflow */ contain: paint; z-index: 2147483647; @@ -438,87 +436,6 @@ tooltip:not([position]) { } } -@media (-moz-panel-animations) and (prefers-reduced-motion: no-preference) { -@media (-moz-platform: macos) { - /* On Mac, use the properties "-moz-window-transform" and "-moz-window-opacity" - instead of "transform" and "opacity" for these animations. - The -moz-window* properties apply to the whole window including the window's - shadow, and they don't affect the window's "shape", so the system doesn't - have to recompute the shadow shape during the animation. This makes them a - lot faster. In fact, Gecko no longer triggers shadow shape recomputations - for repaints. - These properties are not implemented on other platforms. */ - panel[type="arrow"]:not([animate="false"]) { - transition-property: -moz-window-transform, -moz-window-opacity; - transition-duration: 0.18s, 0.18s; - transition-timing-function: - var(--animation-easing-function), ease-out; - } - - /* Only do the fade-in animation on pre-Big Sur to avoid missing shadows on - * Big Sur, see bug 1672091. */ - @media (-moz-mac-big-sur-theme: 0) { - panel[type="arrow"]:not([animate="false"]) { - -moz-window-opacity: 0; - -moz-window-transform: translateY(-70px); - } - - panel[type="arrow"][side="bottom"]:not([animate="false"]) { - -moz-window-transform: translateY(70px); - } - } - - /* [animate] is here only so that this rule has greater specificity than the - * rule right above */ - panel[type="arrow"][animate][animate="open"] { - -moz-window-opacity: 1.0; - transition-duration: 0.18s, 0.18s; - -moz-window-transform: none; - transition-timing-function: - var(--animation-easing-function), ease-in-out; - } - - panel[type="arrow"][animate][animate="cancel"] { - -moz-window-opacity: 0; - -moz-window-transform: none; - } -} /* end of macOS rules */ - -@media not (-moz-platform: macos) { - panel[type="arrow"]:not([animate="false"]) { - opacity: 0; - transform: translateY(-70px); - transition-property: transform, opacity; - transition-duration: 0.18s, 0.18s; - transition-timing-function: - var(--animation-easing-function), ease-out; - will-change: transform, opacity; - } - - panel[type="arrow"][side="bottom"]:not([animate="false"]) { - transform: translateY(70px); - } - - /* [animate] is here only so that this rule has greater specificity than the - * rule right above */ - panel[type="arrow"][animate][animate="open"] { - opacity: 1.0; - transition-duration: 0.18s, 0.18s; - transform: none; - transition-timing-function: - var(--animation-easing-function), ease-in-out; - } - - panel[type="arrow"][animate][animate="cancel"] { - transform: none; - } -} /* end of non-macOS rules */ -} - -panel[type="arrow"][animating] { - pointer-events: none; -} - /******** tree ******/ treecolpicker { |