Reputation: 1
I'm trying to troubleshoot a bug in a basic extension that tracks the most recently accessed tabs and lists them in a popup menu for quick access. However, the tab tracking list seems to randomly clear, and I can't figure out why. E.g., the pop-up menu displays 5 previous tabs. I go to new tab. The list is now blank. Below are the (likely?) relevant code snippets followed by the individual files.
It's supposed to look like this. However, after I uploaded that screenshot, I switched to another stackoverflow tab, and the history was ostensibly erased. (The reload extension button in the popup menu is temporary)
Welcome your thoughts!
popup.js excerpt for DOM Content Loaded Event Listener
document.addEventListener('DOMContentLoaded', function() {
const mruListElement = document.getElementById('mruList');
mruListElement.innerHTML = '';
chrome.tabs.query({active: true, currentWindow: true}, function(currentTabs) {
const currentTabId = currentTabs[0].id;
chrome.storage.local.get(['mruTabs'], function(result) {
if (!result.mruTabs || result.mruTabs.length === 0) {
const listItem = document.createElement('li');
listItem.textContent = 'No tabs tracked yet.';
mruListElement.appendChild(listItem);
} else {
result.mruTabs.forEach(tab => {
if (tab.id !== currentTabId) {
updateTabDisplay(tab, mruListElement);
}
});
}
});
});
});
popup.js excerpt for Chrome Storage on Changed Listener
chrome.storage.onChanged.addListener(function(changes, namespace) {
if (namespace === 'local' && changes.mruTabs) {
mruListElement.innerHTML = ''; // clear and update MRU list if mruTabs has changed
chrome.tabs.query({
active: true,
currentWindow: true
}, function(currentTabs) {
const currentTabId = currentTabs[0].id;
chrome.storage.local.get(['mruTabs'], function(result) {
if (!result.mruTabs || result.mruTabs.length === 0) {
const listItem = document.createElement('li');
listItem.textContent = 'No tabs tracked yet.';
mruListElement.appendChild(listItem);
} else {
result.mruTabs.forEach(tab => {
if (tab.id !== currentTabId) {
updateTabDisplay(tab, mruListElement);
}
});
}
});
});
}
});
background.js excerpt for Listeners for Tab Events
chrome.tabs.onActivated.addListener(activeInfo => { ... });
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { ... });
chrome.tabs.onCreated.addListener(tab => { ... });
chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) { ... });
entire background.js
let mruTabs = [];
let lastSwitchTime = 0;
let cycleIndex = 0;
function logState(action, details) {
console.log(`Action: ${action}`, details, `Current MRU List:`, mruTabs.map(tab => `Tab ID: ${tab.id}, Window ID: ${tab.windowId}, Time: ${tab.time}`));
}
chrome.tabs.onActivated.addListener(activeInfo => {
chrome.tabs.get(activeInfo.tabId, (currentTab) => {
if (!currentTab.incognito) {
const accessTime = new Date().toISOString();
mruTabs = mruTabs.filter(tab => tab.id !== activeInfo.tabId);
mruTabs.unshift({ id: activeInfo.tabId, windowId: currentTab.windowId, time: accessTime });
if (mruTabs.length > 10) mruTabs.length = 10;
chrome.storage.local.set({ 'mruTabs': mruTabs });
logState("Tab Activated", { tabId: activeInfo.tabId, windowId: currentTab.windowId, time: accessTime });
}
});
});
chrome.commands.onCommand.addListener((command) => {
if (command === "switch_tab") {
chrome.windows.getCurrent({ populate: true }, (currentWindow) => {
mruTabs = mruTabs.filter(tab => tab.windowId === currentWindow.id);
cycleIndex = (cycleIndex + 1) % mruTabs.length;
if (mruTabs.length > 0) {
const tabToActivate = mruTabs[cycleIndex];
chrome.tabs.update(tabToActivate.id, { active: true });
logState("Switch Tab Command", { command, cycleIndex, tabToActivate });
}
});
} else if (command === "sort_tabs") {
chrome.windows.getCurrent((currentWindow) => {
const tabsInWindow = mruTabs.filter(tab => tab.windowId === currentWindow.id);
tabsInWindow.sort((a, b) => new Date(a.time) - new Date(b.time));
tabsInWindow.forEach((tab, index) => {
chrome.tabs.move(tab.id, { index });
logState("Sort Tabs Command", { tabId: tab.id, index });
});
chrome.storage.local.set({ 'mruTabs': mruTabs });
});
}
});
chrome.tabs.onCreated.addListener(tab => {
const accessTime = new Date().toISOString();
mruTabs.unshift({ id: tab.id, windowId: tab.windowId, time: accessTime });
if (mruTabs.length > 10) mruTabs.length = 10;
chrome.storage.local.set({ 'mruTabs': mruTabs });
logState("Tab Created", { tabId: tab.id, windowId: tab.windowId, time: accessTime });
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url) {
const accessTime = new Date().toISOString();
mruTabs = mruTabs.filter(tab => tab.id !== tabId);
mruTabs.unshift({ id: tabId, windowId: tab.windowId, time: accessTime });
if (mruTabs.length > 10) mruTabs.length = 10;
chrome.storage.local.set({ 'mruTabs': mruTabs });
logState("Tab Updated", { tabId, changeInfo, tab });
}
});
chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
mruTabs = mruTabs.filter(tab => tab.id !== tabId);
if(cycleIndex >= mruTabs.length) {
cycleIndex = 0;
}
chrome.storage.local.set({ 'mruTabs': mruTabs });
logState("Tab Removed", { tabId, removeInfo });
});
entire popup.js
document.addEventListener('DOMContentLoaded', function() {
const mruListElement = document.getElementById('mruList');
const reloadButton = document.getElementById('reloadExtension');
mruListElement.innerHTML = '';
chrome.tabs.query({active: true, currentWindow: true}, function(currentTabs) {
const currentTabId = currentTabs[0].id;
chrome.storage.local.get(['mruTabs'], function(result) {
if (!result.mruTabs || result.mruTabs.length === 0) {
const listItem = document.createElement('li');
listItem.textContent = 'No tabs tracked yet.';
mruListElement.appendChild(listItem);
} else {
result.mruTabs.forEach(tab => {
if (tab.id !== currentTabId) {
updateTabDisplay(tab, mruListElement);
}
});
}
});
});
reloadButton.addEventListener('click', function() {
chrome.runtime.reload();
});
chrome.storage.onChanged.addListener(function(changes, namespace) {
if (namespace === 'local' && changes.mruTabs) {
mruListElement.innerHTML = '';
chrome.tabs.query({active: true, currentWindow: true}, function(currentTabs) {
const currentTabId = currentTabs[0].id;
chrome.storage.local.get(['mruTabs'], function(result) {
if (!result.mruTabs || result.mruTabs.length === 0) {
const listItem = document.createElement('li');
listItem.textContent = 'No tabs tracked yet.';
mruListElement.appendChild(listItem);
} else {
result.mruTabs.forEach(tab => {
if (tab.id !== currentTabId) {
updateTabDisplay(tab, mruListElement);
}
});
}
});
});
}
});
document.addEventListener('keydown', function(event) {
const key = event.key;
if (key >= '1' && key <= '9') {
const index = parseInt(key) - 1;
const tabElements = document.querySelectorAll('.tab-item');
if (index < tabElements.length) {
const tabId = tabElements[index].getAttribute('data-tab-id');
chrome.tabs.update(parseInt(tabId), { active: true });
}
}
});
});
function updateTabDisplay(tab, listElement) {
chrome.tabs.get(tab.id, (tabInfo) => {
if (!chrome.runtime.lastError) {
const listItem = document.createElement('li');
listItem.classList.add('tab-item');
listItem.setAttribute('data-tab-id', tab.id);
const favicon = document.createElement('img');
favicon.src = tabInfo.favIconUrl ? tabInfo.favIconUrl : 'default_icon.png';
favicon.classList.add('favicon');
const link = document.createElement('a');
link.href = '#';
link.textContent = tabInfo.title;
link.classList.add('tab-title');
link.addEventListener('click', function() {
chrome.tabs.update(tab.id, {active: true}, function() {
chrome.windows.update(tabInfo.windowId, {focused: true});
});
});
const lastAccessed = document.createElement('span');
const accessTime = new Date(tab.time);
lastAccessed.textContent = `${accessTime.toLocaleTimeString()}`;
lastAccessed.classList.add('last-accessed');
listItem.appendChild(favicon);
listItem.appendChild(link);
listItem.appendChild(lastAccessed);
listElement.appendChild(listItem);
}
});
}
entire popup.html
<!DOCTYPE html>
<html>
<head>
<title>MRU Tabs List</title>
<style>
body {
width: 300px;
font-family: Arial, sans-serif;
padding: 10px;
padding-left: 5px;
}
ul {
padding-left: 0;
margin-left: 0;
}
.tab-item {
display: flex;
align-items: center;
margin: 8px 0;
padding-left: 5px;
}
.favicon {
width: 16px;
height: 16px;
margin-right: 8px;
}
.tab-title {
flex-grow: 1;
margin-right: 8px;
text-decoration: none;
color: #333;
}
.last-accessed {
font-size: 0.9em;
color: #666;
}
.tab-item:nth-child(odd) {
background-color: #f9f9f9;
}
.tab-item:nth-child(even) {
background-color: #e9e9e9;
}
</style>
</head>
<body>
<ul id="mruList"></ul>
<button id="sortTabs">Sort Tabs</button>
<button id="reloadExtension">Reload Extension</button>
<script src="popup.js"></script>
</body>
</html>
entire manifest
{
"manifest_version": 3,
"name": "MRU Tab Switcher",
"version": "1.0",
"description": "yada yada",
"permissions": ["tabs", "storage"],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"commands": {
"switch_tab": {
"suggested_key": {
"default": "Ctrl+Shift+Y",
"mac": "Command+Shift+Y"
},
"description": "Switch to the most recently used tab."
}
}
}
E.g., the pop-up menu displays 5 previous tabs. I go to new tab. The list is now blank.
Upvotes: 0
Views: 42