File: util.js

package info (click to toggle)
firefox 149.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,767,760 kB
  • sloc: cpp: 7,416,064; javascript: 6,752,859; ansic: 3,774,850; python: 1,250,473; xml: 641,578; asm: 439,191; java: 186,617; sh: 56,634; makefile: 18,856; objc: 13,092; perl: 12,763; pascal: 5,960; yacc: 4,583; cs: 3,846; lex: 1,720; ruby: 1,002; php: 436; lisp: 258; awk: 105; sql: 66; sed: 53; csh: 10; exp: 6
file content (348 lines) | stat: -rw-r--r-- 11,464 bytes parent folder | download
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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
const kValidAvailabilities =
    ['unavailable', 'downloadable', 'downloading', 'available'];
const kAvailableAvailabilities = ['downloadable', 'downloading', 'available'];

const kAudioPrompt = 'transcribe this';
const kImagePrompt = 'describe this';
const kTestPrompt = 'Please write a sentence in English.';

const kTestContext = 'This is a test; this is only a test.';

const kValidAudioPath = '/media/speech.wav';
const kValidImagePath = '/images/computer.jpg';
const kValidSVGImagePath = '/images/pattern.svg';
const kValidVideoPath = '/media/test.webm';

const kAudioOptions = {
  expectedInputs: [{type: 'audio'}]
};
const kImageOptions = {
  expectedInputs: [{type: 'image'}]
};

const kValidAudioKeywords =
    ['audio', 'speech', 'sentence', 'single', 'segment'];
const kValidCanvasImageKeywords = ['image', 'black', 'square', 'blank'];
const kValidImageKeywords =
    ['image', 'computer', 'keyboard', 'desk', 'PC', 'monitor', 'screen'];
const kValidSVGImageKeywords =
    ['image', 'color', 'red', 'green', 'blue', 'black'];
const kValidVideoKeywords = [
  'image', 'color', 'bip', 'black', 'white', 'yellow', 'green', 'blue', 'red',
  'video', 'screen'
];

const kValidAudioRegex = matchKeywordsRegex(kValidAudioKeywords);
const kValidCanvasImageRegex = matchKeywordsRegex(kValidCanvasImageKeywords);
const kValidImageRegex = matchKeywordsRegex(kValidImageKeywords);
const kValidSVGImageRegex = matchKeywordsRegex(kValidSVGImageKeywords);
const kValidVideoRegex = matchKeywordsRegex(kValidVideoKeywords);

const getId = (() => {
  let idCount = 0;
  return () => idCount++;
})();

// Takes an array of dictionaries mapping keys to value arrays, e.g.:
//   [ {Shape: ["Square", "Circle", undefined]}, {Count: [1, 2]} ]
// Returns an array of dictionaries with all value combinations, i.e.:
//  [ {Shape: "Square", Count: 1}, {Shape: "Square", Count: 2},
//    {Shape: "Circle", Count: 1}, {Shape: "Circle", Count: 2},
//    {Shape: undefined, Count: 1}, {Shape: undefined, Count: 2} ]
// Omits dictionary members when the value is undefined; supports array values.
function generateOptionCombinations(optionsSpec) {
  // 1. Extract keys from the input specification.
  const keys = optionsSpec.map(o => Object.keys(o)[0]);
  // 2. Extract the arrays of possible values for each key.
  const valueArrays = optionsSpec.map(o => Object.values(o)[0]);
  // 3. Compute the Cartesian product of the value arrays using reduce.
  const valueCombinations = valueArrays.reduce((accumulator, currentValues) => {
    // Init the empty accumulator (first iteration), with single-element
    // arrays.
    if (accumulator.length === 0) {
      return currentValues.map(value => [value]);
    }
    // Otherwise, expand existing combinations with current values.
    return accumulator.flatMap(
        existingCombo => currentValues.map(
            currentValue => [...existingCombo, currentValue]));
  }, []);

  // 4. Map each value combination to a result dictionary, skipping
  // undefined.
  return valueCombinations.map(combination => {
    const result = {};
    keys.forEach((key, index) => {
      if (combination[index] !== undefined) {
        result[key] = combination[index];
      }
    });
    return result;
  });
}

// The method should take the AbortSignal as an option and return a promise.
async function testAbortPromise(t, method) {
  // Test abort signal without custom error.
  {
    const controller = new AbortController();
    const promise = method(controller.signal);
    controller.abort();
    await promise_rejects_dom(t, 'AbortError', promise);

    // Using the same aborted controller will get the `AbortError` as well.
    const anotherPromise = method(controller.signal);
    await promise_rejects_dom(t, 'AbortError', anotherPromise);
  }

  // Test abort signal with custom error.
  {
    const err = new Error('test');
    const controller = new AbortController();
    const promise = method(controller.signal);
    controller.abort(err);
    await promise_rejects_exactly(t, err, promise);

    // Using the same aborted controller will get the same error as well.
    const anotherPromise = method(controller.signal);
    await promise_rejects_exactly(t, err, anotherPromise);
  }
};

async function testCreateMonitorWithAbortAt(
    t, loadedToAbortAt, method, options = {}) {
  const {promise: eventPromise, resolve} = Promise.withResolvers();
  let hadEvent = false;
  function monitor(m) {
    m.addEventListener('downloadprogress', e => {
      if (e.loaded != loadedToAbortAt) {
        return;
      }

      if (hadEvent) {
        assert_unreached(
            'This should never be reached since the create() operation was aborted.');
        return;
      }

      resolve();
      hadEvent = true;
    });
  }

  const controller = new AbortController();

  const createPromise =
      method({...options, monitor, signal: controller.signal});

  await eventPromise;

  const err = new Error('test');
  controller.abort(err);
  await promise_rejects_exactly(t, err, createPromise);
}

async function testCreateMonitorWithAbort(t, method, options = {}) {
  await testCreateMonitorWithAbortAt(t, 0, method, options);
  await testCreateMonitorWithAbortAt(t, 1, method, options);
}

// The method should take the AbortSignal as an option and return a
// ReadableStream.
async function testAbortReadableStream(t, method) {
  // Test abort signal without custom error.
  {
    const controller = new AbortController();
    const stream = method(controller.signal);
    controller.abort();
    let writableStream = new WritableStream();
    await promise_rejects_dom(t, 'AbortError', stream.pipeTo(writableStream));

    // Using the same aborted controller will get the `AbortError` as well.
    await promise_rejects_dom(t, 'AbortError', new Promise(() => {
                                method(controller.signal);
                              }));
  }

  // Test abort signal with custom error.
  {
    const error = new DOMException('test', 'VersionError');
    const controller = new AbortController();
    const stream = method(controller.signal);
    controller.abort(error);
    let writableStream = new WritableStream();
    await promise_rejects_exactly(t, error, stream.pipeTo(writableStream));

    // Using the same aborted controller will get the same error.
    await promise_rejects_exactly(t, error, new Promise(() => {
                                    method(controller.signal);
                                  }));
  }
};

async function testMonitor(createFunc, options = {}) {
  let created = false;
  const progressEvents = [];
  function monitor(m) {
    m.addEventListener('downloadprogress', e => {
      // No progress events should be fired after `createFunc` resolves.
      assert_false(created);

      progressEvents.push(e);
    });
  }

  result = await createFunc({...options, monitor});
  created = true;

  assert_greater_than_equal(progressEvents.length, 2);
  assert_equals(progressEvents.at(0).loaded, 0);
  assert_equals(progressEvents.at(-1).loaded, 1);

  let lastProgressEventLoaded = -1;
  for (const progressEvent of progressEvents) {
    assert_equals(progressEvent.lengthComputable, true);
    assert_equals(progressEvent.total, 1);
    assert_less_than_equal(progressEvent.loaded, progressEvent.total);

    // `loaded` must be rounded to the nearest 0x10000th.
    assert_equals(progressEvent.loaded % (1 / 0x10000), 0);

    // Progress events should have monotonically increasing `loaded` values.
    assert_greater_than(progressEvent.loaded, lastProgressEventLoaded);
    lastProgressEventLoaded = progressEvent.loaded;
  }
  return result;
}

async function testCreateMonitorCallbackThrowsError(
    t, createFunc, options = {}) {
  const error = new Error('CreateMonitorCallback threw an error');
  function monitor(m) {
    m.addEventListener('downloadprogress', e => {
      assert_unreached(
          'This should never be reached since monitor throws an error.');
    });
    throw error;
  }

  await promise_rejects_exactly(t, error, createFunc({...options, monitor}));
}

function run_iframe_test(iframe, test_name) {
  const id = getId();
  iframe.contentWindow.postMessage({id, type: test_name}, '*');
  const {promise, resolve, reject} = Promise.withResolvers();
  window.onmessage = message => {
    if (message.data.id !== id) {
      return;
    }
    if (message.data.success) {
      resolve(message.data.success);
    } else {
      reject(message.data.err)
    }
  };
  return promise;
}

function load_iframe(src, permission_policy) {
  let iframe = document.createElement('iframe');
  const {promise, resolve} = Promise.withResolvers();
  iframe.onload = () => {
    resolve(iframe);
  };
  iframe.src = src;
  iframe.allow = permission_policy;
  document.body.appendChild(iframe);
  return promise;
}

async function createLanguageModel(options = {}) {
  await test_driver.bless();
  return LanguageModel.create(options);
}

async function createSummarizer(options = {}) {
  await test_driver.bless();
  return await Summarizer.create(options);
}

async function createWriter(options = {}) {
  await test_driver.bless();
  return await Writer.create(options);
}

async function createRewriter(options = {}) {
  await test_driver.bless();
  return await Rewriter.create(options);
}

async function createProofreader(options = {}) {
  await test_driver.bless();
  return await Proofreader.create(options);
}

async function ensureLanguageModel(options = {}) {
  assert_true(!!LanguageModel);
  const availability = await LanguageModel.availability(options);
  assert_in_array(availability, kValidAvailabilities);
  // Yield PRECONDITION_FAILED if the API is unavailable on this device.
  assert_implements_optional(availability != 'unavailable', 'API unavailable');
};

async function testDestroy(t, createMethod, options, instanceMethods) {
  const instance = await createMethod(options);

  const promises = instanceMethods.map(method => method(instance));

  instance.destroy();

  promises.push(...instanceMethods.map(method => method(instance)));

  for (const promise of promises) {
    await promise_rejects_dom(t, 'AbortError', promise);
  }
}

async function testCreateAbort(t, createMethod, options, instanceMethods) {
  const controller = new AbortController();
  const instance = await createMethod({...options, signal: controller.signal});

  const promises = instanceMethods.map(method => method(instance));

  const error = new Error('The create abort signal was aborted.');
  controller.abort(error);

  promises.push(...instanceMethods.map(method => method(instance)));

  for (const promise of promises) {
    await promise_rejects_exactly(t, error, promise);
  }
}

// Helper function to check that 'actual' is within 'expected +/- delta'.
function isValueInRange(actual, expected, delta = 5) {
  const lowerBound = expected - delta;
  const upperBound = expected + delta;
  return actual >= lowerBound && actual <= upperBound;
}

function consumeTransientUserActivation() {
  const win = window.open('about:blank', '_blank');
  if (win)
    win.close();
}

// Helper function to create a regex from some keywords.
function matchKeywordsRegex(keywords) {
  const keywordsPattern = keywords.join('|');
  return new RegExp(`(${keywordsPattern})`, 'i');
}

function messageWithContent(prompt, type, value) {
  return [{
    role: 'user',
    content: [{type: 'text', value: prompt}, {type: type, value: value}]
  }];
}