Noch mal etliche Versuche gestartet, nun sollte auch der Mittelklick funktionieren:
JavaScript
// ==UserScript==
// @name Toolbox Button
// @version 1.5.2-nightly152
// @author aminomancer
// @homepageURL https://github.com/aminomancer/uc.css.js
// @description Adds a toolbar button for content toolbox, browser toolbox, and popup autohide.
// @downloadURL https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/atoolboxButton.uc.js
// @updateURL https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/atoolboxButton.uc.js
// @license CC BY-NC-SA 4.0
// ==/UserScript==
(() => {
const l10n = {
alreadyOpenMsg: "Browser Werkzeuge sind bereits geöffnet.",
holdingOpenMsg: "Popups werden offen gehalten.",
lettingCloseMsg: "Popups werden automatisch geschlossen.",
bundles: {},
strings: new Map(),
getString(name, where) {
let string = this.strings.get(name);
if (string) return string;
string = this.bundles[where].GetStringFromName(name);
this.strings.set(name, string);
return string;
},
getFluentValue(id, args) {
return this.fluentStrings.formatValueSync(id, args);
},
get defaultLabel() {
return this.getString("toolbox.label", "toolbox");
},
get defaultTooltip() {
return this.defaultLabel;
},
};
ChromeUtils.defineLazyGetter(l10n.bundles, "menu", () =>
Services.strings.createBundle("chrome://devtools/locale/menus.properties")
);
ChromeUtils.defineLazyGetter(l10n.bundles, "toolbox", () =>
Services.strings.createBundle("chrome://devtools/locale/toolbox.properties")
);
ChromeUtils.defineLazyGetter(
l10n,
"fluentStrings",
() => new Localization(["devtools/client/toolbox.ftl"], true)
);
if (
/^chrome:\/\/browser\/content\/browser\.(xul|xhtml)$/i.test(location.href) &&
!CustomizableUI.getPlacementOfWidget("toolbox-button", true)
) {
const { loader } = ChromeUtils.importESModule(
"resource://devtools/shared/loader/Loader.sys.mjs"
);
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserToolboxLauncher:
"resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs",
});
for (const [key, val] of Object.entries({
gDevToolsBrowser:
"resource://devtools/client/framework/devtools-browser.js",
Actor: "resource://devtools/shared/protocol/Actor.js",
dumpn: "resource://devtools/shared/DevToolsUtils.js",
})) {
loader.lazyRequireGetter(lazy, key, val, true);
}
if (location.href !== "chrome://browser/content/browser.xhtml") return;
CustomizableUI.createWidget({
id: "toolbox-button",
type: "custom",
defaultArea: CustomizableUI.AREA_NAVBAR,
label: l10n.defaultLabel,
removable: true,
overflows: true,
tooltiptext: l10n.defaultTooltip,
onBuild(aDoc) {
const win = aDoc?.defaultView || aDoc?.ownerGlobal || window;
if (!win) {
console.error("Toolbox Button: no window available in onBuild()");
return null;
}
let CustomHint = {
...win.ConfirmationHint,
show(anchor, message, options = {}) {
this._reset();
this._message.removeAttribute("data-l10n-id");
this._message.textContent = message;
if (options.description) {
this._description.removeAttribute("data-l10n-id");
this._description.textContent = options.description;
this._description.hidden = false;
this._panel.classList.add("with-description");
} else {
this._description.hidden = true;
this._panel.classList.remove("with-description");
}
if (options.hideArrow) {
this._panel.setAttribute("hidearrow", "true");
}
if (options.hideCheck) {
this._animationBox.setAttribute("hidden", "true");
this._panel.setAttribute("data-message-id", "hideCheckHint");
} else {
this._animationBox.removeAttribute("hidden");
this._panel.setAttribute("data-message-id", "checkmarkHint");
}
const DURATION = options.duration || 1500;
this._panel.addEventListener(
"popupshown",
() => {
this._animationBox.setAttribute("animate", "true");
this._timerID = setTimeout(
() => this._panel.hidePopup(true),
DURATION + 120
);
},
{ once: true }
);
this._panel.addEventListener("popuphidden", () => this._reset(), {
once: true,
});
let { position, x, y } = options;
this._panel.openPopup(null, {
position,
triggerEvent: options.event,
});
this._panel.moveToAnchor(anchor, position, x, y);
},
_reset() {
if (this._timerID) {
clearTimeout(this._timerID);
this._timerID = null;
this._animationBox.removeAttribute("hidden");
}
if (this.__panel) {
this._panel.removeAttribute("hidearrow");
this._animationBox.removeAttribute("animate");
this._panel.removeAttribute("data-message-id");
this._panel.hidePopup();
}
},
_ensurePanel() {
if (!this.__panel) {
let wrapper = win.document.getElementById("confirmation-hint-wrapper");
wrapper?.replaceWith(wrapper.content);
this.__panel = ConfirmationHint.__panel =
win.document.getElementById("confirmation-hint");
}
},
};
let toolbarbutton = aDoc.createXULElement("toolbarbutton");
let badgeStack = aDoc.createXULElement("stack");
let icon = aDoc.createXULElement("image");
let label = aDoc.createXULElement("label");
let badgeLabel = aDoc.createElement("label");
for (const [key, val] of Object.entries({
class: "toolbarbutton-1 chromeclass-toolbar-additional",
badged: true,
label: l10n.defaultLabel,
id: "toolbox-button",
role: "button",
icon: "toolbox",
removable: true,
overflows: true,
tooltiptext: l10n.defaultTooltip,
})) {
toolbarbutton.setAttribute(key, val);
}
toolbarbutton.appendChild(badgeStack);
badgeStack.after(label);
badgeStack.appendChild(icon);
icon.after(badgeLabel);
badgeStack.setAttribute("class", "toolbarbutton-badge-stack");
icon.setAttribute("class", "toolbarbutton-icon");
badgeLabel.setAttribute("class", "toolbarbutton-badge");
for (const [key, val] of Object.entries({
class: "toolbarbutton-text",
crop: "right",
flex: "1",
value: l10n.defaultLabel,
})) {
label.setAttribute(key, val);
}
let prefSvc = Services.prefs;
let defaultPrefs = prefSvc.getDefaultBranch("");
let obSvc = Services.obs;
let toolboxBranch = "userChrome.toolboxButton";
let autoHide = "ui.popup.disable_autohide";
let autoTogglePopups =
"userChrome.toolboxButton.popupAutohide.toggle-on-toolbox-launch";
let mouseConfig = "userChrome.toolboxButton.mouseConfig";
const BROWSER_TOOLBOX_WINDOW_URL =
"chrome://devtools/content/framework/browser-toolbox/window.html";
toolbarbutton.browserToolboxOpen = false;
toolbarbutton.pendingBrowserToolboxOpen = false;
toolbarbutton.manualPopupAutohide = false;
toolbarbutton.autoTogglePopups = true;
toolbarbutton.popupAutoHide = false;
toolbarbutton.mouseConfig = {
contentToolbox: 0,
browserToolbox: 2,
popupHide: 1,
};
function getPref(root, pref) {
switch (root.getPrefType(pref)) {
case root.PREF_BOOL:
return root.getBoolPref(pref);
case root.PREF_INT:
return root.getIntPref(pref);
case root.PREF_STRING:
return root.getStringPref(pref);
default:
return null;
}
}
function hasBrowserToolboxWindow() {
try {
for (const win of Services.wm.getEnumerator(null)) {
if (!win || win.closed) continue;
if (win.location?.href === BROWSER_TOOLBOX_WINDOW_URL) {
return true;
}
}
} catch (ex) {
console.error("Browser toolbox window scan failed:", ex);
}
return false;
}
function refreshState() {
const open = hasBrowserToolboxWindow() || toolbarbutton.pendingBrowserToolboxOpen;
toolbarbutton.browserToolboxOpen = open;
badgeLabel.textContent = open ? "1" : "";
// If the user explicitly toggled the pref via middle click, do not let
// the automatic sync logic override that choice anymore.
if (toolbarbutton.manualPopupAutohide) {
return;
}
if (!toolbarbutton.autoTogglePopups) return;
if (open && !toolbarbutton.popupAutoHide) {
prefSvc.setBoolPref(autoHide, true);
} else if (!open && toolbarbutton.popupAutoHide) {
prefSvc.setBoolPref(autoHide, false);
}
}
function prefObserver(sub, _top, pref) {
let value = getPref(sub, pref);
switch (pref) {
case autoHide:
if (value === null) value = false;
toolbarbutton.popupAutoHide = value;
if (value) {
toolbarbutton.setAttribute("icon", "autohide");
icon.style.fill = "var(--toolbarbutton-icon-fill-attention)";
} else {
toolbarbutton.setAttribute("icon", "toolbox");
icon.style.removeProperty("fill");
}
break;
case autoTogglePopups:
if (value === null) value = true;
toolbarbutton.autoTogglePopups = value;
break;
case mouseConfig:
if (value === null) {
value = JSON.stringify({
contentToolbox: 0,
browserToolbox: 2,
popupHide: 1,
});
}
toolbarbutton.mouseConfig = JSON.parse(value);
toolbarbutton.setStrings();
break;
}
}
toolbarbutton.setStrings = function () {
let hotkey, labelString;
for (const [key, val] of Object.entries(toolbarbutton.mouseConfig)) {
if (val === 0) {
switch (key) {
case "contentToolbox":
labelString = l10n.getString("toolbox.label", "toolbox");
hotkey = aDoc.getElementById("key_toggleToolbox");
break;
case "browserToolbox":
labelString = l10n.getString("browserToolboxMenu.label", "menu");
hotkey = aDoc.getElementById("key_browserToolbox");
break;
case "popupHide":
labelString = l10n.getFluentValue(
"toolbox-meatball-menu-noautohide-label"
);
break;
}
}
}
let shortcut = hotkey ? ` (${ShortcutUtils.prettifyShortcut(hotkey)})` : "";
toolbarbutton.label = labelString;
label.value = labelString;
toolbarbutton.tooltipText = labelString + shortcut;
};
toolbarbutton.triggerAnimation = function () {
this.addEventListener(
"animationend",
() => this.removeAttribute("animate"),
{ once: true }
);
this.setAttribute("animate", "true");
};
let onClick = function (e) {
if (e.type === "auxclick" && e.button !== 1) {
return;
}
let { button } = e;
let accel = e.getModifierState("Accel");
let shift = e.getModifierState("Shift");
if (accel) {
if (button == 2 && !shift) {
return;
}
if (button == 0 && AppConstants.platform == "macosx") {
button = 2;
accel = false;
}
}
switch (button) {
case this.mouseConfig.contentToolbox:
lazy.gDevToolsBrowser.toggleToolboxCommand(win.gBrowser);
break;
case this.mouseConfig.browserToolbox:
refreshState();
if (toolbarbutton.browserToolboxOpen || toolbarbutton.pendingBrowserToolboxOpen) {
CustomHint.show(toolbarbutton, l10n.alreadyOpenMsg, {
event: e,
hideCheck: true,
});
} else {
try {
toolbarbutton.pendingBrowserToolboxOpen = true;
refreshState();
const launcher = lazy.BrowserToolboxLauncher.init({
forceMultiprocess: accel && shift,
onRun() {
toolbarbutton.pendingBrowserToolboxOpen = false;
refreshState();
},
});
if (!launcher) {
toolbarbutton.pendingBrowserToolboxOpen = false;
refreshState();
CustomHint.show(
toolbarbutton,
"Browser Toolbox konnte nicht gestartet werden.",
{ event: e, hideCheck: true }
);
}
} catch (ex) {
toolbarbutton.pendingBrowserToolboxOpen = false;
refreshState();
console.error("Browser Toolbox launch failed:", ex);
CustomHint.show(
toolbarbutton,
"Browser Toolbox konnte nicht gestartet werden.",
{ event: e, hideCheck: true }
);
}
}
break;
case this.mouseConfig.popupHide:
// Middle click should enable popup auto-hide and keep that choice,
// without the automatic refresh logic turning it back off.
toolbarbutton.manualPopupAutohide = true;
prefSvc.setBoolPref(autoHide, true);
CustomHint.show(toolbarbutton, l10n.holdingOpenMsg, {
event: e,
hideCheck: false,
});
toolbarbutton.triggerAnimation();
break;
default:
return;
}
e.preventDefault();
};
if (AppConstants.platform === "macosx") {
toolbarbutton.onmousedown = onClick;
toolbarbutton.onclick = e => {
if (e.getModifierState("Accel")) return;
e.preventDefault();
};
} else {
toolbarbutton.onclick = onClick;
toolbarbutton.onauxclick = onClick;
}
function toolboxInit() {
prefObserver(prefSvc, null, autoHide);
prefObserver(prefSvc, null, autoTogglePopups);
prefObserver(prefSvc, null, mouseConfig);
refreshState();
}
function uninit() {
clearInterval(toolbarbutton._syncTimer);
toolbarbutton.browserToolboxOpen = false;
toolbarbutton.pendingBrowserToolboxOpen = false;
toolbarbutton.manualPopupAutohide = false;
prefSvc.removeObserver(autoHide, prefObserver);
prefSvc.removeObserver(toolboxBranch, prefObserver);
window.removeEventListener("unload", uninit);
}
defaultPrefs.setBoolPref(autoTogglePopups, true);
defaultPrefs.setStringPref(
mouseConfig,
'{"contentToolbox": 0, "browserToolbox": 2, "popupHide": 1}'
);
window.addEventListener("unload", uninit);
prefSvc.addObserver(autoHide, prefObserver);
prefSvc.addObserver(toolboxBranch, prefObserver);
toolbarbutton._syncTimer = setInterval(refreshState, 1000);
refreshState();
if (gBrowserInit.delayedStartupFinished) {
toolboxInit();
} else {
let delayedListener2 = (subject, topic) => {
if (topic == "browser-delayed-startup-finished" && subject == window) {
obSvc.removeObserver(delayedListener2, topic);
toolboxInit();
}
};
obSvc.addObserver(delayedListener2, "browser-delayed-startup-finished");
}
return toolbarbutton;
},
});
}
let styleSvc = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
Ci.nsIStyleSheetService
);
let toolboxCSS = /* css */ `
.toolbarbutton-1#toolbox-button {
--uc-toolbox-button: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAdUlEQVQokZVSwRHAIAgLPYfoXs7RCTpG53Avt7APrhaFU8gLMEEJAkEQgFbc7IxkVjt0r6Sp7VIVITumBpKt00FA2ThmjXzkfMMWO8EZFSj8LrUyjsG9b9DaJXq+qAIVxEUxtLHpaXE95dj1NcK2rmbwaGJ4Af0tIg00j/6iAAAAAElFTkSuQmCC');
--uc-autohide-button: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAATCAYAAABlcqYFAAABeUlEQVQ4ja3UMSiEcRjH8Z8jKWU8q03JYEAhZ7PcZDFYrMpOWaTEgsEom4RFBmEjcSaEU0onFN0Rd9dFzr3er+Ut7vV/eS/vU7/l3/s8n/f/PvVKrkKKIm0hJZEoIUmkTaQO90w3MFniYK8MewHRgACQLKRmE7IRIALSkglJBYzcmhDPhrtwGJ6fIZeDTOYrwF5ri2dfSchBZxumOpoY5+mXvpKQ7NzID+Dl4Z6TitCvn8w3kpbIJS5dxBv7TbV/7sU3chOuAssqIqzMOfGZUV5W1yAWIzE4+D9kp6vLGf0KWLjrIx9nO9L4T6SmhrWePs7GI2A/FgHvJ/PEqwNaPBLHPZEi4HB6ipugdoJEQeJqZRYAO59kt7+B1B+AF+L5101UCgppwOYwWmceWubk6+zChKx7IamVXk6XxzgtD/Hh4wZOFkxIO5LtfjgrsdVdRjzkPdAA55HqfyAONIRU8Pmm2ObzPNKAEfgGNSMtIl37xZxcIy2YbvAJBGtcN/WRF/UAAAAASUVORK5CYII');
list-style-image: var(--uc-toolbox-button);
align-items: center;
}
.toolbarbutton-1#toolbox-button[icon="autohide"] {
list-style-image: var(--uc-autohide-button);
}
.toolbarbutton-1#toolbox-button .toolbarbutton-badge-stack {
justify-items: center;
}
.toolbarbutton-1#toolbox-button .toolbarbutton-icon {
height: 16px;
width: 16px;
transition: fill 50ms ease-in-out 0s;
background-image: var(--uc-toolbox-button), var(--uc-autohide-button);
background-size: 0, 0;
}
@media (prefers-reduced-motion: no-preference) {
.toolbarbutton-1#toolbox-button[animate] .toolbarbutton-icon {
animation-name: toolboxButtonPulse;
animation-duration: 200ms;
animation-iteration-count: 1;
animation-timing-function: ease-in-out;
}
}
@keyframes toolboxButtonPulse {
from { transform: scale(1); }
40% { transform: scale(0.7); }
to { transform: scale(1); }
}
#confirmation-hint[data-message-id="hideCheckHint"] #confirmation-hint-message {
margin-inline: 0;
}
#confirmation-hint-checkmark-animation-container {
width: 16px !important;
height: 16px !important;
}
#confirmation-hint-checkmark-animation-container[animate] > #confirmation-hint-checkmark-image {
background-image: url("file:///E:/ICONS/FF/Checkbox.checked16-2.png") !important;
min-width: 16px !important;
max-width: 16px !important;
min-height: 16px !important;
max-height: 16px !important;
animation-name: unset !important;
}
`;
if (location.href !== "chrome://browser/content/browser.xhtml") return;
let styleURI = makeURI(`data:text/css;charset=UTF=8,${encodeURIComponent(toolboxCSS)}`);
if (!styleSvc.sheetRegistered(styleURI, styleSvc.AUTHOR_SHEET)) {
styleSvc.loadAndRegisterSheet(styleURI, styleSvc.AUTHOR_SHEET);
}
let observer = new MutationObserver(() => {
if (document.getElementById("key_toggleToolbox")) {
CustomizableUI.getWidget("toolbox-button")
.forWindow(window)
.node.setStrings();
observer.disconnect();
observer = null;
}
});
observer.observe(document.body, { childList: true });
})();
Alles anzeigen
