Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MRU caching initial implementation #32

Merged
merged 9 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 49 additions & 30 deletions src/background.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,63 @@
// keep an mru cache for every window

// mru cache, not in use
var mruCache = [];
const DEBUG = true;

async function setMRU(tabs) {
await chrome.storage.session.set({"MRU_ID_Cache": tabs});
if (DEBUG) {
await logMRU();
}
}

async function logMRU() {
const cache = await getMRU();
console.log("MRU Cache: " + cache);
}

async function getMRU() {
const result = await chrome.storage.session.get("MRU_ID_Cache");
return result.MRU_ID_Cache;
}

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();
console.log(cache);
if (cache === undefined) {
// we haven't initialized a cache yet
// 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
const tabIds = [activeInfo.tabId];
cache = tabIds;
await setMRU(tabIds);
}

// 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);
Soreloser2 marked this conversation as resolved.
Show resolved Hide resolved
if (index != -1) {
cache.splice(index, 1);
}
cache.push(activeTab);
await setMRU(cache);
});
77 changes: 77 additions & 0 deletions test/integration/test-switch-tab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'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 Get/Set MRU", async (t) => {
const cache = await service_worker.evaluate(async () => {
const [tab] = await chrome.tabs.query({"currentWindow": true});
await setMRU([tab.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', 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");
});
});

3 changes: 3 additions & 0 deletions test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
2 changes: 1 addition & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const assert = require('node:assert');

test('basic test', async (t) => {
assert.strictEqual(1 + 2, 3, 'expected 3');
})
});

test('test puppeteer javascript Eval', async (t) => {
const browser = await puppeteer.launch();
Expand Down
29 changes: 29 additions & 0 deletions test/utils.js
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];
}