diff --git a/JS/allTabsMenuExpansionPack.uc.js b/JS/allTabsMenuExpansionPack.uc.js index 0e1e005d..4693080c 100644 --- a/JS/allTabsMenuExpansionPack.uc.js +++ b/JS/allTabsMenuExpansionPack.uc.js @@ -1,6 +1,6 @@ // ==UserScript== // @name All Tabs Menu Expansion Pack -// @version 2.0.1 +// @version 2.0.2 // @author aminomancer // @homepage https://github.com/aminomancer // @description Next to the "new tab" button in Firefox there's a V-shaped button that opens a @@ -148,9 +148,10 @@ gTabsPanel.hiddenTabsPopup, ]; if (!window.E10SUtils) - XPCOMUtils.defineLazyModuleGetters(window, { - E10SUtils: `resource://gre/modules/E10SUtils.jsm`, + XPCOMUtils.defineLazyModuleGetters(gTabsPanel, { + E10SUtils: "resource://gre/modules/E10SUtils.jsm", }); + else gTabsPanel.E10SUtils = window.E10SUtils; let vanillaTooltip = document.getElementById("tabbrowser-tab-tooltip"); let tooltip = vanillaTooltip.cloneNode(true); vanillaTooltip.after(tooltip); @@ -171,6 +172,7 @@ }; let label; let align = true; + let { linkedBrowser } = tab; const selectedTabs = gBrowser.selectedTabs; const contextTabInSelection = selectedTabs.includes(tab); const affectedTabsLength = contextTabInSelection ? selectedTabs.length : 1; @@ -188,7 +190,7 @@ } else if (row.querySelector("[toggle-mute]").matches(":hover")) { let stringID; if (contextTabInSelection) { - stringID = tab.linkedBrowser.audioMuted + stringID = linkedBrowser.audioMuted ? "tabs.unmuteAudio2.tooltip" : "tabs.muteAudio2.tooltip"; label = stringWithShortcut(stringID, "key_toggleMute", affectedTabsLength); @@ -196,7 +198,7 @@ if (tab.hasAttribute("activemedia-blocked")) stringID = "tabs.unblockAudio2.tooltip"; else - stringID = tab.linkedBrowser.audioMuted + stringID = linkedBrowser.audioMuted ? "tabs.unmuteAudio2.background.tooltip" : "tabs.muteAudio2.background.tooltip"; label = PluralForm.get( @@ -208,9 +210,9 @@ } else { label = tab._fullLabel || tab.getAttribute("label"); if (Services.prefs.getBoolPref("browser.tabs.tooltipsShowPidAndActiveness", false)) - if (tab.linkedBrowser) { - let [contentPid, ...framePids] = E10SUtils.getBrowserPids( - tab.linkedBrowser, + if (linkedBrowser) { + let [contentPid, ...framePids] = gTabsPanel.E10SUtils.getBrowserPids( + linkedBrowser, gFissionBrowser ); if (contentPid) { @@ -221,7 +223,7 @@ label += "]"; } } - if (tab.linkedBrowser.docShellIsActive) label += " [A]"; + if (linkedBrowser.docShellIsActive) label += " [A]"; } if (tab.userContextId) { label = gTabBrowserBundle.formatStringFromName("tabs.containers.tooltip", [ @@ -239,15 +241,12 @@ let url = e.target.querySelector(".places-tooltip-uri"); let icon = e.target.querySelector("#places-tooltip-insecure-icon"); title.textContent = label; - url.value = tab.linkedBrowser?.currentURI?.spec.replace(/^https:\/\//, ""); + url.value = linkedBrowser?.currentURI?.spec.replace(/^https:\/\//, ""); // show a lock icon to show tab security/encryption - if (tab.getAttribute("pending")) { - icon.hidden = true; - icon.removeAttribute("type"); - icon.setAttribute("pending", true); - return; - } else icon.removeAttribute("pending"); - let docURI = tab.linkedBrowser?.documentURI; + let pending = tab.hasAttribute("pending") || !linkedBrowser.browsingContext; + let docURI = pending + ? linkedBrowser?.currentURI + : linkedBrowser?.documentURI || linkedBrowser?.currentURI; if (docURI) { let homePage = new RegExp( `(${BROWSER_NEW_TAB_URL}|${HomePage.get(window)})`, @@ -275,6 +274,11 @@ icon.hidden = false; return; } + if (docURI.filePath === "blocked") { + icon.setAttribute("type", "blocked-page"); + icon.hidden = false; + return; + } icon.setAttribute("type", "about-page"); icon.hidden = false; return; @@ -284,30 +288,32 @@ return; } } - let prog = Ci.nsIWebProgressListener; - let state = tab.linkedBrowser?.securityUI?.state; - if (typeof state != "number" || state & prog.STATE_IS_SECURE) { - icon.hidden = true; - icon.setAttribute("type", "secure"); - return; - } - if (state & prog.STATE_IS_INSECURE) { - icon.setAttribute("type", "insecure"); - icon.hidden = false; - return; - } - if (state & prog.STATE_IS_BROKEN) { - if (state & prog.STATE_LOADED_MIXED_ACTIVE_CONTENT) { - icon.hidden = false; + if (linkedBrowser.browsingContext) { + let prog = Ci.nsIWebProgressListener; + let state = linkedBrowser?.securityUI?.state; + if (typeof state != "number" || state & prog.STATE_IS_SECURE) { + icon.hidden = true; + icon.setAttribute("type", "secure"); + return; + } + if (state & prog.STATE_IS_INSECURE) { icon.setAttribute("type", "insecure"); - } else { - icon.setAttribute("type", "mixed-passive"); icon.hidden = false; + return; + } + if (state & prog.STATE_IS_BROKEN) { + if (state & prog.STATE_LOADED_MIXED_ACTIVE_CONTENT) { + icon.hidden = false; + icon.setAttribute("type", "insecure"); + } else { + icon.setAttribute("type", "mixed-passive"); + icon.hidden = false; + } + return; } - return; } icon.hidden = true; - icon.setAttribute("type", "secure"); + icon.setAttribute("type", pending ? "pending" : "secure"); }; gTabsPanel.allTabsButton.setAttribute( "onmouseover", diff --git a/JS/restoreTabSoundButton.uc.js b/JS/restoreTabSoundButton.uc.js index 24da1273..332e85a1 100644 --- a/JS/restoreTabSoundButton.uc.js +++ b/JS/restoreTabSoundButton.uc.js @@ -1,6 +1,6 @@ // ==UserScript== // @name Restore pre-Proton Tab Sound Button -// @version 2.3 +// @version 2.3.1 // @author aminomancer // @homepage https://github.com/aminomancer/uc.css.js // @description Proton makes really big changes to tabs, in particular removing the tab sound button in favor of the overlay button and a whole row of text. This script keeps the new tab tooltip enabled by the pref "browser.proton.places-tooltip.enabled" but allows it to work with the old .tab-icon-sound. So you get the nice parts of the proton tab changes without the second row of text about the audio playing. Instead it will show the mute/unmute tooltip inside the normal tab tooltip. It also changes the tooltip a bit so that it's always anchored to the tab rather than floating around tethered to the exact mouse position. This makes it easier to modify the tooltip appearance without the tooltip getting in your way. It also lets the insecure tooltip icon show other security states, like mixed passive content or error page. This way the tooltip icon should usually match the identity icon for the tab you're hovering. This script *requires* that you either 1) use my theme, complete with chrome.manifest and the resources folder, or 2) download resources/script-override/tabMods.uc.js and put it in the same location in your chrome folder, then edit your utils/chrome.manifest file to add the following line (without the "//"): @@ -23,13 +23,11 @@ * @param {object} tab (the tab's DOM node) */ function configureIcon(icon, tab) { - if (tab.getAttribute("pending")) { - icon.hidden = true; - icon.removeAttribute("type"); - icon.setAttribute("pending", true); - return; - } else icon.removeAttribute("pending"); - let docURI = tab.linkedBrowser?.documentURI; + let { linkedBrowser } = tab; + let pending = tab.hasAttribute("pending") || !linkedBrowser.browsingContext; + let docURI = pending + ? linkedBrowser?.currentURI + : linkedBrowser?.documentURI || linkedBrowser?.currentURI; if (docURI) { let homePage = new RegExp(`(${BROWSER_NEW_TAB_URL}|${HomePage.get(window)})`, "i").test( docURI.spec @@ -56,6 +54,11 @@ icon.hidden = false; return; } + if (docURI.filePath === "blocked") { + icon.setAttribute("type", "blocked-page"); + icon.hidden = false; + return; + } icon.setAttribute("type", "about-page"); icon.hidden = false; return; @@ -66,45 +69,53 @@ return; } } - // web progress listener has some constants representing different states. - // using the bitwise AND operator we can get the tab's current state, whether it's selected or not. - // we can then compare it against these constants to determine which icon to show. - let prog = Ci.nsIWebProgressListener; - let state = tab.linkedBrowser?.securityUI?.state; - // if state is secure or state doesn't exist, the rest below can't apply, so break out immediately. - if (typeof state != "number" || state & prog.STATE_IS_SECURE) { - icon.hidden = true; - icon.setAttribute("type", "secure"); - return; - } - // if state is insecure, that means the actual scheme is insecure. - // there are other cases besides remote transfer protocols, but they were covered in the logic above. - // by this point the only options are remote, so we use the fully insecure icon here. - // if there is mixed content but the scheme is secure then this will evaluate to false... - if (state & prog.STATE_IS_INSECURE) { - icon.setAttribute("type", "insecure"); - icon.hidden = false; - return; - } - // broken state means the scheme is secure but something's wrong with the connection - if (state & prog.STATE_IS_BROKEN) { - // loaded mixed active content means the page is effectively as insecure as if the page itself was loaded by http. - // therefore we'll use the fully insecure icon for this case, even though state is not technically insecure. - if (state & prog.STATE_LOADED_MIXED_ACTIVE_CONTENT) { - icon.hidden = false; + if (linkedBrowser.browsingContext) { + // web progress listener has some bit flags we can use to decide which security icon to show. + let prog = Ci.nsIWebProgressListener; + let state = linkedBrowser?.securityUI?.state; + // if state is secure or state doesn't exist, the rest below can't + // apply, so bail out immediately. + if (typeof state != "number" || state & prog.STATE_IS_SECURE) { + icon.hidden = true; + icon.setAttribute("type", "secure"); + return; + } + // if state is insecure, that means the actual scheme is insecure. + // there are other cases besides remote transfer protocols, but they + // were covered in the logic above. by this point the only options + // are remote, so we use the fully insecure icon here. if there is + // mixed content but the scheme is secure then this will be false + if (state & prog.STATE_IS_INSECURE) { icon.setAttribute("type", "insecure"); - } else { - // any other case when state is broken means some kind of state where the warning lock icon is shown. - // it doesn't only include mixed passive/display content, e.g. it could also be the weak cipher state, - // but the same icon is shown regardless. so we'll use the same attribute that shows the warning lock (rather than the insecure lock) - icon.setAttribute("type", "mixed-passive"); icon.hidden = false; + return; + } + // broken state means the scheme is secure but something's wrong + // with the connection e.g. mixed content + if (state & prog.STATE_IS_BROKEN) { + // loaded mixed active content means the page is effectively as + // insecure as if the page itself was loaded by http. therefore + // we'll use the fully insecure icon for this case, even though + // state is not technically insecure. + if (state & prog.STATE_LOADED_MIXED_ACTIVE_CONTENT) { + icon.hidden = false; + icon.setAttribute("type", "insecure"); + } else { + // any other case when state is broken means some kind of + // state where the warning lock icon is shown. it doesn't + // only include mixed passive/display content, e.g. it could + // also be the weak cipher state, but the same icon is shown + // regardless. so we'll use the same attribute that shows + // the warning lock (rather than the insecure lock) + icon.setAttribute("type", "mixed-passive"); + icon.hidden = false; + } + return; } - return; } // we probably should have returned by now, but just in case, set the icon to the default secure state. icon.hidden = true; - icon.setAttribute("type", "secure"); + icon.setAttribute("type", pending ? "pending" : "secure"); } gBrowser.createTooltip = function (e) { e.stopPropagation(); diff --git a/JS/urlbarMods.uc.js b/JS/urlbarMods.uc.js index a3862524..985b0812 100644 --- a/JS/urlbarMods.uc.js +++ b/JS/urlbarMods.uc.js @@ -1,653 +1,675 @@ -// ==UserScript== -// @name Urlbar Mods -// @version 1.6.0 -// @author aminomancer -// @homepage https://github.com/aminomancer/uc.css.js -// @description Make some minor modifications to the urlbar. See the code comments below for more details. -// @license This Source Code Form is subject to the terms of the Creative Commons Attribution-NonCommercial-ShareAlike International License, v. 4.0. If a copy of the CC BY-NC-SA 4.0 was not distributed with this file, You can obtain one at http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. -// ==/UserScript== - -class UrlbarMods { - static config = { - // recently the context menu for the search engine one-off buttons in the urlbar results - // panel has been disabled. but the context menu for the one-off buttons in the searchbar is - // still enabled. I'm not sure why they did this, and it's a really minor thing, but it's - // not like right-clicking the buttons does anything else, (at least on windows) so you may - // want to restore the context menu. - "restore one-offs context menu": false, - - // when you click & drag the identity box in the urlbar, it lets you drag and drop the URL - // into text fields, the tab bar, desktop, etc. while dragging it shows a little white box - // with the URL and favicon as the drag image. this can't be styled with CSS because it's - // drawn by the canvas 2D API. but we can easily change the function so that it sets the - // background and text colors equal to some CSS variables. it uses --tooltip-bgcolor, - // --tooltip-color, and --tooltip-border-color, or if those don't exist, it uses the vanilla - // variables --arrowpanel-background, --arrowpanel-color, and --arrowpanel-border-color. - // so if you use my theme duskFox it'll look similar to a tooltip. if you don't use my theme - // it'll look similar to a popup panel. - "style identity icon drag box": true, - - // the identity icon is missing tooltips and/or identifying icons for several states. - // in particular, there is no tooltip on pages with mixed content. on these pages the identity - // icon generally shows a lock icon with a warning sign on it. so this is the intermediate - // state between "secure" and "not secure." both of those states have their own special - // tooltips but the mixed security state does not. (see https://bugzilla.mozilla.org/show_bug.cgi?id=1736354) - // this is unfortunate because the mixed state is actually a composite of several states. - // in vanilla firefox, you currently can't tell which one without clicking the identity box to - // open the popup. hopefully this feature will be added to firefox but for now we can add it - // ourselves. we add tooltips so the user can distinguish between blocked active content, - // loaded active content, loaded passive content, weak cipher, or untrustworthy certificate. - // we also add tooltips to show when the user had HTTPS-only mode enabled but the site - // failed to load over https. there's also no tooltip to show on chrome UI pages or local - // files. hovering the identity icon just shows nothing. so we add these as well, so that it - // should say "This is a secure Nightly page" or "This page is stored on your computer" in a - // similar way to how it already shows on extension pages. While working on this I noticed - // that some types of pages are missing a unique class, which means they can't be given a - // unique icon. these pages are about:neterror and about:blocked. the former shows under - // many circumstances when a page fails to load; the latter shows when the user has blocked - // a specific page. if you use duskFox you might have noticed that the theme adds custom - // icons for certain error pages. previously this was just the HTTPS-only error page and - // about:certerror (shows when the certificate has a serious problem and firefox won't load - // the page without user interaction). we couldn't include these other error pages because - // they were just marked as "unknownIdentity" which is the same as the default class. so I - // couldn't style them without styling some perfectly valid local or system pages too, such - // as those with chrome:// URIs. so I'm extending the icon-setting method so it'll give the - // icon a unique class on these pages: "aboutNetErrorPage" and "aboutBlockedPage" which are - // pretty self-explanatory. they still keep .unknownIdentity, they just get an extra class. - // so you can style these yourself if you want but duskFox already styles them just like the - // other error pages — with the triangular warning sign. - "add new tooltips and classes for identity icon": true, - - // this sets attributes on each urlbar result according to 1) its corresponding search - // engine; and 2) its source device, if it's a remote tab. this allows CSS to give them - // unique icons, or to replace a search engine urlbar result's icon. my theme increases the - // prominence of the "type icon" in urlbar results. for bookmarks, this is a star. for open - // tabs, it's a tab icon. for remote tabs, aka synced tabs, it's a sync icon. with this - // option enabled, however, instead of showing a sync icon it will show a device icon - // specific to the type of device. so if the tab was synced from a cell phone, the type icon - // will show a phone. if it was synced from a laptop, it'll show a laptop, etc. the script - // will add some CSS to set the icons, but it won't change the way type icons are layed out. - // that's particular to my theme and it's purely a CSS issue, so I don't want the script to - // get involved in that. my urlbar-results.css file makes the type icon look basically like - // a 2nd favicon. it goes next to the favicon, rather than displaying on top of it. the - // script's CSS just changes the icon, so it'll fit with however you have type icons styled. - // if you don't use my theme but you want type icons to look more prominent, see - // urlbar-results.css and search "type-icon" - "show detailed icons in urlbar results": true, - - // normally, when you type something like "firefox install" or "clear cookies" in the - // urlbar, it suggests an "intervention" or "tip" which acts as a kind of shortcut to - // various settings and profile operations that some beginners might have a hard time - // finding. this is a fine feature for people who are new to firefox. but people who use - // firefox a lot may use the urlbar quickly and could easily accidentally hit enter on one - // of those results. I decided to add this option to the script because I very nearly wiped - // my entire profile due to the tip that lets you "Restore default settings and remove old - // add-ons for optimal performance." From my point of view, these results just waste space - // while presenting a hazard to the user, which makes them more of a liability than an - // asset. so they will be removed entirely by this setting. - "disable urlbar intervention tips": true, - - // by default, urlbar results are not sorted consistently between regular mode and search - // mode. when you use the urlbar normally, the order of urlbar results is determined by a - // pref. (browser.urlbar.showSearchSuggestionsFirst) it's true by default, so search - // suggestions are shown at the top of the list. when you enter "search mode," e.g. by - // clicking a one-off search engine button in the urlbar results panel, this pref is no - // longer used. suggestions are shown at the top of the list no matter what — it's - // hard-coded into the urlbar muxer's sort function. if you don't change the pref this - // shouldn't matter, since the suggestions show at the top of the list by default, whether - // in search mode or not. but if you set the pref to false, (e.g. if you want top or recent - // site URLs to appear at the top of the list so you don't have to hit Tab so many times to - // reach them) you'll get URLs at the top of the list in regular mode, and URLs at the - // bottom of the list in search mode. there are no plans to change this behavior (see - // https://bugzilla.mozilla.org/show_bug.cgi?id=1727904 for more details) so I'm releasing - // this mod (debatably a bug fix) as part of this autoconfig script. - "sort urlbar results consistently": true, - - // when you type nothing but space or tab characters in the urlbar, the first result will - // have an empty title. consecutive whitespace characters don't add to the displayed node - // width so it ends up looking basically empty. we can change this by setting it to use - // non-breaking spaces instead of space characters, and adding an attribute "all-whitespace" - // to the title element. then your CSS can underline it. this is already done in - // uc-urlbar-results.css but if you wanna do it yourself: - // .urlbarView-title[all-whitespace] {text-decoration: underline} - "underline whitespace results": true, - }; - constructor() { - if (UrlbarMods.config["add new tooltips and classes for identity icon"]) - this.extendIdentityIcons(); - if (UrlbarMods.config["style identity icon drag box"]) this.styleIdentityIconDragBox(); - if (UrlbarMods.config["restore one-offs context menu"]) this.restoreOneOffsContextMenu(); - if (UrlbarMods.config["show detailed icons in urlbar results"]) - this.urlbarResultsDetailedIcons(); - if (UrlbarMods.config["disable urlbar intervention tips"]) - this.disableUrlbarInterventions(); - if (UrlbarMods.config["sort urlbar results consistently"]) this.urlbarResultsSorting(); - if (UrlbarMods.config["underline whitespace results"]) this.underlineSpaceResults(); - } - get urlbarOneOffs() { - return this._urlbarOneOffs || (this._urlbarOneOffs = gURLBar.view.oneOffSearchButtons); - } - async extendIdentityIcons() { - // Load the fluent strings into document.l10n if they haven't already been loaded. - MozXULElement.insertFTLIfNeeded("browser/browser.ftl"); - let [ - chromeUI, - localResource, - mixedDisplayContentLoadedActiveBlocked, - mixedDisplayContent, - mixedActiveContent, - weakCipher, - aboutNetErrorPage, - httpsOnlyErrorPage, - ] = await document.l10n // Retrieve strings from Firefox's built-in localization files. - .formatValues([ - "identity-connection-internal", - "identity-connection-file", - "identity-active-blocked", - "identity-passive-loaded", - "identity-active-loaded", - "identity-weak-encryption", - "identity-connection-failure", - "identity-https-only-info-no-upgrade", - ]) - // These strings were intended to be shown as descriptions in the identity popup, not as - // tooltips on the identity icon. As such, they have trailing periods, but the general - // convention with tooltips is to omit punctuation. So we need to remove them - // programmatically in a way that works for any and all translations. Therefore we'll - // use unicode property escapes with the new property Sentence_Terminal. This should - // include periods and every equivalent unicode character for sentence terminating punctuation. - .then((arr) => - arr.map((str) => - str.replace(/(^\p{Sentence_Terminal}+)|(\p{Sentence_Terminal}+$)/gu, "") - ) - ); - gIdentityHandler._fluentStrings = { - chromeUI, - localResource, - mixedDisplayContentLoadedActiveBlocked, - mixedDisplayContent, - mixedActiveContent, - weakCipher, - aboutNetErrorPage, - httpsOnlyErrorPage, - }; - gIdentityHandler._localPDF = function () { - if (!this._isPDFViewer) return false; - switch (gBrowser.selectedBrowser.documentURI?.scheme) { - case "chrome": - case "file": - case "resource": - case "about": - return true; - default: - return false; - } - }; - // Extend the built-in method that sets the identity icon's tooltip and class. - gIdentityHandler._refreshIdentityIcons = function () { - let icon_label = ""; - let tooltip = ""; - if (this._isSecureInternalUI) { - this._identityBox.className = isInitialPage(this._uri) ? "initialPage" : "chromeUI"; - let brandBundle = document.getElementById("bundle_brand"); - icon_label = brandBundle.getString("brandShorterName"); - tooltip = this._fluentStrings.chromeUI; - } else if (this._pageExtensionPolicy) { - this._identityBox.className = isInitialPage(this._uri) - ? "initialPage" - : "extensionPage"; - let extensionName = this._pageExtensionPolicy.name; - icon_label = gNavigatorBundle.getFormattedString("identity.extension.label", [ - extensionName, - ]); - } else if (this._uriHasHost && this._isSecureConnection) { - this._identityBox.className = "verifiedDomain"; - if (this._isMixedActiveContentBlocked) { - this._identityBox.classList.add("mixedActiveBlocked"); - tooltip = this._fluentStrings.mixedDisplayContentLoadedActiveBlocked; - } else if (!this._isCertUserOverridden) - tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", [ - this.getIdentityData().caOrg, - ]); - } else if (this._isBrokenConnection) { - this._identityBox.className = "unknownIdentity"; - if (this._isMixedActiveContentLoaded) { - this._identityBox.classList.add("mixedActiveContent"); - tooltip = this._fluentStrings.mixedActiveContent; - } else if (this._isMixedActiveContentBlocked) { - this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked"); - tooltip = this._fluentStrings.mixedDisplayContentLoadedActiveBlocked; - } else if (this._isMixedPassiveContentLoaded) { - this._identityBox.classList.add("mixedDisplayContent"); - tooltip = this._fluentStrings.mixedDisplayContent; - } else { - this._identityBox.classList.add("weakCipher"); - tooltip = this._fluentStrings.weakCipher; - } - } else if (this._isCertErrorPage) { - this._identityBox.className = "certErrorPage notSecureText"; - icon_label = gNavigatorBundle.getString("identity.notSecure.label"); - tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip"); - } else if (this._isAboutHttpsOnlyErrorPage) { - this._identityBox.className = "httpsOnlyErrorPage"; - tooltip = this._fluentStrings.httpsOnlyErrorPage; - } else if (this._isAboutNetErrorPage) { - // By default, about:neterror and about:blocked get the same "neutral icon." - // I'm adding classes here, "aboutNetErrorPage" and "aboutBlockedPage" so that - // userChrome.css can style them. Since duskFox gives other error pages a warning - // icon, I want to style these error pages the same icon. - this._identityBox.className = "aboutNetErrorPage unknownIdentity"; - tooltip = this._fluentStrings.aboutNetErrorPage; - } else if (this._isAboutBlockedPage) { - // A rare connection state, not really sure how to reproduce this in the wild. - this._identityBox.className = "aboutBlockedPage unknownIdentity"; - tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip"); - } else if (this._isPotentiallyTrustworthy || this._localPDF()) { - this._identityBox.className = isInitialPage(this._uri) - ? "initialPage" - : "localResource"; - tooltip = this._fluentStrings.localResource; - if (this._uri.spec.startsWith("about:reader?")) - this._identityBox.classList.add("readerMode"); - } else { - let warnOnInsecure = - this._insecureConnectionIconEnabled || - (this._insecureConnectionIconPBModeEnabled && - PrivateBrowsingUtils.isWindowPrivate(window)); - let className = warnOnInsecure ? "notSecure" : "unknownIdentity"; - this._identityBox.className = className; - tooltip = warnOnInsecure - ? gNavigatorBundle.getString("identity.notSecure.tooltip") - : ""; - let warnTextOnInsecure = - this._insecureConnectionTextEnabled || - (this._insecureConnectionTextPBModeEnabled && - PrivateBrowsingUtils.isWindowPrivate(window)); - if (warnTextOnInsecure) { - icon_label = gNavigatorBundle.getString("identity.notSecure.label"); - this._identityBox.classList.add("notSecureText"); - } - } - if (this._isCertUserOverridden) { - this._identityBox.classList.add("certUserOverridden"); - tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you"); - } - this._updateAttribute(this._identityIcon, "lock-icon-gray", this._useGrayLockIcon); - this._identityIcon.setAttribute("tooltiptext", tooltip); - if (this._pageExtensionPolicy) { - let extensionName = this._pageExtensionPolicy.name; - this._identityIcon.setAttribute( - "tooltiptext", - gNavigatorBundle.getFormattedString("identity.extension.tooltip", [ - extensionName, - ]) - ); - } - this._identityIconLabel.setAttribute("tooltiptext", tooltip); - this._identityIconLabel.setAttribute("value", icon_label); - this._identityIconLabel.collapsed = !icon_label; - }; - gIdentityHandler._refreshIdentityIcons(); - } - styleIdentityIconDragBox() { - // for a given string in CSS3 custom property syntax, e.g. "var(--tooltip-color)" or - // "var(--tooltip-color, rgb(255, 255, 255))", convert it to a hex code string e.g. - // "#FFFFFF" - function varToHex(variable) { - let temp = document.createElement("div"); - document.body.appendChild(temp); - temp.style.color = variable; - let rgb = getComputedStyle(temp).color; - temp.remove(); - rgb = rgb - .split("(")[1] - .split(")")[0] - .split(rgb.indexOf(",") > -1 ? "," : " "); - rgb.length = 3; - rgb.forEach((c, i) => { - c = (+c).toString(16); - rgb[i] = c.length === 1 ? "0" + c : c.slice(0, 2); - }); - return "#" + rgb.join(""); - } - // draw a rectangle with rounded corners - function roundRect(ctx, x, y, width, height, radius = 5, fill, stroke) { - if (typeof radius === "number") - radius = { tl: radius, tr: radius, br: radius, bl: radius }; - else { - let defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 }; - for (let side in defaultRadius) radius[side] = radius[side] || defaultRadius[side]; - } - ctx.beginPath(); - ctx.moveTo(x + radius.tl, y); - ctx.lineTo(x + width - radius.tr, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); - ctx.lineTo(x + width, y + height - radius.br); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height); - ctx.lineTo(x + radius.bl, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); - ctx.lineTo(x, y + radius.tl); - ctx.quadraticCurveTo(x, y, x + radius.tl, y); - ctx.closePath(); - if (fill) { - ctx.fillStyle = fill; - ctx.fill(); - } - if (stroke) { - ctx.strokeStyle = stroke; - ctx.stroke(); - } - } - function fontString(style, scale) { - let { fontWeight, fontFamily, fontSize } = style; - return `${fontWeight} ${parseFloat(fontSize) * scale}px ${fontFamily}`; - } - // override the internal dragstart callback so it uses variables instead of "white" and "black" - gIdentityHandler.onDragStart = function (event) { - const inputStyle = getComputedStyle(gURLBar.inputField); - const identityStyle = getComputedStyle(gURLBar._identityBox); - const iconStyle = getComputedStyle(document.getElementById("identity-icon")); - const IMAGE_SIZE = parseFloat(iconStyle.width); - const INPUT_HEIGHT = parseFloat(inputStyle.height); - const SPACING = (INPUT_HEIGHT - IMAGE_SIZE) / 2; - - if (gURLBar.getAttribute("pageproxystate") != "valid") return; - - let value = gBrowser.currentURI.displaySpec; - let urlString = value + "\n" + gBrowser.contentTitle; - let htmlString = '' + value + ""; - - let windowUtils = window.windowUtils; - let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom; - let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); - canvas.width = 600 * scale; - let ctx = canvas.getContext("2d"); - ctx.font = fontString(inputStyle, scale); - let tabIcon = gBrowser.selectedTab.iconImage; - let image = new Image(); - image.src = tabIcon.src; - let textWidth = ctx.measureText(value).width / scale; - let imageHorizontalOffset = SPACING; - let imageVerticalOffset = SPACING; - let textHorizontalOffset = image.width - ? IMAGE_SIZE + - SPACING + - parseFloat(identityStyle.marginInlineEnd) + - parseFloat(inputStyle.paddingInlineStart) - : SPACING; - let textVerticalOffset = (INPUT_HEIGHT - parseInt(inputStyle.fontSize) + 1) / 2; - let backgroundColor = varToHex("var(--tooltip-bgcolor, var(--arrowpanel-background))"); - let textColor = varToHex("var(--tooltip-color, var(--arrowpanel-color))"); - let totalWidth = image.width - ? textWidth + IMAGE_SIZE + 3 * SPACING - : textWidth + 2 * SPACING; - let totalHeight = INPUT_HEIGHT; - roundRect( - ctx, - 0, - 0, - totalWidth * scale, - totalHeight * scale, - 5, - backgroundColor, - varToHex("var(--tooltip-border-color, var(--arrowpanel-border-color))") - ); - ctx.fillStyle = textColor; - ctx.textBaseline = "top"; - ctx.fillText(`${value}`, textHorizontalOffset * scale, textVerticalOffset * scale); - try { - ctx.drawImage( - image, - imageHorizontalOffset * scale, - imageVerticalOffset * scale, - IMAGE_SIZE * scale, - IMAGE_SIZE * scale - ); - } catch (e) { - // Sites might specify invalid data URIs favicons that will result in errors when - // trying to draw, we can just ignore this case and not paint any favicon. - } - - let dt = event.dataTransfer; - dt.setData("text/x-moz-url", urlString); - dt.setData("text/uri-list", value); - dt.setData("text/plain", value); - dt.setData("text/html", htmlString); - dt.setDragImage(canvas, 16, 16); - - // Don't cover potential drop targets on the toolbars or in content. - gURLBar.view.close(); - }; - // below is a less extreme, old version. the above adapts to many CSS styled aspects of the - // urlbar and identity box, to make the drag box look like a more accurate "copy" of the - // urlbar contents. - // eval( - // `gIdentityHandler.onDragStart = function ` + - // gIdentityHandler.onDragStart - // .toSource() - // .replace( - // /(let backgroundColor = ).*;/, - // `$1varToHex("var(--tooltip-bgcolor, var(--arrowpanel-background))");` - // ) - // .replace( - // /(let textColor = ).*;/, - // `$1varToHex("var(--tooltip-color, var(--arrowpanel-color))");` - // ) - // .replace(/ctx\.fillStyle = backgroundColor;/, ``) - // .replace( - // /ctx\.fillRect.*;/, - // `roundRect(ctx, 0, 0, totalWidth * scale, totalHeight * scale, 5, backgroundColor, varToHex("var(--tooltip-border-color, var(--arrowpanel-border-color))"));` - // ) - // ); - } - restoreOneOffsContextMenu() { - const urlbarOneOffsProto = Object.getPrototypeOf(this.urlbarOneOffs); - const oneOffsBase = Object.getPrototypeOf(urlbarOneOffsProto); - this.urlbarOneOffs._on_contextmenu = oneOffsBase._on_contextmenu; - } - urlbarResultsDetailedIcons() { - XPCOMUtils.defineLazyPreferenceGetter( - this, - "showRemoteIconsPref", - "services.sync.syncedTabs.showRemoteIcons", - true - ); - const { UrlbarResult } = ChromeUtils.import("resource:///modules/UrlbarResult.jsm"); - const { UrlbarSearchUtils } = ChromeUtils.import( - "resource:///modules/UrlbarSearchUtils.jsm" - ); - const { UrlbarProviderAutofill } = ChromeUtils.import( - "resource:///modules/UrlbarProviderAutofill.jsm" - ); - // these variables look unused but they're for the functions that will be modified - // dynamically and evaluated later like provider.startQuery.toSource() - let showRemoteIconsPref = this.showRemoteIconsPref; - let gUniqueIdSerial = 1; - const RECENT_REMOTE_TAB_THRESHOLD_MS = 72 * 60 * 60 * 1000; - function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - } - function getUniqueId(prefix) { - return prefix + (gUniqueIdSerial++ % 9999); - } - // modified functions for TabToSearch provider, to add engine icon. - function makeTTSOnboardingResult(engine, satisfiesAutofillThreshold = false) { - let [url] = UrlbarUtils.stripPrefixAndTrim(engine.getResultDomain(), { - stripWww: true, - }); - url = url.substr(0, url.length - engine.searchUrlPublicSuffix.length); - let result = new UrlbarResult( - UrlbarUtils.RESULT_TYPE.DYNAMIC, - UrlbarUtils.RESULT_SOURCE.SEARCH, - { - engine: engine.name, - url, - providesSearchMode: true, - icon: engine.iconURI?.spec || UrlbarUtils.ICON.SEARCH_GLASS, - dynamicType: "onboardTabToSearch", - satisfiesAutofillThreshold, - } - ); - result.resultSpan = 2; - result.suggestedIndex = 1; - return result; - } - function makeTTSResult(context, engine, satisfiesAutofillThreshold = false) { - let [url] = UrlbarUtils.stripPrefixAndTrim(engine.getResultDomain(), { - stripWww: true, - }); - url = url.substr(0, url.length - engine.searchUrlPublicSuffix.length); - let result = new UrlbarResult( - UrlbarUtils.RESULT_TYPE.SEARCH, - UrlbarUtils.RESULT_SOURCE.SEARCH, - ...UrlbarResult.payloadAndSimpleHighlights(context.tokens, { - engine: engine.name, - isGeneralPurposeEngine: engine.isGeneralPurposeEngine, - url, - providesSearchMode: true, - icon: engine.iconURI?.spec || UrlbarUtils.ICON.SEARCH_GLASS, - query: "", - satisfiesAutofillThreshold, - }) - ); - result.suggestedIndex = 1; - return result; - } - let RemoteTabs = gURLBar.view.controller.manager.getProvider("RemoteTabs"); - let TabToSearch = gURLBar.view.controller.manager.getProvider("TabToSearch"); - UrlbarUtils.RESULT_PAYLOAD_SCHEMA[ - UrlbarUtils.RESULT_TYPE.REMOTE_TAB - ].properties.clientType = { - type: "string", - }; - let src1 = RemoteTabs.startQuery.toSource(); - let src2 = gURLBar.view._updateRow.toSource(); - let src3 = TabToSearch.startQuery.toSource(); - if (!src1.includes("client.clientType")) { - eval( - `RemoteTabs.startQuery = async function ` + - src1 - .replace(/async startQuery/, ``) - .replace(/(device\: client\.name\,)/, `$1 clientType: client.clientType,`) - ); - } - if (!src2.includes("result.payload.clientType")) { - eval( - `gURLBar.view._updateRow = function ` + - src2 - .replace( - /(item\.removeAttribute\(\"stale\"\);)/, - `$1 item.removeAttribute("clientType"); item.removeAttribute("engine");` - ) - .replace( - /(item\.setAttribute\(\"type\", \"search\"\);)/, - `$1 if (result.payload.engine) item.setAttribute("engine", result.payload.engine);` - ) - .replace( - /(item\.setAttribute\(\"type\", \"tabtosearch\"\);)\n } else {/, - `$1 if (result.payload.engine) item.setAttribute("engine", result.payload.engine);\n } else if (result.providerName == "TokenAliasEngines") {\n item.setAttribute("type", "tokenaliasengine");\n if (result.payload.engine) item.setAttribute("engine", result.payload.engine);\n } else {` - ) - .replace( - /(item\.setAttribute\(\"type\"\, \"remotetab\"\);)/, - `$1 if (result.payload.clientType) item.setAttribute("clientType", result.payload.clientType);` - ) - ); - } - if (!src3.includes("uc_startQuery")) { - eval( - `TabToSearch.startQuery = async function uc_startQuery` + - src3 - .replace(/async startQuery/, ``) - .replace(/makeResult/, "makeTTSResult") - .replace(/makeOnboardingResult/, "makeTTSOnboardingResult") - ); - } - let css = `.urlbarView-row[type="remotetab"] .urlbarView-type-icon{background:var(--device-icon,url("chrome://browser/skin/sync.svg")) center/contain no-repeat;}.urlbarView-row[type="remotetab"][clientType="phone"]{--device-icon:url("chrome://browser/skin/device-phone.svg");}.urlbarView-row[type="remotetab"][clientType="tablet"]{--device-icon:url("chrome://browser/skin/device-tablet.svg");}.urlbarView-row[type="remotetab"][clientType="desktop"]{--device-icon:url("chrome://browser/skin/device-desktop.svg");}.urlbarView-row[type="remotetab"][clientType="tv"]{--device-icon:url("chrome://browser/skin/device-tv.svg");}.urlbarView-row[type="remotetab"][clientType="vr"]{--device-icon:url("chrome://browser/skin/device-vr.svg");}`; - let sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService( - Ci.nsIStyleSheetService - ); - let uri = makeURI("data:text/css;charset=UTF=8," + encodeURIComponent(css)); - if (sss.sheetRegistered(uri, sss.AUTHOR_SHEET)) return; - sss.loadAndRegisterSheet(uri, sss.AUTHOR_SHEET); - } - disableUrlbarInterventions() { - let { manager } = gURLBar.controller; - let interventions = manager.providers.find( - (provider) => provider.name === "UrlbarProviderInterventions" - ); - if (interventions) manager.unregisterProvider(interventions); - } - urlbarResultsSorting() { - let UnifiedComplete = gURLBar.view.controller.manager.muxers.get("UnifiedComplete"); - let sortSrc = UnifiedComplete.sort.toSource(); - if (!sortSrc.includes("getLogger")) - eval( - `UnifiedComplete.sort = function ` + - sortSrc - .replace(/sort/, ``) - .replace( - /logger/, - `UrlbarUtils.getLogger({ prefix: "MuxerUnifiedComplete" })` - ) - .replace( - /showSearchSuggestionsFirst\: true/, - `showSearchSuggestionsFirst: UrlbarPrefs.get("showSearchSuggestionsFirst")` - ) - ); - } - underlineSpaceResults() { - gURLBar.view._addTextContentWithHighlights = function (node, text, highlights) { - node.textContent = ""; - node.removeAttribute("no-highlights"); - if (!text) return; - // add an extra attribute to the text node when there are no highlights and the text is - // short. I was experimenting it but decided not to use it in duskFox. but you can use - // it in your own CSS. for example... - // .urlbarView-row[has-url]:not([has-action]) .urlbarView-title[no-highlights] {color: #FF0022} - // will highlight these titles when the row would otherwise seem kind of dull and empty. - // the problem with doing that is it kind of obfuscates the meaning otherwise conveyed - // by the text colors. firefox doesn't really make much use of that by default but - // duskFox ascribes very specific meanings to the 5 different colors used in urlbar - // result titles. applying one of them to titles just because they're otherwise empty - // could mess that up, and it runs the risk of confusing users. so I didn't use it but I - // left the option open in this script for user customization. - if (!highlights.length && text.length < 30) node.setAttribute("no-highlights", true); - // if the string contains nothing but 2 or more whitespace characters, replace them all - // with a non-breaking space character and set an attribute. CSS can then use - // .urlbarView-title[all-whitespace] to underline these titles so the otherwise - // invisible space characters will be visible. - if (/^\s{2,}$/.test(text) && !highlights.length) { - text = text.replace(/\s/g, `\u00A0`); - node.setAttribute("all-whitespace", true); - } else node.removeAttribute("all-whitespace"); - highlights = (highlights || []).concat([[text.length, 0]]); - let index = 0; - for (let [highlightIndex, highlightLength] of highlights) { - if (highlightIndex - index > 0) - node.appendChild( - this.document.createTextNode(text.substring(index, highlightIndex)) - ); - if (highlightLength > 0) { - let strong = this._createElement("strong"); - strong.textContent = text.substring( - highlightIndex, - highlightIndex + highlightLength - ); - node.appendChild(strong); - } - index = highlightIndex + highlightLength; - } - }; - } -} - -if (gBrowserInit.delayedStartupFinished) new UrlbarMods(); -else { - let delayedListener = (subject, topic) => { - if (topic == "browser-delayed-startup-finished" && subject == window) { - Services.obs.removeObserver(delayedListener, topic); - new UrlbarMods(); - } - }; - Services.obs.addObserver(delayedListener, "browser-delayed-startup-finished"); -} +// ==UserScript== +// @name Urlbar Mods +// @version 1.6.0 +// @author aminomancer +// @homepage https://github.com/aminomancer/uc.css.js +// @description Make some minor modifications to the urlbar. See the code comments below for more details. +// @license This Source Code Form is subject to the terms of the Creative Commons Attribution-NonCommercial-ShareAlike International License, v. 4.0. If a copy of the CC BY-NC-SA 4.0 was not distributed with this file, You can obtain one at http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +// ==/UserScript== + +class UrlbarMods { + static config = { + // recently the context menu for the search engine one-off buttons in + // the urlbar results panel has been disabled. but the context menu for + // the one-off buttons in the searchbar is still enabled. I'm not sure + // why they did this, and it's a really minor thing, but it's not like + // right-clicking the buttons does anything else, (at least on windows) + // so you may want to restore the context menu. + "restore one-offs context menu": false, + + // when you click & drag the identity box in the urlbar, it lets you + // drag and drop the URL into text fields, the tab bar, desktop, etc. + // while dragging it shows a little white box with the URL and favicon + // as the drag image. this can't be styled with CSS because it's drawn + // by the canvas 2D API. but we can easily change the function so that + // it sets the background and text colors equal to some CSS variables. + // it uses --tooltip-bgcolor, --tooltip-color, and --tooltip-border-color, + // or if those don't exist, it uses the vanilla variables + // --arrowpanel-background, --arrowpanel-color, and --arrowpanel-border-color. + // so if you use my theme duskFox it'll look similar to a tooltip. if + // you don't use my theme it'll look similar to a popup panel. + "style identity icon drag box": true, + + // the identity icon is missing tooltips and/or identifying icons for + // several states. in particular, there is no tooltip on pages with + // mixed content. on these pages the identity icon generally shows a + // lock icon with a warning sign on it. so this is the intermediate + // state between "secure" and "not secure." both of those states have + // their own special tooltips but the mixed security state does not. + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1736354) this is + // unfortunate because the mixed state is actually a composite of + // several states. in vanilla firefox, you currently can't tell which + // one without clicking the identity box to open the popup. hopefully + // this feature will be added to firefox but for now we can add it + // ourselves. we add tooltips so the user can distinguish between + // blocked active content, loaded active content, loaded passive + // content, weak cipher, or untrustworthy certificate. we also add + // tooltips to show when the user had HTTPS-only mode enabled but the + // site failed to load over https. there's also no tooltip to show on + // chrome UI pages or local files. hovering the identity icon just shows + // nothing. so we add these as well, so that it should say "This is a + // secure Nightly page" or "This page is stored on your computer" in a + // similar way to how it already shows on extension pages. While working + // on this I noticed that some types of pages are missing a unique + // class, which means they can't be given a unique icon. these pages are + // about:neterror and about:blocked. the former shows under many + // circumstances when a page fails to load; the latter shows when the + // user has blocked a specific page. if you use duskFox you might have + // noticed that the theme adds custom icons for certain error pages. + // previously this was just the HTTPS-only error page and + // about:certerror (shows when the certificate has a serious problem and + // firefox won't load the page without user interaction). we couldn't + // include these other error pages because they were just marked as + // "unknownIdentity" which is the same as the default class. so I + // couldn't style them without styling some perfectly valid local or + // system pages too, such as those with chrome:// URIs. so I'm extending + // the icon-setting method so it'll give the icon a unique class on + // these pages: "aboutNetErrorPage" and "aboutBlockedPage" which are + // pretty self-explanatory. they still keep .unknownIdentity, they just + // get an extra class. so you can style these yourself if you want but + // duskFox already styles them just like the other error pages — with + // the triangular warning sign. + "add new tooltips and classes for identity icon": true, + + // this sets attributes on each urlbar result according to 1) its + // corresponding search engine; and 2) its source device, if it's a + // remote tab. this allows CSS to give them unique icons, or to replace + // a search engine urlbar result's icon. my theme increases the + // prominence of the "type icon" in urlbar results. for bookmarks, this + // is a star. for open tabs, it's a tab icon. for remote tabs, aka + // synced tabs, it's a sync icon. with this option enabled, however, + // instead of showing a sync icon it will show a device icon specific to + // the type of device. so if the tab was synced from a cell phone, the + // type icon will show a phone. if it was synced from a laptop, it'll + // show a laptop, etc. the script will add some CSS to set the icons, + // but it won't change the way type icons are layed out. that's + // particular to my theme and it's purely a CSS issue, so I don't want + // the script to get involved in that. my urlbar-results.css file makes + // the type icon look basically like a 2nd favicon. it goes next to the + // favicon, rather than displaying on top of it. the script's CSS just + // changes the icon, so it'll fit with however you have type icons + // styled. if you don't use my theme but you want type icons to look + // more prominent, see urlbar-results.css and search "type-icon" + "show detailed icons in urlbar results": true, + + // normally, when you type something like "firefox install" or "clear + // cookies" in the urlbar, it suggests an "intervention" or "tip" which + // acts as a kind of shortcut to various settings and profile operations + // that some beginners might have a hard time finding. this is a fine + // feature for people who are new to firefox. but people who use firefox + // a lot may use the urlbar quickly and could easily accidentally hit + // enter on one of those results. I decided to add this option to the + // script because I very nearly wiped my entire profile due to the tip + // that lets you "Restore default settings and remove old add-ons for + // optimal performance." From my point of view, these results just waste + // space while presenting a hazard to the user, which makes them more of + // a liability than an asset. so they will be removed by this setting. + "disable urlbar intervention tips": true, + + // by default, urlbar results are not sorted consistently between + // regular mode and search mode. when you use the urlbar normally, the + // order of urlbar results is determined by a pref. + // (browser.urlbar.showSearchSuggestionsFirst) it's true by default, so + // search suggestions are shown at the top of the list. when you enter + // "search mode," e.g. by clicking a one-off search engine button in the + // urlbar results panel, this pref is no longer used. suggestions are + // shown at the top of the list no matter what — it's hard-coded into + // the urlbar muxer's sort function. if you don't change the pref this + // shouldn't matter, since the suggestions show at the top of the list + // by default, whether in search mode or not. but if you set the pref to + // false, (e.g. if you want top or recent site URLs to appear at the top + // of the list so you don't have to hit Tab so many times to reach them) + // you'll get URLs at the top of the list in regular mode, and URLs at + // the bottom of the list in search mode. + "sort urlbar results consistently": true, + + // when you type nothing but space or tab characters in the urlbar, the + // first result will have an empty title. consecutive whitespace + // characters don't add to the displayed node width so it ends up + // looking basically empty. we can change this by setting it to use + // non-breaking spaces instead of space characters, and adding an + // attribute "all-whitespace" to the title element. then your CSS can + // underline it. this is already done in uc-urlbar-results.css but if + // you wanna do it yourself: .urlbarView-title[all-whitespace] + // {text-decoration: underline} + "underline whitespace results": true, + }; + constructor() { + if (UrlbarMods.config["add new tooltips and classes for identity icon"]) + this.extendIdentityIcons(); + if (UrlbarMods.config["style identity icon drag box"]) this.styleIdentityIconDragBox(); + if (UrlbarMods.config["restore one-offs context menu"]) this.restoreOneOffsContextMenu(); + if (UrlbarMods.config["show detailed icons in urlbar results"]) + this.urlbarResultsDetailedIcons(); + if (UrlbarMods.config["disable urlbar intervention tips"]) + this.disableUrlbarInterventions(); + if (UrlbarMods.config["sort urlbar results consistently"]) this.urlbarResultsSorting(); + if (UrlbarMods.config["underline whitespace results"]) this.underlineSpaceResults(); + } + get urlbarOneOffs() { + return this._urlbarOneOffs || (this._urlbarOneOffs = gURLBar.view.oneOffSearchButtons); + } + async extendIdentityIcons() { + // Load the fluent strings into document.l10n if they haven't already been loaded. + MozXULElement.insertFTLIfNeeded("browser/browser.ftl"); + let [ + chromeUI, + localResource, + mixedDisplayContentLoadedActiveBlocked, + mixedDisplayContent, + mixedActiveContent, + weakCipher, + aboutNetErrorPage, + httpsOnlyErrorPage, + ] = await document.l10n // Retrieve strings from Firefox's built-in localization files. + .formatValues([ + "identity-connection-internal", + "identity-connection-file", + "identity-active-blocked", + "identity-passive-loaded", + "identity-active-loaded", + "identity-weak-encryption", + "identity-connection-failure", + "identity-https-only-info-no-upgrade", + ]) + // These strings were intended to be shown as descriptions in the identity popup, not as + // tooltips on the identity icon. As such, they have trailing periods, but the general + // convention with tooltips is to omit punctuation. So we need to remove them + // programmatically in a way that works for any and all translations. Therefore we'll + // use unicode property escapes with the new property Sentence_Terminal. This should + // include periods and every equivalent unicode character for sentence terminating punctuation. + .then((arr) => + arr.map((str) => + str.replace(/(^\p{Sentence_Terminal}+)|(\p{Sentence_Terminal}+$)/gu, "") + ) + ); + gIdentityHandler._fluentStrings = { + chromeUI, + localResource, + mixedDisplayContentLoadedActiveBlocked, + mixedDisplayContent, + mixedActiveContent, + weakCipher, + aboutNetErrorPage, + httpsOnlyErrorPage, + }; + gIdentityHandler._localPDF = function () { + if (!this._isPDFViewer) return false; + switch (gBrowser.selectedBrowser.documentURI?.scheme) { + case "chrome": + case "file": + case "resource": + case "about": + return true; + default: + return false; + } + }; + // Extend the built-in method that sets the identity icon's tooltip and class. + gIdentityHandler._refreshIdentityIcons = function () { + let icon_label = ""; + let tooltip = ""; + if (this._isSecureInternalUI) { + this._identityBox.className = isInitialPage(this._uri) ? "initialPage" : "chromeUI"; + let brandBundle = document.getElementById("bundle_brand"); + icon_label = brandBundle.getString("brandShorterName"); + tooltip = this._fluentStrings.chromeUI; + } else if (this._pageExtensionPolicy) { + this._identityBox.className = isInitialPage(this._uri) + ? "initialPage" + : "extensionPage"; + let extensionName = this._pageExtensionPolicy.name; + icon_label = gNavigatorBundle.getFormattedString("identity.extension.label", [ + extensionName, + ]); + } else if (this._uriHasHost && this._isSecureConnection) { + this._identityBox.className = "verifiedDomain"; + if (this._isMixedActiveContentBlocked) { + this._identityBox.classList.add("mixedActiveBlocked"); + tooltip = this._fluentStrings.mixedDisplayContentLoadedActiveBlocked; + } else if (!this._isCertUserOverridden) + tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", [ + this.getIdentityData().caOrg, + ]); + } else if (this._isBrokenConnection) { + this._identityBox.className = "unknownIdentity"; + if (this._isMixedActiveContentLoaded) { + this._identityBox.classList.add("mixedActiveContent"); + tooltip = this._fluentStrings.mixedActiveContent; + } else if (this._isMixedActiveContentBlocked) { + this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked"); + tooltip = this._fluentStrings.mixedDisplayContentLoadedActiveBlocked; + } else if (this._isMixedPassiveContentLoaded) { + this._identityBox.classList.add("mixedDisplayContent"); + tooltip = this._fluentStrings.mixedDisplayContent; + } else { + this._identityBox.classList.add("weakCipher"); + tooltip = this._fluentStrings.weakCipher; + } + } else if (this._isCertErrorPage) { + this._identityBox.className = "certErrorPage notSecureText"; + icon_label = gNavigatorBundle.getString("identity.notSecure.label"); + tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip"); + } else if (this._isAboutHttpsOnlyErrorPage) { + this._identityBox.className = "httpsOnlyErrorPage"; + tooltip = this._fluentStrings.httpsOnlyErrorPage; + } else if (this._isAboutNetErrorPage) { + // By default, about:neterror and about:blocked get the same "neutral icon." + // I'm adding classes here, "aboutNetErrorPage" and "aboutBlockedPage" so that + // userChrome.css can style them. Since duskFox gives other error pages a warning + // icon, I want to style these error pages the same icon. + this._identityBox.className = "aboutNetErrorPage unknownIdentity"; + tooltip = this._fluentStrings.aboutNetErrorPage; + } else if (this._isAboutBlockedPage) { + // A rare connection state, not really sure how to reproduce this in the wild. + this._identityBox.className = "aboutBlockedPage unknownIdentity"; + tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip"); + } else if (this._isPotentiallyTrustworthy || this._localPDF()) { + this._identityBox.className = isInitialPage(this._uri) + ? "initialPage" + : "localResource"; + tooltip = this._fluentStrings.localResource; + if (this._uri.spec.startsWith("about:reader?")) + this._identityBox.classList.add("readerMode"); + } else { + let warnOnInsecure = + this._insecureConnectionIconEnabled || + (this._insecureConnectionIconPBModeEnabled && + PrivateBrowsingUtils.isWindowPrivate(window)); + let className = warnOnInsecure ? "notSecure" : "unknownIdentity"; + this._identityBox.className = className; + tooltip = warnOnInsecure + ? gNavigatorBundle.getString("identity.notSecure.tooltip") + : ""; + let warnTextOnInsecure = + this._insecureConnectionTextEnabled || + (this._insecureConnectionTextPBModeEnabled && + PrivateBrowsingUtils.isWindowPrivate(window)); + if (warnTextOnInsecure) { + icon_label = gNavigatorBundle.getString("identity.notSecure.label"); + this._identityBox.classList.add("notSecureText"); + } + } + if (this._isCertUserOverridden) { + this._identityBox.classList.add("certUserOverridden"); + tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you"); + } + this._updateAttribute(this._identityIcon, "lock-icon-gray", this._useGrayLockIcon); + this._identityIcon.setAttribute("tooltiptext", tooltip); + if (this._pageExtensionPolicy) { + let extensionName = this._pageExtensionPolicy.name; + this._identityIcon.setAttribute( + "tooltiptext", + gNavigatorBundle.getFormattedString("identity.extension.tooltip", [ + extensionName, + ]) + ); + } + this._identityIconLabel.setAttribute("tooltiptext", tooltip); + this._identityIconLabel.setAttribute("value", icon_label); + this._identityIconLabel.collapsed = !icon_label; + }; + gIdentityHandler._refreshIdentityIcons(); + } + styleIdentityIconDragBox() { + // for a given string in CSS3 custom property syntax, e.g. "var(--tooltip-color)" or + // "var(--tooltip-color, rgb(255, 255, 255))", convert it to a hex code string e.g. + // "#FFFFFF" + function varToHex(variable) { + let temp = document.createElement("div"); + document.body.appendChild(temp); + temp.style.color = variable; + let rgb = getComputedStyle(temp).color; + temp.remove(); + rgb = rgb + .split("(")[1] + .split(")")[0] + .split(rgb.indexOf(",") > -1 ? "," : " "); + rgb.length = 3; + rgb.forEach((c, i) => { + c = (+c).toString(16); + rgb[i] = c.length === 1 ? "0" + c : c.slice(0, 2); + }); + return "#" + rgb.join(""); + } + // draw a rectangle with rounded corners + function roundRect(ctx, x, y, width, height, radius = 5, fill, stroke) { + if (typeof radius === "number") + radius = { tl: radius, tr: radius, br: radius, bl: radius }; + else { + let defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 }; + for (let side in defaultRadius) radius[side] = radius[side] || defaultRadius[side]; + } + ctx.beginPath(); + ctx.moveTo(x + radius.tl, y); + ctx.lineTo(x + width - radius.tr, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); + ctx.lineTo(x + width, y + height - radius.br); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height); + ctx.lineTo(x + radius.bl, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); + ctx.lineTo(x, y + radius.tl); + ctx.quadraticCurveTo(x, y, x + radius.tl, y); + ctx.closePath(); + if (fill) { + ctx.fillStyle = fill; + ctx.fill(); + } + if (stroke) { + ctx.strokeStyle = stroke; + ctx.stroke(); + } + } + function fontString(style, scale) { + let { fontWeight, fontFamily, fontSize } = style; + return `${fontWeight} ${parseFloat(fontSize) * scale}px ${fontFamily}`; + } + // override the internal dragstart callback so it uses variables instead of "white" and "black" + gIdentityHandler.onDragStart = function (event) { + const inputStyle = getComputedStyle(gURLBar.inputField); + const identityStyle = getComputedStyle(gURLBar._identityBox); + const iconStyle = getComputedStyle(document.getElementById("identity-icon")); + const IMAGE_SIZE = parseFloat(iconStyle.width); + const INPUT_HEIGHT = parseFloat(inputStyle.height); + const SPACING = (INPUT_HEIGHT - IMAGE_SIZE) / 2; + + if (gURLBar.getAttribute("pageproxystate") != "valid") return; + + let value = gBrowser.currentURI.displaySpec; + let urlString = value + "\n" + gBrowser.contentTitle; + let htmlString = '' + value + ""; + + let windowUtils = window.windowUtils; + let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom; + let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + canvas.width = 600 * scale; + let ctx = canvas.getContext("2d"); + ctx.font = fontString(inputStyle, scale); + let tabIcon = gBrowser.selectedTab.iconImage; + let image = new Image(); + image.src = tabIcon.src; + let textWidth = ctx.measureText(value).width / scale; + let imageHorizontalOffset = SPACING; + let imageVerticalOffset = SPACING; + let textHorizontalOffset = image.width + ? IMAGE_SIZE + + SPACING + + parseFloat(identityStyle.marginInlineEnd) + + parseFloat(inputStyle.paddingInlineStart) + : SPACING; + let textVerticalOffset = (INPUT_HEIGHT - parseInt(inputStyle.fontSize) + 1) / 2; + let backgroundColor = varToHex("var(--tooltip-bgcolor, var(--arrowpanel-background))"); + let textColor = varToHex("var(--tooltip-color, var(--arrowpanel-color))"); + let totalWidth = image.width + ? textWidth + IMAGE_SIZE + 3 * SPACING + : textWidth + 2 * SPACING; + let totalHeight = INPUT_HEIGHT; + roundRect( + ctx, + 0, + 0, + totalWidth * scale, + totalHeight * scale, + 5, + backgroundColor, + varToHex("var(--tooltip-border-color, var(--arrowpanel-border-color))") + ); + ctx.fillStyle = textColor; + ctx.textBaseline = "top"; + ctx.fillText(`${value}`, textHorizontalOffset * scale, textVerticalOffset * scale); + try { + ctx.drawImage( + image, + imageHorizontalOffset * scale, + imageVerticalOffset * scale, + IMAGE_SIZE * scale, + IMAGE_SIZE * scale + ); + } catch (e) { + // Sites might specify invalid data URIs favicons that will result in errors when + // trying to draw, we can just ignore this case and not paint any favicon. + } + + let dt = event.dataTransfer; + dt.setData("text/x-moz-url", urlString); + dt.setData("text/uri-list", value); + dt.setData("text/plain", value); + dt.setData("text/html", htmlString); + dt.setDragImage(canvas, 16, 16); + + // Don't cover potential drop targets on the toolbars or in content. + gURLBar.view.close(); + }; + // below is a less extreme, old version. the above adapts to many CSS styled aspects of the + // urlbar and identity box, to make the drag box look like a more accurate "copy" of the + // urlbar contents. + // eval( + // `gIdentityHandler.onDragStart = function ` + + // gIdentityHandler.onDragStart + // .toSource() + // .replace( + // /(let backgroundColor = ).*;/, + // `$1varToHex("var(--tooltip-bgcolor, var(--arrowpanel-background))");` + // ) + // .replace( + // /(let textColor = ).*;/, + // `$1varToHex("var(--tooltip-color, var(--arrowpanel-color))");` + // ) + // .replace(/ctx\.fillStyle = backgroundColor;/, ``) + // .replace( + // /ctx\.fillRect.*;/, + // `roundRect(ctx, 0, 0, totalWidth * scale, totalHeight * scale, 5, backgroundColor, varToHex("var(--tooltip-border-color, var(--arrowpanel-border-color))"));` + // ) + // ); + } + restoreOneOffsContextMenu() { + const urlbarOneOffsProto = Object.getPrototypeOf(this.urlbarOneOffs); + const oneOffsBase = Object.getPrototypeOf(urlbarOneOffsProto); + this.urlbarOneOffs._on_contextmenu = oneOffsBase._on_contextmenu; + } + urlbarResultsDetailedIcons() { + XPCOMUtils.defineLazyPreferenceGetter( + this, + "showRemoteIconsPref", + "services.sync.syncedTabs.showRemoteIcons", + true + ); + const { UrlbarResult } = ChromeUtils.import("resource:///modules/UrlbarResult.jsm"); + const { UrlbarSearchUtils } = ChromeUtils.import( + "resource:///modules/UrlbarSearchUtils.jsm" + ); + const { UrlbarProviderAutofill } = ChromeUtils.import( + "resource:///modules/UrlbarProviderAutofill.jsm" + ); + // these variables look unused but they're for the functions that will be modified + // dynamically and evaluated later like provider.startQuery.toSource() + let showRemoteIconsPref = this.showRemoteIconsPref; + let gUniqueIdSerial = 1; + const RECENT_REMOTE_TAB_THRESHOLD_MS = 72 * 60 * 60 * 1000; + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + function getUniqueId(prefix) { + return prefix + (gUniqueIdSerial++ % 9999); + } + // modified functions for TabToSearch provider, to add engine icon. + function makeTTSOnboardingResult(engine, satisfiesAutofillThreshold = false) { + let [url] = UrlbarUtils.stripPrefixAndTrim(engine.getResultDomain(), { + stripWww: true, + }); + url = url.substr(0, url.length - engine.searchUrlPublicSuffix.length); + let result = new UrlbarResult( + UrlbarUtils.RESULT_TYPE.DYNAMIC, + UrlbarUtils.RESULT_SOURCE.SEARCH, + { + engine: engine.name, + url, + providesSearchMode: true, + icon: engine.iconURI?.spec || UrlbarUtils.ICON.SEARCH_GLASS, + dynamicType: "onboardTabToSearch", + satisfiesAutofillThreshold, + } + ); + result.resultSpan = 2; + result.suggestedIndex = 1; + return result; + } + function makeTTSResult(context, engine, satisfiesAutofillThreshold = false) { + let [url] = UrlbarUtils.stripPrefixAndTrim(engine.getResultDomain(), { + stripWww: true, + }); + url = url.substr(0, url.length - engine.searchUrlPublicSuffix.length); + let result = new UrlbarResult( + UrlbarUtils.RESULT_TYPE.SEARCH, + UrlbarUtils.RESULT_SOURCE.SEARCH, + ...UrlbarResult.payloadAndSimpleHighlights(context.tokens, { + engine: engine.name, + isGeneralPurposeEngine: engine.isGeneralPurposeEngine, + url, + providesSearchMode: true, + icon: engine.iconURI?.spec || UrlbarUtils.ICON.SEARCH_GLASS, + query: "", + satisfiesAutofillThreshold, + }) + ); + result.suggestedIndex = 1; + return result; + } + let RemoteTabs = gURLBar.view.controller.manager.getProvider("RemoteTabs"); + let TabToSearch = gURLBar.view.controller.manager.getProvider("TabToSearch"); + UrlbarUtils.RESULT_PAYLOAD_SCHEMA[ + UrlbarUtils.RESULT_TYPE.REMOTE_TAB + ].properties.clientType = { + type: "string", + }; + let src1 = RemoteTabs.startQuery.toSource(); + let src2 = gURLBar.view._updateRow.toSource(); + let src3 = TabToSearch.startQuery.toSource(); + if (!src1.includes("client.clientType")) { + eval( + `RemoteTabs.startQuery = async function ` + + src1 + .replace(/async startQuery/, ``) + .replace(/(device\: client\.name\,)/, `$1 clientType: client.clientType,`) + ); + } + if (!src2.includes("result.payload.clientType")) { + eval( + `gURLBar.view._updateRow = function ` + + src2 + .replace( + /(item\.removeAttribute\(\"stale\"\);)/, + `$1 item.removeAttribute("clientType"); item.removeAttribute("engine");` + ) + .replace( + /(item\.setAttribute\(\"type\", \"search\"\);)/, + `$1 if (result.payload.engine) item.setAttribute("engine", result.payload.engine);` + ) + .replace( + /(item\.setAttribute\(\"type\", \"tabtosearch\"\);)\n } else {/, + `$1 if (result.payload.engine) item.setAttribute("engine", result.payload.engine);\n } else if (result.providerName == "TokenAliasEngines") {\n item.setAttribute("type", "tokenaliasengine");\n if (result.payload.engine) item.setAttribute("engine", result.payload.engine);\n } else {` + ) + .replace( + /(item\.setAttribute\(\"type\"\, \"remotetab\"\);)/, + `$1 if (result.payload.clientType) item.setAttribute("clientType", result.payload.clientType);` + ) + ); + } + if (!src3.includes("uc_startQuery")) { + eval( + `TabToSearch.startQuery = async function uc_startQuery` + + src3 + .replace(/async startQuery/, ``) + .replace(/makeResult/, "makeTTSResult") + .replace(/makeOnboardingResult/, "makeTTSOnboardingResult") + ); + } + let css = `.urlbarView-row[type="remotetab"] .urlbarView-type-icon{background:var(--device-icon,url("chrome://browser/skin/sync.svg")) center/contain no-repeat;}.urlbarView-row[type="remotetab"][clientType="phone"]{--device-icon:url("chrome://browser/skin/device-phone.svg");}.urlbarView-row[type="remotetab"][clientType="tablet"]{--device-icon:url("chrome://browser/skin/device-tablet.svg");}.urlbarView-row[type="remotetab"][clientType="desktop"]{--device-icon:url("chrome://browser/skin/device-desktop.svg");}.urlbarView-row[type="remotetab"][clientType="tv"]{--device-icon:url("chrome://browser/skin/device-tv.svg");}.urlbarView-row[type="remotetab"][clientType="vr"]{--device-icon:url("chrome://browser/skin/device-vr.svg");}`; + let sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService( + Ci.nsIStyleSheetService + ); + let uri = makeURI("data:text/css;charset=UTF=8," + encodeURIComponent(css)); + if (sss.sheetRegistered(uri, sss.AUTHOR_SHEET)) return; + sss.loadAndRegisterSheet(uri, sss.AUTHOR_SHEET); + } + disableUrlbarInterventions() { + let { manager } = gURLBar.controller; + let interventions = manager.providers.find( + (provider) => provider.name === "UrlbarProviderInterventions" + ); + if (interventions) manager.unregisterProvider(interventions); + } + urlbarResultsSorting() { + let UnifiedComplete = gURLBar.view.controller.manager.muxers.get("UnifiedComplete"); + let sortSrc = UnifiedComplete.sort.toSource(); + if (!sortSrc.includes("getLogger")) + eval( + `UnifiedComplete.sort = function ` + + sortSrc + .replace(/sort/, ``) + .replace( + /logger/, + `UrlbarUtils.getLogger({ prefix: "MuxerUnifiedComplete" })` + ) + .replace( + /showSearchSuggestionsFirst\: true/, + `showSearchSuggestionsFirst: UrlbarPrefs.get("showSearchSuggestionsFirst")` + ) + ); + } + underlineSpaceResults() { + gURLBar.view._addTextContentWithHighlights = function (node, text, highlights) { + node.textContent = ""; + node.removeAttribute("no-highlights"); + if (!text) return; + // add an extra attribute to the text node when there are no highlights and the text is + // short. I was experimenting it but decided not to use it in duskFox. but you can use + // it in your own CSS. for example... + // .urlbarView-row[has-url]:not([has-action]) .urlbarView-title[no-highlights] {color: #FF0022} + // will highlight these titles when the row would otherwise seem kind of dull and empty. + // the problem with doing that is it kind of obfuscates the meaning otherwise conveyed + // by the text colors. firefox doesn't really make much use of that by default but + // duskFox ascribes very specific meanings to the 5 different colors used in urlbar + // result titles. applying one of them to titles just because they're otherwise empty + // could mess that up, and it runs the risk of confusing users. so I didn't use it but I + // left the option open in this script for user customization. + if (!highlights.length && text.length < 30) node.setAttribute("no-highlights", true); + // if the string contains nothing but 2 or more whitespace characters, replace them all + // with a non-breaking space character and set an attribute. CSS can then use + // .urlbarView-title[all-whitespace] to underline these titles so the otherwise + // invisible space characters will be visible. + if (/^\s{2,}$/.test(text) && !highlights.length) { + text = text.replace(/\s/g, `\u00A0`); + node.setAttribute("all-whitespace", true); + } else node.removeAttribute("all-whitespace"); + highlights = (highlights || []).concat([[text.length, 0]]); + let index = 0; + for (let [highlightIndex, highlightLength] of highlights) { + if (highlightIndex - index > 0) + node.appendChild( + this.document.createTextNode(text.substring(index, highlightIndex)) + ); + if (highlightLength > 0) { + let strong = this._createElement("strong"); + strong.textContent = text.substring( + highlightIndex, + highlightIndex + highlightLength + ); + node.appendChild(strong); + } + index = highlightIndex + highlightLength; + } + }; + } +} + +if (gBrowserInit.delayedStartupFinished) new UrlbarMods(); +else { + let delayedListener = (subject, topic) => { + if (topic == "browser-delayed-startup-finished" && subject == window) { + Services.obs.removeObserver(delayedListener, topic); + new UrlbarMods(); + } + }; + Services.obs.addObserver(delayedListener, "browser-delayed-startup-finished"); +} diff --git a/JS/verticalTabsPane.uc.js b/JS/verticalTabsPane.uc.js index 57bbd2cf..1b886ad6 100644 --- a/JS/verticalTabsPane.uc.js +++ b/JS/verticalTabsPane.uc.js @@ -1,6 +1,6 @@ // ==UserScript== // @name Vertical Tabs Pane -// @version 1.5.5 +// @version 1.5.6 // @author aminomancer // @homepage https://github.com/aminomancer/uc.css.js // @description Create a vertical pane across from the sidebar that functions like the vertical @@ -119,11 +119,9 @@ this.registerSheet(); // ensure E10SUtils are available. required for showing tab's process ID in its tooltip, if the pref for that is enabled. if (!window.E10SUtils) - XPCOMUtils.defineLazyModuleGetters( - this, - "E10SUtils", - `resource://gre/modules/E10SUtils.jsm` - ); + XPCOMUtils.defineLazyModuleGetters(this, { + E10SUtils: "resource://gre/modules/E10SUtils.jsm", + }); else this.E10SUtils = window.E10SUtils; // get some localized strings for the tooltip XPCOMUtils.defineLazyGetter(this, "l10n", function () { @@ -1470,6 +1468,7 @@ }; let label; let align = true; // should we align to the tab or to the mouse? depends on which element was hovered. + let { linkedBrowser } = tab; const selectedTabs = gBrowser.selectedTabs; const contextTabInSelection = selectedTabs.includes(tab); const affectedTabsLength = contextTabInSelection ? selectedTabs.length : 1; @@ -1488,7 +1487,7 @@ } else if (row.audioButton.matches(":hover")) { let stringID; if (contextTabInSelection) { - stringID = tab.linkedBrowser.audioMuted + stringID = linkedBrowser.audioMuted ? "tabs.unmuteAudio2.tooltip" : "tabs.muteAudio2.tooltip"; label = stringWithShortcut(stringID, "key_toggleMute", affectedTabsLength); @@ -1496,7 +1495,7 @@ if (tab.hasAttribute("activemedia-blocked")) stringID = "tabs.unblockAudio2.tooltip"; else - stringID = tab.linkedBrowser.audioMuted + stringID = linkedBrowser.audioMuted ? "tabs.unmuteAudio2.background.tooltip" : "tabs.muteAudio2.background.tooltip"; label = PluralForm.get( @@ -1509,9 +1508,9 @@ label = tab._fullLabel || tab.getAttribute("label"); // show the tab's process ID in the tooltip? if (prefSvc.getBoolPref("browser.tabs.tooltipsShowPidAndActiveness", false)) - if (tab.linkedBrowser) { + if (linkedBrowser) { let [contentPid, ...framePids] = this.E10SUtils.getBrowserPids( - tab.linkedBrowser, + linkedBrowser, gFissionBrowser ); if (contentPid) { @@ -1522,7 +1521,7 @@ label += "]"; } } - if (tab.linkedBrowser.docShellIsActive) label += " [A]"; + if (linkedBrowser.docShellIsActive) label += " [A]"; } // add the container name to the tooltip? if (tab.userContextId) @@ -1536,7 +1535,7 @@ label += ` (${this.fluentStrings[ tab.hasAttribute("activemedia-blocked") ? "blockedString" - : tab.linkedBrowser.audioMuted + : linkedBrowser.audioMuted ? "mutedString" : "playingString" ].toLowerCase()})`; @@ -1553,15 +1552,12 @@ let url = e.target.querySelector(".places-tooltip-uri"); let icon = e.target.querySelector("#places-tooltip-insecure-icon"); title.textContent = label; - url.value = tab.linkedBrowser?.currentURI?.spec.replace(/^https:\/\//, ""); + url.value = linkedBrowser?.currentURI?.spec.replace(/^https:\/\//, ""); // show a lock icon to show tab security/encryption - if (tab.getAttribute("pending")) { - icon.hidden = true; - icon.removeAttribute("type"); - icon.setAttribute("pending", true); - return; - } else icon.removeAttribute("pending"); - let docURI = tab.linkedBrowser?.documentURI; + let pending = tab.hasAttribute("pending") || !linkedBrowser.browsingContext; + let docURI = pending + ? linkedBrowser?.currentURI + : linkedBrowser?.documentURI || linkedBrowser?.currentURI; if (docURI) { let homePage = new RegExp( `(${BROWSER_NEW_TAB_URL}|${HomePage.get(window)})`, @@ -1589,6 +1585,11 @@ icon.hidden = false; return; } + if (docURI.filePath == "blocked") { + icon.setAttribute("type", "blocked-page"); + icon.hidden = false; + return; + } icon.setAttribute("type", "about-page"); icon.hidden = false; return; @@ -1598,30 +1599,32 @@ return; } } - let prog = Ci.nsIWebProgressListener; - let state = tab.linkedBrowser?.securityUI?.state; - if (typeof state != "number" || state & prog.STATE_IS_SECURE) { - icon.hidden = true; - icon.setAttribute("type", "secure"); - return; - } - if (state & prog.STATE_IS_INSECURE) { - icon.setAttribute("type", "insecure"); - icon.hidden = false; - return; - } - if (state & prog.STATE_IS_BROKEN) { - if (state & prog.STATE_LOADED_MIXED_ACTIVE_CONTENT) { - icon.hidden = false; + if (linkedBrowser.browsingContext) { + let prog = Ci.nsIWebProgressListener; + let state = linkedBrowser?.securityUI?.state; + if (typeof state != "number" || state & prog.STATE_IS_SECURE) { + icon.hidden = true; + icon.setAttribute("type", "secure"); + return; + } + if (state & prog.STATE_IS_INSECURE) { icon.setAttribute("type", "insecure"); - } else { - icon.setAttribute("type", "mixed-passive"); icon.hidden = false; + return; + } + if (state & prog.STATE_IS_BROKEN) { + if (state & prog.STATE_LOADED_MIXED_ACTIVE_CONTENT) { + icon.hidden = false; + icon.setAttribute("type", "insecure"); + } else { + icon.setAttribute("type", "mixed-passive"); + icon.hidden = false; + } + return; } - return; } icon.hidden = true; - icon.setAttribute("type", "secure"); + icon.setAttribute("type", pending ? "pending" : "secure"); } // container tab settings affect what we need to show in the "New Tab" button's tooltip and context menu. // so we need to observe this preference and respond accordingly. diff --git a/README.md b/README.md index 193ea0e2..2956f047 100644 --- a/README.md +++ b/README.md @@ -1177,7 +1177,7 @@ Allows the urlbar to autofill full file paths from history instead of just host If you don't change the pref this shouldn't matter, since they both show suggestions at the top of the list. But if you set the pref to false, (e.g. if you want top or recent site URLs to appear at the top of the list so you don't have to hit Tab so many times to reach them) you'll get URLs at the top of the list in regular mode, and URLs at the bottom of the list in search mode. - In my opinion [this is a bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1727904), but as of 09/21 it does not look like Mozilla intends to accept a fix, so I'm putting my fix in this script. This setting changes the way sorting is calculated in search mode. Instead of automatically and invariably showing suggestions first, it will simply respect the `showSearchSuggestionsFirst` preference, just like it does in regular mode. + This setting changes the way sorting is calculated in search mode. Instead of automatically and invariably showing suggestions first, it will simply respect the `showSearchSuggestionsFirst` preference, just like it does in regular mode. 3. Optionally disable urlbar intervention tips. These are the urlbar results that prompt you to reset your profile or clear your cache when you type something similar. They're intended to make it easier for new users to find these functions, but they take up result slots and selecting them by mistake can have serious repercussions, even including a nearly complete profile wipe. So I disable them entirely. 4. Replace the search glass icon in "tab to search" and search engine alias urlbar results with the specific engine's icon. This doesn't apply to normal search results for your default engine, it only applies to results that appear when you type `@` or the name of an engine in the urlbar. These are search engine alias results and tab to search results, respectively. Using the engine's icon makes them easier to recognize. 5. When you have syncing enabled, typing `%` in the urlbar will show tabs that were synced from your other devices. Normally, the only indication that a result is a synced tab is the "action text" that shows the name of the device from which the tab was synced. duskFox (the CSS theme) adds a little type indicator icon to urlbar results like bookmarks, open tabs, pinned results, and synced tabs. duskFox's indicator for synced tabs is normally a little sync icon. diff --git a/resources/script-override/message-bar.js b/resources/script-override/message-bar.js new file mode 100644 index 00000000..c0d5ae94 --- /dev/null +++ b/resources/script-override/message-bar.js @@ -0,0 +1,88 @@ +/* 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/. */ + +"use strict"; + +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. +{ + class MessageBarElement extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({ mode: "open" }); + MozXULElement.insertFTLIfNeeded("toolkit/global/notification.ftl"); + document.l10n.connectRoot(this.shadowRoot); + const content = this.constructor.template.content.cloneNode(true); + shadowRoot.append(content); + this.closeButton.addEventListener("click", () => this.dismiss(), { + once: true, + }); + } + + disconnectedCallback() { + this.dispatchEvent(new CustomEvent("message-bar:close")); + } + + get closeButton() { + return this.shadowRoot.querySelector("button.close"); + } + + static get template() { + const template = document.createElement("template"); + + const commonStyles = document.createElement("link"); + commonStyles.rel = "stylesheet"; + commonStyles.href = "chrome://global/skin/in-content/common.css"; + const messageBarStyles = document.createElement("link"); + messageBarStyles.rel = "stylesheet"; + messageBarStyles.href = + "chrome://global/content/elements/message-bar.css"; + template.content.append(commonStyles, messageBarStyles); + + // A container for the entire message bar content, + // most of the css rules needed to provide the + // expected message bar layout is applied on this + // element. + const container = document.createElement("div"); + container.classList.add("container"); + template.content.append(container); + + // @aminomancer - put the close button ahead of the icon. + const closeIcon = document.createElement("button"); + closeIcon.classList.add("close", "ghost-button"); + document.l10n.setAttributes(closeIcon, "close-button-label"); + container.append(closeIcon); + + const icon = document.createElement("span"); + icon.classList.add("icon"); + container.append(icon); + + const barcontent = document.createElement("span"); + barcontent.classList.add("content"); + barcontent.append(document.createElement("slot")); + container.append(barcontent); + + const spacer = document.createElement("span"); + spacer.classList.add("spacer"); + container.append(spacer); + + Object.defineProperty(this, "template", { + value: template, + }); + + return template; + } + + dismiss() { + this.dispatchEvent(new CustomEvent("message-bar:user-dismissed")); + this.close(); + } + + close() { + this.remove(); + } + } + + customElements.define("message-bar", MessageBarElement); +} diff --git a/uc-misc.css b/uc-misc.css index 01ac6c3b..599c9f3a 100644 --- a/uc-misc.css +++ b/uc-misc.css @@ -21,6 +21,7 @@ tooltip { background-color: transparent !important; color: var(--tooltip-color) !important; border: none !important; + max-width: max(30vw, 40em); padding: 5px !important; margin-inline-start: -5px !important; margin-block-start: 16px !important; @@ -68,7 +69,6 @@ tooltip .uc-tooltip-box { .places-tooltip-box > hbox { display: flex !important; align-items: center !important; - max-width: 500px !important; color: var(--panel-shortcut-color) !important; } @@ -137,7 +137,7 @@ mixed active content still shows the normal lock with red strikethrough. */ /* extension pages, namely moz-extension:// */ #places-tooltip-insecure-icon[type="extension-page"], #tab-nav-tooltip-insecure-icon[type="extension-page"] { - list-style-image: url(chrome://browser/content/extension.svg) !important; + list-style-image: url(chrome://mozapps/skin/extensions/extension.svg) !important; } /* new tab or home page */ @@ -158,10 +158,15 @@ mixed active content still shows the normal lock with red strikethrough. */ -moz-context-properties: fill, stroke !important; */ } -/* show nothing on unloaded tabs, since determining which icon to show requires accessing the browser in session storage, -which lazy loads the browser and thereby wastes memory and computation */ -#places-tooltip-insecure-icon[pending], -#tab-nav-tooltip-insecure-icon[pending] { +/* blocked pages e.g., about:blocked */ +#places-tooltip-insecure-icon[type="blocked-page"], +#tab-nav-tooltip-insecure-icon[type="blocked-page"] { + list-style-image: url(chrome://userchrome/content/blocked.svg) !important; +} + +/* don't show secure badges on pending tabs */ +#places-tooltip-insecure-icon[type="pending"], +#tab-nav-tooltip-insecure-icon[type="pending"] { display: none !important; } @@ -433,12 +438,20 @@ checkbox .checkbox-check { } :focus:not(:focus-visible), -:focus:not(:focus-visible) > * { +:focus:not(:focus-visible) > *, +radiogroup:focus-visible radio[focused="true"] > .radio-label-box, +checkbox[native]:-moz-focusring > .checkbox-label-box { outline: unset !important; outline-offset: unset !important; -moz-outline-radius: unset !important; } +checkbox[native]:focus-visible > .checkbox-check, +radiogroup:focus-visible radio[focused="true"] > .radio-check, +radiogroup deck menulist:focus-visible { + outline: var(--default-focusring); +} + toolbarbutton:focus-visible, .popup-notification-button:focus-visible, .keyboard-focused-tab > .tab-stack > .tab-background, diff --git a/uc-navbar.css b/uc-navbar.css index 91e382b5..565b4972 100644 --- a/uc-navbar.css +++ b/uc-navbar.css @@ -712,7 +712,7 @@ link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infob background-color: var(--notification-box-bgcolor) !important; background-image: var(--notification-box-bgimage) !important; backdrop-filter: var(--notification-box-filter) !important; - padding-inline: 4px !important; + padding-inline: 4px 0 !important; align-items: center !important; flex-grow: 1 !important; text-overflow: ellipsis !important; @@ -755,21 +755,25 @@ link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infob white-space: nowrap !important; text-overflow: ellipsis !important; overflow: hidden !important; - margin-inline-start: var(--uc-notification-message-content-margin-inline-start, 8px) !important; + padding-inline: 0 4px !important; + margin-inline: 0 !important; + height: 100% !important; } link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar .notification-content .notification-message { - padding-block: 0 !important; + padding-block: 6px !important; min-height: auto !important; text-overflow: ellipsis !important; overflow: hidden !important; - margin-inline-end: 8px !important; + padding-inline: var(--uc-notification-message-content-margin-inline-start, 8px) 8px !important; + margin-inline: 0 !important; } link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar > .spacer { flex-grow: 0.1 !important; + display: none !important; } link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar .notification-content .notification-button-container @@ -787,6 +791,7 @@ link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infob link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar .notification-content .notification-button { margin: revert !important; + outline-offset: 0 !important; } .notificationbox-stack notification :is(button, toolbarbutton), @@ -795,6 +800,7 @@ link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infob border-radius: var(--in-content-button-border-radius) !important; font-weight: inherit !important; padding-block: 0 !important; + outline-offset: 0 !important; } link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar .notification-content .notification-button-container > .notification-button.small @@ -856,17 +862,20 @@ link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infob margin: 0 4px !important; position: absolute !important; opacity: var(--uc-notification-message-close-button-opacity, 0) !important; - transform: var(--uc-notification-message-close-button-transform, translateX(-15px) scale(0.5)) !important; + transform: var( + --uc-notification-message-close-button-transform, + translateX(-15px) scale(0.5) + ) !important; } -notification:hover .messageCloseButton, -link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar:hover .notification-close +notification:is(:hover, :focus-within) .messageCloseButton, +link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar:is(:hover, :focus-within) .notification-close { transform: none !important; opacity: 1 !important; } -link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar button.ghost-button:not(.semi-transparent):enabled:hover +link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar button.ghost-button:not(.semi-transparent):enabled:is(:hover, :focus-visible) { background-color: var(--in-content-button-background) !important; } @@ -876,8 +885,8 @@ link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infob background-color: var(--in-content-button-background-hover) !important; } -notification:hover .messageImage, -link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar:hover .icon +notification:is(:hover, :focus-within) .messageImage, +link[href="chrome://global/content/elements/message-bar.css"] ~ .container.infobar:is(:hover, :focus-within) .icon { transform: translateX(15px) scale(0.5) !important; opacity: 0 !important; diff --git a/uc-urlbar.css b/uc-urlbar.css index 67bbb115..0af258d9 100644 --- a/uc-urlbar.css +++ b/uc-urlbar.css @@ -1024,13 +1024,18 @@ so you can use those classes to set the icon. */ margin: 0 0 0 8px !important; } -#identity-permission-box:not([open]) #webrtc-sharing-icon, -.permission-popup-permission-icon.in-use:is(.camera-icon, .microphone-icon, .screen-icon) { +#identity-permission-box:not([open]) #webrtc-sharing-icon:not([paused="true"]) { fill: currentColor !important; -moz-context-properties: fill, fill-opacity !important; animation: 3s linear identity-sharing-icon-pulse infinite; } +.permission-popup-permission-icon.in-use:is(.camera-icon, .microphone-icon, .screen-icon) { + fill: currentColor !important; + -moz-context-properties: fill !important; + animation: 3s linear identity-sharing-icon-pulse infinite; +} + @keyframes identity-sharing-icon-pulse { 0%, 16.66%, diff --git a/userChrome.ag.css b/userChrome.ag.css index 4ae292be..b81bfe90 100644 --- a/userChrome.ag.css +++ b/userChrome.ag.css @@ -422,6 +422,7 @@ tooltip { background-color: transparent; color: var(--tooltip-color); border: none; + max-width: max(30vw, 40em); padding: 5px; pointer-events: none; --uc-tooltip-inner-padding: 4px 7px 6px 7px; diff --git a/utils/chrome.manifest b/utils/chrome.manifest index abeb81d8..91c37d52 100644 --- a/utils/chrome.manifest +++ b/utils/chrome.manifest @@ -14,11 +14,15 @@ override chrome://global/skin/arrowscrollbox.css ../resources/layout/arrowscroll # Override the tab element to restore the tab sound button and move the close button, among other things. override chrome://browser/content/tabbrowser-tab.js ../resources/script-override/tabMods.uc.js +# Override the notification message bar element to move the close button to the left in tab order. +override chrome://global/content/elements/message-bar.js ../resources/script-override/message-bar.js + # Override a bunch of icons that appear all over the browser. override chrome://global/skin/icons/check.svg ../resources/check.svg override chrome://global/skin/icons/success.svg ../resources/check.svg override chrome://global/skin/icons/check-partial.svg ../resources/check-partial.svg override chrome://global/skin/icons/radio.svg ../resources/radio.svg +override chrome://global/skin/icons/blocked.svg ../resources/blocked.svg override chrome://global/skin/icons/security-broken.svg ../resources/connection-mixed-active-loaded.svg override chrome://global/skin/icons/security-warning.svg ../resources/connection-mixed-passive-loaded.svg override chrome://global/skin/icons/security.svg ../resources/skin/connection-secure.svg