firefox使用uc脚本实现内置面可使用鼠标手势 及其他功能
参考链接:
https://github.com/xiaoxiaoflood/firefox-scripts/tree/master/chrome
https://zhuanlan.zhihu.com/p/566320674
https://icloudnative.io/posts/customize-firefox/
请注意你必须使用ESR128版本,也就是最新的ESR版本才能实现
步骤展示:
1、打开about:config页面,查找toolkit.legacyUserProfileCustomizations.stylesheets选项,设置为true
2、下载fx-folder.zip并将解压得到的config.js和defaults文件夹都放到C:\Program Files\Mozilla Firefox(firefox的安装文件夹)中
2、点击 firefox 的三道杠--帮助--更多排障信息
在 配置文件文件夹 C:\Users\xxx\AppData\Roaming\Mozilla\Firefox\Profiles\l6h2owy7.default-release中新建一个chrome文件夹
3、把utils.zip(或者其他两个)下载并解压出来得到的utils文件夹放到上面新建的chrome文件夹里
4、现在可以去找.uc.js文件并放到chrome文件夹中来使用了
5、有的脚本需要在“更多排障信息”那里点击“清除启动缓存”才生效
其他常用配置:
新标签页打开书签:browser.tabs.loadBookmarksInTabs
新标签页打开小搜索框搜索结果:browser.search.openintab
去除顶部标签页并且可以控制自动隐藏侧边栏的CSS文件
这个也是直接放chrome文件夹里
userChrome.css
/* @filename: userChrome.css */
@media (prefers-color-scheme:dark) {
:root {
--bg: #1a1b26;
--urlbar-bg: #0d0d15;
--urlbar-border-top: #000;
--urldrop-bg: #0d0d15;
--urlbar-border-bottom: #404040;
--urlbar-height: 30px;
--urlbar-outline: #414868;
--fullscreen-warn: rgb(25, 25, 25);
--arrowpanel-background: var(--urlbar-bg) !important;
--button-hover-bgcolor: rgba(159, 159, 159, 0.35) !important;
--button-active-bgcolor: rgba(255, 255, 255, .2) !important;
--button-bgcolor: rgba(117, 117, 117, 0.9) !important;
--toolbarbutton-icon-fill-opacity: 0.8 !important;
--arrowpanel-border-color: rgb(55, 55, 55) !important;
--identity-btn-hover-color: rgba(117, 117, 117) !important;
--dark-menu-background-color: rgba(34, 34, 36, .5) !important;
--dark-menu-border-color: rgb(55, 55, 55) !important;
--dark-menuitem-hover-background-color: rgba(159, 159, 159, 0.35) !important;
}
}
@media (prefers-color-scheme:light) {
:root {
--bg: #f1f5f9;
--urlbar-bg: #e1e8ef;
--urlbar-border-top: #9ca3af;
--urlbar-border-bottom: #fff;
--urldrop-bg: #e1e8ef;
--urlbar-outline: #94a3b8;
--fullscreen-warn: rgb(25, 25, 25);
--arrowpanel-background: #e2e8f0 !important;
--button-hover-bgcolor: #e1e8ef !important;
--button-active-bgcolor: #e1e8ef !important;
--identity-btn-hover-color: white !important;
}
}
/* URL BAR */
#urlbar {
border-radius: 30px !important;
border: 0 !important;
}
#urlbar:not(.megabar):hover {
outline: 1px solid var(--urlbar-outline) !important;
}
#urlbar[focused="true"] {
border: 0 !important;
border-top: 1px solid var(--urlbar-border-top) !important;
border-bottom: 1px solid var(--urlbar-border-bottom) !important;
border-radius: 30px !important;
}
#urlbar[breakout][breakout-extend] {
border: none !important;
box-shadow: rgba(0, 0, 0, 0.09) 0px 2px 1px, rgba(0, 0, 0, 0.09) 0px 4px 2px,
rgba(0, 0, 0, 0.09) 0px 8px 4px, rgba(0, 0, 0, 0.09) 0px 16px 8px,
rgba(0, 0, 0, 0.09) 0px 32px 16px !important;
top: calc((var(--urlbar-toolbar-height) - var(--urlbar-height)) / 2) !important;
/* left: 0 !important; */
/* width: 100% !important; */
width: calc(--urlbar-width) !important;
}
#urlbar-container {
margin-top: 3px;
display: initial;
}
#urlbar-background {
box-shadow: none !important;
border-radius: 30px !important;
background: var(--urlbar-bg) !important;
}
#urlbar[focused="true"] #urlbar-background {
box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.35) !important;
outline: none !important;
}
#wrapper-urlbar-container #urlbar {
height: var(--urlbar-height) !important;
}
/* Active Address/Search Field Dropdown */
#urlbar[breakout][breakout-extend]>#urlbar-background {
outline: none !important;
box-shadow: none !important;
background: var(--urldrop-bg) !important;
border-radius: 15px !important;
}
#urlbar[breakout][breakout-extend]>#urlbar-input-container,
#urlbar-input-container {
height: var(--urlbar-height) !important;
padding-block: 0px !important;
padding-inline: 0px !important;
transition: none !important;
}
/* Idendity icon button */
#identity-icon-box.identity-box-button {
margin: 3px 0 3px 3px;
border-radius: 14px !important;
background-color: var(--bg) !important;
opacity: 0.8
}
#identity-icon-box.identity-box-button:hover {
cursor: pointer;
background-color: var(--identity-btn-hover-color) !important;
}
/* Track protection icon */
#tracking-protection-icon-container {
border-radius: 16px !important;
}
#tracking-protection-icon-container:hover {
cursor: pointer !important;
}
/* Star button */
#star-button-box {
border-radius: 16px !important;
}
#star-button-box {
cursor: pointer !important;
}
/* | Borders | */
.tabbrowser-tab::after {
border: 0 !important;
}
.titlebar-spacer[type="pre-tabs"] {
border: 0 !important;
}
#navigator-toolbox {
border: 1 !important;
}
#urlbar[open]>.urlbarView>.urlbarView-body-outer>.urlbarView-body-inner {
border-top: 0 !important;
}
/* Active Tab */
:root:not([lwt-default-theme-in-dark-mode]) .tab-background[selected],
.tab-background[multiselected="true"] {
background: rgba(0, 0, 0, .05) !important;
}
:root[lwt-default-theme-in-dark-mode] .tab-background[selected],
.tab-background[multiselected="true"] {
background: rgba(0, 0, 0, .2) !important;
}
/* Navigation Bar */
#nav-bar {
background-color: var(--bg) !important;
}
/* Bookmarks Bar */
#PersonalToolbar {
background-color: var(--bg) !important;
}
/* Navigation Bar Separator */
#navigator-toolbox {
border-color: var(--bg) !important;
}
/* Navigation bar Buttons */
toolbarbutton:hover {
cursor: pointer;
}
/* Show Tab close button on hover */
.tabbrowser-tab:not([pinned]) .tab-close-button {
display: -moz-box !important;
opacity: 0;
visibility: collapse !important;
transition: opacity 0.25s, visibility 0.25s ease-in !important;
}
.tabbrowser-tab:not([pinned]):hover .tab-close-button {
opacity: 1;
visibility: visible !important;
border-radius: 3px 3px 3px 3px !important;
}
/* Show URL buttons on Hover */
#nav-bar:not([customizing="true"])>#nav-bar-customization-target>#urlbar-container:not(:hover)>#urlbar:not([focused])>#urlbar-input-container>#page-action-buttons {
opacity: 0;
}
#page-action-buttons {
transition: opacity 0.15s ease;
}
#nav-bar:not([customizing="true"])>#nav-bar-customization-target>#urlbar-container:not(:hover)>#urlbar:not([focused])>#urlbar-input-container>#tracking-protection-icon-container {
opacity: 0;
}
#tracking-protection-icon-container {
transition: opacity 0.15s ease;
}
/*Full Screen Warning*/
#fullscreen-warning {
background-color: var(--fullscreen-warn) !important;
border-color: var(--fullscreen-warn) !important;
max-width: 500px !important;
max-height: 50px !important;
border-radius: 50px !important;
font-size: 12px !important;
opacity: 0.8 !important;
}
.pointerlockfswarning-generic-text,
.pointerlockfswarning-domain-text {
font-size: 15px !important;
color: rgb(255, 255, 255) !important;
text-shadow: none !important;
}
/* Uncomment to Hide tabs bar for Tree style tabs (Credit u/jfgxyz on Reddit) */
toolbar#TabsToolbar {
height: 0px;
min-height: 0 !important;
background-color: var(--bg) !important;
}
.toolbar-items {
visibility: collapse;
}
/* ----- Move menu buttons ----- */
/* :root {
--toolbar-start-end-padding: 2px !important;
} */
#nav-bar #PanelUI-button {
-moz-box-ordinal-group: 0 !important;
}
#nav-bar #PanelUI-button #PanelUI-menu-button {
margin-right: 2px !important;
margin-left: 2px !important;
}
/* toolbar:not([customizing]) > #nav-bar-overflow-button {
-moz-box-ordinal-group: 1 !important;
}
toolbar:not([customizing]) > #nav-bar-customization-target {
-moz-box-ordinal-group: 2 !important;
} */
/* ----- Close/min/max fix ----- */
/* Fix popup position */
#appMenu-popup {
margin-inline: -244px !important;
}
#nav-bar {
padding-left: 0px !important;
padding-right: 118px !important;
position: static !important;
}
#navigator-toolbox:not([inFullscreen]) #TabsToolbar .titlebar-buttonbox-container {
visibility: visible !important;
display: block !important;
position: absolute !important;
top: 1px !important;
left: unset !important;
right: 0 !important;
}
#TabsToolbar .titlebar-min {
-moz-box-ordinal-group: 0 !important;
}
#TabsToolbar .titlebar-max,
.titlebar-restore {
-moz-box-ordinal-group: 1 !important;
}
#TabsToolbar .titlebar-close {
-moz-box-ordinal-group: 2 !important;
}
#navigator-toolbox[inFullscreen] #navigator-toolbox #TabsToolbar .titlebar-buttonbox-container {
-moz-box-ordinal-group: 1 !important;
}
#navigator-toolbox #TabsToolbar .titlebar-buttonbox-container {
-moz-box-ordinal-group: 1 !important;
}
@media only screen and (max-width: 670px) {
#main-window #navigator-toolbox:not([inFullscreen]) #TabsToolbar .titlebar-buttonbox-container {
visibility: visible !important;
position: relative !important;
display: block !important;
}
#main-window #navigator-toolbox:not([inFullscreen]) #nav-bar {
padding-right: initial !important;
}
#TabsToolbar.browser-toolbar {
display: flex !important;
justify-content: flex-end !important;
}
}
#toolbar-menubar {
/* menubar bg color */
background-color: var(--bg) !important;
}
/* Line up the Windows controls with the rest of the icons in the toolbar. */
:root:not([sizemode="maximized"]) .titlebar-buttonbox-container {
padding-top: 3px;
}
.titlebar-buttonbox {
z-index: 3 !important;
padding-right: 3px;
}
.titlebar-buttonbox * {
border-radius: 5px;
width: 35px;
height: 38px;
}
/* SIDEBERY */
/* hides the sidebar header */
#sidebar-header {
display: none !important;
}
.tab[selected="true"] {
visibility: collapse;
height: 0px;
}
.tabbrowser-tab {
visibility: collapse;
height: 0px;
}
.tabbrowser-tab[visuallyselected="true"] {
visibility: collapse;
height: 0px;
}
/* 锁定状态 - 红色背景 */
#sidebar-autohide-toggle:not(.autohide-disabled) {
background-color: #ff4d4d !important;
}
/* 自动隐藏状态 - 绿色背景 */
#sidebar-autohide-toggle.autohide-disabled {
background-color: #4dff88 !important;
}
/* AUTO HIDE SIDE BAR */
/* === 侧边栏拖动调节宽度 === */
#sidebar-box {
--uc-sidebar-min-width: 40px !important;
--uc-sidebar-max-width: 500px !important;
--uc-sidebar-default-width: 150px !important;
--uc-sidebar-current-width: var(--uc-sidebar-default-width);
--uc-sidebar-hover-width: var(--uc-sidebar-current-width, --uc-sidebar-default-width) !important;
--uc-autohide-sidebar-delay: 1ms !important;
position: relative !important;
min-width: var(--uc-sidebar-min-width) !important;
width: var(--uc-sidebar-current-width) !important;
max-width: var(--uc-sidebar-max-width) !important;
transition: width 0.2s ease !important;
z-index: 100 !important;
}
/* 自动隐藏状态保持过渡 */
#sidebar-box.autohide-enabled {
position: relative !important;
min-width: var(--uc-sidebar-min-width) !important;
max-width: var(--uc-sidebar-min-width) !important;
z-index: 100 !important;
transition: all 0.2s ease !important;
}
#sidebar-box.autohide-enabled #sidebar {
transition: min-width 115ms linear var(--uc-autohide-sidebar-delay) !important;
min-width: var(--uc-sidebar-min-width) !important;
will-change: min-width;
}
#sidebar-box.autohide-enabled:hover>#sidebar {
min-width: var(--uc-sidebar-current-width) !important;
transition-delay: 0ms !important;
}
/* 拖动时临时禁用过渡动画 */
#sidebar-box.dragging {
transition: none !important;
/* 添加这个确保没有其他过渡干扰 */
-webkit-transition: none !important;
-moz-transition: none !important;
}
/* 添加拖动手柄 */
#sidebar-resizer {
position: absolute !important;
top: 0 !important;
right: 0 !important;
width: 5px !important;
height: 100% !important;
background: rgba(128, 128, 128, 0.3) !important;
cursor: col-resize !important;
z-index: 1000 !important;
opacity: 0 !important;
transition: opacity 0.2s ease !important;
user-select: none !important;
}
#sidebar-resizer:hover {
background: rgba(128, 128, 128, 0.6) !important;
opacity: 1 !important;
}
#sidebar-box.autohide-disabled {
min-width: var(--uc-sidebar-min-width) !important;
max-width: var(--uc-sidebar-max-width) !important;
width: var(--uc-sidebar-current-width, var(--uc-sidebar-default-width)) !important;
transition: width 0.4s ease,
min-width 0.4s ease,
max-width 0.4s ease !important;
}
/* 隐藏侧边栏头部 */
#sidebar-header {
display: none !important;
}
/* 侧边栏面板样式 */
.sidebar-panel {
background-color: transparent !important;
color: var(--newtab-text-primary-color) !important;
}
.sidebar-panel #search-box {
-moz-appearance: none !important;
background-color: rgba(249, 249, 250, 0.1) !important;
color: inherit !important;
}
/* 添加侧边栏分隔线 */
#sidebar,
#sidebar-header {
background-color: inherit !important;
border-inline-width: 0px 1px;
}
#sidebar-box:not([positionend])> :-moz-locale-dir(rtl),
#sidebar-box[positionend]>* {
border-inline-width: 1px 0px;
}
/* 移动状态面板避免被侧边栏覆盖 */
#sidebar-box:not([positionend]):hover~#appcontent #statuspanel {
inset-inline: auto 0px !important;
}
#sidebar-box:not([positionend]):hover~#appcontent #statuspanel-label {
margin-inline: 0px !important;
border-left-style: solid !important;
}
控制自动展开和侧边栏宽度的.uc.js文件
sidebarResizerAndAutohide.uc.js
// @filename sidebarResizerAndAutohide.uc.js
// @description Add drag-to-resize and autohide toggle for Firefox sidebar
// @version 1.4
// @author exfeitu
// ==/UserScript==
(function () {
'use strict';
// 全局防重初始化
if (window._sidebarResizerAndAutohideInitialized) return;
window._sidebarResizerAndAutohideInitialized = true;
// 日志输出优化
const DEBUG = true;
function log(...args) {
if (DEBUG) console.log("[SidebarResizerAndAutohide]", ...args);
}
// SVG 图标数据
const ICONS = {
locked: 'image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="16" height="16" fill="%23666666"/></svg>',
autohide: 'image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="8" fill="%23666666"/></svg>'
};
// 异步等待元素函数 - 非阻塞
function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
// 先检查是否已经存在
const element = document.querySelector(selector);
if (element) {
log("Element found immediately:", selector);
resolve(element);
return;
}
// 使用 MutationObserver 监听 DOM 变化
const observer = new MutationObserver((mutations) => {
const element = document.querySelector(selector);
if (element) {
log("Element found via observer:", selector);
observer.disconnect();
resolve(element);
}
});
// 开始观察
observer.observe(document.documentElement || document, {
childList: true,
subtree: true
});
// 超时处理
if (timeout > 0) {
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout waiting for element: ${selector}`));
}, timeout);
}
});
}
// 初始化主函数 - 分步异步执行
async function initSidebarResizerAndAutohide() {
try {
log("开始初始化侧边栏功能...");
// 等待关键元素加载
await Promise.all([
waitForElement('#sidebar-box'),
waitForElement('#nav-bar')
]);
// 异步执行各项初始化
setTimeout(() => {
loadSavedWidth();
createResizer();
initSidebarAutohide();
}, 0); // 放入下一个事件循环
} catch (e) {
console.error('Sidebar initialization failed:', e);
}
}
// 创建拖动手柄
function createResizer() {
try {
const sidebarBox = document.getElementById('sidebar-box');
if (!sidebarBox) {
console.warn("[SidebarResizer] sidebar-box not found");
return;
}
log("Found sidebar-box, creating resizer...");
// 如果已经存在,则移除旧的
const existing = document.getElementById('sidebar-resizer');
if (existing) {
log("[SidebarResizer] Removing old resizer");
existing.remove();
}
// 创建新的 resizer 元素
const resizer = document.createElement('div');
resizer.id = 'sidebar-resizer';
resizer.title = 'Drag to resize sidebar';
// 立即添加到 DOM
sidebarBox.appendChild(resizer);
log("✅ Sidebar resizer added to DOM");
// 绑定事件
setupResizerEvents(resizer, sidebarBox);
} catch (e) {
console.error("Failed to create resizer:", e);
}
}
// 设置拖动事件 - 分离函数便于管理
function setupResizerEvents(resizer, sidebarBox) {
let isResizing = false;
let startX = 0;
let startWidth = 0;
let currentWidth = 0;
log("[SidebarResizer] Setting up resizer events");
// 默认隐藏调节条
resizer.style.opacity = '0';
resizer.style.pointerEvents = 'auto'; // 始终启用鼠标事件
resizer.addEventListener('mousedown', function (e) {
if (e.button !== 0) return; // 只响应左键
log("[SidebarResizer] Mousedown on resizer");
isResizing = true;
startX = e.clientX;
// 获取当前宽度
const computedStyle = window.getComputedStyle(sidebarBox);
startWidth = parseInt(computedStyle.width) || 200;
log("[SidebarResizer] Start resizing from width:", startWidth);
// 临时禁用过渡动画以便拖动流畅
sidebarBox.classList.add('dragging');
// 设置全局样式
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
// 保持调节条可见
resizer.style.opacity = '1';
e.preventDefault();
e.stopPropagation();
}, true);
document.addEventListener('mousemove', function (e) {
if (!isResizing) return;
const deltaX = e.clientX - startX;
currentWidth = Math.max(40, Math.min(500, startWidth + deltaX));
log("[SidebarResizer] Dragging - new width:", currentWidth);
// 应用新宽度(实时更新)
sidebarBox.style.setProperty('--uc-sidebar-current-width', currentWidth + 'px');
sidebarBox.style.width = currentWidth + 'px';
}, true);
document.addEventListener('mouseup', function (e) {
if (!isResizing) return;
log("[SidebarResizer] Mouseup - final width:", currentWidth);
isResizing = false;
// 恢复过渡动画
sidebarBox.classList.remove('dragging');
// 恢复全局样式
document.body.style.cursor = '';
document.body.style.userSelect = '';
// 保存宽度
if (currentWidth > 0) {
saveSidebarWidth(currentWidth);
}
// 隐藏调节条
resizer.style.opacity = '0';
e.preventDefault();
e.stopPropagation();
}, true);
// 防止事件冒泡
resizer.addEventListener('click', function (e) {
e.stopPropagation();
});
// 鼠标悬停显示调节条
resizer.addEventListener('mouseenter', function () {
if (!isResizing) {
log("[SidebarResizer] Mouse enter resizer");
resizer.style.opacity = '1';
}
});
resizer.addEventListener('mouseleave', function () {
if (!isResizing) {
log("[SidebarResizer] Mouse leave resizer");
resizer.style.opacity = '0';
}
});
}
// 保存侧边栏宽度
function saveSidebarWidth(width) {
try {
xPref.set('userChrome.sidebar.width', width.toString());
} catch (e) {
console.warn("Failed to save sidebar width");
}
}
// 加载保存的宽度
function loadSavedWidth() {
try {
const savedWidth = xPref.get('userChrome.sidebar.width', '');
if (savedWidth) {
const width = parseInt(savedWidth);
if (width >= 40 && width <= 500) {
const sidebarBox = document.getElementById('sidebar-box');
if (sidebarBox) {
setTimeout(() => {
sidebarBox.style.setProperty('--uc-sidebar-current-width', width + 'px');
sidebarBox.style.width = width + 'px';
}, 0);
}
}
}
} catch (e) {
console.warn('Failed to load sidebar width');
}
}
// 自动隐藏功能部分
function initSidebarAutohide() {
try {
log("开始初始化侧边栏自动隐藏功能...");
// 异步添加CSS和按钮
setTimeout(() => {
createToggleButtons();
applyAutohideState();
}, 0);
log("侧边栏自动隐藏初始化完成");
} catch (e) {
console.error("侧边栏自动隐藏初始化失败:", e);
}
}
// 创建切换按钮
function createToggleButtons() {
let button = document.getElementById("sidebar-autohide-toggle");
if (button) {
log("Autohide button already exists, updating attributes");
button.setAttribute("removable", "true");
button.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
return;
}
// 创建按钮元素
const buttonContainer = document.createElement("div");
buttonContainer.id = "sidebar-autohide-toggle";
buttonContainer.className = "toolbarbutton-1 chromeclass-toolbar-additional";
buttonContainer.setAttribute("tooltiptext", "切换侧边栏自动隐藏功能");
buttonContainer.setAttribute("removable", "true");
// 设置按钮样式
buttonContainer.style.cssText = `
width: 32px !important;
height: 32px !important;
min-width: 32px !important;
margin: 0 2px !important;
border-radius: 4px !important;
background: transparent !important;
border: none !important;
cursor: pointer !important;
user-select: none !important;
position: relative !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
`;
// 绑定点击事件
buttonContainer.addEventListener("click", function (event) {
log("[Autohide] Button clicked");
event.stopPropagation();
event.preventDefault();
toggleSidebarAutohide(event);
}, {
capture: true,
passive: false
});
// 异步添加到导航栏
setTimeout(() => {
const navbar = document.getElementById("nav-bar");
if (navbar && !document.getElementById("sidebar-autohide-toggle")) {
navbar.insertBefore(buttonContainer, navbar.firstChild);
log("按钮已添加到导航栏最左侧");
}
}, 0);
}
// 获取保存的状态
function getAutohideState() {
try {
const value = xPref.get("uc.sidebar.autohide");
return value === true || value === "true";
} catch (e) {
return true; // 默认值
}
}
// 保存状态
function setAutohideState(state) {
try {
xPref.set("uc.sidebar.autohide", state);
} catch (e) {
// 静默失败或简单记录
console.warn("Failed to save autohide state");
}
}
// 切换自动隐藏状态
function toggleSidebarAutohide(event) {
event.stopPropagation();
event.preventDefault();
try {
const currentStatus = getAutohideState();
const newStatus = !currentStatus;
log("[Autohide] Toggling from", currentStatus, "to", newStatus);
setAutohideState(newStatus);
applyAutohideState();
// 强制刷新按钮状态(避免延迟)
updateAutohideButton();
} catch (e) {
console.error("切换侧边栏自动隐藏失败:", e);
}
return false;
}
// 更新按钮图标和状态
function updateAutohideButton() {
const button = document.getElementById("sidebar-autohide-toggle");
if (!button) {
log("[Autohide] Button not found for update");
return;
}
try {
const isEnabled = getAutohideState();
log("[Autohide] Updating button state:", isEnabled);
button.style.backgroundImage = 'none';
if (isEnabled) {
button.classList.remove("autohide-disabled");
button.setAttribute("tooltiptext", "侧边栏自动隐藏功能 (当前: 自动隐藏)");
button.style.setProperty('background-image', `url("${ICONS.autohide}")`, 'important');
} else {
button.classList.add("autohide-disabled");
button.setAttribute("tooltiptext", "侧边栏自动隐藏功能 (当前: 锁定显示)");
button.style.setProperty('background-image', `url("${ICONS.locked}")`, 'important');
}
button.style.backgroundSize = '16px 16px';
button.style.backgroundRepeat = 'no-repeat';
button.style.backgroundPosition = 'center';
} catch (e) {
console.error("更新按钮状态失败:", e);
}
}
// 应用自动隐藏状态
function applyAutohideState() {
const sidebarBox = document.getElementById("sidebar-box");
if (!sidebarBox) {
log("[Autohide] Sidebar box not found");
return;
}
try {
const isEnabled = getAutohideState();
log("[Autohide] Applying state:", isEnabled);
if (isEnabled) {
enableAutohide(sidebarBox);
} else {
disableAutohide(sidebarBox);
}
updateAutohideButton();
} catch (e) {
console.error("应用自动隐藏状态失败:", e);
}
}
// 启用自动隐藏
function enableAutohide(sidebarBox) {
log("[Autohide] Enabling autohide");
sidebarBox.classList.add("autohide-enabled");
sidebarBox.classList.remove("autohide-disabled");
}
// 禁用自动隐藏
function disableAutohide(sidebarBox) {
log("[Autohide] Disabling autohide");
sidebarBox.classList.remove("autohide-enabled");
sidebarBox.classList.add("autohide-disabled");
}
// 启动初始化 - 使用事件监听器确保时机正确
function startInitialization() {
if (document.readyState === 'loading') {
log("Document still loading, waiting for DOMContentLoaded");
document.addEventListener('DOMContentLoaded', () => {
initSidebarResizerAndAutohide();
});
} else {
// 使用 setTimeout 确保不会阻塞当前执行
log("Document loaded, starting initialization");
setTimeout(() => {
initSidebarResizerAndAutohide();
}, 100);
}
}
// 启动初始化过程
startInitialization();
})();
自己用AI写了一个能调整侧边栏宽度的脚本,但是有些小bug,我不太懂js和css,也懒得弄了,现在也勉强能用,过段时间回去用claude改下试试好了,qwen-coder3效果实在是不太行。
顺带一提,我这个是配合sidebery食用的,我的配置文件如下:
sidebery-data-2025.08.25-16.24.39.json
{
"containers": {},
"settings": {
"nativeScrollbars": true,
"nativeScrollbarsThin": false,
"nativeScrollbarsLeft": true,
"selWinScreenshots": true,
"updateSidebarTitle": true,
"markWindow": false,
"markWindowPreface": "[Sidebery] ",
"ctxMenuNative": true,
"ctxMenuRenderInact": true,
"ctxMenuRenderIcons": true,
"ctxMenuIgnoreContainers": "",
"navBarLayout": "hidden",
"navBarInline": true,
"navBarSide": "left",
"hideAddBtn": false,
"hideSettingsBtn": false,
"navBtnCount": true,
"hideEmptyPanels": false,
"hideDiscardedTabPanels": false,
"navActTabsPanelLeftClickAction": "none",
"navActBookmarksPanelLeftClickAction": "none",
"navTabsPanelMidClickAction": "discard",
"navBookmarksPanelMidClickAction": "none",
"navSwitchPanelsWheel": true,
"subPanelRecentlyClosedBar": true,
"subPanelBookmarks": true,
"subPanelHistory": true,
"subPanelSync": true,
"groupLayout": "list",
"containersSortByName": true,
"skipEmptyPanels": false,
"dndTabAct": false,
"dndTabActDelay": 750,
"dndTabActMod": "none",
"dndExp": "pointer",
"dndExpDelay": 750,
"dndExpMod": "none",
"dndOutside": "win",
"dndActTabFromLink": true,
"dndActSearchTab": true,
"dndMoveTabs": false,
"dndMoveBookmarks": false,
"searchBarMode": "none",
"searchPanelSwitch": "same_type",
"searchBookmarksShortcut": "",
"searchHistoryShortcut": "",
"warnOnMultiTabClose": "collapsed",
"activateLastTabOnPanelSwitching": true,
"activateLastTabOnPanelSwitchingLoadedOnly": true,
"switchPanelAfterSwitchingTab": "always",
"tabRmBtn": "hover",
"activateAfterClosing": "prev",
"activateAfterClosingStayInPanel": false,
"activateAfterClosingGlobal": true,
"activateAfterClosingNoFolded": true,
"activateAfterClosingNoDiscarded": true,
"askNewBookmarkPlace": true,
"tabsRmUndoNote": true,
"tabsUnreadMark": false,
"tabsUpdateMark": "all",
"tabsUpdateMarkFirst": true,
"tabsReloadLimit": 5,
"tabsReloadLimitNotif": true,
"showNewTabBtns": true,
"newTabBarPosition": "after_tabs",
"tabsPanelSwitchActMove": true,
"tabsPanelSwitchActMoveAuto": true,
"tabsUrlInTooltip": "full",
"newTabCtxReopen": false,
"tabWarmupOnHover": true,
"tabSwitchDelay": 0,
"forceDiscard": true,
"moveNewTabPin": "end",
"moveNewTabParent": "last_child",
"moveNewTabParentActPanel": false,
"moveNewTab": "after",
"moveNewTabActivePin": "end",
"pinnedTabsPosition": "panel",
"pinnedTabsList": false,
"pinnedAutoGroup": false,
"pinnedNoUnload": false,
"pinnedForcedDiscard": false,
"tabsTree": true,
"groupOnOpen": true,
"tabsTreeLimit": 5,
"autoFoldTabs": false,
"autoFoldTabsExcept": "none",
"autoExpandTabs": true,
"autoExpandTabsOnNew": true,
"rmChildTabs": "folded",
"tabsLvlDots": true,
"discardFolded": false,
"discardFoldedDelay": 0,
"discardFoldedDelayUnit": "sec",
"tabsTreeBookmarks": false,
"treeRmOutdent": "branch",
"autoGroupOnClose": false,
"autoGroupOnClose0Lvl": false,
"autoGroupOnCloseMouseOnly": false,
"ignoreFoldedParent": false,
"showNewGroupConf": true,
"sortGroupsFirst": true,
"colorizeTabs": false,
"colorizeTabsSrc": "domain",
"colorizeTabsBranches": false,
"colorizeTabsBranchesSrc": "url",
"inheritCustomColor": true,
"previewTabs": false,
"previewTabsMode": "p",
"previewTabsPageModeFallback": "n",
"previewTabsInlineHeight": 70,
"previewTabsPopupWidth": 280,
"previewTabsTitle": 2,
"previewTabsUrl": 1,
"previewTabsSide": "right",
"previewTabsDelay": 500,
"previewTabsFollowMouse": true,
"previewTabsWinOffsetY": 36,
"previewTabsWinOffsetX": 6,
"previewTabsInPageOffsetY": 0,
"previewTabsInPageOffsetX": 0,
"previewTabsCropRight": 0,
"hideInact": false,
"hideFoldedTabs": false,
"hideFoldedParent": "none",
"nativeHighlight": true,
"warnOnMultiBookmarkDelete": "collapsed",
"autoCloseBookmarks": false,
"autoRemoveOther": false,
"highlightOpenBookmarks": false,
"activateOpenBookmarkTab": false,
"showBookmarkLen": true,
"bookmarksRmUndoNote": true,
"loadBookmarksOnDemand": true,
"pinOpenedBookmarksFolder": true,
"oldBookmarksAfterSave": "ask",
"loadHistoryOnDemand": true,
"fontSize": "m",
"animations": true,
"animationSpeed": "fast",
"theme": "proton",
"density": "default",
"colorScheme": "ff",
"snapNotify": false,
"snapExcludePrivate": false,
"snapInterval": 6,
"snapIntervalUnit": "hr",
"snapLimit": 0,
"snapLimitUnit": "snap",
"snapAutoExport": false,
"snapAutoExportType": "json",
"snapAutoExportPath": "Sidebery/snapshot-%Y.%M.%D-%h.%m.%s",
"snapMdFullTree": true,
"hScrollAction": "none",
"onePanelSwitchPerScroll": false,
"wheelAccumulationX": true,
"wheelAccumulationY": true,
"navSwitchPanelsDelay": 128,
"scrollThroughTabs": "panel",
"scrollThroughVisibleTabs": true,
"scrollThroughTabsSkipDiscarded": true,
"scrollThroughTabsExceptOverflow": false,
"scrollThroughTabsCyclic": true,
"scrollThroughTabsScrollArea": -10,
"autoMenuMultiSel": true,
"multipleMiddleClose": false,
"longClickDelay": 500,
"wheelThreshold": false,
"wheelThresholdX": 10,
"wheelThresholdY": 60,
"tabDoubleClick": "duplicate",
"tabsSecondClickActPrev": false,
"tabsSecondClickActPrevPanelOnly": false,
"tabsSecondClickActPrevNoUnload": false,
"shiftSelAct": true,
"activateOnMouseUp": false,
"tabLongLeftClick": "none",
"tabLongRightClick": "none",
"tabMiddleClick": "close",
"tabPinnedMiddleClick": "discard",
"tabMiddleClickCtrl": "discard",
"tabMiddleClickShift": "duplicate",
"tabCloseMiddleClick": "close",
"tabsPanelLeftClickAction": "none",
"tabsPanelDoubleClickAction": "tab",
"tabsPanelRightClickAction": "menu",
"tabsPanelMiddleClickAction": "tab",
"newTabMiddleClickAction": "new_child",
"bookmarksLeftClickAction": "open_in_new",
"bookmarksLeftClickActivate": true,
"bookmarksLeftClickPos": "default",
"bookmarksMidClickAction": "open_in_new",
"bookmarksMidClickActivate": false,
"bookmarksMidClickRemove": false,
"bookmarksMidClickPos": "default",
"historyLeftClickAction": "open_in_act",
"historyLeftClickActivate": true,
"historyLeftClickPos": "after",
"historyMidClickAction": "open_in_new",
"historyMidClickActivate": true,
"historyMidClickPos": "default",
"syncName": "",
"syncUseFirefox": true,
"syncUseGoogleDrive": false,
"syncUseGoogleDriveApi": false,
"syncUseGoogleDriveApiClientId": "",
"syncSaveSettings": false,
"syncSaveCtxMenu": false,
"syncSaveStyles": false,
"syncSaveKeybindings": false,
"selectActiveTabFirst": true,
"selectCyclic": false
},
"sidebar": {
"nav": [
"3pL0qgntz1AB",
"add_tp",
"settings"
],
"panels": {
"3pL0qgntz1AB": {
"type": 2,
"id": "3pL0qgntz1AB",
"name": "default",
"color": "toolbar",
"iconSVG": "icon_tabs",
"iconIMGSrc": "",
"iconIMG": "",
"lockedPanel": false,
"skipOnSwitching": false,
"noEmpty": false,
"newTabCtx": "none",
"dropTabCtx": "none",
"moveRules": [],
"moveExcludedTo": -1,
"bookmarksFolderId": -1,
"newTabBtns": [
""
],
"srcPanelConfig": null
}
}
},
"sidebarCSS": "#root {\n /* 与 Firefox 界面字体保持一致 */\n --tabs-font: menu;\n --tabs-count-font: .625rem menu;\n --bookmarks-bookmark-font: .875rem menu;\n --bookmarks-folder-font: 10pt menu;\n}\n\n/* Adjust styles according to sidebar width */\n@media screen and (max-width: 49px) {\n #root {\n --tabs-indent: unset;\n }\n .ScrollBox > .scroll-container {\n overflow: hidden;\n }\n .Tab .audio {\n left: 10px;\n transform: scale(.80);\n transform: translateY(4px);\n z-index: 99 !important;\n }\n .Tab .title {\n visibility: collapse;\n }\n}\n\n@media screen and (min-width: 49px) {\n .Tab .audio {\n left: 28px;\n }\n}\n\n/*\n * Add margins and rounding around tabs\n */ \n\n#root {\n --tabs-height: 33px;\n}\n\n/* Background layer */\n\n.Tab {\n margin: 0 4px;\n width: unset;\n}\n.Tab .lvl-wrapper:after {\n content: '';\n position: absolute;\n top: 4px;\n width: 100%;\n height: calc(100% - 5px);\n border-radius: 4px;\n z-index: -1;\n}\n\n@media (prefers-color-scheme:light) {\n #root {\n --tabs-activated-bg: white !important;\n --tabs-bg-active: var(--tabs-activated-bg) !important;\n --tabs-selected-fg: var(--tabs-activated-fg) !important;\n --tabs-selected-bg: var(--tabs-activated-bg) !important;\n --bg: #f0f0f0 !important;\n }\n .Tab[data-selected] .lvl-wrapper:after,\n .Tab[data-active] .lvl-wrapper:after {\n box-shadow: 0 0 1px rgba(128,128,142,0.9), 0 0 4px rgba(128,128,142,0.5);\n }\n}\n\n\n/* Reset default styles */\n.Tab:hover,\n.Tab:active,\n.Tab[data-active],\n.Tab[data-active]:active,\n.Tab[data-selected],\n.Tab[data-selected]:hover,\n.Tab[data-selected]:active {\n background: transparent;\n}\n\n/* Reapply styles */\n\n.Tab:hover .lvl-wrapper:after {\n background-color: var(--tabs-bg-hover);\n}\n\n.Tab:active .lvl-wrapper:after,\n.Tab[data-active]:active .lvl-wrapper:after {\n background-color: var(--tabs-bg-active);\n}\n\n.Tab[data-active] .lvl-wrapper:after {\n background-color: var(--tabs-activated-bg);\n}\n\n\n.Tab[data-selected] .lvl-wrapper:after {\n background-color: var(--tabs-selected-bg);,\n}\n\n/* Resize and reposition favicons */\n.Tab .fav {\n width: 18px;\n height: 18px;\n margin-left: 10px;\n}\n\n.Tab .placeholder > svg {\n width: 18px;\n height: 18px;\n}\n\n.Tab .fav,\n.Tab .placeholder,\n.Tab .t-box {\n margin-bottom: -2px;\n}\n\n/* OLD STYLES\n#root {\n --tabs-font: 10pt Segoe UI;\n --tabs-count-font: .625rem Segoe UI;\n --bookmarks-bookmark-font: .875rem Segoe UI;\n --bookmarks-folder-font: 10pt Segoe UI;\n}\n\n/* Adjust styles according to sidebar width */\n@media screen and (max-width: 49px) {\n #root {\n --tabs-indent: unset;\n }\n .ScrollBox > .scroll-container {\n overflow: hidden;\n }\n .Tab .audio {\n left: 10px;\n transform: scale(.80);\n transform: translateY(4px);\n z-index: 99 !important;\n }\n .Tab .title {\n visibility: collapse;\n }\n}\n\n@media screen and (min-width: 49px) {\n .Tab .audio {\n left: 28px;\n }\n}\n\n/*\n * Add margins and rounding around tabs\n */ \n\n#root {\n --tabs-height: 33px;\n}\n\n/* Background layer */\n\n.Tab {\n margin: 0 4px;\n width: unset;\n}\n.Tab .lvl-wrapper:after {\n content: '';\n position: absolute;\n top: 4px;\n width: 100%;\n height: calc(100% - 5px);\n border-radius: 4px;\n z-index: -1;\n}\n\n@media (prefers-color-scheme:light) {\n #root {\n --tabs-activated-bg: white !important;\n --tabs-bg-active: var(--tabs-activated-bg) !important;\n --tabs-selected-fg: var(--tabs-activated-fg) !important;\n --tabs-selected-bg: var(--tabs-activated-bg) !important;\n --bg: #f0f0f0 !important;\n }\n .Tab[data-selected] .lvl-wrapper:after,\n .Tab[data-active] .lvl-wrapper:after {\n box-shadow: 0 0 1px rgba(128,128,142,0.9), 0 0 4px rgba(128,128,142,0.5);\n }\n}\n\n\n/* Reset default styles */\n.Tab:hover,\n.Tab:active,\n.Tab[data-active],\n.Tab[data-active]:active,\n.Tab[data-selected],\n.Tab[data-selected]:hover,\n.Tab[data-selected]:active {\n background: transparent;\n}\n\n/* Reapply styles */\n\n.Tab:hover .lvl-wrapper:after {\n background-color: var(--tabs-bg-hover);\n}\n\n.Tab:active .lvl-wrapper:after,\n.Tab[data-active]:active .lvl-wrapper:after {\n background-color: var(--tabs-bg-active);\n}\n\n.Tab[data-active] .lvl-wrapper:after {\n background-color: var(--tabs-activated-bg);\n}\n\n\n.Tab[data-selected] .lvl-wrapper:after {\n background-color: var(--tabs-selected-bg);,\n}\n\n/* Resize and reposition favicons */\n.Tab .fav {\n width: 18px;\n height: 18px;\n margin-left: 10px;\n}\n\n.Tab .placeholder > svg {\n width: 18px;\n height: 18px;\n}\n\n.Tab .fav,\n.Tab .placeholder,\n.Tab .t-box {\n margin-bottom: -2px;\n}\n\n/* OLD STYLES\n#root {\n --nav-btn-width: 22px;\n --name-font-size: 12px;\n --count-font-size: 10px;\n}\n\n.NavigationBar .panel-btn {\n display: flex;\n flex-direction: column-reverse;\n padding: 6px 0;\n height: auto;\n}\n\n.NavigationBar .panels-box .panel-btn[data-type=\"add\"] {\n height: var(--nav-btn-width);\n}\n\n.NavigationBar .panels-box .panel-btn:not([data-type=\"add\"]) > svg,\n.NavigationBar .panels-box .panel-btn > img {\n top: 0;\n}\n\n.NavigationBar .panel-btn .update-badge {\n top: 1px;\n left: 1px;\n}\n\n.NavigationBar .panel-btn .ok-badge,\n.NavigationBar .panel-btn .err-badge,\n.NavigationBar .panel-btn .progress-spinner {\n display: none;\n}\n\n.NavigationBar .panel-btn .len {\n position: relative;\n font-size: var(--count-font-size);\n writing-mode: sideways-lr;\n text-orientation: mixed;\n background-color: transparent;\n padding: 0;\n margin: 0 2px 0 0;\n top: 0;\n right: 0;\n color: var(--container-fg, var(--nav-btn-fg));\n}\n.NavigationBar .panel-btn .len:before {\n content: \": \";\n font-size: var(--name-font-size);\n}\n\n.NavigationBar .panel-btn .name {\n position: relative;\n display: block;\n padding: 0;\n padding-top: 1rem;\n margin: 0 2px 0 0;\n font-size: var(--name-font-size);\n color: var(--container-fg, var(--nav-btn-fg));\n writing-mode: sideways-lr;\n text-orientation: mixed;\n}\n\n.Tab[data-active] {\n background-color: #FFF;\n box-shadow: 0 0.2rem 0.6rem rgba(0,0,0,.2);\n}\n\n*/\n*/\n\n\n/* OLD STYLES\n#root {\n /* 与 Firefox 界面字体保持一致 */\n --tabs-font: menu;\n --tabs-count-font: .625rem menu;\n --bookmarks-bookmark-font: .875rem menu;\n --bookmarks-folder-font: 10pt menu;\n}\n\n/* Adjust styles according to sidebar width */\n@media screen and (max-width: 49px) {\n #root {\n --tabs-indent: unset;\n }\n .ScrollBox > .scroll-container {\n overflow: hidden;\n }\n .Tab .audio {\n left: 10px;\n transform: scale(.80);\n transform: translateY(4px);\n z-index: 99 !important;\n }\n .Tab .title {\n visibility: collapse;\n }\n}\n\n@media screen and (min-width: 49px) {\n .Tab .audio {\n left: 28px;\n }\n}\n\n/*\n * Add margins and rounding around tabs\n */ \n\n#root {\n --tabs-height: 33px;\n}\n\n/* Background layer */\n\n.Tab {\n margin: 0 4px;\n width: unset;\n}\n.Tab .lvl-wrapper:after {\n content: '';\n position: absolute;\n top: 4px;\n width: 100%;\n height: calc(100% - 5px);\n border-radius: 4px;\n z-index: -1;\n}\n\n@media (prefers-color-scheme:light) {\n #root {\n --tabs-activated-bg: white !important;\n --tabs-bg-active: var(--tabs-activated-bg) !important;\n --tabs-selected-fg: var(--tabs-activated-fg) !important;\n --tabs-selected-bg: var(--tabs-activated-bg) !important;\n --bg: #f0f0f0 !important;\n }\n .Tab[data-selected] .lvl-wrapper:after,\n .Tab[data-active] .lvl-wrapper:after {\n box-shadow: 0 0 1px rgba(128,128,142,0.9), 0 0 4px rgba(128,128,142,0.5);\n }\n}\n\n\n/* Reset default styles */\n.Tab:hover,\n.Tab:active,\n.Tab[data-active],\n.Tab[data-active]:active,\n.Tab[data-selected],\n.Tab[data-selected]:hover,\n.Tab[data-selected]:active {\n background: transparent;\n}\n\n/* Reapply styles */\n\n.Tab:hover .lvl-wrapper:after {\n background-color: var(--tabs-bg-hover);\n}\n\n.Tab:active .lvl-wrapper:after,\n.Tab[data-active]:active .lvl-wrapper:after {\n background-color: var(--tabs-bg-active);\n}\n\n.Tab[data-active] .lvl-wrapper:after {\n background-color: var(--tabs-activated-bg);\n}\n\n\n.Tab[data-selected] .lvl-wrapper:after {\n background-color: var(--tabs-selected-bg);,\n}\n\n/* Resize and reposition favicons */\n.Tab .fav {\n width: 18px;\n height: 18px;\n margin-left: 10px;\n}\n\n.Tab .placeholder > svg {\n width: 18px;\n height: 18px;\n}\n\n.Tab .fav,\n.Tab .placeholder,\n.Tab .t-box {\n margin-bottom: -2px;\n}\n\n/* OLD STYLES\n#root {\n --tabs-font: 10pt Segoe UI;\n --tabs-count-font: .625rem Segoe UI;\n --bookmarks-bookmark-font: .875rem Segoe UI;\n --bookmarks-folder-font: 10pt Segoe UI;\n}\n\n/* Adjust styles according to sidebar width */\n@media screen and (max-width: 49px) {\n #root {\n --tabs-indent: unset;\n }\n .ScrollBox > .scroll-container {\n overflow: hidden;\n }\n .Tab .audio {\n left: 10px;\n transform: scale(.80);\n transform: translateY(4px);\n z-index: 99 !important;\n }\n .Tab .title {\n visibility: collapse;\n }\n}\n\n@media screen and (min-width: 49px) {\n .Tab .audio {\n left: 28px;\n }\n}\n\n/*\n * Add margins and rounding around tabs\n */ \n\n#root {\n --tabs-height: 33px;\n}\n\n/* Background layer */\n\n.Tab {\n margin: 0 4px;\n width: unset;\n}\n.Tab .lvl-wrapper:after {\n content: '';\n position: absolute;\n top: 4px;\n width: 100%;\n height: calc(100% - 5px);\n border-radius: 4px;\n z-index: -1;\n}\n\n@media (prefers-color-scheme:light) {\n #root {\n --tabs-activated-bg: white !important;\n --tabs-bg-active: var(--tabs-activated-bg) !important;\n --tabs-selected-fg: var(--tabs-activated-fg) !important;\n --tabs-selected-bg: var(--tabs-activated-bg) !important;\n --bg: #f0f0f0 !important;\n }\n .Tab[data-selected] .lvl-wrapper:after,\n .Tab[data-active] .lvl-wrapper:after {\n box-shadow: 0 0 1px rgba(128,128,142,0.9), 0 0 4px rgba(128,128,142,0.5);\n }\n}\n\n\n/* Reset default styles */\n.Tab:hover,\n.Tab:active,\n.Tab[data-active],\n.Tab[data-active]:active,\n.Tab[data-selected],\n.Tab[data-selected]:hover,\n.Tab[data-selected]:active {\n background: transparent;\n}\n\n/* Reapply styles */\n\n.Tab:hover .lvl-wrapper:after {\n background-color: var(--tabs-bg-hover);\n}\n\n.Tab:active .lvl-wrapper:after,\n.Tab[data-active]:active .lvl-wrapper:after {\n background-color: var(--tabs-bg-active);\n}\n\n.Tab[data-active] .lvl-wrapper:after {\n background-color: var(--tabs-activated-bg);\n}\n\n\n.Tab[data-selected] .lvl-wrapper:after {\n background-color: var(--tabs-selected-bg);,\n}\n\n/* Resize and reposition favicons */\n.Tab .fav {\n width: 18px;\n height: 18px;\n margin-left: 10px;\n}\n\n.Tab .placeholder > svg {\n width: 18px;\n height: 18px;\n}\n\n.Tab .fav,\n.Tab .placeholder,\n.Tab .t-box {\n margin-bottom: -2px;\n}\n\n/* OLD STYLES\n#root {\n --nav-btn-width: 22px;\n --name-font-size: 12px;\n --count-font-size: 10px;\n}\n\n.NavigationBar .panel-btn {\n display: flex;\n flex-direction: column-reverse;\n padding: 6px 0;\n height: auto;\n}\n\n.NavigationBar .panels-box .panel-btn[data-type=\"add\"] {\n height: var(--nav-btn-width);\n}\n\n.NavigationBar .panels-box .panel-btn:not([data-type=\"add\"]) > svg,\n.NavigationBar .panels-box .panel-btn > img {\n top: 0;\n}\n\n.NavigationBar .panel-btn .update-badge {\n top: 1px;\n left: 1px;\n}\n\n.NavigationBar .panel-btn .ok-badge,\n.NavigationBar .panel-btn .err-badge,\n.NavigationBar .panel-btn .progress-spinner {\n display: none;\n}\n\n.NavigationBar .panel-btn .len {\n position: relative;\n font-size: var(--count-font-size);\n writing-mode: sideways-lr;\n text-orientation: mixed;\n background-color: transparent;\n padding: 0;\n margin: 0 2px 0 0;\n top: 0;\n right: 0;\n color: var(--container-fg, var(--nav-btn-fg));\n}\n.NavigationBar .panel-btn .len:before {\n content: \": \";\n font-size: var(--name-font-size);\n}\n\n.NavigationBar .panel-btn .name {\n position: relative;\n display: block;\n padding: 0;\n padding-top: 1rem;\n margin: 0 2px 0 0;\n font-size: var(--name-font-size);\n color: var(--container-fg, var(--nav-btn-fg));\n writing-mode: sideways-lr;\n text-orientation: mixed;\n}\n\n.Tab[data-active] {\n background-color: #FFF;\n box-shadow: 0 0.2rem 0.6rem rgba(0,0,0,.2);\n}\n\n*/\n*/\n*/",
"ver": "5.3.3",
"keybindings": {
"_execute_sidebar_action": "F1",
"next_panel": "Alt+Period",
"prev_panel": "Alt+Comma",
"new_tab_on_panel": "Ctrl+Space",
"new_tab_in_group": "Ctrl+Shift+Space",
"up": "Alt+Up",
"down": "Alt+Down",
"up_shift": "Alt+Shift+Up",
"down_shift": "Alt+Shift+Down",
"activate": "Alt+Space",
"reset_selection": "Alt+R"
}
}
鼠标手势
MouseGesture.uc.js
// ==UserScript==
// @name Mousegestures.uc.js
// @namespace Mousegestures@gmail.com
// @description 自定义鼠标手势
// @author 紫云飞&黒仪大螃蟹
// @homepageURL http://www.cnblogs.com/ziyunfei/archive/2011/12/15/2289504.html
// @include chrome://browser/content/browser.xhtml
// @include chrome://browser/content/browser.xul
// @version 2021-01-27 workaround for firefox 85
// @charset UTF-8
// ==/UserScript==
(() => {
"use strict";
/// region utils
function weakAssign(target, source) {
if (!source) return target;
if (!target) return source;
for (const key in source) {
if (source.hasOwnProperty(key) && !(key in target)) {
target[key] = source[key];
}
}
return target;
}
function log(...args) {
if (typeof console !== "undefined" && console.log) {
console.log(...args);
}
}
/// endregion utils
/// region renderer
/**
* 创建箭头路径
* @return {Path2D}
*/
function createPathOfArrow() {
const path = new Path2D();
path.lineTo(89, 534);
path.bezierCurveTo(70, 515, 70, 485, 89, 466);
path.lineTo(478, 78);
path.bezierCurveTo(496, 59, 527, 59, 545, 78);
path.lineTo(934, 466);
path.bezierCurveTo(953, 485, 953, 515, 934, 534);
path.lineTo(890, 578);
path.bezierCurveTo(871, 597, 840, 597, 821, 578);
path.lineTo(592, 337);
path.lineTo(592, 912);
path.bezierCurveTo(592, 938, 570, 960, 544, 960);
path.lineTo(480, 960);
path.bezierCurveTo(453, 960, 432, 938, 432, 912);
path.lineTo(432, 337);
path.lineTo(202, 578);
path.bezierCurveTo(183, 597, 152, 598, 133, 579);
return path;
}
/**
* 变换箭头路径
* @param {Path2D} path 箭头路径
* @param {number} size 目标大小
* @param {string} rotate 方向
* @return {Path2D}
*/
function transformPathOfArrow(path, size, rotate) {
const domMatrix = new DOMMatrix();
switch (rotate) {
case "D":
// in degrees
domMatrix.translateSelf(size, size);
domMatrix.rotateSelf(180);
break;
case "R":
domMatrix.translateSelf(size, 0);
domMatrix.rotateSelf(90);
break;
case "L":
domMatrix.translateSelf(0, size);
domMatrix.rotateSelf(-90);
break;
case "Lu":
domMatrix.translateSelf(-(size >> 3) - (size >> 5), size >> 1);
domMatrix.rotateSelf(-45);
break;
case "Ld":
domMatrix.translateSelf((size >> 1) + (size >> 5), size + (size >> 2));
domMatrix.rotateSelf(-135);
break;
case "Ru":
domMatrix.translateSelf(size >> 1, -(size >> 3) - (size >> 5));
domMatrix.rotateSelf(45);
break;
case "Rd":
domMatrix.translateSelf(
size + (size >> 3) + (size >> 4),
(size >> 1) + (size >> 5)
);
domMatrix.rotateSelf(135);
break;
case "U":
default:
break;
}
domMatrix.scaleSelf(size / 1024, size / 1024);
let path2D = new Path2D();
path2D.addPath(path, domMatrix);
return path2D;
}
/**
* 默认渲染器配置
*/
const defaultMouseGestureRendererConfig = {
// 鼠标手势颜色
gestureColor: "#0065ff",
// 鼠标手势线宽
gestureLineWidth: 4,
// 鼠标手势盒子颜色
gestureBoxColor: "rgba(0, 0, 0, 0.8)",
// 鼠标手势盒子横向边距
gestureBoxPaddingX: 8,
// 鼠标手势文本颜色
gestureTextColor: "#ffffff",
// 箭头
// 箭头路径基准大小为 1024x1024,箭头应居中
pathOfArrow: createPathOfArrow(),
};
/**
* 默认渲染器
*/
class MouseGestureRenderer {
// Public class fields, starting with firefox 69
static STATUS_IDLE = 0;
static STATUS_ACTIVE = 1;
/// region status
/**
* 状态
* @type {number}
*/
status = MouseGestureRenderer.STATUS_IDLE;
/**
* 绘制的鼠标手势中各条线的坐标
* @type {Path2D | null}
*/
mouseMovePath = null;
/**
* 绘制鼠标手势的canvas的容器
* @type {HTMLElement | Element | null}
*/
containerElement = null;
/**
* 绘制鼠标手势的canvas的 CanvasRenderingContext2D
* @type {CanvasRenderingContext2D | null}
*/
renderingContext = null;
/**
* 当前鼠标手势编码
* @type {string}
*/
directionChain = "";
/**
* 当前鼠标手势名称
* @type {string | void}
*/
gestureName = "";
/**
* 一个 long 整数,请求 ID ,是回调列表中唯一的标识。
* 是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。
* @type {number}
*/
animationFrameHandle = 0;
/// endregion status
constructor(config) {
/// region config
config = weakAssign(config, defaultMouseGestureRendererConfig);
/**
* 鼠标手势颜色
* @type {string}
*/
this.gestureColor = config.gestureColor;
/**
* 鼠标手势线宽
* @type {number}
*/
this.gestureLineWidth = config.gestureLineWidth;
/**
* 鼠标手势盒子颜色
* @type {string}
*/
this.gestureBoxColor = config.gestureBoxColor;
/**
* 鼠标手势盒子横向边距
* @type {number}
*/
this.gestureBoxPaddingX = config.gestureBoxPaddingX;
/**
* 鼠标手势文本颜色
* @type {string}
*/
this.gestureTextColor = config.gestureTextColor;
/**
* 箭头
* @type {Path2D}
*/
this.pathOfArrow = config.pathOfArrow;
/// endregion config
this.animationFrameCallback = () => {
try {
this.internalRenderGesture();
} catch (e) {
log("animationFrameCallback", e);
} finally {
// reset animationFrameHandle since it is called
this.animationFrameHandle = 0;
}
};
}
/// region active
/**
* Create css text for {@link containerElement}
* @param {number} clientHeight
* @return {string} css text
*/
containerElementCssText(clientHeight) {
return `
-moz-user-focus: none !important;
-moz-user-select: none !important;
display: -moz-box !important;
box-sizing: border-box !important;
pointer-events: none !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
height: ${clientHeight}px !important;
border: none !important;
box-shadow: none !important;
overflow: hidden !important;
background: none !important;
position: fixed !important;
z-index: 2147483647 !important;`.trim();
}
createCanvas() {
this.containerElement = document.createXULElement("hbox");
const { selectedBrowser } = gBrowser;
const canvasContainer = selectedBrowser.parentNode;
const canvas = document.createElementNS(
"http://www.w3.org/1999/xhtml",
"canvas"
);
let { clientHeight, clientWidth } = selectedBrowser;
// high dpi scale support
let devicePixelRatio = window.devicePixelRatio || 1;
if (devicePixelRatio !== 1) {
canvas.width = clientWidth * devicePixelRatio;
canvas.height = clientHeight * devicePixelRatio;
// transform displays better on firefox
canvas.style.transform = "scale(" + 1 / devicePixelRatio + ")";
canvas.style.transformOrigin = "0 0";
// 20221123
// fix for firefox 107
canvas.style.position = "absolute";
// canvas.style.width = '100%';
// canvas.style.height = '100%';
} else {
canvas.width = clientWidth;
canvas.height = clientHeight;
}
this.renderingContext = canvas.getContext("2d");
this.containerElement.style.cssText =
this.containerElementCssText(clientHeight);
this.containerElement.appendChild(canvas);
canvasContainer.insertBefore(
this.containerElement,
selectedBrowser.nextSibling
);
}
/**
* @param {number?} x initial x
* @param {number?} y initial y
*/
createPath2D(x, y) {
this.mouseMovePath = new Path2D();
if (x && y) {
// high dpi scale support
let devicePixelRatio = window.devicePixelRatio || 1;
this.mouseMovePath.moveTo(
// make it faster by discarding non-integer part
(x * devicePixelRatio) | 0,
(y * devicePixelRatio) | 0
);
}
}
/**
* Setup dom or xul elements for rendering
* @param {number?} x initial x
* @param {number?} y initial y
*/
active(x, y) {
if (this.isActive()) {
this.dispose();
}
this.status = MouseGestureRenderer.STATUS_ACTIVE;
this.createCanvas();
this.createPath2D(x, y);
}
/**
* True if this is active
* @return {boolean}
*/
isActive() {
return this.status === MouseGestureRenderer.STATUS_ACTIVE;
}
/// endregion active
/// region render
renderGesturePath() {
const ctx = this.renderingContext;
const path = this.mouseMovePath;
ctx.strokeStyle = this.gestureColor;
ctx.lineJoin = "bevel";
ctx.lineCap = "butt";
ctx.lineWidth = this.gestureLineWidth;
if (path instanceof Path2D) {
ctx.stroke(path);
} else if (Array.isArray(path)) {
// should never enter this branch
// kept for historical reason
ctx.beginPath();
ctx.moveTo(path[0], path[1]);
for (let i = 2, l = path.length; i < l;) {
ctx.lineTo(path[i++], path[i++]);
}
// ctx.closePath();
ctx.stroke();
}
}
internalRenderGesture() {
/**
* @type {CanvasRenderingContext2D}
*/
let ctx;
if (!(ctx = this.renderingContext)) {
return;
}
let { mouseMovePath } = this;
if (!mouseMovePath) {
return;
}
const { width, height } = ctx.canvas;
ctx.clearRect(0, 0, width, height);
this.renderGesturePath();
const { directionChain, gestureName: name } = this;
// 如果没有手势(一般是鼠标首次移动),不渲染盒子
if (!directionChain) {
return;
}
let boxW = width >> 3,
boxH = Math.max(48, height >> 3),
midW = width >> 1,
boxX = midW - (boxW >> 1),
boxY = (height >> 1) - (boxH >> 1),
textH = boxH >> 2,
lineH = textH + (textH >> 1),
textY = boxY;
let text = [
// directionChain,
name || "未知手势: " + directionChain,
];
// calculate text width
ctx.font = textH + "px sans-serif";
let textWidthArray = [];
for (let i = 0, l = text.length, t, w; i < l; i++) {
t = text[i];
w = ctx.measureText(t);
// text width overflow, add some padding
if (w.width >= boxW) {
boxW = (w.width | 0) + this.gestureBoxPaddingX;
boxX = midW - (boxW >> 1);
}
textWidthArray[i] = midW - (w.width >> 1);
}
// calculate arrow width
let arrowH = textH + (textH >> 1),
arrowW = arrowH * this.directionChainLength(directionChain);
// arrow width overflow, add some padding
if (arrowW >= boxW) {
boxW = (arrowW | 0) + this.gestureBoxPaddingX;
boxX = midW - (boxW >> 1);
}
// render the box
ctx.fillStyle = this.gestureBoxColor;
ctx.fillRect(Math.max(boxX, 0), boxY, Math.min(boxW, width), boxH);
/// region render the arrows
ctx.fillStyle = this.gestureTextColor;
textY += textH >> 1;
const arrowPathCache = {};
for (
let i = 0, l = directionChain.length, x = midW - (arrowW >> 1), d, n, c;
i < l;
i++
) {
d = directionChain[i];
// 检测斜向手势
n = i + 1;
if (n < l) {
// 单个手势编码最多包含2个字符
c = directionChain.charCodeAt(n);
// 97: 'a'.charCodeAt(0)
// 122: 'z'.charCodeAt(0)
if (c >= 97 && c <= 122) {
d += directionChain[++i];
}
}
// only draw visible arrows
if (x + arrowH >= 0 && x <= width) {
if (!arrowPathCache[d]) {
arrowPathCache[d] = transformPathOfArrow(
this.pathOfArrow,
arrowH,
d
);
}
ctx.translate(x, textY);
ctx.fill(arrowPathCache[d]);
// reset current transformation matrix to the identity matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
x += arrowH;
}
/// endregion render the arrows
// render text
textY += (textH << 1) + (textH >> 1) + (textH >> 2);
for (let i = 0, l = text.length, t, w; i < l; i++) {
t = text[i];
w = textWidthArray[i];
ctx.fillText(t, w, textY);
textY += lineH;
}
}
/**
* @param {number} x
* @param {number} y
* @param {string} directionChain
* @param {string?} gestureName
*/
render(x, y, directionChain, gestureName) {
if (!this.isActive()) {
this.active(x, y);
}
// high dpi scale support
let devicePixelRatio = window.devicePixelRatio || 1;
// maybe we can optimize the line by reducing points
this.mouseMovePath.lineTo(
// make it faster by discarding non-integer part
(x * devicePixelRatio) | 0,
(y * devicePixelRatio) | 0
);
this.directionChain = directionChain;
this.gestureName = gestureName;
// requestAnimationFrame only if last AnimationFrame finished
if (!this.animationFrameHandle) {
// arrow func, no bind needed
this.animationFrameHandle = requestAnimationFrame(
this.animationFrameCallback
);
}
}
/// endregion render
dispose() {
this.status = MouseGestureRenderer.STATUS_IDLE;
if (this.containerElement) {
this.containerElement.parentNode.removeChild(this.containerElement);
this.containerElement = null;
}
this.renderingContext = null;
this.mouseMovePath = null;
this.directionChain = "";
this.gestureName = "";
}
/**
* 计算鼠标手势编码长度
* 每个大写字母代表一个手势
* @param directionChain 鼠标手势编码
* @return {number} 长度
*/
directionChainLength(directionChain) {
let len = 0;
for (let i = 0, l = directionChain.length, c; i < l; i++) {
c = directionChain.charCodeAt(i);
// 65: 'A'.charCodeAt(0)
// 90: 'Z'.charCodeAt(0)
if (c >= 65 && c <= 90) {
len++;
}
}
return len;
}
}
/// endregion renderer
/// region direction handler
const defaultMouseGestureHandlerConfig = {
/**
* 鼠标移动阈值
* @type {number}
*/
mouseMoveThreshold: 9,
/**
* 阈值计算方式
* hypot: 平方和的平方根 Math.hypot(dx, dy) > {@link mouseMoveThreshold}
* linear: dx > {@link mouseMoveThreshold} && dy > {@link mouseMoveThreshold}
* @type {string}
*/
thresholdMethod: "hypot",
/**
* 是否要求鼠标手势必须有两个连续的动作
* https://github.com/marklieberman/foxygestures/blob/
* 844d6a581068d09004eb12f10156c3865908eb33/src/common/GestureDetector.js#L32
* @type {boolean}
*/
twoConsecutiveMoves: true,
/**
* 鼠标手势方向
* default: 4个方向,按90度划分 U, D, L, R
* 45deg: 8个方向,按45度划分 U, D, L, R, Lu, Ld, Ru, Rd
* 60deg: 8个方向,按30度划分 U, D, L, R ; 按60度划分 Lu, Ld, Ru, Rd
* https://github.com/marklieberman/foxygestures/blob/master/extra/gesture-types.html
* @type {string}
*/
detectMethod: "default",
};
/**
* 处理鼠标手势方向
*/
class MouseGestureDirectionHandler {
constructor(config) {
config = weakAssign(config, defaultMouseGestureHandlerConfig);
/**
* 鼠标移动阈值
* @type {number}
*/
this.mouseMoveThreshold = config.mouseMoveThreshold;
/**
* 阈值计算方式
* @type {string}
*/
this.thresholdMethod = config.thresholdMethod;
/**
* 是否要求鼠标手势必须有两个连续的动作
* @type {boolean}
*/
this.twoConsecutiveMoves = config.twoConsecutiveMoves;
/**
* 上次实际的鼠标手势方向
* @see twoConsecutiveMoves
* @type {string}
*/
this.lastDirection = "";
/**
* 上次返回的鼠标手势方向
* @type {string}
*/
this.lastValidDirection = "";
// 支持多种鼠标手势方向实现
if (config.detectMethod === "45deg") {
this.detectDirection = this.detectDirection45deg;
} else if (config.detectMethod === "60deg") {
this.detectDirection = this.detectDirection60deg;
}
}
/**
* 检测鼠标手势方向
* @param {number} dx x方向移动距离
* @param {number} dy y方向移动距离
* @return {string} 方向
*/
detectDirection(dx, dy) {
let move;
if (dx > 0) {
if (dy > 0) {
move = dy > dx ? "D" : "R";
} else {
move = -dy > dx ? "U" : "R";
}
} else {
if (dy > 0) {
move = dy > -dx ? "D" : "L";
} else {
move = -dy > -dx ? "U" : "L";
}
}
return move;
}
/**
* 检测鼠标手势方向 (按45度划分)
* Gesture implementation for intercardinal directions with 45deg slices.
* Coordinate grid is divided into 8 slices of 45deg: U, D, L, R, Lu, Ld, Ru, Rd.
* @param {number} dx x方向移动距离
* @param {number} dy y方向移动距离
* @return {string} 方向
*/
detectDirection45deg(dx, dy) {
let move = "";
let deg = (180 / Math.PI) * Math.atan2(dy, dx);
if (deg >= 22.5 && deg < 67.5) {
move = "Rd";
} else if (deg >= 67.5 && deg < 112.5) {
move = "D";
} else if (deg >= 112.5 && deg < 157.5) {
move = "Ld";
} else if (deg >= -22.5 && deg < 22.5) {
move = "R";
} else if (deg >= -67.5 && deg < -22.5) {
move = "Ru";
} else if (deg >= -112.5 && deg < -67.5) {
move = "U";
} else if (deg >= -157.5 && deg < -112.5) {
move = "Lu";
} else if (deg >= 157.5 || deg < -157.5) {
move = "L";
}
return move;
}
/**
* 检测鼠标手势方向 (按60度划分)
* Gesture implementation for intercardinal directions with 60deg slices.
* Coordinate grid is divided into 4 slices of 30deg for U, D, L, R
* and 4 slices of 60deg for Lu, Ld, Ru, Rd.
* This should be more forgiving when performing diagonal gestures.
* @param {number} dx x方向移动距离
* @param {number} dy y方向移动距离
* @return {string} 方向
*/
detectDirection60deg(dx, dy) {
let move = "";
let deg = (180 / Math.PI) * Math.atan2(dy, dx);
if (deg >= 15 && deg < 75) {
move = "Rd";
} else if (deg >= 75 && deg < 105) {
move = "D";
} else if (deg >= 105 && deg < 165) {
move = "Ld";
} else if (deg >= -15 && deg < 15) {
move = "R";
} else if (deg >= -75 && deg < -15) {
move = "Ru";
} else if (deg >= -105 && deg < -75) {
move = "U";
} else if (deg >= -165 && deg < -105) {
move = "Lu";
} else if (deg >= 165 || deg < -165) {
move = "L";
}
return move;
}
/**
* 检测阈值要求
* @param {number} dx x方向移动距离
* @param {number} dy y方向移动距离
* @return {boolean}
*/
checkThreshold(dx, dy) {
let { mouseMoveThreshold } = this;
let devicePixelRatio = window.devicePixelRatio || 1;
if (devicePixelRatio !== 1) {
mouseMoveThreshold /= devicePixelRatio;
}
if (this.thresholdMethod === "hypot") {
return Math.hypot(dx, dy) > mouseMoveThreshold;
}
return dx > mouseMoveThreshold && dy > mouseMoveThreshold;
}
/**
* 处理鼠标手势方向
* @param {number} dx x方向移动距离
* @param {number} dy y方向移动距离
* @return {string|boolean} 鼠标手势方向, false 为不满足阈值要求
*/
handleMove(dx, dy) {
if (!this.checkThreshold(dx, dy)) {
return false;
}
let direction = this.detectDirection(dx, dy);
if (!direction || direction === this.lastValidDirection) {
return "";
}
if (this.twoConsecutiveMoves) {
let isTwoConsecutiveMoves = direction !== this.lastDirection;
this.lastDirection = direction;
if (isTwoConsecutiveMoves) {
return "";
}
}
this.lastValidDirection = direction;
return direction;
}
/**
* 处理鼠标滚轮手势方向
* @see WheelEvent
* @param {number} deltaX 横向滚动量
* @param {number} deltaY 纵向滚动量
* @param {number} deltaZ z轴方向上的滚动量
* @return {string | void} 鼠标手势方向
*/
handleWheel(deltaX, deltaY, deltaZ) {
let direction;
if (deltaY) {
// 纵向滚动
direction = deltaY > 0 ? "+" : "-";
} else if (deltaX) {
// 横向滚动
direction = deltaX > 0 ? "R" : "L";
} else if (deltaZ) {
// 滚轮的z轴方向上的滚动
direction = deltaZ > 0 ? "U" : "D";
}
return direction;
}
/**
* 清理
*/
clear() {
this.lastDirection = "";
this.lastValidDirection = "";
}
}
/// endregion direction handler
class MouseGestureCommand {
// 后退
static historyGoBack() {
const nav = gBrowser.webNavigation;
if (nav.canGoBack) {
nav.goBack();
}
}
// 前进
static historyGoForward() {
var nav = gBrowser.webNavigation;
if (nav.canGoForward) {
nav.goForward();
}
}
// 向上滚动
static scrollPageUp() {
goDoCommand("cmd_scrollPageUp");
}
// 向下滚动
static scrollPageDown() {
goDoCommand("cmd_scrollPageDown");
}
// 转到页首
static scrollTop() {
goDoCommand("cmd_scrollTop");
}
// 转到页尾
static scrollBottom() {
goDoCommand("cmd_scrollBottom");
}
// 刷新当前页面
static reloadCurrentPage() {
const reload = document.getElementById("Browser:Reload");
reload.removeAttribute("disabled");
reload.doCommand();
}
// 刷新当前页面
static reloadCurrentPageSkipCache() {
const reload = document.getElementById("Browser:ReloadSkipCache");
reload.removeAttribute("disabled");
reload.doCommand();
}
// 打开新标签
static openNewTab() {
BrowserOpenTab();
}
// 恢复关闭的标签
static restoreClosedTab() {
try {
document.getElementById("History:UndoCloseTab").doCommand();
} catch (ex) {
log("恢复关闭的标签", ex);
if ("undoRemoveTab" in gBrowser) {
gBrowser.undoRemoveTab();
} else {
throw "Session Restore feature is disabled.";
}
}
}
// 激活左边的标签页
static advanceLeftTab() {
gBrowser.tabContainer.advanceSelectedTab(-1, true);
}
// 激活右边的标签页
static advanceRightTab() {
gBrowser.tabContainer.advanceSelectedTab(1, true);
}
// 滚动到左边的标签页
static scrollLeftTab() {
if (!("__scrollTabPos" in MouseGestureCommand)) {
MouseGestureCommand.__scrollTabPos = 0;
}
MouseGestureCommand.__scrollTabPos -= 1;
setTimeout(() => {
if (!MouseGestureCommand.__scrollTabPos) return;
gBrowser.tabContainer.advanceSelectedTab(
MouseGestureCommand.__scrollTabPos,
true
);
MouseGestureCommand.__scrollTabPos = 0;
}, 5);
}
// 滚动到右边的标签页
static scrollRightTab() {
if (!("__scrollTabPos" in MouseGestureCommand)) {
MouseGestureCommand.__scrollTabPos = 0;
}
MouseGestureCommand.__scrollTabPos += 1;
setTimeout(() => {
if (!MouseGestureCommand.__scrollTabPos) return;
gBrowser.tabContainer.advanceSelectedTab(
MouseGestureCommand.__scrollTabPos,
true
);
MouseGestureCommand.__scrollTabPos = 0;
}, 5);
}
// 激活第一个标签页
static advanceFirstTab() {
const tabs = gBrowser.visibleTabs || gBrowser.mTabs;
if (!tabs) {
return;
}
gBrowser.selectedTab = tabs[0];
}
// 激活最后一个标签页
static advanceLastTab() {
const tabs = gBrowser.visibleTabs || gBrowser.mTabs;
if (!tabs) {
return;
}
gBrowser.selectedTab = tabs[tabs.length - 1];
}
// 关闭当前标签并激活左侧标签
static closeCurrentTabAndGotoLeftTab() {
const tabs = gBrowser.visibleTabs || gBrowser.mTabs;
let t = gBrowser.selectedTab;
// 如果是最左侧标签页则关闭当前标签并激活最左侧标签
if (!t._tPos) {
// t._tPos === 0
gBrowser.removeTab(t);
// rejected by onbeforeunload
if (t !== gBrowser.selectedTab) {
gBrowser.selectedTab = tabs[0];
}
return;
}
gBrowser.tabContainer.advanceSelectedTab(-1, true);
let n = gBrowser.selectedTab;
gBrowser.removeTab(t);
// rejected by onbeforeunload
if (t !== gBrowser.selectedTab) {
gBrowser.selectedTab = n;
}
}
// 关闭当前标签并激活右侧标签
static closeCurrentTabAndGotoRightTab() {
const tabs = gBrowser.visibleTabs || gBrowser.mTabs;
let t = gBrowser.selectedTab;
let lastTab = tabs[tabs.length - 1];
// 如果是最右侧标签页则关闭当前标签并激活最右侧标签
if (t === lastTab) {
gBrowser.removeTab(t);
// rejected by onbeforeunload
if (t !== gBrowser.selectedTab) {
// new last tab
gBrowser.selectedTab = tabs[tabs.length - 1];
}
return;
}
gBrowser.tabContainer.advanceSelectedTab(1, true);
let n = gBrowser.selectedTab;
gBrowser.removeTab(t);
// rejected by onbeforeunload
if (t !== gBrowser.selectedTab) {
gBrowser.selectedTab = n;
}
}
// 将当前窗口置顶(未测试)(仅 windows)
static tabStickOnTop() {
try {
let mainWindow = document.getElementById("main-window");
MouseGestureCommand._onTop = !mainWindow.hasAttribute("ontop");
if (typeof ctypes === "undefined") {
Components.utils.import("resource://gre/modules/ctypes.jsm");
}
var lib = ctypes.open("user32.dll");
var funcActiveWindow;
// noinspection UnusedCatchParameterJS
try {
// noinspection JSDeprecatedSymbols
funcActiveWindow = lib.declare(
"GetActiveWindow",
ctypes.winapi_abi,
ctypes.int32_t
);
} catch (ex) {
// noinspection JSDeprecatedSymbols
funcActiveWindow = lib.declare(
"GetActiveWindow",
ctypes.stdcall_abi,
ctypes.int32_t
);
}
if (funcActiveWindow !== 0) {
var activeWindow = funcActiveWindow();
var funcSetWindowPos;
// noinspection UnusedCatchParameterJS
try {
// noinspection JSDeprecatedSymbols
funcSetWindowPos = lib.declare(
"SetWindowPos",
ctypes.winapi_abi,
ctypes.bool,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.uint32_t
);
} catch (ex) {
// noinspection JSDeprecatedSymbols
funcSetWindowPos = lib.declare(
"SetWindowPos",
ctypes.stdcall_abi,
ctypes.bool,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.int32_t,
ctypes.uint32_t
);
}
var hwndAfter = -2;
if (MouseGestureCommand._onTop) {
hwndAfter = -1;
mainWindow.setAttribute("ontop", "true");
} else mainWindow.removeAttribute("ontop");
funcSetWindowPos(activeWindow, hwndAfter, 0, 0, 0, 0, 19);
}
lib.close();
} catch (ex) {
log("MouseGestureCommand::TabStickOnTop", ex);
}
}
// 添加/移除书签(未测试)
static toggleBookmark() {
document.getElementById("Browser:AddBookmarkAs").doCommand();
}
// 关闭当前标签
static closeCurrentTab() {
if (gBrowser.selectedTab.getAttribute("pinned") !== "true") {
gBrowser.removeCurrentTab();
}
}
// 打开附加组件
static openAddonManager() {
BrowserOpenAddonsMgr();
}
// 打开选项
static openPreferences() {
openPreferences();
}
// 查看页面信息
static showPageInfo() {
BrowserPageInfo();
}
// 打开/关闭历史窗口(侧边栏)
static toggleHistorySidebar() {
SidebarUI.toggle("viewHistorySidebar");
}
static toggleBookmarkToolbar() {
const bar = document.getElementById("PersonalToolbar");
if (bar) {
setToolbarVisibility(bar, bar.collapsed);
}
}
// 重启浏览器
static restartBrowser() {
// BrowserUtils.restartApplication();
Services.startup.quit(
Services.startup.eRestart | Services.startup.eAttemptQuit
);
}
// 重启浏览器
static stopBrowser() {
// Services.startup.quit(Services.startup.eAttemptQuit);
goQuitApplication();
}
// 清除启动缓存并重启浏览器
static invalidateCacheAndRestart() {
Services.appinfo.invalidateCachesOnRestart();
BrowserUtils.restartApplication();
}
// 关闭左侧标签页
static closeAllLeftTabs() {
for (let i = gBrowser.selectedTab._tPos - 1; i >= 0; i--) {
if (!gBrowser.tabs[i].pinned) {
gBrowser.removeTab(gBrowser.tabs[i], { animate: true });
}
}
}
// 关闭右侧标签页
static closeAllRightTabs() {
// gBrowser.removeTabsToTheEndFrom(gBrowser.selectedTab);
// gBrowser.removeTabsToTheEndFrom(gBrowser.selectedTab);
gBrowser.removeTabsToTheEndFrom(gBrowser.selectedTab);
}
// 关闭其他标签页
static closeAllOtherTabs() {
gBrowser.removeAllTabsBut(gBrowser.selectedTab);
}
// 最小化内存使用
static minimizeMemoryUsage() {
Cc["@mozilla.org/memory-reporter-manager;1"]
.getService(Ci.nsIMemoryReporterManager)
.minimizeMemoryUsage(() => {
StatusPanel._label = "最小化内存使用完成";
});
}
}
// const defaultGestures = {
// 'L': {name: '后退', cmd: MouseGestureCommand.historyGoBack},
// 'R': {name: '前进', cmd: MouseGestureCommand.historyGoForward},
// 'U': {name: '向上滚动', cmd: MouseGestureCommand.scrollPageUp},
// 'D': {name: '向下滚动', cmd: MouseGestureCommand.scrollPageDown},
// 'DU': {name: '转到页首', cmd: MouseGestureCommand.scrollTop},
// 'UD': {name: '转到页尾', cmd: MouseGestureCommand.scrollBottom},
// 'LR': {name: '刷新当前页面', cmd: MouseGestureCommand.reloadCurrentPage},
// 'LRL': {name: '跳过缓存刷新当前页面', cmd: MouseGestureCommand.reloadCurrentPageSkipCache},
// 'RU': {name: '打开新标签', cmd: MouseGestureCommand.openNewTab},
// 'RL': {name: '恢复关闭的标签', cmd: MouseGestureCommand.restoreClosedTab},
// 'RLR': {name: '最小化内存使用', cmd: MouseGestureCommand.minimizeMemoryUsage},
// 'UL': {name: '激活左边的标签页', cmd: MouseGestureCommand.advanceLeftTab},
// 'UR': {name: '激活右边的标签页', cmd: MouseGestureCommand.advanceRightTab},
// 'ULU': {name: '激活第一个标签页', cmd: MouseGestureCommand.advanceFirstTab},
// 'URU': {name: '激活最后一个标签页', cmd: MouseGestureCommand.advanceLastTab},
// 'DL': {
// name: '关闭当前标签并激活左侧标签',
// cmd: MouseGestureCommand.closeCurrentTabAndGotoLeftTab
// },
// 'DR': {
// name: '关闭当前标签并激活右侧标签',
// cmd: MouseGestureCommand.closeCurrentTabAndGotoRightTab
// },
// 'W+': {name: '激活右边的标签页', cmd: MouseGestureCommand.scrollRightTab},
// 'W-': {name: '激活左边的标签页', cmd: MouseGestureCommand.scrollLeftTab},
// 'WR': {name: '激活右边的标签页', cmd: MouseGestureCommand.scrollRightTab},
// 'WL': {name: '激活左边的标签页', cmd: MouseGestureCommand.scrollLeftTab},
// };
const defaultGestures = {
RD: { name: "滚动至底部", cmd: MouseGestureCommand.scrollBottom },
RU: { name: "滚动至顶部", cmd: MouseGestureCommand.scrollTop },
U: { name: "左侧标签页", cmd: MouseGestureCommand.advanceLeftTab },
D: { name: "右侧标签页", cmd: MouseGestureCommand.advanceRightTab },
DR: { name: "关闭当前标签页", cmd: MouseGestureCommand.closeCurrentTab },
DL: { name: "恢复关闭标签页", cmd: MouseGestureCommand.restoreClosedTab },
DU: { name: '刷新当前页面', cmd: MouseGestureCommand.reloadCurrentPage },
UD: { name: '跳过缓存刷新当前页面', cmd: MouseGestureCommand.reloadCurrentPageSkipCache },
};
class UcMouseGesture {
constructor(gestures = defaultGestures) {
// 上一次事件时的screenX
this.lastX = 0;
// 上一次事件时的screenY
this.lastY = 0;
// 当前的鼠标手势
this.directionChain = "";
// 是否在绘制鼠标手势
this.isMouseDownR = false;
// 是否在开始绘制鼠标手势
// 用于开启了 twoConsecutiveMoves 时首次手势没有正确的销毁
this.isStarting = false;
// 是否拦截右键菜单触发
this.hideFireContext = false;
// 鼠标手势事件
this.events = ["mousedown", "mousemove", "keydown"];
// 非 passive 鼠标手势事件,
this.noPassiveEvents = ["contextmenu", "wheel"];
// 鼠标手势列表
this.gestures = gestures;
this.setGestures(gestures);
/**
* 鼠标手势渲染器
*/
this.renderer = new MouseGestureRenderer();
/**
* 处理鼠标手势方向
*/
this.directionHandler = new MouseGestureDirectionHandler();
}
setGestures(gestures) {
this.gestures = gestures;
// allow cmd of gestures to be string
if (this.gestures !== defaultGestures) {
for (let g of Object.values(this.gestures)) {
if (typeof g.cmd === "function") {
continue;
}
let c = MouseGestureCommand[g.cmd];
if (c) {
g.cmd = c;
} else {
Reflect.deleteProperty(g, "cmd");
}
}
}
}
bindEvent() {
for (let i = 0, a = this.events, l = a.length, type; i < l; i++) {
type = a[i];
gBrowser.tabpanels.addEventListener(type, this, {
capture: true,
passive: true,
});
}
// 需要拦截此事件,故不可为 passive
for (
let i = 0, a = this.noPassiveEvents, l = a.length, type;
i < l;
i++
) {
type = a[i];
document.addEventListener(type, this, {
capture: true,
passive: false,
});
}
const { mPanelContainer } = gBrowser;
if (mPanelContainer) {
mPanelContainer.addEventListener("mousemove", this, {
capture: false,
passive: true,
});
}
// 鼠标在浏览器其他位置松开
window.addEventListener("mouseup", this, {
capture: true,
passive: true,
});
gBrowser.tabpanels.addEventListener(
"unload",
() => this.onUnload(),
false
);
window.addEventListener("blur", this);
}
onUnload() {
this.unbindEvent();
}
unbindEvent() {
this.clear();
for (let i = 0, a = this.events, l = a.length, type; i < l; i++) {
type = a[i];
gBrowser.tabpanels.removeEventListener(type, this, true);
}
for (
let i = 0, a = this.noPassiveEvents, l = a.length, type;
i < l;
i++
) {
type = a[i];
document.removeEventListener(type, this, true);
}
const { mPanelContainer } = gBrowser;
if (mPanelContainer) {
mPanelContainer.removeEventListener("mousemove", this, false);
}
window.removeEventListener("mouseup", this, true);
window.removeEventListener("blur", this);
}
endGesture() {
this.isMouseDownR = false;
// this.shouldFireContext = false;
this.hideFireContext = true;
this.directionChain = "";
this.stopGesture();
}
stopGesture() {
let g = this.gestures[this.directionChain];
if (g && g.cmd) {
try {
Reflect.set(gBrowser, "__mozIsInGesture", 1);
g.cmd();
} catch (e) {
log(this.directionChain, this.gestures[this.directionChain], e);
}
if (!Reflect.deleteProperty(gBrowser, "__mozIsInGesture")) {
Reflect.set(gBrowser, "__mozIsInGesture", 0);
}
}
this.clear();
setTimeout(() => (StatusPanel._label = ""), 2000);
this.hideFireContext = true;
}
clear() {
this.renderer.dispose();
this.directionHandler.clear();
this.directionChain = "";
this.lastX = 0;
this.lastY = 0;
this.isStarting = false;
}
/// region event handlers
mousedown(event) {
if (event.button === 2) {
this.isMouseDownR = true;
this.hideFireContext = false;
let { screenX: x, screenY: y } = event;
const { screenX, screenY } = gBrowser.selectedBrowser;
x -= screenX;
y -= screenY;
[this.lastX, this.lastY, this.directionChain] = [x, y, ""];
}
if (event.button === 0) {
this.endGesture();
}
}
mousemove(event) {
if (!this.isMouseDownR) {
if (this.isStarting && this.renderer.isActive()) {
this.clear();
}
return;
}
let { screenX: x, screenY: y } = event;
const { screenX, screenY } = gBrowser.selectedBrowser;
x -= screenX;
y -= screenY;
let [dx, dy] = [x - this.lastX, y - this.lastY];
let direction = this.directionHandler.handleMove(dx, dy);
if (direction === false) return;
if (!this.renderer.isActive()) {
this.renderer.active(this.lastX, this.lastY);
}
this.lastX = x;
this.lastY = y;
let g;
if (direction) {
this.directionChain += direction;
g = this.gestures[this.directionChain];
StatusPanel._label = g
? "手势: " + this.directionChain + " " + g.name
: "未知手势: " + this.directionChain;
} else {
g = this.gestures[this.directionChain];
}
this.isStarting = !direction && !this.directionChain;
this.renderer.render(x, y, this.directionChain, g && g.name);
}
mouseup(event) {
if (this.isMouseDownR && event.button === 2) {
// if (this.directionChain) this.shouldFireContext = false;
this.isMouseDownR = false;
this.directionChain && this.stopGesture();
// event.stopImmediatePropagation();
}
}
contextmenu(event) {
if (this.isMouseDownR || this.hideFireContext) {
// this.shouldFireContext = true;
this.hideFireContext = false;
event.preventDefault();
event.stopImmediatePropagation();
} else if (this.isStarting) {
this.clear();
}
}
wheel(event) {
if (!this.isMouseDownR) {
return;
}
// this.shouldFireContext = false;
this.hideFireContext = true;
let direction = this.directionHandler.handleWheel(
event.deltaX,
event.deltaY,
event.deltaZ
);
if (!direction) {
this.endGesture();
return;
}
this.directionChain = "W" + direction;
let g = this.gestures[this.directionChain];
// https://developer.mozilla.org/zh-CN/docs/Web/API/Event/cancelable
const shouldCancelEvent =
g &&
g.cmd &&
(typeof event.cancelable !== "boolean" || event.cancelable);
if (shouldCancelEvent) {
event.preventDefault();
event.stopImmediatePropagation();
}
this.stopGesture();
}
keydown(event) {
if (this.isMouseDownR && event.key === "Escape") {
this.endGesture();
}
}
blur() {
// delay it
setTimeout(() => this.realBlur(), 5);
}
/**
* This is a dirty workaround for firefox 85,
* which dispatches blur event on tab switch
*/
realBlur() {
if (document.hasFocus()) {
// tab switched
this.clear();
return;
}
if (this.isMouseDownR) {
this.endGesture();
}
}
/// endregion event handlers
// noinspection JSUnusedGlobalSymbols
handleEvent(event) {
let fn = this[event.type];
if (typeof fn === "function") {
return fn.call(this, event);
}
}
}
let ucMouseGestures = new UcMouseGesture();
ucMouseGestures.bindEvent();
})();
浙公网安备 33010602011771号