Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
47 changes: 47 additions & 0 deletions entrypoints/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ import {
saveState,
setUrlDetection,
} from "@/src/utils/storage";

// =================================================================
// DIAGNOSTIC TIMING - Remove after debugging
// =================================================================
const DIAG_ENABLED = true;
const diagLog = (label: string, startTime: number) => {
if (!DIAG_ENABLED) return;
const elapsed = Math.round(performance.now() - startTime);
console.log(`[DIAG] ${label}: ${elapsed}ms`);
};
const diagStart = () => performance.now();

import { closeTab, focusTab, queryZendeskTabs, updateLotusRoute } from "@/src/utils/tabs";
import type { RouteMatch, UrlDetectionMode, ZendeskTabInfo } from "@/src/utils/types";
import { isZendeskAgentUrl, matchZendeskUrl } from "@/src/utils/url-matching";
Expand Down Expand Up @@ -211,16 +223,25 @@ async function handleNavigation(
state: AllStorageData["state"],
): Promise<void> {
// Find existing LIVE agent tab for this subdomain (not the source tab)
const t1 = diagStart();
const existingTab = await findExistingAgentTab(match.subdomain, sourceTabId, state.zendeskTabs);
diagLog("handleNavigation.findExistingAgentTab", t1);

if (existingTab?.id) {
// Route to existing tab via postMessage to Zendesk SPA
// This avoids triggering browser navigation events (prevents cascade)
const t2 = diagStart();
const updated = await updateLotusRoute(existingTab.id, match.path);
diagLog("handleNavigation.updateLotusRoute", t2);

if (updated) {
const t3 = diagStart();
await focusTab(existingTab.id);
diagLog("handleNavigation.focusTab", t3);

const t4 = diagStart();
await closeTab(sourceTabId);
diagLog("handleNavigation.closeTab", t4);

// Update lastActive for the target tab
state.zendeskTabs[existingTab.id] = {
Expand All @@ -230,7 +251,9 @@ async function handleNavigation(

// Remove source tab from tracking (it's now closed)
delete state.zendeskTabs[sourceTabId];
const t5 = diagStart();
await saveState(state);
diagLog("handleNavigation.saveState", t5);
}
// If update failed, tab no longer exists - just let source tab continue
} else {
Expand All @@ -239,7 +262,9 @@ async function handleNavigation(
subdomain: match.subdomain,
lastActive: Date.now(),
};
const t6 = diagStart();
await saveState(state);
diagLog("handleNavigation.saveState(new)", t6);
}
}

Expand Down Expand Up @@ -352,6 +377,7 @@ export default defineBackground(() => {
*/
chrome.webNavigation.onBeforeNavigate.addListener(
async (details) => {
const t0 = diagStart();
// Only handle main frame navigation
if (details.frameId !== 0) return;

Expand All @@ -366,7 +392,9 @@ export default defineBackground(() => {

// PERFORMANCE: Load ALL storage data in ONE API call
// This batches getUrlDetection + loadState into a single chrome.storage.local.get()
const t1 = diagStart();
const { mode, state } = await loadAll();
diagLog("onBeforeNavigate.loadAll", t1);
if (mode === "noUrls") return;

const match = matchZendeskUrl(details.url);
Expand All @@ -375,7 +403,10 @@ export default defineBackground(() => {
// For ticketUrls mode, only intercept ticket routes
if (mode === "ticketUrls" && match.type !== "ticket") return;

const t2 = diagStart();
await handleNavigation(details.tabId, details.url, match, state);
diagLog("onBeforeNavigate.handleNavigation", t2);
diagLog("onBeforeNavigate.TOTAL", t0);
},
{ url: [{ hostSuffix: "zendesk.com" }] },
);
Expand All @@ -387,6 +418,7 @@ export default defineBackground(() => {
*/
chrome.webNavigation.onCommitted.addListener(
async (details) => {
const t0 = diagStart();
// Only handle main frame navigation
if (details.frameId !== 0) return;

Expand All @@ -401,7 +433,9 @@ export default defineBackground(() => {
if (isDuplicateNavigation(details.tabId, details.url)) return;

// PERFORMANCE: Load ALL storage data in ONE API call
const t1 = diagStart();
const { mode, state } = await loadAll();
diagLog("onCommitted.loadAll", t1);
if (mode === "noUrls") return;

const match = matchZendeskUrl(details.url);
Expand All @@ -410,7 +444,10 @@ export default defineBackground(() => {
// For ticketUrls mode, only intercept ticket routes
if (mode === "ticketUrls" && match.type !== "ticket") return;

const t2 = diagStart();
await handleNavigation(details.tabId, details.url, match, state);
diagLog("onCommitted.handleNavigation", t2);
diagLog("onCommitted.TOTAL", t0);
},
{ url: [{ hostSuffix: "zendesk.com" }] },
);
Expand All @@ -427,21 +464,31 @@ export default defineBackground(() => {
*/
chrome.webNavigation.onDOMContentLoaded.addListener(
async (details) => {
const t0 = diagStart();
// Only handle main frame
if (details.frameId !== 0) return;

// PERFORMANCE: Load ALL storage data in ONE API call
// This replaces: trackTab() + getUrlDetection() which was 3 separate reads
const t1 = diagStart();
const { mode, state } = await loadAll();
diagLog("onDOMContentLoaded.loadAll", t1);

// Track this tab (using pre-loaded state)
const t2 = diagStart();
await trackTabWithState(details.tabId, details.url, state);
diagLog("onDOMContentLoaded.trackTabWithState", t2);

// Enable action icon for this tab
const t3 = diagStart();
chrome.action.enable(details.tabId);
diagLog("onDOMContentLoaded.action.enable", t3);

// Set icon state for THIS tab only (not all tabs)
const t4 = diagStart();
await setTabIcon(details.tabId, mode);
diagLog("onDOMContentLoaded.setTabIcon", t4);
diagLog("onDOMContentLoaded.TOTAL", t0);
},
{ url: [{ urlContains: "zendesk.com/agent" }] },
);
Expand Down
13 changes: 13 additions & 0 deletions src/utils/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@

import type { StorageSchema, UrlDetectionMode, ZendeskTabInfo } from "./types";

// DIAGNOSTIC TIMING - Remove after debugging
const DIAG_ENABLED = true;
const diagLog = (label: string, startTime: number) => {
if (!DIAG_ENABLED) return;
const elapsed = Math.round(performance.now() - startTime);
console.log(`[DIAG] ${label}: ${elapsed}ms`);
};
const diagStart = () => performance.now();

/** Storage key for QuickTab state */
const STORAGE_KEY = "quicktab_state" as const;

Expand Down Expand Up @@ -38,7 +47,9 @@ export async function loadState(): Promise<StorageSchema["quicktab_state"]> {
*/
export async function saveState(state: StorageSchema["quicktab_state"]): Promise<void> {
try {
const t1 = diagStart();
await chrome.storage.local.set({ [STORAGE_KEY]: state });
diagLog("saveState.storage.local.set", t1);
} catch (error) {
// Silent fallback per CONTEXT.md - no user-visible error
console.warn("QuickTab: Storage write failed", error);
Expand Down Expand Up @@ -106,7 +117,9 @@ export interface AllStorageData {
*/
export async function loadAll(): Promise<AllStorageData> {
try {
const t1 = diagStart();
const result = await chrome.storage.local.get(["urlDetection", STORAGE_KEY]);
diagLog("loadAll.storage.local.get", t1);
return {
mode: (result.urlDetection as UrlDetectionMode) ?? "allUrls",
state: (result[STORAGE_KEY] as StorageSchema["quicktab_state"]) ?? DEFAULT_STATE,
Expand Down
26 changes: 25 additions & 1 deletion src/utils/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
* All tab operations wrapped in try/catch for defensive handling per RESEARCH.md pitfall #3
*/

// DIAGNOSTIC TIMING - Remove after debugging
const DIAG_ENABLED = true;
const diagLog = (label: string, startTime: number) => {
if (!DIAG_ENABLED) return;
const elapsed = Math.round(performance.now() - startTime);
console.log(`[DIAG] ${label}: ${elapsed}ms`);
};
const diagStart = () => performance.now();

/**
* Focus a tab and bring its window to front.
* Per CONTEXT.md: Different window = focus only, bring window to front, don't move tabs.
Expand All @@ -12,9 +21,17 @@
*/
export async function focusTab(tabId: number): Promise<boolean> {
try {
const t1 = diagStart();
const tab = await chrome.tabs.get(tabId);
diagLog("focusTab.tabs.get", t1);

const t2 = diagStart();
await chrome.tabs.update(tabId, { active: true, highlighted: true });
diagLog("focusTab.tabs.update", t2);

const t3 = diagStart();
await chrome.windows.update(tab.windowId, { focused: true });
diagLog("focusTab.windows.update", t3);
return true;
} catch {
// Tab no longer exists - caller should clean up from storage
Expand Down Expand Up @@ -48,7 +65,9 @@ export async function updateTabUrl(tabId: number, url: string): Promise<boolean>
*/
export async function closeTab(tabId: number): Promise<void> {
try {
const t1 = diagStart();
await chrome.tabs.remove(tabId);
diagLog("closeTab.tabs.remove", t1);
} catch {
// Tab already closed - ignore per RESEARCH.md pitfall #3
}
Expand All @@ -61,7 +80,10 @@ export async function closeTab(tabId: number): Promise<void> {
* @returns Array of tabs matching Zendesk agent URL pattern
*/
export async function queryZendeskTabs(): Promise<chrome.tabs.Tab[]> {
return chrome.tabs.query({ url: "*://*.zendesk.com/agent/*" });
const t1 = diagStart();
const result = await chrome.tabs.query({ url: "*://*.zendesk.com/agent/*" });
diagLog("queryZendeskTabs.tabs.query", t1);
return result;
}

/**
Expand All @@ -88,6 +110,7 @@ export async function queryZendeskTabs(): Promise<chrome.tabs.Tab[]> {
*/
export async function updateLotusRoute(tabId: number, route: string): Promise<boolean> {
try {
const t1 = diagStart();
await chrome.scripting.executeScript({
target: { tabId },
func: (routePath: string) => {
Expand All @@ -97,6 +120,7 @@ export async function updateLotusRoute(tabId: number, route: string): Promise<bo
},
args: [route],
});
diagLog("updateLotusRoute.scripting.executeScript", t1);
return true;
} catch (error) {
console.warn("QuickTab: Failed to update lotus route", tabId, error);
Expand Down
Loading