File: test_interactive_widget.html

package info (click to toggle)
firefox 141.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,550,616 kB
  • sloc: cpp: 7,426,508; javascript: 6,367,238; ansic: 3,707,354; python: 1,368,984; xml: 623,983; asm: 426,916; java: 184,324; sh: 64,488; makefile: 19,203; objc: 13,059; perl: 12,955; yacc: 4,583; cs: 3,846; pascal: 3,352; lex: 1,720; ruby: 1,071; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 54; csh: 10
file content (306 lines) | stat: -rw-r--r-- 12,661 bytes parent folder | download | duplicates (4)
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>interactive-widget tests</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script>
  <script type="text/javascript" src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
  <style>
  textarea {
    height: 100px;
    width: 100px;
  }
  </style>
</head>
<body>
<textarea></textarea>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script>
async function getViewportMetrics() {
  return SpecialPowers.spawn(parent, [], () => {
    return [ content.window.innerHeight,
             content.window.visualViewport.height,
             content.window.visualViewport.width ];
  });
}

let initial_window_height, initial_visual_viewport_width, initial_visual_viewport_height;

// setup a meta viewport tag in the top document.
add_setup(async () => {
  // Try to close the software keyboard to invoke `documentElement.focus()`.
  document.documentElement.focus();
  await SimpleTest.promiseWaitForCondition(
    () => document.activeElement == document.documentElement,
    "Waiting for focus");

  await SpecialPowers.spawn(parent, [], async () => {
    const initial_scale = content.window.visualViewport.scale;

    const meta = content.document.createElement("meta");
    meta.setAttribute("id", "interactive-widget");
    meta.setAttribute("name", "viewport");
    meta.setAttribute("content", "width=device-width, initial-scale=1, user-scalable=no");
    content.document.documentElement.appendChild(meta);

    const eventPromise =  new Promise(resolve => content.window.addEventListener("resize", resolve));
    // Flush the viewport change.
    content.document.documentElement.getBoundingClientRect();

    // If this top level content is rendered as `scale < 1.0`, it means there
    // was no meta viewport tag at all, so that adding the above meta viewport
    // tag will fire a resize event, thus we need to wait for the event here.
    // Otherwise, we will wait for the event in the first `resizes-content`
    // test and the test will fail.
    //
    // NOTE: We need this `scale < 1.0` check for --run-until-failure option.
    if (initial_scale < 1.0) {
      await eventPromise;
    }
  });

  SimpleTest.registerCleanupFunction(async () => {
    await SpecialPowers.spawn(parent, [], async () => {
      const meta = content.document.querySelector("#interactive-widget");
      meta.setAttribute("content", "");
      // Flush the change above.
      content.document.documentElement.getBoundingClientRect();
      // A dummy Promise to make sure that SpecialPowers.spawn's Promise will
      // never be resolved until this script has run in the parent context.
      await new Promise(resolve => resolve());
    });
  });
  [ initial_window_height,
    initial_visual_viewport_height, initial_visual_viewport_width ] = await getViewportMetrics();
  ok(initial_visual_viewport_width < initial_visual_viewport_height,
     `the visual viewport height (${initial_visual_viewport_height}) is less ` +
     `than the visual viewport width (${initial_visual_viewport_width}), ` +
     `it hightly suspects the virtual keyboard persists there, thus ` +
     `we can't run this interactive-widget tests properly`);
});

async function setupInteractiveWidget(aValue) {
  await SpecialPowers.spawn(parent, [aValue], async (value) => {
    const meta = content.document.querySelector("#interactive-widget");
    meta.setAttribute("content", `width=device-width, initial-scale=1, user-scalable=no, interactive-widget=${value}`);

    // Flush the viewport change.
    content.document.documentElement.getBoundingClientRect();

    // A dummy Promise to make sure that SpecialPowers.spawn's Promise will
    // never be resolved until these script have run in the parent context.
    await new Promise(resolve => resolve());
  });
}

// SpecialPowers.spawn doesn't provide any reasonable way to make sure event
// listeners have been set in the given context (bug 1743857), so here we post
// a message just before setting up a resize event listener and return two
// Promises, one will be resolved when we received the message, the other will
// be resolved when we got a resize event.
function setupResizeEventListener(aInteractiveWidget) {
  const ready = new Promise(resolve => {
    window.addEventListener("message", msg => {
      if (msg.data == "interactive-widget:ready") {
        resolve(msg.data)
      }
    }, { once: true });
  });

  const resizePromise = SpecialPowers.spawn(parent, [aInteractiveWidget], async (interactiveWidget) => {
    // #testframe is the iframe id where our mochitest harness loads each test
    // document, but if this test runs solely just like ./mach test TEST_PATH,
    // the test document gets loaded in the top level content.
    const target = content.document.querySelector("#testframe") ?
      content.document.querySelector("#testframe").contentWindow : content.window;

    let eventPromise;
    if (interactiveWidget == "resizes-content") {
      eventPromise =  new Promise(resolve => content.window.addEventListener("resize", resolve));
    } else if (interactiveWidget == "resizes-visual") {
      eventPromise = new Promise(resolve => content.window.visualViewport.addEventListener("resize", resolve));
    } else {
      ok(false, `Unexpected interactive-widget=${interactiveWidget}`);
    }
    target.postMessage("interactive-widget:ready", "*");
    await eventPromise;
  });

  return [ ready, resizePromise ];
}

// A utility function to hide the software keyboard.
// This function needs to be called while the software keyboard is shown on
// `resizes-content' or `resizes-visual` mode.
async function hideKeyboard() {
  const interactiveWidget = await SpecialPowers.spawn(parent, [], () => {
    const meta = content.document.querySelector("#interactive-widget");
    return meta.getAttribute("content").match(/interactive-widget=([\w-].+?)[,\s]*$/)[1];
  });

  let [ readyPromise, resizePromise ] = setupResizeEventListener(interactiveWidget);
  await readyPromise;

  // Tap outside the textarea to hide the software keyboard.
  await synthesizeNativeTap(document.querySelector("textarea"), 150, 50);
  await resizePromise;

  await SimpleTest.promiseWaitForCondition(
    async () => {
      let [ current_window_height, current_visual_viewport_height ] = await getViewportMetrics();
      return current_window_height == initial_window_height &&
             current_visual_viewport_height == initial_visual_viewport_height;
    },
    "Waiting for restoring the initial state");
}

// `resizes-content` test
add_task(async () => {
  await setupInteractiveWidget("resizes-content");

  // Setup a resize event listener in the top level document.
  let [ readyPromise, resizePromise ] = setupResizeEventListener("resizes-content");
  // Make sure the event listener has been set.
  await readyPromise;

  // Tap the textarea to show the software keyboard.
  await synthesizeNativeTap(document.querySelector("textarea"), 50, 50);

  await resizePromise;

  // Now the software keyboard has appeared, before running the next test we
  // need to hide the keyboard.
  SimpleTest.registerCurrentTaskCleanupFunction(async () => await hideKeyboard());

  await promiseAfterPaint();

  await SimpleTest.promiseWaitForCondition(
    () => document.activeElement == document.querySelector("textarea"),
    "Waiting for focus");

  let [ window_height, visual_viewport_height ] = await getViewportMetrics();
  ok(window_height < initial_window_height,
     `The layout viewport got resized to ${window_height} from ${initial_window_height}`);
  ok(visual_viewport_height < initial_visual_viewport_height,
     `The visual viewport got resized to ${visual_viewport_height} from ${initial_visual_viewport_height}`);
});

// `resizes-visual` test
add_task(async () => {
  await setupInteractiveWidget("resizes-visual");

  // Setup a resize event listener in the top level document.
  let [ readyPromise, resizePromise ] = setupResizeEventListener("resizes-visual");
  // Make sure the event listener has been set.
  await readyPromise;

  // Tap the textarea to show the software keyboard.
  await synthesizeNativeTap(document.querySelector("textarea"), 50, 50);

  await resizePromise;

  // Now the software keyboard has appeared, before running the next test we
  // need to hide the keyboard.
  SimpleTest.registerCurrentTaskCleanupFunction(async () => await hideKeyboard());

  await promiseAfterPaint();
  await SimpleTest.promiseWaitForCondition(
    () => document.activeElement == document.querySelector("textarea"),
    "Waiting for focus");

  let [ window_height, visual_viewport_height ] = await getViewportMetrics();
  is(window_height, initial_window_height,
     "The layout viewport is not resized on resizes-visual");
  ok(visual_viewport_height < initial_visual_viewport_height,
     `The visual viewport got resized to ${visual_viewport_height} from ${initial_visual_viewport_height}`);
});

// Append an element in the top level document that the element will be the
// underneath the software keyboard.
async function appendSpacer() {
  await SpecialPowers.spawn(parent, [], async () => {
    const div = content.document.createElement("div");
    div.setAttribute("id", "interactive-widget-test-spacer");
    div.style = "height: 200vh; position: absolute; top: 90vh;";
    content.document.body.appendChild(div);

    // Flush the change.
    content.document.documentElement.getBoundingClientRect();

    // A dummy Promise to make sure that SpecialPowers.spawn's Promise will
    // never be resolved until these script have run in the parent context.
    await new Promise(resolve => resolve());
  });

  SimpleTest.registerCurrentTaskCleanupFunction(async () => {
    await SpecialPowers.spawn(parent, [], async () => {
      const div = content.document.querySelector("#interactive-widget-test-spacer");
      div.remove();
      // Flush the change.
      content.document.documentElement.getBoundingClientRect();

      // A dummy Promise to make sure that SpecialPowers.spawn's Promise will
      // never be resolved until these script have run in the parent context.
      await new Promise(resolve => resolve());
    });
  });
}

// `overlays-content` test
add_task(async () => {
  await setupInteractiveWidget("overlays-content");

  await appendSpacer();

  // Tap the textarea to show the software keyboard.
  await synthesizeNativeTap(document.querySelector("textarea"), 50, 50);

  // Now the software keyboard has appeared, before running the next test we
  // need to hide the keyboard.
  SimpleTest.registerCurrentTaskCleanupFunction(async () => {
    // Switch back to `resizes-content` mode so that we can receive a resize
    // event when the keyboard gets hidden.
    await setupInteractiveWidget("resizes-content");
    await hideKeyboard();
  });

  await promiseAfterPaint();
  await SimpleTest.promiseWaitForCondition(
    () => document.activeElement == document.querySelector("textarea"),
    "Waiting for focus");

  let [ window_height, visual_viewport_height ] = await getViewportMetrics();
  is(window_height, initial_window_height,
     "The layout viewport is not resized on overlays-content");
  is(visual_viewport_height, initial_visual_viewport_height,
     "The visual viewport is not resized on overlays-content");

  // Call a scrollIntoView() on an element underneath the keyboard and see if
  // the current scroll position changes.
  const scrollPosition = await SpecialPowers.spawn(parent, [], () => {
    return content.window.scrollY;
  });
  await SpecialPowers.spawn(parent, [], async () => {
    const div = content.document.querySelector("#interactive-widget-test-spacer");
    div.scrollIntoView({ behavior: "instant" });

    // Though two rAFs ensure there's at least one scroll event if there is,
    // we use two additional rAFs just in case.
    await new Promise(resolve => content.window.requestAnimationFrame(resolve));
    await new Promise(resolve => content.window.requestAnimationFrame(resolve));
    await new Promise(resolve => content.window.requestAnimationFrame(resolve));
    await new Promise(resolve => content.window.requestAnimationFrame(resolve));
  });

  const newScrollPosition = await SpecialPowers.spawn(parent, [], () => {
    return content.window.scrollY;
  });
  is(scrollPosition, newScrollPosition, "The scrollIntoView() call has no effect");
});
</script>
</body>
</html>