├── background.js ├── manifest.json ├── styles.css └── content_script.js /background.js: -------------------------------------------------------------------------------- 1 | // アイコンがクリックされた際は、content_script.js は既に静的に注入されている前提で処理を行う 2 | chrome.action.onClicked.addListener((tab) => { 3 | // content_script.js を動的に注入せず、直接タブリストの取得とメッセージ送信を実施する 4 | chrome.tabs.query({}, function(tabs) { 5 | chrome.tabs.sendMessage(tab.id, { action: "showMenu", tabs: tabs }); 6 | }); 7 | }); 8 | 9 | // content_script.js からのタブ切り替え要求の処理 10 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 11 | if (message.action === "activateTab") { 12 | chrome.tabs.update(message.tabId, { active: true }); 13 | } 14 | return true; // 非同期レスポンスを返すために true を返す 15 | }); -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Floating Tabs", 4 | "version": "1.0", 5 | "description": "Displays a floating menu of currently open tabs.", 6 | "permissions": ["tabs", "scripting", "activeTab"], 7 | "host_permissions": [""], 8 | "action": { 9 | "default_title": "Show Tabs" 10 | }, 11 | "background": { 12 | "service_worker": "background.js", 13 | "type": "module" 14 | }, 15 | "content_scripts": [ 16 | { 17 | "matches": [""], 18 | "js": ["content_script.js"], 19 | "css": ["styles.css"] 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | body #my-tab-menu { 3 | font-family: sans-serif !important; 4 | font-size: 11px !important; 5 | color: #fff !important; /* 文字色を明るく */ 6 | background: #333 !important; /* 背景を黒っぽく */ 7 | border: 1px solid #444 !important; /* 枠の色をダークに */ 8 | box-shadow: 0 2px 5px rgba(0,0,0,0.5) !important; /* 影をダークに */ 9 | padding: 5px 0 !important; 10 | z-index: 10000 !important; 11 | position: absolute !important; 12 | } 13 | body #my-tab-menu div { 14 | font-size: 11px !important; 15 | color: #fff !important; /* 文字色を明るく */ 16 | padding: 5px 10px !important; 17 | cursor: pointer !important; 18 | } 19 | body #my-tab-menu div:hover { 20 | background: #555 !important; /* ホバー時の背景を少し明るく */ 21 | } 22 | /* Added: Force font size for all child elements */ 23 | body #my-tab-menu * { 24 | font-size: 11px !important; 25 | } 26 | 27 | @media (prefers-color-scheme: dark) { 28 | body #my-tab-menu { 29 | font-family: sans-serif !important; 30 | font-size: 11px !important; 31 | color: #fff !important; 32 | background: #333 !important; 33 | border: 1px solid #444 !important; 34 | box-shadow: 0 2px 5px rgba(0,0,0,0.5) !important; 35 | padding: 5px 0 !important; 36 | z-index: 10000 !important; 37 | position: absolute !important; 38 | } 39 | body #my-tab-menu div { 40 | font-size: 11px !important; 41 | color: #fff !important; 42 | padding: 5px 10px !important; 43 | cursor: pointer !important; 44 | } 45 | body #my-tab-menu div:hover { 46 | background: #555 !important; 47 | } 48 | body #my-tab-menu * { 49 | font-size: 11px !important; 50 | } 51 | } 52 | 53 | @media (prefers-color-scheme: light) { 54 | body #my-tab-menu { 55 | font-family: sans-serif !important; 56 | font-size: 11px !important; 57 | color: #000 !important; 58 | background: #fff !important; 59 | border: 1px solid #ccc !important; 60 | box-shadow: 0 2px 5px rgba(0,0,0,0.15) !important; 61 | padding: 5px 0 !important; 62 | z-index: 10000 !important; 63 | position: absolute !important; 64 | } 65 | body #my-tab-menu div { 66 | font-size: 11px !important; 67 | color: #000 !important; 68 | padding: 5px 10px !important; 69 | cursor: pointer !important; 70 | } 71 | body #my-tab-menu div:hover { 72 | background: #eee !important; 73 | } 74 | body #my-tab-menu * { 75 | font-size: 11px !important; 76 | } 77 | } -------------------------------------------------------------------------------- /content_script.js: -------------------------------------------------------------------------------- 1 | // Variable to store the last mouse position 2 | let lastMousePosition = { x: 0, y: 0 }; 3 | // Flag to track if the menu is visible 4 | let isMenuVisible = false; 5 | 6 | // Monitor mouse movement events on the page and record the latest coordinates 7 | document.addEventListener("mousemove", (event) => { 8 | lastMousePosition = { x: event.pageX, y: event.pageY }; 9 | }); 10 | 11 | // Display information in console when extension is loaded 12 | console.log("Tab List Context Menu: Content script loaded"); 13 | 14 | // Hide menu when tab is switched 15 | document.addEventListener("visibilitychange", () => { 16 | if (document.hidden && isMenuVisible) { 17 | removeContextMenu(); 18 | } 19 | }); 20 | 21 | // Function to ensure styles are properly applied 22 | function ensureStyles() { 23 | // Check if already added 24 | if (document.getElementById('tab-context-menu-style')) return; 25 | 26 | // Insert inline styles 27 | const styleElement = document.createElement('style'); 28 | styleElement.id = 'tab-context-menu-style'; 29 | styleElement.textContent = ` 30 | #my-tab-menu { 31 | font-family: sans-serif !important; 32 | font-size: 11px !important; 33 | color: #000 !important; 34 | background: #fff !important; 35 | border: 1px solid #ccc !important; 36 | box-shadow: 0 2px 5px rgba(0,0,0,0.15) !important; 37 | padding: 5px 0 !important; 38 | z-index: 10000 !important; 39 | position: absolute !important; 40 | } 41 | #my-tab-menu div { 42 | font-size: 11px !important; 43 | color: #000 !important; 44 | padding: 5px 10px !important; 45 | cursor: pointer !important; 46 | } 47 | #my-tab-menu div:hover { 48 | background: #eee !important; 49 | } 50 | #my-tab-menu * { 51 | font-size: 11px !important; 52 | } 53 | `; 54 | document.head.appendChild(styleElement); 55 | } 56 | 57 | // Listen for messages from background.js 58 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 59 | console.log("Message received:", message); 60 | if (message.action === "showMenu") { 61 | // If menu is already visible, remove it and respond 62 | if (isMenuVisible) { 63 | removeContextMenu(); 64 | sendResponse({status: "Menu hidden"}); 65 | } else { 66 | // Ensure styles are applied 67 | ensureStyles(); 68 | showContextMenu(lastMousePosition, message.tabs); 69 | sendResponse({status: "Menu shown"}); 70 | } 71 | } else if (message.action === "hideMenu") { 72 | // When receiving a message that another tab has become active 73 | if (isMenuVisible) { 74 | removeContextMenu(); 75 | } 76 | sendResponse({status: "Menu hidden"}); 77 | } 78 | return true; // Return true for asynchronous response 79 | }); 80 | 81 | // Updated showContextMenu with dynamic theme colors 82 | function showContextMenu(position, tabs) { 83 | // Remove existing menu if any 84 | removeContextMenu(); 85 | 86 | // Determine theme colors based on prefers-color-scheme 87 | const darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; 88 | const bgColor = darkMode ? "#333" : "#fff"; 89 | const textColor = darkMode ? "#fff" : "#000"; 90 | const borderColor = darkMode ? "1px solid #444" : "1px solid #ccc"; 91 | const boxShadow = darkMode ? "0 2px 5px rgba(0,0,0,0.5)" : "0 2px 5px rgba(0,0,0,0.15)"; 92 | const hoverColor = darkMode ? "#555" : "#eee"; 93 | 94 | // Create menu container 95 | const menu = document.createElement("div"); 96 | menu.id = "my-tab-menu"; 97 | menu.style.position = "absolute"; 98 | menu.style.zIndex = 10000; 99 | menu.style.background = bgColor; 100 | menu.style.color = textColor; 101 | menu.style.border = borderColor; 102 | menu.style.boxShadow = boxShadow; 103 | menu.style.padding = "5px 0"; 104 | menu.style.fontFamily = "sans-serif"; 105 | menu.style.fontSize = "11px"; 106 | document.body.appendChild(menu); 107 | 108 | // Set menu as visible 109 | isMenuVisible = true; 110 | 111 | // Generate menu items from tab list 112 | tabs.forEach((tab) => { 113 | const item = document.createElement("div"); 114 | item.style.padding = "5px 10px"; 115 | item.style.cursor = "pointer"; 116 | item.style.display = "flex"; 117 | item.style.alignItems = "center"; 118 | item.style.fontSize = "11px"; 119 | item.style.color = textColor; 120 | 121 | // Add favicon 122 | if (tab.favIconUrl) { 123 | const favicon = document.createElement("img"); 124 | favicon.src = tab.favIconUrl; 125 | favicon.style.height = "16px"; 126 | favicon.style.marginRight = "8px"; 127 | favicon.style.flexShrink = "0"; 128 | item.appendChild(favicon); 129 | } else { 130 | // Reserve space if no favicon 131 | const placeholder = document.createElement("div"); 132 | placeholder.style.width = "16px"; 133 | placeholder.style.height = "16px"; 134 | placeholder.style.marginRight = "8px"; 135 | placeholder.style.flexShrink = "0"; 136 | item.appendChild(placeholder); 137 | } 138 | 139 | // Add title text 140 | const title = document.createElement("span"); 141 | title.textContent = tab.title; 142 | title.style.whiteSpace = "nowrap"; 143 | title.style.overflow = "hidden"; 144 | title.style.textOverflow = "ellipsis"; 145 | title.style.fontSize = "11px"; 146 | title.style.color = textColor; 147 | item.appendChild(title); 148 | 149 | // Send tab switch request on click 150 | item.addEventListener("click", (e) => { 151 | e.stopPropagation(); // Prevent event propagation for clicks outside menu 152 | chrome.runtime.sendMessage({ action: "activateTab", tabId: tab.id }); 153 | removeContextMenu(); 154 | }); 155 | 156 | // Change background color on mouse events 157 | item.addEventListener("mouseover", () => { item.style.background = hoverColor; }); 158 | item.addEventListener("mouseout", () => { item.style.background = bgColor; }); 159 | 160 | menu.appendChild(item); 161 | }); 162 | 163 | // Calculate the best menu location given the viewport 164 | const viewportWidth = window.innerWidth; 165 | const viewportHeight = window.innerHeight; 166 | const menuWidth = menu.offsetWidth; 167 | const menuHeight = menu.offsetHeight; 168 | let menuX = position.x; 169 | let menuY = position.y; 170 | const scrollX = window.scrollX; 171 | const scrollY = window.scrollY; 172 | 173 | if (menuWidth <= viewportWidth && menuX + menuWidth > viewportWidth + scrollX) { 174 | menuX = menuX - menuWidth; 175 | } 176 | 177 | if (menuHeight <= viewportHeight && menuY + menuHeight > viewportHeight + scrollY) { 178 | menuY = menuY - menuHeight; 179 | } 180 | 181 | menu.style.left = menuX + "px"; 182 | menu.style.top = menuY + "px"; 183 | 184 | // Hide menu when clicking outside 185 | document.addEventListener("click", removeContextMenu); 186 | } 187 | 188 | // Remove menu function 189 | function removeContextMenu() { 190 | const menu = document.getElementById("my-tab-menu"); 191 | if (menu) { 192 | menu.remove(); 193 | } 194 | // Turn off menu visibility flag 195 | isMenuVisible = false; 196 | document.removeEventListener("click", removeContextMenu); 197 | } --------------------------------------------------------------------------------