-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MRU caching initial implementation (#32)
* Started MRU Caching by tab id * Added tab openlistener to work with MRU * Added MRU caching and tests. Works pretty good. * renamed MRU tests to integration since they are larger scale * doing some refactoring but broke tests * Some small cleanup * Made tests faster idk how * Got switch tab to work by manually invoking * Uses window-based dict needs more tests some race conditions --------- Co-authored-by: sorendahl <[email protected]>
- Loading branch information
1 parent
8485e35
commit 3a6c0db
Showing
5 changed files
with
178 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,81 @@ | ||
// keep an mru cache for every window | ||
|
||
// mru cache, not in use | ||
var mruCache = []; | ||
const DEBUG = true; | ||
|
||
async function setMRU(tabs) { | ||
const window = await chrome.windows.getCurrent(); | ||
const result = await chrome.storage.session.get("MRU_ID_Cache"); | ||
var dict = result.MRU_ID_Cache; | ||
if (dict === undefined) { | ||
dict = {}; | ||
} | ||
dict[window.id] = tabs; | ||
await chrome.storage.session.set({"MRU_ID_Cache": dict}); | ||
if (DEBUG) { | ||
await logMRU(); | ||
} | ||
} | ||
|
||
async function initializeMRU(tabId, windowId) { | ||
var dict = {}; | ||
dict[windowId] = [tabId]; | ||
await chrome.storage.session.set({"MRU_ID_Cache": dict}); | ||
return dict[windowId]; | ||
} | ||
|
||
async function logMRU() { | ||
const cache = await getMRU(); | ||
console.log(cache); | ||
} | ||
|
||
async function getMRU() { | ||
const window = await chrome.windows.getCurrent(); | ||
const result = await chrome.storage.session.get("MRU_ID_Cache"); | ||
// if there isn't a cache already set, don't try to query that window | ||
if (result.MRU_ID_Cache === undefined) { | ||
return undefined; | ||
} | ||
return result.MRU_ID_Cache[window.id]; | ||
} | ||
|
||
async function switch_tab() { | ||
if (DEBUG) { | ||
console.log("command triggered"); | ||
} | ||
const cache = await getMRU(); | ||
// this automatically calls to the tab onActivated callback so we don't | ||
// need to set the cache to anything here | ||
await chrome.tabs.update(cache.at(cache.length - 2), {active: true, highlighted: true}); | ||
} | ||
|
||
// Listen for keyboard shortcut | ||
chrome.commands.onCommand.addListener(function(command) { | ||
chrome.commands.onCommand.addListener(async function(command) { | ||
// the 'switch-tab' command is defined in the manifest | ||
if (command === "switch-tab") { | ||
console.log("command triggered"); | ||
|
||
// we're looking within session data to grab the lastTabId | ||
chrome.storage.session.get(["lastTabId"]).then((result) => { | ||
console.log("Value currently is " + result.lastTabId); | ||
// the update method grabs the tab it receives in the first parameter and | ||
// makes its 'active' and 'highlight' features true, which acts to switch to it | ||
// 'active' lets the tab be computationally active | ||
// 'highlighted' does the actual switching to the tab | ||
chrome.tabs.update(result.lastTabId, {active: true, highlighted: true}); | ||
}); | ||
|
||
await switch_tab(); | ||
} | ||
}); | ||
|
||
// Listen for every new tab | ||
chrome.tabs.onActivated.addListener(function(activeInfo) { | ||
chrome.tabs.onActivated.addListener(async function(activeInfo) { | ||
// need to keep session data stored. otherwise, data will get removed when the service worker | ||
// gets shut down, which happens if another program is in focus within the device | ||
var cache = await getMRU(); | ||
if (cache === undefined) { | ||
// we haven't initialized a cache yet, need to add | ||
// based on current tab and window | ||
// the reason we don't do this on session/Chrome startup is | ||
// because race conditions can still cause the session to get | ||
// asked for the cache before the cache has been set from the startup handler\ | ||
cache = await initializeMRU(activeInfo.tabId, activeInfo.windowId); | ||
} | ||
console.log(cache); | ||
|
||
// this gets the value of currTabId from session storage, and sets lastTabId to it | ||
chrome.storage.session.get(["currTabId"]).then((result) => { | ||
chrome.storage.session.set({ "lastTabId": result.currTabId }).then(() => { | ||
console.log("lastTabId is set to " + result.currTabId); | ||
}); | ||
}); | ||
// this set the value of currTabId to the current tab's id | ||
chrome.storage.session.set({ "currTabId": activeInfo.tabId }).then(() => { | ||
console.log("currTabId is set to " + activeInfo.tabId); | ||
}); | ||
|
||
// TODO: should we implement an inverted index to have quick removing? | ||
|
||
// mruCache.push(activeInfo.tabId); | ||
const activeTab = activeInfo.tabId; | ||
const index = cache.indexOf(activeTab); | ||
if (index != -1) { | ||
cache.splice(index, 1); | ||
} | ||
cache.push(activeTab); | ||
await setMRU(cache); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
'use strict' | ||
|
||
import puppeteer from "puppeteer"; | ||
import test from "node:test"; | ||
import assert from "node:assert" | ||
import {initializeExtension, sleep} from "../utils.js"; | ||
|
||
test("Top level extension test", async (t) => { | ||
// can comment this out and just have | ||
// var browser; | ||
// but this line gives type hinting | ||
var browser = await puppeteer.launch(); await browser.close(); | ||
var service_worker; | ||
t.beforeEach(async (t) => { | ||
[browser, service_worker] = await initializeExtension(); | ||
}); | ||
t.afterEach(async (t) => { | ||
await browser.close(); | ||
}); | ||
|
||
await t.test("Test manual Initialization of MRU", async (t) => { | ||
const cache = await service_worker.evaluate(async () => { | ||
const [tab] = await chrome.tabs.query({"currentWindow": true}); | ||
const window = await chrome.windows.getCurrent(); | ||
await initializeMRU(tab.id, window.id); | ||
return await getMRU(); | ||
}); | ||
assert.notDeepStrictEqual(cache, {}, "expected a real object result"); | ||
assert.notStrictEqual(cache, undefined, "expected a cache to be found"); | ||
assert.strictEqual(typeof(cache), typeof([])); | ||
assert.strictEqual(cache.length, 1); | ||
}); | ||
|
||
await t.test('Test Initialization of MRU by trigger', async (t) => { | ||
const page = await browser.newPage(); | ||
await page.bringToFront(); | ||
const cache = await service_worker.evaluate(async () => { | ||
return await getMRU(); | ||
}); | ||
assert.strictEqual(typeof(cache), typeof([])); | ||
assert.strictEqual(cache.length, 1, "expect one tab currently in the cache"); | ||
}); | ||
|
||
await t.test('Test Initialization of MRU with 2 tabs', async (t) => { | ||
const page1 = await browser.newPage(); | ||
await page1.bringToFront(); | ||
const page2 = await browser.newPage(); | ||
await page2.bringToFront(); | ||
const cache = await service_worker.evaluate(async () => { | ||
return await getMRU(); | ||
}); | ||
assert.strictEqual(cache.length, 2, "expect two tabs currently in the cache"); | ||
}); | ||
|
||
// can't utilize keyboard shortcuts to test tab switching but can just call the method | ||
await t.test('Test switch tabs', async (t) => { | ||
const page1 = await browser.newPage(); | ||
await page1.bringToFront(); | ||
const page2 = await browser.newPage(); | ||
await page2.bringToFront(); | ||
|
||
const cache1 = await service_worker.evaluate(async () => { | ||
return await getMRU(); | ||
}); | ||
// for some reason if we put this call in the block where we get cache2, it fails | ||
// I think it's a timing thing and sending the eval request takes long enough | ||
// so that the new tab listener triggers. | ||
await service_worker.evaluate(async () => { | ||
await switch_tab(); | ||
}); | ||
const cache2 = await service_worker.evaluate(async () => { | ||
return await getMRU(); | ||
}); | ||
assert.strictEqual(cache1[0], cache2[1], "tabs should be swapped"); | ||
assert.strictEqual(cache2[0], cache1[1], "tabs should be swapped"); | ||
}); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"type": "module" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
'use strict' | ||
|
||
import puppeteer from 'puppeteer'; | ||
import path from "path"; | ||
|
||
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | ||
|
||
export async function initializeExtension() { | ||
const pathToExtension = path.join(process.cwd(), 'src'); | ||
const browser = await puppeteer.launch({ | ||
headless: false, | ||
// uncomment for trying to actually see what puppeteer is doing | ||
// or if there could be race conditions | ||
// if there are race conditions, put a sleep in the code instead of leaving it on | ||
//slowMo: 500, | ||
args: [ | ||
`--disable-extensions-except=${pathToExtension}`, | ||
`--load-extension=${pathToExtension}`, | ||
], | ||
}); | ||
const serviceWorkerTarget = await browser.waitForTarget( | ||
target => target.type() === 'service_worker' | ||
); | ||
const service_worker = await serviceWorkerTarget.worker(); | ||
// need a small pause to allow chrome to set certain things | ||
// I'm not sure why a pause of 0 ms works but without this line some tests fail | ||
await sleep(0); | ||
return [browser, service_worker]; | ||
} |