summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/streams/CommonOperations.js
blob: 4f04dacf2d53f2ef2739f61f77aa0925fb866023 (plain)
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
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Implementation of functions that are shared between ReadableStream and
// WritableStream.

(function(global, binding, v8) {
  'use strict';

  // Common private symbols. These correspond directly to internal slots in the
  // standard. "[[X]]" in the standard is spelt _X here.
  const _queue = v8.createPrivateSymbol('[[queue]]');
  const _queueTotalSize = v8.createPrivateSymbol('[[queueTotalSize]]');

  // A symbol to protect against double-resolution of promises. This
  // functionality is not explicit in the standard, but is implied in the way
  // the operations are defined.
  const _isSettled = v8.createPrivateSymbol('isSettled');

  // Javascript functions. It is important to use these copies for security and
  // robustness. See "V8 Extras Design Doc", section "Security Considerations".
  // https://docs.google.com/document/d/1AT5-T0aHGp7Lt29vPWFr2-qG8r3l9CByyvKwEuA8Ec0/edit#heading=h.9yixony1a18r
  const Boolean = global.Boolean;
  const Number = global.Number;
  const Number_isFinite = Number.isFinite;
  const Number_isNaN = Number.isNaN;

  const RangeError = global.RangeError;
  const TypeError = global.TypeError;

  const hasOwnProperty = v8.uncurryThis(global.Object.hasOwnProperty);

  function hasOwnPropertyNoThrow(x, property) {
    // The cast of |x| to Boolean will eliminate undefined and null, which would
    // cause hasOwnProperty to throw a TypeError, as well as some other values
    // that can't be objects and so will fail the check anyway.
    return Boolean(x) && hasOwnProperty(x, property);
  }

  //
  // Assert is not normally enabled, to avoid the space and time overhead. To
  // enable, uncomment this definition and then in the file you wish to enable
  // asserts for, uncomment the assert statements and add this definition:
  // const assert = pred => binding.SimpleAssert(pred);
  //
  // binding.SimpleAssert = pred => {
  //   if (pred) {
  //     return;
  //   }
  //   v8.log('\n\n\n  *** ASSERTION FAILURE ***\n\n');
  //   v8.logStackTrace();
  //   v8.log('**************************************************\n\n');
  //   class StreamsAssertionError extends Error {}
  //   throw new StreamsAssertionError('Streams Assertion Failure');
  // };

  //
  // Promise-manipulation functions
  //

  // Not exported.
  function streamInternalError() {
    throw new RangeError('Stream API Internal Error');
  }

  function rejectPromise(p, reason) {
    if (!v8.isPromise(p)) {
      streamInternalError();
    }

    if (p[_isSettled]) {
      return;
    }
    p[_isSettled] = true;

    v8.rejectPromise(p, reason);
  }

  function resolvePromise(p, value) {
    if (!v8.isPromise(p)) {
      streamInternalError();
    }

    if (p[_isSettled]) {
      return;
    }
    p[_isSettled] = true;

    v8.resolvePromise(p, value);
  }

  function markPromiseAsHandled(p) {
    if (!v8.isPromise(p)) {
      streamInternalError();
    }
    v8.markPromiseAsHandled(p);
  }

  function promiseState(p) {
    if (!v8.isPromise(p)) {
      streamInternalError();
    }
    return v8.promiseState(p);
  }

  //
  // Queue-with-Sizes Operations
  //
  function DequeueValue(container) {
    // assert(
    //     hasOwnProperty(container, _queue) &&
    //         hasOwnProperty(container, _queueTotalSize),
    //     '_container_ has [[queue]] and [[queueTotalSize]] internal slots.');
    // assert(container[_queue].length !== 0,
    //        '_container_.[[queue]] is not empty.');
    const pair = container[_queue].shift();
    container[_queueTotalSize] -= pair.size;
    if (container[_queueTotalSize] < 0) {
      container[_queueTotalSize] = 0;
    }
    return pair.value;
  }

  function EnqueueValueWithSize(container, value, size) {
    // assert(
    //     hasOwnProperty(container, _queue) &&
    //         hasOwnProperty(container, _queueTotalSize),
    //     '_container_ has [[queue]] and [[queueTotalSize]] internal 'slots.');
    size = Number(size);
    if (!IsFiniteNonNegativeNumber(size)) {
      throw new RangeError(binding.streamErrors.invalidSize);
    }

    container[_queue].push({value, size});
    container[_queueTotalSize] += size;
  }

  function PeekQueueValue(container) {
    // assert(
    //     hasOwnProperty(container, _queue) &&
    //         hasOwnProperty(container, _queueTotalSize),
    //     '_container_ has [[queue]] and [[queueTotalSize]] internal slots.');
    // assert(container[_queue].length !== 0,
    //        '_container_.[[queue]] is not empty.');
    const pair = container[_queue].peek();
    return pair.value;
  }

  function ResetQueue(container) {
    // assert(
    //     hasOwnProperty(container, _queue) &&
    //         hasOwnProperty(container, _queueTotalSize),
    //     '_container_ has [[queue]] and [[queueTotalSize]] internal slots.');
    container[_queue] = new binding.SimpleQueue();
    container[_queueTotalSize] = 0;
  }

  // Not exported.
  function IsFiniteNonNegativeNumber(v) {
    return Number_isFinite(v) && v >= 0;
  }

  function ValidateAndNormalizeHighWaterMark(highWaterMark) {
    highWaterMark = Number(highWaterMark);
    if (Number_isNaN(highWaterMark)) {
      throw new RangeError(binding.streamErrors.invalidHWM);
    }
    if (highWaterMark < 0) {
      throw new RangeError(binding.streamErrors.invalidHWM);
    }
    return highWaterMark;
  }

  // Unlike the version in the standard, this implementation returns the
  // original function as-is if it is set. This means users of the return value
  // need to be careful to explicitly set |this| when calling it.
  function MakeSizeAlgorithmFromSizeFunction(size) {
    if (size === undefined) {
      return () => 1;
    }

    if (typeof size !== 'function') {
      throw new TypeError(binding.streamErrors.sizeNotAFunction);
    }

    return size;
  }

  //
  // Invoking functions.
  // These differ from the Invoke versions in the spec in that they take a fixed
  // number of arguments rather than a list, and also take a name to be used for
  // the function on error.
  //

  // Internal utility functions. Not exported.
  const callFunction = v8.uncurryThis(global.Function.prototype.call);
  const errTmplMustBeFunctionOrUndefined = name =>
      `${name} must be a function or undefined`;
  const Promise_resolve = Promise.resolve.bind(Promise);
  const Promise_reject = Promise.reject.bind(Promise);
  const Function_bind = v8.uncurryThis(global.Function.prototype.bind);

  function resolveMethod(O, P, nameForError) {
    const method = O[P];

    if (typeof method !== 'function' && typeof method !== 'undefined') {
      throw new TypeError(errTmplMustBeFunctionOrUndefined(nameForError));
    }

    return method;
  }

  function CreateAlgorithmFromUnderlyingMethod(
      underlyingObject, methodName, algoArgCount, methodNameForError) {
    // assert(underlyingObject !== undefined,
    //        'underlyingObject is not undefined.');
    // assert(IsPropertyKey(methodName),
    // '! IsPropertyKey(methodName) is true.');
    // assert(algoArgCount === 0 || algoArgCount === 1,
    // 'algoArgCount is 0 or 1.');
    // assert(
    //     typeof methodNameForError === 'string',
    //     'methodNameForError is a string');
    const method =
        resolveMethod(underlyingObject, methodName, methodNameForError);
    // The implementation uses bound functions rather than lambdas where
    // possible to give the compiler the maximum opportunity to optimise.
    if (method === undefined) {
      return () => Promise_resolve();
    }

    if (algoArgCount === 0) {
      return Function_bind(PromiseCall0, undefined, method, underlyingObject);
    }

    return Function_bind(PromiseCall1, undefined, method, underlyingObject);
  }

  function CreateAlgorithmFromUnderlyingMethodPassingController(
      underlyingObject, methodName, algoArgCount, controller,
      methodNameForError) {
    // assert(underlyingObject !== undefined,
    //        'underlyingObject is not undefined.');
    // assert(IsPropertyKey(methodName),
    // '! IsPropertyKey(methodName) is true.');
    // assert(algoArgCount === 0 || algoArgCount === 1,
    // 'algoArgCount is 0 or 1.');
    // assert(typeof controller === 'object', 'controller is an object');
    // assert(
    //     typeof methodNameForError === 'string',
    //     'methodNameForError is a string');
    const method =
        resolveMethod(underlyingObject, methodName, methodNameForError);
    if (method === undefined) {
      return () => Promise_resolve();
    }

    if (algoArgCount === 0) {
      return Function_bind(
          PromiseCall1, undefined, method, underlyingObject, controller);
    }

    return arg => PromiseCall2(method, underlyingObject, arg, controller);
  }

  // Modified from InvokeOrNoop in spec. Takes 1 argument.
  function CallOrNoop1(O, P, arg0, nameForError) {
    const method = resolveMethod(O, P, nameForError);
    if (method === undefined) {
      return undefined;
    }

    return callFunction(method, O, arg0);
  }

  function PromiseCall0(F, V) {
    // assert(typeof F === 'function', 'IsCallable(F) is true.');
    // assert(V !== undefined, 'V is not undefined.');
    try {
      return Promise_resolve(callFunction(F, V));
    } catch (e) {
      return Promise_reject(e);
    }
  }

  function PromiseCall1(F, V, arg0) {
    // assert(typeof F === 'function', 'IsCallable(F) is true.');
    // assert(V !== undefined, 'V is not undefined.');
    try {
      return Promise_resolve(callFunction(F, V, arg0));
    } catch (e) {
      return Promise_reject(e);
    }
  }

  function PromiseCall2(F, V, arg0, arg1) {
    // assert(typeof F === 'function', 'IsCallable(F) is true.');
    // assert(V !== undefined, 'V is not undefined.');
    try {
      return Promise_resolve(callFunction(F, V, arg0, arg1));
    } catch (e) {
      return Promise_reject(e);
    }
  }

  binding.streamOperations = {
    _queue,
    _queueTotalSize,
    hasOwnPropertyNoThrow,
    rejectPromise,
    resolvePromise,
    markPromiseAsHandled,
    promiseState,
    CreateAlgorithmFromUnderlyingMethod,
    CreateAlgorithmFromUnderlyingMethodPassingController,
    DequeueValue,
    EnqueueValueWithSize,
    PeekQueueValue,
    ResetQueue,
    ValidateAndNormalizeHighWaterMark,
    MakeSizeAlgorithmFromSizeFunction,
    CallOrNoop1,
    PromiseCall2
  };
});