timing the amount of time focused on a tab with chrome.tab api with chrome extension

Viewed 195

I am trying to store information relating to how long a person stays on a specific tab / URL.

Using the chrome API - https://developer.chrome.com/docs/extensions/reference/tabs/#type-Tab - I can trace the active and the highlighted information of a tab to do this.

I'm currently tracing a few different datapoints with my extension, as below, and would love to be able to trace how long I am on a specific tab (so how long I am on that tab specifically), as well as how often I am moving between tabs or creating new ones.

export type TAB_USAGE = {
  tabId: number;
  index?: number;
  highlighted: boolean;
  url: string;
  startTime: string;
  endTime?: string;
  startFocus?: string;
  endFocus?: string;
};

const getHostname = (url: string) => {
  // use URL constructor and return hostname
  return new URL(url).hostname;
};

export function enableTabListener(
  socket: any,
  currentPages: TAB_USAGE[],
  visitedPages: Partial<TAB_USAGE>[]
) {


  chrome.tabs.onUpdated.addListener(
   
  });



  setInterval(async () => {
      console.log('sent tab data:', visitedPages);
  }, 50000);
}

I'm not 100% sure on the best approach here - I was thinking, do a check to see if the old data is active versus the new data is not?

so something like,

if (tabExisting.highlighted != tab.highlighted) {
//set endFocus?
} 

When doing this approach, it doesn't appear to work, tabExisting coming out as null basically a lot of the time.

Or use another listener API so that when highlight changes, there's a counter set per hour somewhere? I'd love to see how many tabs, how often I'm switching between them, staying on one of them, on a per hour basis really, trying to work out a good way to do that.

Love some feedback here - my brain is a bit fried from staring at this documentation and would love some guidance.

1 Answers

Your approach is somewhat correct. However, with the kind of activity you want to track I would change the type TAB_USAGE to:

export type FocusPeriod = {
  startTime: string,
  endTime: string
}

export type TAB_USAGE = {
  tabId: number;
  index?: number;
  active?: boolean;             //we're using active instead of highlighted
  url?: string;                 //notice url is now optional
  startTime: string;
  endTime?: string;
  focus?: Array<FocusPeriod>;   //storing multiple focus events of a tab
};

since one would be switching between tabs a lot and there would be multiple focus periods for a specific tab. Also note that url is now optional as we'll be listening to .onActivated event where url might not be available in the callback.

Now, in order to track the switching between tabs, I'd use .onActivated event instead of .onHighlighted event. Please mind the difference between an active tab and a highlighted tab.

An active tab is always highlighted. There is only one active tab per window. You can have several highlighted tabs in a same window. But then all highlighted tabs are not active. Example: select a tab, hold your Shift key, and select another tab. The last tab clicked will become the active tab for this window, but all tabs between them (including the two limit ones) are now highlighted.

A listener on .onActivated event can be added like this:

chrome.tabs.onActivated.addListener(
  (activeInfo: object) => {
    const previousActiveTabIndex = currentPages.findIndex((page) => page.active);
    if(previousActiveTabIndex !== -1) {
      currentPages[previousActiveTabIndex].active = false;
      const focusLength = currentPages[previousActiveTabIndex].focus.length;
      currentPages[previousActiveTabIndex].focus[focusLength - 1].endTime = new Date().toISOString();
    }
    const newActiveTabIndex = currentPages.findIndex((page) => page.tabId === activeInfo.tabId);
    if(newActiveTabIndex > 0) {
      currentPages[newActiveTabIndex].active = true;
      currentPages[newActiveTabIndex].focus.push({
        startTime: new Date().toISOString(),
        endTime: ''
      });
    }
    else {
      currentPages = [
        ..currentPages,
        {
          tabId: activeInfo.tabId;
          active: true;
          startTime: new Date().toISOString();
          focus: [{ startTime: new Date().toISOString(), endTime: '' }]; 
        }
      ]
    }
  }
)

If you're only interested in tracking the tabs that the user actually visits the above code should work just fine for you. Though there are cases where we do Cmd+Click or Ctrl+Click to open a url in a new tab which will not make the newly created tab active. And what if the user never selects that tab, you won't be able to track it even when the user closes that tab due to this condition: if (tabExists) in your .onRemoved event handler function. For those cases, we should add the .onCreated event handler.

chrome.tabs.onCreated.addListener(
  (tab: Tab) => {
    const tabIndex = currentPages.findIndex((page) => page.tabId === tab.id);
    if(tabIndex === -1) {
      currentPages = [
        ..currentPages,
        {
          tabId: tab.id;
          startTime: new Date().toISOString();
          focus: [];
        }
      ]
    }
  }
)

So far so good. We've added the above mentioned event handlers. But at the time the .onActivated or .onCreated events are fired, it's not guaranteed that there'd be a url. So, when the url of the tabs are updated, the .onUpdated event is fired. That's why we need to change the code a little bit. We add an entry to the visitedPages only if the tabExisting.url exists (not null, undefined or '');

Lastly, in order to get all the tabs (an initialisation) from all the windows when the extension is installed, we should add another event handler like this:

chrome.runtime.onInstalled.addListener((details) => {
  if(['install', 'chrome_update'].includes(details.reason)) {
    chrome.windows.getAll((windows: Window[]) => {
      windows.forEach(window => {
        chrome.tabs.getAllInWindow(window.id, (tabs: Tab[]) => {
          tabs.forEach(tab => {
            currentPages = [
              ..currentPages,
              {
                tabId: tab.id;
                startTime: new Date().toISOString();
                focus: [];
                url: tab.url;
              }
            ]
          });
        });
        chrome.tabs.query({active: true, windowId: window.id}, (tab: Tab) => {
          const tabIndex = currentPages.findIndex((page) => page.tabId === tab.id);
          if(tabIndex > 0) {
            currentPages[tabIndex - 1].active = true;
          }
        });
      });
    });
  }
  else if(details.reason == 'update') {
    //do whatever necessary 
  }
});

There can be many correct approaches to achieve this. This is how I think I'd do it. Please comment if anything is not clear.

Related