Skip to content

chrome. implement offscreen document to keep service worker alive#209

Merged
ErikBjare merged 1 commit intoActivityWatch:masterfrom
Tokisaki-Galaxy:new-chrome-keepalive
Apr 10, 2026
Merged

chrome. implement offscreen document to keep service worker alive#209
ErikBjare merged 1 commit intoActivityWatch:masterfrom
Tokisaki-Galaxy:new-chrome-keepalive

Conversation

@Tokisaki-Galaxy
Copy link
Copy Markdown
Contributor

@Tokisaki-Galaxy Tokisaki-Galaxy commented Jan 5, 2026

https://github.com/Tokisaki-Galaxy/aw-watcher-web/actions
Passed the action compilation and grammar check, and has been tested to be normal.
resolve #199


Important

Implements Offscreen API in Chrome to keep the service worker alive by adding offscreen document and periodic messaging.

  • Behavior:
    • Implements setupOffscreen() in main.ts to keep the service worker alive using the Offscreen API for Chrome.
    • Sends periodic KEEP_ALIVE messages from offscreen.ts to prevent service worker termination.
    • Listens for KEEP_ALIVE messages in main.ts and responds with status 'ok'.
  • Files:
    • Adds offscreen.html to load offscreen.ts.
    • Adds offscreen.ts to send periodic messages to the background script.
  • Configuration:
    • Updates manifest.json to include offscreen permission for Chrome.
    • Updates vite.config.ts to include src/offscreen.html in additionalInputs.

This description was created by Ellipsis for 712cbab. You can customize this summary. It will automatically update as commits are pushed.

Copilot AI review requested due to automatic review settings January 5, 2026 01:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements Chrome's Offscreen API to keep the service worker alive, replacing the previous keep-alive mechanism that used periodic alarm checks. The new approach creates an offscreen document that sends periodic heartbeat messages to the background service worker.

Key Changes:

  • Replaced interval-based alarm polling with Chrome Offscreen API for service worker lifecycle management
  • Added offscreen document that sends keep-alive messages every 20 seconds
  • Added "offscreen" permission to the manifest

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/manifest.json Added "offscreen" permission required for Chrome Offscreen API
src/offscreen.html Created minimal HTML file to host the offscreen script
src/offscreen.ts Implemented keep-alive logic that sends messages every 20 seconds to background
src/background/main.ts Replaced old keep-alive mechanism with offscreen document creation and message handler
vite.config.ts Added offscreen.html to build inputs for bundling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to 712cbab in 1 minute and 47 seconds. Click for details.
  • Reviewed 122 lines of code in 5 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. src/background/main.ts:110
  • Draft comment:
    Duplicate invocation of setupOffscreen: It's added as listeners on onStartup and onInstalled, then called directly. If intentional, add a clarifying comment; otherwise, consider removing the direct call.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% The comment is asking the author to either clarify or change the code. However, this pattern seems intentional: the direct call ensures immediate setup when the background script loads, while the event listeners handle browser restarts. The function has proper guards to prevent duplicate document creation. This comment is essentially asking the author to "confirm their intention" or "add clarifying comments," which violates the rules. The comment doesn't identify a bug or required code change - it's more of a "this might be confusing" observation. The pattern is actually quite common in browser extensions where you want something to run both immediately and on specific events. The pattern could genuinely be confusing to future maintainers, and a clarifying comment might improve code readability. The comment does suggest a concrete action (adding a comment or removing the call), which could be considered actionable rather than purely speculative. While the pattern might benefit from clarification, the rules explicitly state not to ask the author to confirm their intention or add clarifying comments. The comment uses the phrase "If intentional, add a clarifying comment; otherwise, consider removing" which is exactly the type of "verify/ensure/confirm" language the rules say to avoid. There's no actual bug here - the code works correctly with the guards in place. This comment should be deleted. It's asking the author to either confirm their intention or add a clarifying comment, which violates the rules. The code has proper guards and the pattern is intentional and functional. No actual code change is required.
2. src/offscreen.ts:3
  • Draft comment:
    The comment is in Chinese; consider using English (or a consistent language) for clarity across the codebase.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% This is a style/consistency comment about using English instead of Chinese. While language consistency can be valuable, the rules state "Do NOT comment unless there is clearly a code change required" and "Do NOT make comments that are obvious or unimportant." This is more of a stylistic preference rather than a functional issue. The code works fine with Chinese comments. Without seeing the rest of the codebase, I can't verify if there's actually an established pattern of English-only comments. The comment is also somewhat presumptuous about what language should be used - maybe this is a Chinese-language project. I might be missing context about whether this codebase has an established English-only policy. If the entire codebase uses English comments and this is the only Chinese comment, it could be a legitimate consistency issue worth addressing. Even if there is an English-only policy, this falls under "obvious or unimportant" comments. The author can see the comment is in Chinese. This is a stylistic preference that doesn't affect functionality, and without strong evidence of a codebase-wide policy visible in this diff, I should err on the side of deletion per the rules. This comment is about style/language preference rather than a functional code issue. It falls under "obvious or unimportant" comments that don't clearly require a code change. Without strong evidence of a codebase policy in the diff itself, this should be deleted.

Workflow ID: wflow_BI7DONsYuOb4zSim

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@0xbrayo
Copy link
Copy Markdown
Member

0xbrayo commented Jan 5, 2026

Have you tested this locally? I keep getting error creating bucket for some reason. Properly set up cors and everything in the server. @BelKed Maybe you can give this a go.

@Tokisaki-Galaxy
Copy link
Copy Markdown
Contributor Author

Tokisaki-Galaxy commented Jan 5, 2026

Have you tested this locally? I keep getting error creating bucket for some reason. Properly set up cors and everything in the server. @BelKed Maybe you can give this a go.

I have used it normally, and after modification, it works well at least at windows 11 edge(143.0.3650.96).
If there is an error, maybe there is a problem with code compatibility. If possible, please put it forward so that I can revise it.

image 636254b9-a0cb-412b-a239-e098cf955f3d

@0xbrayo
Copy link
Copy Markdown
Member

0xbrayo commented Jan 5, 2026

Got it working on the python server. Some cors issues on the rust server, I presume. Seems to be working perfectly.🚀

@Tokisaki-Galaxy
Copy link
Copy Markdown
Contributor Author

Got it working on the python server. Some cors issues on the rust server, I presume. Seems to be working perfectly.🚀

The issues on the Rust server appear to be CORS-related; likely because its security policy doesn't yet permit the chrome-extension:// origin/scheme.

Since this has been a long-standing issue affecting many users, I’d appreciate it if we could expedite the merge and release of this fix. Thanks!

@BelKed
Copy link
Copy Markdown
Contributor

BelKed commented Jan 8, 2026

The Rust server has hard-coded UUID which only extensions installed from the Chrome Web Store have
If an extension is installed manually, it gets a different UUID, so seeing a CORS issue is normal in this case

I'll manually test it in the upcoming days, thank you for the fix 💙

Copy link
Copy Markdown
Contributor

@BelKed BelKed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manually tested, seems to be working correctly :)

@robertu94
Copy link
Copy Markdown

This seems tested and approved, is there a reason it hasn't been merged yet?

@BelKed
Copy link
Copy Markdown
Contributor

BelKed commented Apr 10, 2026

@ErikBjare :)

@ErikBjare
Copy link
Copy Markdown
Member

@greptileai review

@ErikBjare ErikBjare merged commit a3e3b9b into ActivityWatch:master Apr 10, 2026
6 checks passed
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 10, 2026

Greptile Summary

This PR replaces the previous alarm-based service-worker keep-alive with Chrome's Offscreen API: a minimal offscreen.html/offscreen.ts is loaded as an offscreen document that pings the service worker every 20 seconds, preventing Chrome from terminating it.

  • P1 – incorrect reasons value: reasons: ['BLOBS'] is declared but the offscreen document never uses the Blob API; Chrome Web Store reviewers verify this field matches actual usage and can reject extensions for mismatches.
  • The Chrome-specific build-time guard (VITE_TARGET_BROWSER === 'chrome') was removed; the KEEP_ALIVE message listener and event registrations now run on all browsers, which is harmless but widens scope unnecessarily.

Confidence Score: 4/5

Functional approach is sound but the reasons: ['BLOBS'] mismatch should be corrected before publishing to the Chrome Web Store.

One P1 finding (incorrect offscreen reasons value) that could cause Chrome Web Store rejection, plus two P2 style notes. The core keep-alive logic is correct and the overall approach follows established patterns.

src/background/main.ts — incorrect reasons: ['BLOBS'] in createDocument call and missing Chrome-specific build guard.

Important Files Changed

Filename Overview
src/background/main.ts Replaces alarm-based keep-alive with Offscreen API; uses incorrect reasons: ['BLOBS'] that doesn't match actual document behavior, and removes the Chrome-specific build-time guard.
src/offscreen.ts New file that sends KEEP_ALIVE messages every 20 seconds; interval is more aggressive than needed but otherwise correct.
src/offscreen.html Minimal HTML shell that loads offscreen.ts as a module; straightforward and correct.
src/manifest.json Adds offscreen permission to Chrome-only permissions block; correctly scoped and placed.
vite.config.ts Adds src/offscreen.html to additionalInputs so Vite bundles the offscreen document; correct change.

Sequence Diagram

sequenceDiagram
    participant SW as Service Worker (main.ts)
    participant Chrome as chrome.offscreen API
    participant OD as Offscreen Document (offscreen.ts)

    SW->>SW: setupOffscreen() on startup/install
    SW->>Chrome: hasDocument()?
    Chrome-->>SW: false
    SW->>Chrome: createDocument({ url: 'src/offscreen.html', reasons: ['BLOBS'] })
    Chrome-->>OD: Load offscreen.html → offscreen.ts

    loop Every 20 seconds
        OD->>SW: runtime.sendMessage({ type: 'KEEP_ALIVE' })
        SW-->>OD: { status: 'ok' }
        Note over SW: Service worker stays alive
    end
Loading

Reviews (1): Last reviewed commit: "feat: chrome. implement offscreen docume..." | Re-trigger Greptile

Comment on lines +88 to +93
try {
await _chrome.offscreen.createDocument({
url: 'src/offscreen.html',
reasons: ['BLOBS'],
justification: 'Keep service worker alive for heartbeat packets',
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Incorrect reasons value for offscreen document

'BLOBS' requires the offscreen document to use the Blob URL API, but offscreen.ts only sends messages — it never touches the Blob API. Chrome Web Store reviewers check that reasons accurately reflects actual usage; a mismatch can lead to extension rejection. The closest valid reason for a "keep-alive timer" pattern is none of the built-in reasons, so the most defensible alternative is 'WORKERS' (background JS) or the fact that this pattern may simply need to be re-evaluated. At minimum the reason should match actual behavior.

Suggested change
try {
await _chrome.offscreen.createDocument({
url: 'src/offscreen.html',
reasons: ['BLOBS'],
justification: 'Keep service worker alive for heartbeat packets',
})
await _chrome.offscreen.createDocument({
url: 'src/offscreen.html',
reasons: ['DOM_SCRAPING'],
justification: 'Keep service worker alive by maintaining an active DOM context',
})

If none of the available reasons honestly apply (AUDIO_PLAYBACK, BLOBS, CLIPBOARD, DOM_PARSER, DOM_SCRAPING, GEOLOCATION, LOCAL_STORAGE, MATCH_MEDIA, PICTURE_IN_PICTURE, PREFER_OFFLINE, USER_MEDIA, WEB_RTC, WORKERS), consider whether the offscreen document can be given a secondary real purpose (e.g. parsing some DOM) to justify the reason used.

Comment on lines +10 to +11
setInterval(keepAlive, 20 * 1000)
keepAlive()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Keep-alive interval is very aggressive

Messaging every 20 seconds is significantly more frequent than necessary. Chrome terminates idle service workers after ~5 minutes, so a 25–30 second interval still provides ample safety margin with far less overhead and power consumption. The original alarm-based approach used 4 minutes.

Suggested change
setInterval(keepAlive, 20 * 1000)
keepAlive()
setInterval(keepAlive, 25 * 1000)

Comment on lines +99 to +110
browser.runtime.onMessage.addListener((message: any) => {
if (message.type === 'KEEP_ALIVE') {
return Promise.resolve({ status: 'ok' })
}
return undefined
})

// Initialize on startup and installation
browser.runtime.onStartup.addListener(setupOffscreen)
browser.runtime.onInstalled.addListener(setupOffscreen)

setupOffscreen()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Chrome guard removed from onMessage and event-listener registration

The previous implementation wrapped the keep-alive logic in if (import.meta.env.VITE_TARGET_BROWSER === 'chrome'). The new code registers the KEEP_ALIVE message listener and calls setupOffscreen() unconditionally for all browsers. While setupOffscreen() safely no-ops on Firefox/Safari (no chrome.offscreen), the KEEP_ALIVE message listener is now always active. This is harmless today but silently widens scope. Wrapping the entire block in a Chrome check keeps the intent explicit:

if (import.meta.env.VITE_TARGET_BROWSER === 'chrome') {
  browser.runtime.onMessage.addListener((message: any) => {
    if (message.type === 'KEEP_ALIVE') {
      return Promise.resolve({ status: 'ok' })
    }
    return undefined
  })

  browser.runtime.onStartup.addListener(setupOffscreen)
  browser.runtime.onInstalled.addListener(setupOffscreen)
  setupOffscreen()
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aw-watcher-web-chrome reports only beginning and end of tab use

6 participants