--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -342,6 +342,17 @@ pref("browser.overlink-delay", 80); +#ifdef UNIX_BUT_NOT_MAC + pref("browser.urlbar.clickSelectsAll", false); +#else + pref("browser.urlbar.clickSelectsAll", true); +#endif +#ifdef UNIX_BUT_NOT_MAC + pref("browser.urlbar.doubleClickSelectsAll", true); +#else + pref("browser.urlbar.doubleClickSelectsAll", false); +#endif + pref("browser.theme.colorway-closet", false); // Whether using `ctrl` when hitting return/enter in the URL bar --- a/browser/components/search/content/searchbar.js +++ b/browser/components/search/content/searchbar.js @@ -443,15 +443,16 @@ /** * Determines if we should select all the text in the searchbar based on the - * searchbar state, and whether the selection is empty. + * clickSelectsAll pref, searchbar state, and whether the selection is empty. */ _maybeSelectAll() { if ( !this._preventClickSelectsAll && + UrlbarPrefs.get("clickSelectsAll") && document.activeElement == this._textbox && this._textbox.selectionStart == this._textbox.selectionEnd ) { - this.select(); + this._textbox.editor.selectAll(); } } @@ -556,6 +557,11 @@ // is text in the textbox. this.openSuggestionsPanel(true); } + + if (event.detail == 2 && UrlbarPrefs.get("doubleClickSelectsAll")) { + this._textbox.editor.selectAll(); + event.preventDefault(); + } }); } --- a/browser/components/urlbar/tests/browser/browser_doubleClickSelectsAll.js +++ b/browser/components/urlbar/tests/browser/browser_doubleClickSelectsAll.js @@ -0,0 +1,45 @@ +/* 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/. */ + +function doubleClick(target) { + let promise = BrowserTestUtils.waitForEvent(target, "dblclick"); + EventUtils.synthesizeMouseAtCenter( + target, + { clickCount: 1 }, + target.ownerGlobal + ); + EventUtils.synthesizeMouseAtCenter( + target, + { clickCount: 2 }, + target.ownerGlobal + ); + return promise; +} + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.clickSelectsAll", false], + ["browser.urlbar.doubleClickSelectsAll", true], + ], + }); + + let url = "about:mozilla"; + let win = await BrowserTestUtils.openNewBrowserWindow(); + await BrowserTestUtils.openNewForegroundTab({ gBrowser: win.gBrowser, url }); + + await doubleClick(win.gURLBar.inputField); + is( + win.gURLBar.selectionStart, + 0, + "Selection should start at the beginning of the urlbar value" + ); + is( + win.gURLBar.selectionEnd, + url.length, + "Selection should end at the end of the urlbar value" + ); + + win.close(); +}); --- a/browser/components/urlbar/tests/browser/browser.ini +++ b/browser/components/urlbar/tests/browser/browser.ini @@ -86,6 +86,7 @@ [browser_display_selectedAction_Extensions.js] [browser_dns_first_for_single_words.js] skip-if = verify && os == 'linux' # Bug 1581635 +[browser_doubleClickSelectsAll.js] [browser_downArrowKeySearch.js] https_first_disabled = true [browser_dragdropURL.js] --- a/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js +++ b/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js @@ -71,7 +71,10 @@ add_setup(async function() { await SpecialPowers.pushPrefEnv({ - set: [["browser.urlbar.autoFill", true]], + set: [ + ["browser.urlbar.autoFill", true], + ["browser.urlbar.clickSelectsAll", true] + ], }); // Add some history for the empty panel and autofill. await PlacesTestUtils.addVisits([ --- a/browser/components/urlbar/tests/browser/browser_urlbar_selection.js +++ b/browser/components/urlbar/tests/browser/browser_urlbar_selection.js @@ -62,27 +62,11 @@ return promise; } -function resetPrimarySelection(val = "") { - if (Services.clipboard.supportsSelectionClipboard()) { - // Reset the clipboard. - clipboardHelper.copyStringToClipboard( - val, - Services.clipboard.kSelectionClipboard - ); - } -} - -function checkPrimarySelection(expectedVal = "") { - if (Services.clipboard.supportsSelectionClipboard()) { - let primaryAsText = SpecialPowers.getClipboardData( - "text/unicode", - SpecialPowers.Ci.nsIClipboard.kSelectionClipboard - ); - Assert.equal(primaryAsText, expectedVal); - } -} - add_setup(async function() { + SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.clickSelectsAll", true]], + }); + // On macOS, we must "warm up" the Urlbar to get the first test to pass. gURLBar.value = ""; await click(gURLBar.inputField); @@ -90,7 +74,6 @@ }); add_task(async function leftClickSelectsAll() { - resetPrimarySelection(); gURLBar.value = exampleSearch; await click(gURLBar.inputField); Assert.equal( @@ -104,11 +87,9 @@ "The entire search term should be selected." ); gURLBar.blur(); - checkPrimarySelection(); }); add_task(async function leftClickSelectsUrl() { - resetPrimarySelection(); gURLBar.value = exampleUrl; await click(gURLBar.inputField); Assert.equal(gURLBar.selectionStart, 0, "The entire url should be selected."); @@ -118,18 +99,42 @@ "The entire url should be selected." ); gURLBar.blur(); - checkPrimarySelection(); +}); + +// Test to ensure that the doubleClickSelectsAll pref does not interfere with +// single click behaviour (Double CSA itself is tested in +// urlbar/tests/browser_doubleClickSelectsAll.js). +add_task(async function bothPrefsEnabled() { + Services.prefs.setBoolPref("browser.urlbar.doubleClickSelectsAll", true); + gURLBar.value = exampleSearch; + await click(gURLBar.inputField); + Assert.equal( + gURLBar.selectionStart, + 0, + "The entire search term should be selected." + ); + Assert.equal( + gURLBar.selectionEnd, + exampleSearch.length, + "The entire search term should be selected." + ); + gURLBar.blur(); + Services.prefs.clearUserPref("browser.urlbar.doubleClickSelectsAll"); }); add_task(async function rightClickSelectsAll() { + // The text should be selected even when the pref is disabled. + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.clickSelectsAll", false]], + }); + + gURLBar.inputField.focus(); gURLBar.value = exampleUrl; // Remove the selection so the focus() call above doesn't influence the test. gURLBar.selectionStart = gURLBar.selectionEnd = 0; - resetPrimarySelection(); - await openContextMenu(gURLBar.inputField); Assert.equal(gURLBar.selectionStart, 0, "The entire URL should be selected."); @@ -139,8 +144,6 @@ "The entire URL should be selected." ); - checkPrimarySelection(); - let contextMenu = gURLBar.querySelector("moz-input-box").menupopup; // While the context menu is open, test the "Select All" button. @@ -178,7 +181,6 @@ gURLBar.querySelector("moz-input-box").menupopup.hidePopup(); gURLBar.blur(); - checkPrimarySelection(gURLBar.value); await SpecialPowers.popPrefEnv(); }); @@ -189,8 +191,6 @@ gURLBar.selectionStart = 3; gURLBar.selectionEnd = 7; - resetPrimarySelection(); - await openContextMenu(gURLBar.inputField); Assert.equal( @@ -206,11 +206,9 @@ gURLBar.querySelector("moz-input-box").menupopup.hidePopup(); gURLBar.blur(); - checkPrimarySelection(); }); add_task(async function dragSelect() { - resetPrimarySelection(); gURLBar.value = exampleSearch.repeat(10); // Drags from an artibrary offset of 30 to test for bug 1562145: that the // selection does not start at the beginning. @@ -221,12 +219,7 @@ "Selection should not start at the beginning of the string." ); - let selectedVal = gURLBar.value.substring( - gURLBar.selectionStart, - gURLBar.selectionEnd - ); gURLBar.blur(); - checkPrimarySelection(selectedVal); }); /** @@ -234,7 +227,6 @@ * Urlbar is dragged following a selectsAll event then a blur. */ add_task(async function dragAfterSelectAll() { - resetPrimarySelection(); gURLBar.value = exampleSearch.repeat(10); await click(gURLBar.inputField); Assert.equal( @@ -249,7 +241,6 @@ ); gURLBar.blur(); - checkPrimarySelection(); // The offset of 30 is arbitrary. await drag(gURLBar.inputField, 30, 0, 60, 0); @@ -264,10 +255,6 @@ exampleSearch.repeat(10).length, "Only part of the search term should be selected." ); - - checkPrimarySelection( - gURLBar.value.substring(gURLBar.selectionStart, gURLBar.selectionEnd) - ); }); /** --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -2810,16 +2810,19 @@ /** * Determines if we should select all the text in the Urlbar based on the - * Urlbar state, and whether the selection is empty. + * clickSelectsAll pref, Urlbar state, and whether the selection is empty. + * @param {boolean} [ignoreClickSelectsAllPref] + * If true, the browser.urlbar.clickSelectsAll pref will be ignored. */ - _maybeSelectAll() { + _maybeSelectAll(ignoreClickSelectsAllPref = false) { if ( !this._preventClickSelectsAll && + (ignoreClickSelectsAllPref || UrlbarPrefs.get("clickSelectsAll")) && this._compositionState != UrlbarUtils.COMPOSITION.COMPOSING && this.document.activeElement == this.inputField && this.inputField.selectionStart == this.inputField.selectionEnd ) { - this.select(); + this.editor.selectAll(); } } @@ -2936,7 +2939,9 @@ return; } - this._maybeSelectAll(); + // If the user right clicks, we select all regardless of the value of + // the browser.urlbar.clickSelectsAll pref. + this._maybeSelectAll(/* ignoreClickSelectsAllPref */ event.button == 2); } _on_focus(event) { @@ -2967,7 +2972,7 @@ if (this.focusedViaMousedown) { this.view.autoOpen({ event }); } else if (this.inputField.hasAttribute("refocused-by-panel")) { - this._maybeSelectAll(); + this._maybeSelectAll(true); } this._updateUrlTooltip(); @@ -3028,7 +3033,10 @@ this.selectionStart = this.selectionEnd = 0; } - if (event.target.id == SEARCH_BUTTON_ID) { + if (event.detail == 2 && UrlbarPrefs.get("doubleClickSelectsAll")) { + this.editor.selectAll(); + event.preventDefault(); + } else if (event.target.id == SEARCH_BUTTON_ID) { this._preventClickSelectsAll = true; this.search(UrlbarTokenizer.RESTRICT.SEARCH); } else { --- a/browser/components/urlbar/UrlbarPrefs.jsm +++ b/browser/components/urlbar/UrlbarPrefs.jsm @@ -70,6 +70,11 @@ // this value. See UrlbarProviderPlaces. ["autoFill.stddevMultiplier", [0.0, "float"]], + // If true, this optimizes for replacing the full URL rather than editing + // part of it. This also copies the urlbar value to the selection clipboard + // on systems that support it. + ["clickSelectsAll", false], + // Whether best match results can be blocked. This pref is a fallback for the // Nimbus variable `bestMatchBlockingEnabled`. ["bestMatch.blockingEnabled", false], @@ -97,6 +102,11 @@ // but this would mean flushing layout.) ["disableExtendForTests", false], + // If true, this optimizes for replacing the full URL rather than selecting a + // portion of it. This also copies the urlbar value to the selection + // clipboard on systems that support it. + ["doubleClickSelectsAll", false], + // Ensure we use trailing dots for DNS lookups for single words that could // be hosts. ["dnsResolveFullyQualifiedNames", true], --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3124,6 +3124,8 @@ pref("middlemouse.openNewWindow", true); pref("middlemouse.scrollbarPosition", true); + pref("browser.urlbar.clickSelectsAll", false); + // Tab focus model bit field: // 1 focuses text controls, 2 focuses other form elements, 4 adds links. // Leave this at the default, 7, to match mozilla1.0-era user expectations.