Skip to content

Commit 312b003

Browse files
committed
Attempt a fix for issue #25 by scanning sessions in the db and matching the tabs (if window id does not match). If this works I will get some unit tests for this scenario
1 parent 4161ba6 commit 312b003

File tree

6 files changed

+74
-18
lines changed

6 files changed

+74
-18
lines changed

js/background/background.js

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { spacesService } from './spacesService.js';
1212
import * as common from '../common.js';
1313
/** @typedef {common.SessionPresence} SessionPresence */
1414
/** @typedef {common.Space} Space */
15+
/** @typedef {common.Window} Window */
1516
/** @typedef {import('./dbService.js').WindowBounds} WindowBounds */
1617

1718
// eslint-disable-next-line no-unused-vars, no-var
@@ -276,7 +277,11 @@ async function processMessage(request, sender) {
276277
case 'requestSpaceFromWindowId':
277278
windowId = cleanParameter(request.windowId);
278279
if (windowId) {
279-
return requestSpaceFromWindowId(windowId);
280+
let matchByTabs = undefined;
281+
if (request.matchByTabs) {
282+
matchByTabs = cleanParameter(request.matchByTabs);
283+
}
284+
return requestSpaceFromWindowId(windowId, matchByTabs);
280285
}
281286
return undefined;
282287

@@ -786,9 +791,11 @@ async function requestCurrentSpace() {
786791

787792
/**
788793
* @param {number} windowId
794+
* @param {boolean|undefined} matchByTabs - Whether to match the space by tabs if matching by
795+
* windowId fails. If undefined, the default is to match by windowId only.
789796
* @returns {Promise<Space|false>}
790797
*/
791-
async function requestSpaceFromWindowId(windowId) {
798+
async function requestSpaceFromWindowId(windowId, matchByTabs) {
792799
// first check for an existing session matching this windowId
793800
const session = await dbService.fetchSessionByWindowId(windowId);
794801

@@ -802,11 +809,37 @@ async function requestSpaceFromWindowId(windowId) {
802809
history: session.history,
803810
};
804811
return space;
805-
806-
// otherwise build a space object out of the actual window
807812
} else {
808813
try {
814+
/** @type {Window} */
809815
const window = await chrome.windows.get(windowId, { populate: true });
816+
817+
if (matchByTabs) {
818+
console.log(`matchByTabs=true`);
819+
const allSpaces = await requestAllSpaces();
820+
// If any space in the database has the exact same tabs in the exact same order as
821+
// the currently-open window, then we assume the window got out of sync (due to a
822+
// Chrome restart or other factors). Update the database with the new window id and
823+
// return it.
824+
for (const space of allSpaces) {
825+
if (
826+
space.tabs.length === window.tabs.length &&
827+
space.tabs.every((tab, index) => tab.url === window.tabs[index].url)
828+
) {
829+
// Update the database object.
830+
const dbSession = await dbService.fetchSessionById(space.sessionId);
831+
dbSession.windowId = windowId;
832+
await dbService.updateSession(dbSession);
833+
834+
// Update the space object and return it.
835+
space.windowId = windowId;
836+
console.log(`matchByTabs: Found a session and updated it.`);
837+
return space;
838+
}
839+
}
840+
}
841+
842+
// Otherwise build a space object out of the actual window.
810843
/** @type {Space} */
811844
const space = {
812845
sessionId: false,
@@ -1273,9 +1306,9 @@ function calculateSessionBounds(displayBounds, sessionBounds) {
12731306
}
12741307

12751308
/**
1276-
* Ensures the parameter is a number.
1309+
* Ensures the parameter is a number or boolean.
12771310
* @param {string|number} param - The parameter to clean.
1278-
* @returns {number} - The cleaned parameter.
1311+
* @returns {number|boolean} - The cleaned parameter.
12791312
*/
12801313
function cleanParameter(param) {
12811314
if (typeof param === 'number') {

js/background/dbService.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* global db */
22

33
import { db, Server } from './db.js';
4+
import * as common from '../common.js';
5+
/** @typedef {common.Tab} Tab */
46

57
/**
68
* @typedef WindowBounds
@@ -11,11 +13,13 @@ import { db, Server } from './db.js';
1113
*/
1214

1315
/**
16+
* The storage format for a space in the database.
1417
* @typedef Session
1518
* @property {number} id Auto-generated indexedDb object id
19+
* @property {number|false} windowId The window id associated with the session, or false.
1620
* @property {number} sessionHash A hash formed from the combined urls in the session window
1721
* @property {string} name The saved name of the session
18-
* @property {Array} tabs An array of chrome tab objects (often taken from the chrome window obj)
22+
* @property {Array<Tab>} tabs An array of chrome tab objects (often taken from the chrome window obj)
1923
* @property {Array} history An array of chrome tab objects that have been removed from the session
2024
* @property {Date} lastAccess Timestamp that gets updated with every window focus
2125
* @property {WindowBounds?} windowBounds Optional saved window position and size

js/background/spacesService.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,8 @@ class SpacesService {
442442
* @returns {Promise<Session|null>} The session object if found, null otherwise
443443
*/
444444
async _getSessionByWindowIdInternal(windowId) {
445-
// First check in-memory sessions (includes temporary sessions)
445+
// First check in-memory sessions (includes temporary sessions). During initialization,
446+
// this will be the full set of sessions from the database.
446447
const memorySession = this.sessions.find(session => session.windowId === windowId);
447448
if (memorySession) {
448449
return memorySession;
@@ -1195,10 +1196,10 @@ function cleanUrl(url) {
11951196
* filterInternalWindows({ tabs: [{ url: 'https://example.com' }], type: 'normal' }) // returns false
11961197
*/
11971198
function filterInternalWindows(curWindow) {
1198-
// sanity check to make sure window isnt an internal spaces window
1199+
// Sanity check to make sure window isn't an internal spaces window.
11991200
if (
12001201
curWindow.tabs.length === 1 &&
1201-
curWindow.tabs[0].url.indexOf(chrome.runtime.id) >= 0
1202+
curWindow.tabs[0].url.includes(chrome.runtime.id)
12021203
) {
12031204
return true;
12041205
}

js/common.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@
99
* Copyright (C) 2025 by the Contributors.
1010
*/
1111

12+
/**
13+
* https://developer.chrome.com/docs/extensions/reference/api/tabs/#type-Tab
14+
* @typedef Tab
15+
* @property {string} favIconUrl The URL of the tab's favicon.
16+
* @property {string} title The title of the tab.
17+
* @property {string} url The URL of the tab.
18+
*/
19+
20+
/**
21+
* https://developer.chrome.com/docs/extensions/reference/api/windows/#type-Window
22+
* @typedef Window
23+
* @property {Array<Tab>} tabs The tabs in the window.
24+
*/
25+
1226
/**
1327
* @typedef Space
1428
* @property {number|false} sessionId The unique identifier for the session, or false if not saved.
1529
* @property {number|false} windowId The ID of the window associated with the space, or false if not open.
1630
* @property {string|false} name The name of the space, or false if not named.
17-
* @property {Array<Object>} tabs Array of tab objects containing URL and other tab properties.
31+
* @property {Array<Tab>} tabs Array of tab objects containing URL and other tab properties.
1832
* @property {Array<Object>|false} history Array of tab history objects, or false if no history.
1933
*/
2034

js/popup.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { getHashVariable } from './common.js';
44
import { spacesRenderer } from './spacesRenderer.js';
55
import { checkSessionOverwrite, escapeHtml } from './utils.js';
6+
import * as common from './common.js';
7+
/** @typedef {common.Space} Space */
68

79
const UNSAVED_SESSION = '(unnamed window)';
810
const NO_HOTKEY = 'no hotkey set';
@@ -19,6 +21,7 @@ export async function handlePopupMenuClick(action) {
1921
}
2022

2123
const nodes = {};
24+
/** @type {Space|false} */
2225
let globalCurrentSpace;
2326
let globalTabId;
2427
let globalUrl;
@@ -47,7 +50,11 @@ function initializePopup() {
4750
const action = getHashVariable('action', window.location.href);
4851

4952
const requestSpacePromise = globalWindowId
50-
? chrome.runtime.sendMessage({ action: 'requestSpaceFromWindowId', windowId: globalWindowId })
53+
? chrome.runtime.sendMessage({
54+
action: 'requestSpaceFromWindowId',
55+
windowId: globalWindowId,
56+
matchByTabs: true,
57+
})
5158
: chrome.runtime.sendMessage({ action: 'requestCurrentSpace' });
5259

5360
requestSpacePromise.then(space => {
@@ -78,11 +85,7 @@ function routeView(action) {
7885
*/
7986

8087
function renderCommon() {
81-
document.getElementById(
82-
'activeSpaceTitle'
83-
).value = globalCurrentSpace.name
84-
? globalCurrentSpace.name
85-
: UNSAVED_SESSION;
88+
document.getElementById('activeSpaceTitle').value = globalCurrentSpace.name ?? UNSAVED_SESSION;
8689

8790
document.querySelector('body').onkeyup = e => {
8891
// listen for escape key
@@ -368,6 +371,7 @@ async function renderMoveCard() {
368371
}
369372
}
370373

374+
// TODO: Is this used for anything anymore? When are globalTabId or globalUrl set?
371375
async function updateTabDetails() {
372376
let faviconSrc;
373377

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "Spaces",
33
"description": "Intuitive tab management",
4-
"version": "1.1.8.0",
4+
"version": "1.1.8.1",
55
"permissions": [
66
"contextMenus",
77
"downloads",

0 commit comments

Comments
 (0)