1

I'm using chrome.alarm to display countdown on extension icon, which updates every second'ish. The problem I'm facing is if I change system's time forward (manually or if computer woke up from sleep), the counter starts racing, updating several times a second in attempt to catch up to new system time, or if I change time backwards, the timer stops.

How can I fix this so it would simply "jump" to current time?

Test extension:

manifest.json

{
  "manifest_version": 3,
  "name": "Test timer",
  "author": "test",
  "description": "Test chrome.alarm",
  "version": "0.0.1",

  "permissions":
  [
    "alarms"
  ],
  "action": {},
  "background": {
    "service_worker": "service_worker.js"
  }
}

service_worker.js

let i = 0,
    start = new Date().getTime(),
    pad = (n,s=2) => ("0"+n).slice(-s),
    time = d => pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + "." + pad(d.getMilliseconds(),3);

chrome.alarms.onAlarm.addListener(loop);

console.log("started");
loop();

function loop()
{
  const now = new Date().getTime(),
        //make sure timer doesn't drift from starting point
        next = now - ((now - start) % 1000);

  //repeat after 1sec
  chrome.alarms.create({ when: next + 1000 });

  chrome.action.setBadgeText({text:"" + (i = ++i % 1000)});
  console.log("Date:", time(new Date(now)), "alarm:", time(new Date(next)));
}
vanowm
  • 9,466
  • 2
  • 21
  • 37

3 Answers3

2

I've tested your code, and I've got some new discoveries. I've run into some Service Worker issues and I think it might have something to do with your "racing" alarm.

  1. If I keep the service worker page open all the time, it runs smoothly and properly.
  2. If I don't open the service worker, it will either "racing" or restart after a while, even if I don't change the system time or let my device fall asleep.

Since you're using Manifest V3, I have to tell you that Manifest V3 has some issues of Service Worker. It breaks sometimes. For more information, you can read this doc. You can refer to these workarounds for sure.

Kendrick Li
  • 1,355
  • 1
  • 2
  • 10
0

Patient: "doctor, I have a pain in my back".
Doctor: "and when does it hurt?"
Patient: "only when I breathe"
Doctor: "thus it's all resolved. Don't breathe anymore"

Fiddling with system time and chorme.alarms isn't very wise. Normally you can move the clock forward (just for testing), but turning it back can create unusual issues. (unless You refresh\reload the extension first). Instead, we should investigate the behavior after waking up the PC.
Furthermore, these alarms have been designed for intervals of not less than one minute.
We agree that this limitation doesn't apply in development, but setting one-second alarms is a bit bold, isn't it?
So avoid breathing and the problem will resolve itself :-)
Joke apart, You could open a minimized tab and establish a connection between service worker and that tab making a continous ping-pong.
Till the service worker will be alive you''ll be able to dispay the countdown in extension icon using just setTimeour\ setInterval.

Robbi
  • 1,254
  • 2
  • 8
  • 11
  • Ping-pong doesn't seem to work, service worker getting killed after 5 minutes. – vanowm Aug 06 '22 at 23:27
  • I did it without too many problems instead. The counter on the icon badge continues beyond 5 minutes. Try following the advice in this thread: [link](https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension) Obviously you must use a tab\window for a continuous "sw" <--> "entension page" connection. – Robbi Aug 08 '22 at 08:57
  • I think the problem in my case is that I reuse existing tab, not creating new one, in my case creating new tab each time extension launched is not acceptable.. – vanowm Aug 11 '22 at 00:43
0

Here is my current solution by using combination of performance.now() and setTimeout:

let i = 0,
    start = new Date().getTime(),
    pad = (n,s=2) => ("0"+n).slice(-s),
    time = d => pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + "." + pad(d.getMilliseconds(),3),
    timer,
    perfPrev = 0;

chrome.alarms.onAlarm.addListener(loop);

console.log("started");
loop();

function loop()
{
  const now = new Date().getTime(),
        perfNow = performance.now();

  //make sure timer doesn't drift from starting point
  const next = now - ((now - start) % 1000);

  //repeat after 1sec
  chrome.alarms.create("loop", { when: next + 1000 });

  //detect "racing", when system time changed forward
  if (perfNow - perfPrev < 800)
    return;

  perfPrev = perfNow;
  clearTimeout(timer);

  //backup plan for when system time changed backwards the alarm won't fire on time
  timer = setTimeout(() => 
  {
    chrome.alarms.clear("loop");
    loop();
  }, 1000);

  chrome.action.setBadgeText({text:"" + (i = ++i % 1000)});
  console.log("Date:", time(new Date(now)), "alarm:", time(new Date(next)));
}
vanowm
  • 9,466
  • 2
  • 21
  • 37
  • If that's what you were looking for then we're all happy. Upvote your answer and we consider the problem closed. – Robbi Aug 11 '22 at 09:35