File: test_ext_idle.html

package info (click to toggle)
firefox 144.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,637,504 kB
  • sloc: cpp: 7,576,692; javascript: 6,430,831; ansic: 3,748,119; python: 1,398,978; xml: 628,810; asm: 438,679; java: 186,194; sh: 63,212; makefile: 19,159; objc: 13,086; perl: 12,986; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 53; csh: 10
file content (197 lines) | stat: -rw-r--r-- 7,980 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<!DOCTYPE HTML>
<html>
<head>
  <title>WebExtension test</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
  <script type="text/javascript" src="head.js"></script>
  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<body>

<script type="text/javascript">
"use strict";

add_setup(async () => {
  // Enable logging to get actionable information when the test fails.
  // E.g. the idleService.idleTime getter triggers the following logs:
  // https://searchfox.org/mozilla-central/rev/1eb4f27ece7cb96ac94b635ad0dc95c00d1443db/widget/nsUserIdleService.cpp#633-635,641-648
  // "idleService: Get idle time: polled %u msec, valid = %d"
  // "idleService: Get idle time: time since reset %u msec"
  await SpecialPowers.pushPrefEnv({
    set: [
      ["logging.config.add_timestamp", true],
      ["logging.idleService", 5], // Logs from nsUserIdleService.cpp
      ["logging.nsIUserIdleService", 5], // Logs from nsUserIdleServiceGTK.cpp
    ],
  });
});

async function resetIdleToActive() {
  await SpecialPowers.spawnChrome([], () => {
    const idleService = Cc["@mozilla.org/widget/useridleservice;1"].getService(Ci.nsIUserIdleServiceInternal);
    idleService.resetIdleTimeOut(0);
  });
}

add_task(async function testWithRealIdleService() {
  async function background() {
    // The minimum detection interval accepted by idle.queryState() and
    // idle.setDetectionInterval() is 15 seconds.
    const MINIMUM_DETECTION_INTERVAL = 15;

    async function callResetIdleToActive() {
      return new Promise(resolve => {
        browser.test.onMessage.addListener(function listener(msg) {
          browser.test.assertEq("resetIdleToActiveDone", msg, "Expected msg");
          browser.test.onMessage.removeListener(listener);
          resolve();
        });
        browser.test.sendMessage("resetIdleToActive");
      });
    }

    await callResetIdleToActive();
    // We have just reset idle, so idle.queryState() should return "active".
    let status = await browser.idle.queryState(MINIMUM_DETECTION_INTERVAL);
    browser.test.assertEq("active", status, "Idle status is active");

    let counter = 0;
    let resetToActiveAttempts = 0;
    let seenIdle = false;
    let seenActive = false;
    browser.idle.onStateChanged.addListener(async function listener(newState) {
      const num = ++counter;
      // At the first event we expect active->idle, and then back and forth.
      const expectedState = num % 2 === 1 ? "idle" : "active";
      browser.test.assertEq(
        expectedState,
        newState,
        `state when idle.onStateChanged fires ${num}x`
      );

      // Note: although we pass MINIMUM_DETECTION_INTERVAL here, we lower its
      // value to SMALL_IDLE_DETECTION_INTERVAL_IN_SECONDS elsewhere, to make
      // sure that the test has a high chance of detecting idle.
      let state = await browser.idle.queryState(MINIMUM_DETECTION_INTERVAL);
      // idle->active can happen during dispatch if the user triggers input.
      // active->idle can happen if the test runs slow and the idle interval
      // has elapsed, especially because we simulate a very short interval.
      if (state !== expectedState) {
        browser.test.log(`Unexpected queryState at ${num}: ${state}`);
        if (expectedState === "active" && seenIdle && !seenActive) {
          // To avoid retrying forever, give up after a few attempts.
          // When we give up, the test will time out awaiting idle_to_active.
          // We don't expect to hit this situation more than a handful of times
          // but retry 20 times to fail fast (tests usually time out after 30
          // seconds of inactivity).
          const MAX_RETRY_RESET_TO_ACTIVE_ATTEMPTS = 20;
          if (++resetToActiveAttempts > MAX_RETRY_RESET_TO_ACTIVE_ATTEMPTS) {
            browser.test.fail("queryState() does not match onStateChanged");
            return;
          }
          browser.test.log(`Retry attempt ${resetToActiveAttempts}`);
          // Trigger idle -> active transition to retry the check.
          callResetIdleToActive();
        }
        // If expecting "idle", just wait a little bit. Eventually enough time
        // has elapsed to trigger the idle state.
        return;
      }

      browser.test.assertEq(expectedState, state, `queryState at ${num}`);

      if (!seenIdle && newState === "idle") {
        seenIdle = true;
        browser.test.sendMessage("active_to_idle");
        return;
      }

      if (seenIdle && !seenActive && newState === "active") {
        seenActive = true;
        browser.idle.onStateChanged.removeListener(listener);
        browser.test.sendMessage("idle_to_active");
      }
    });

    // The default detection interval is 60 seconds. Lower it to the minimum.
    await browser.idle.setDetectionInterval(MINIMUM_DETECTION_INTERVAL);

    browser.test.sendMessage("listenerAdded");
  }

  let extension = ExtensionTestUtils.loadExtension({
    background,
    manifest: {
      permissions: ["idle"],
    },
  });

  // The test can call resetIdleToActive repeatedly. Collect the pending calls
  // to ensure that we wait for the calls to complete and clean up at the end
  // of the test.
  const pendingIdleResetters = [];
  extension.onMessage("resetIdleToActive", async () => {
    info(`Going to reset idle timeout to force idle state to be "active"`);
    let promise = resetIdleToActive();
    pendingIdleResetters.push(promise);
    await promise;
    extension.sendMessage("resetIdleToActiveDone");
  });

  await extension.startup();

  await extension.awaitMessage("listenerAdded");
  info("Listener added");

  // The test extension now waits for idle.onStateChanged, to detect transition
  // from "active" to "idle". The minimum detectionInterval accepted by the
  // idle.queryState & idle.setDetectionInterval APIs is 15 seconds. It is very
  // unlikely for the system to be idle for 15 seconds straight.
  //
  // To avoid waiting for at least 15 seconds or even timing out due to a busy
  // system, we are artificially lowering the detectionInterval from 15 seconds
  // to 1 second.
  //
  // If you intentionally want to delay the completion of this test, repeatedly
  // hit a keyboard key while this test is running. That also confirms that the
  // idle API is working as intended.
  info(`Going to artificially lower detectionInterval to enter "idle" state`);
  await SpecialPowers.spawnChrome([extension.id], (extId) => {
    const SMALL_IDLE_DETECTION_INTERVAL_IN_SECONDS = 1;
    const { WebExtensionPolicy } = Cu.getGlobalForObject(Services);
    const { backgroundContext } = WebExtensionPolicy.getByID(extId).extension;

    // The idle extension APIs have already been invoked by the test extension,
    // so its implementation is expected to be cached here:
    const apiPaths = backgroundContext.apiCan.apiPaths;
    const queryStateOrig = apiPaths.get("idle.queryState");
    is(typeof queryStateOrig, "function", "idle.queryState is a function");
    function queryState(...args) {
      info(`Intercepted idle.queryState call: using small detection interval`);
      args[0] = SMALL_IDLE_DETECTION_INTERVAL_IN_SECONDS;
      return queryStateOrig.apply(this, args);
    }
    apiPaths.set("idle.queryState", queryState);
    const setDetectionInterval = apiPaths.get("idle.setDetectionInterval")
    is(typeof setDetectionInterval, "function", "setDetectionInterval is func");
    setDetectionInterval(SMALL_IDLE_DETECTION_INTERVAL_IN_SECONDS);
  });

  info("Waiting for active-to-idle transition");
  await extension.awaitMessage("active_to_idle");

  info(`Going to reset idle timeout to force idle state to be "active" again`);
  await resetIdleToActive();

  info("Waiting for idle-to-active transition");
  await extension.awaitMessage("idle_to_active");
  await extension.unload();

  await Promise.all(pendingIdleResetters);
});

</script>

</body>
</html>