1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/streams/transform_stream.h"
6 
7 #include "testing/gmock/include/gmock/gmock.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
10 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
11 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
12 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
13 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
14 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
15 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
16 #include "third_party/blink/renderer/bindings/core/v8/v8_extras_test_utils.h"
17 #include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
18 #include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
19 #include "third_party/blink/renderer/core/streams/readable_stream.h"
20 #include "third_party/blink/renderer/core/streams/transform_stream_default_controller.h"
21 #include "third_party/blink/renderer/core/streams/transform_stream_transformer.h"
22 #include "third_party/blink/renderer/core/streams/writable_stream.h"
23 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
24 #include "third_party/blink/renderer/platform/bindings/microtask.h"
25 #include "third_party/blink/renderer/platform/bindings/script_state.h"
26 #include "third_party/blink/renderer/platform/bindings/to_v8.h"
27 #include "third_party/blink/renderer/platform/bindings/v8_binding.h"
28 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
29 #include "v8/include/v8.h"
30 
31 namespace blink {
32 
33 namespace {
34 
35 using ::testing::_;
36 using ::testing::ByMove;
37 using ::testing::Mock;
38 using ::testing::Return;
39 
40 class TransformStreamTest : public ::testing::Test {
41  public:
TransformStreamTest()42   TransformStreamTest() {}
43 
Stream() const44   TransformStream* Stream() const { return stream_; }
45 
Init(TransformStreamTransformer * transformer,ScriptState * script_state,ExceptionState & exception_state)46   void Init(TransformStreamTransformer* transformer,
47             ScriptState* script_state,
48             ExceptionState& exception_state) {
49     stream_ =
50         TransformStream::Create(script_state, transformer, exception_state);
51   }
52 
53   // This takes the |readable| and |writable| properties of the TransformStream
54   // and copies them onto the global object so they can be accessed by Eval().
CopyReadableAndWritableToGlobal(const V8TestingScope & scope)55   void CopyReadableAndWritableToGlobal(const V8TestingScope& scope) {
56     auto* script_state = scope.GetScriptState();
57     ReadableStream* readable = Stream()->Readable();
58     WritableStream* writable = Stream()->Writable();
59     v8::Local<v8::Object> global = script_state->GetContext()->Global();
60     EXPECT_TRUE(global
61                     ->Set(scope.GetContext(),
62                           V8String(scope.GetIsolate(), "readable"),
63                           ToV8(readable, script_state))
64                     .IsJust());
65     EXPECT_TRUE(global
66                     ->Set(scope.GetContext(),
67                           V8String(scope.GetIsolate(), "writable"),
68                           ToV8(writable, script_state))
69                     .IsJust());
70   }
71 
72  private:
73   Persistent<TransformStream> stream_;
74 };
75 
76 // A convenient base class to make tests shorter. Subclasses need not implement
77 // both Transform() and Flush(), and can override the void versions to avoid the
78 // need to create a promise to return. Not appropriate for use in production.
79 class TestTransformer : public TransformStreamTransformer {
80  public:
TestTransformer(ScriptState * script_state)81   explicit TestTransformer(ScriptState* script_state)
82       : script_state_(script_state) {}
83 
TransformVoid(v8::Local<v8::Value>,TransformStreamDefaultController *,ExceptionState &)84   virtual void TransformVoid(v8::Local<v8::Value>,
85                              TransformStreamDefaultController*,
86                              ExceptionState&) {}
87 
Transform(v8::Local<v8::Value> chunk,TransformStreamDefaultController * controller,ExceptionState & exception_state)88   ScriptPromise Transform(v8::Local<v8::Value> chunk,
89                           TransformStreamDefaultController* controller,
90                           ExceptionState& exception_state) override {
91     TransformVoid(chunk, controller, exception_state);
92     return ScriptPromise::CastUndefined(script_state_);
93   }
94 
FlushVoid(TransformStreamDefaultController *,ExceptionState &)95   virtual void FlushVoid(TransformStreamDefaultController*, ExceptionState&) {}
96 
Flush(TransformStreamDefaultController * controller,ExceptionState & exception_state)97   ScriptPromise Flush(TransformStreamDefaultController* controller,
98                       ExceptionState& exception_state) override {
99     FlushVoid(controller, exception_state);
100     return ScriptPromise::CastUndefined(script_state_);
101   }
102 
GetScriptState()103   ScriptState* GetScriptState() override { return script_state_; }
104 
Trace(Visitor * visitor) const105   void Trace(Visitor* visitor) const override {
106     visitor->Trace(script_state_);
107     TransformStreamTransformer::Trace(visitor);
108   }
109 
110  private:
111   const Member<ScriptState> script_state_;
112 };
113 
114 class IdentityTransformer final : public TestTransformer {
115  public:
IdentityTransformer(ScriptState * script_state)116   explicit IdentityTransformer(ScriptState* script_state)
117       : TestTransformer(script_state) {}
118 
TransformVoid(v8::Local<v8::Value> chunk,TransformStreamDefaultController * controller,ExceptionState & exception_state)119   void TransformVoid(v8::Local<v8::Value> chunk,
120                      TransformStreamDefaultController* controller,
121                      ExceptionState& exception_state) override {
122     controller->enqueue(GetScriptState(),
123                         ScriptValue(GetScriptState()->GetIsolate(), chunk),
124                         exception_state);
125   }
126 };
127 
128 class MockTransformStreamTransformer : public TransformStreamTransformer {
129  public:
MockTransformStreamTransformer(ScriptState * script_state)130   explicit MockTransformStreamTransformer(ScriptState* script_state)
131       : script_state_(script_state) {}
132 
133   MOCK_METHOD3(Transform,
134                ScriptPromise(v8::Local<v8::Value> chunk,
135                              TransformStreamDefaultController*,
136                              ExceptionState&));
137   MOCK_METHOD2(Flush,
138                ScriptPromise(TransformStreamDefaultController*,
139                              ExceptionState&));
140 
GetScriptState()141   ScriptState* GetScriptState() override { return script_state_; }
142 
Trace(Visitor * visitor) const143   void Trace(Visitor* visitor) const override {
144     visitor->Trace(script_state_);
145     TransformStreamTransformer::Trace(visitor);
146   }
147 
148  private:
149   const Member<ScriptState> script_state_;
150 };
151 
152 // If this doesn't work then nothing else will.
TEST_F(TransformStreamTest,Construct)153 TEST_F(TransformStreamTest, Construct) {
154   V8TestingScope scope;
155   Init(MakeGarbageCollected<IdentityTransformer>(scope.GetScriptState()),
156        scope.GetScriptState(), ASSERT_NO_EXCEPTION);
157   EXPECT_TRUE(Stream());
158 }
159 
TEST_F(TransformStreamTest,Accessors)160 TEST_F(TransformStreamTest, Accessors) {
161   V8TestingScope scope;
162   Init(MakeGarbageCollected<IdentityTransformer>(scope.GetScriptState()),
163        scope.GetScriptState(), ASSERT_NO_EXCEPTION);
164   ReadableStream* readable = Stream()->Readable();
165   WritableStream* writable = Stream()->Writable();
166   EXPECT_TRUE(readable);
167   EXPECT_TRUE(writable);
168 }
169 
TEST_F(TransformStreamTest,TransformIsCalled)170 TEST_F(TransformStreamTest, TransformIsCalled) {
171   V8TestingScope scope;
172   auto* mock = MakeGarbageCollected<MockTransformStreamTransformer>(
173       scope.GetScriptState());
174   Init(mock, scope.GetScriptState(), ASSERT_NO_EXCEPTION);
175   // Need to run microtasks so the startAlgorithm promise resolves.
176   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
177   CopyReadableAndWritableToGlobal(scope);
178 
179   EXPECT_CALL(*mock, Transform(_, _, _))
180       .WillOnce(
181           Return(ByMove(ScriptPromise::CastUndefined(scope.GetScriptState()))));
182 
183   // The initial read is needed to relieve backpressure.
184   EvalWithPrintingError(&scope,
185                         "readable.getReader().read();\n"
186                         "const writer = writable.getWriter();\n"
187                         "writer.write('a');\n");
188 
189   Mock::VerifyAndClear(mock);
190   Mock::AllowLeak(mock);
191 }
192 
TEST_F(TransformStreamTest,FlushIsCalled)193 TEST_F(TransformStreamTest, FlushIsCalled) {
194   V8TestingScope scope;
195   auto* mock = MakeGarbageCollected<MockTransformStreamTransformer>(
196       scope.GetScriptState());
197   Init(mock, scope.GetScriptState(), ASSERT_NO_EXCEPTION);
198   // Need to run microtasks so the startAlgorithm promise resolves.
199   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
200   CopyReadableAndWritableToGlobal(scope);
201 
202   EXPECT_CALL(*mock, Flush(_, _))
203       .WillOnce(
204           Return(ByMove(ScriptPromise::CastUndefined(scope.GetScriptState()))));
205 
206   EvalWithPrintingError(&scope,
207                         "const writer = writable.getWriter();\n"
208                         "writer.close();\n");
209 
210   Mock::VerifyAndClear(mock);
211   Mock::AllowLeak(mock);
212 }
213 
IsIteratorForStringMatching(ScriptState * script_state,ScriptValue value,const String & expected)214 bool IsIteratorForStringMatching(ScriptState* script_state,
215                                  ScriptValue value,
216                                  const String& expected) {
217   if (!value.IsObject()) {
218     return false;
219   }
220   bool done = false;
221   auto chunk = V8UnpackIteratorResult(
222       script_state,
223       value.V8Value()->ToObject(script_state->GetContext()).ToLocalChecked(),
224       &done);
225   if (done || chunk.IsEmpty())
226     return false;
227   return ToCoreStringWithUndefinedOrNullCheck(chunk.ToLocalChecked()) ==
228          expected;
229 }
230 
IsTypeError(ScriptState * script_state,ScriptValue value,const String & message)231 bool IsTypeError(ScriptState* script_state,
232                  ScriptValue value,
233                  const String& message) {
234   v8::Local<v8::Object> object;
235   if (!value.V8Value()->ToObject(script_state->GetContext()).ToLocal(&object)) {
236     return false;
237   }
238   if (!object->IsNativeError())
239     return false;
240 
241   const auto& Has = [script_state, object](const String& key,
242                                            const String& value) -> bool {
243     v8::Local<v8::Value> actual;
244     return object
245                ->Get(script_state->GetContext(),
246                      V8AtomicString(script_state->GetIsolate(), key))
247                .ToLocal(&actual) &&
248            ToCoreStringWithUndefinedOrNullCheck(actual) == value;
249   };
250 
251   return Has("name", "TypeError") && Has("message", message);
252 }
253 
TEST_F(TransformStreamTest,EnqueueFromTransform)254 TEST_F(TransformStreamTest, EnqueueFromTransform) {
255   V8TestingScope scope;
256   auto* script_state = scope.GetScriptState();
257   Init(MakeGarbageCollected<IdentityTransformer>(scope.GetScriptState()),
258        script_state, ASSERT_NO_EXCEPTION);
259 
260   CopyReadableAndWritableToGlobal(scope);
261 
262   EvalWithPrintingError(&scope,
263                         "const writer = writable.getWriter();\n"
264                         "writer.write('a');\n");
265 
266   ReadableStream* readable = Stream()->Readable();
267   auto* reader =
268       readable->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
269   ScriptPromiseTester tester(script_state,
270                              reader->read(script_state, ASSERT_NO_EXCEPTION));
271   tester.WaitUntilSettled();
272   EXPECT_TRUE(tester.IsFulfilled());
273   EXPECT_TRUE(IsIteratorForStringMatching(script_state, tester.Value(), "a"));
274 }
275 
TEST_F(TransformStreamTest,EnqueueFromFlush)276 TEST_F(TransformStreamTest, EnqueueFromFlush) {
277   class EnqueueFromFlushTransformer final : public TestTransformer {
278    public:
279     explicit EnqueueFromFlushTransformer(ScriptState* script_state)
280         : TestTransformer(script_state) {}
281 
282     void FlushVoid(TransformStreamDefaultController* controller,
283                    ExceptionState& exception_state) override {
284       controller->enqueue(GetScriptState(),
285                           ScriptValue::From(GetScriptState(), "a"),
286                           exception_state);
287     }
288   };
289 
290   V8TestingScope scope;
291   auto* script_state = scope.GetScriptState();
292   Init(MakeGarbageCollected<EnqueueFromFlushTransformer>(script_state),
293        script_state, ASSERT_NO_EXCEPTION);
294 
295   CopyReadableAndWritableToGlobal(scope);
296 
297   EvalWithPrintingError(&scope,
298                         "const writer = writable.getWriter();\n"
299                         "writer.close();\n");
300 
301   ReadableStream* readable = Stream()->Readable();
302   auto* reader =
303       readable->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
304   ScriptPromiseTester tester(script_state,
305                              reader->read(script_state, ASSERT_NO_EXCEPTION));
306   tester.WaitUntilSettled();
307   EXPECT_TRUE(tester.IsFulfilled());
308   EXPECT_TRUE(IsIteratorForStringMatching(script_state, tester.Value(), "a"));
309 }
310 
TEST_F(TransformStreamTest,ThrowFromTransform)311 TEST_F(TransformStreamTest, ThrowFromTransform) {
312   static constexpr char kMessage[] = "errorInTransform";
313   class ThrowFromTransformTransformer final : public TestTransformer {
314    public:
315     explicit ThrowFromTransformTransformer(ScriptState* script_state)
316         : TestTransformer(script_state) {}
317 
318     void TransformVoid(v8::Local<v8::Value>,
319                        TransformStreamDefaultController*,
320                        ExceptionState& exception_state) override {
321       exception_state.ThrowTypeError(kMessage);
322     }
323   };
324 
325   V8TestingScope scope;
326   auto* script_state = scope.GetScriptState();
327   Init(MakeGarbageCollected<ThrowFromTransformTransformer>(
328            scope.GetScriptState()),
329        script_state, ASSERT_NO_EXCEPTION);
330 
331   CopyReadableAndWritableToGlobal(scope);
332 
333   ScriptValue promise =
334       EvalWithPrintingError(&scope,
335                             "const writer = writable.getWriter();\n"
336                             "writer.write('a');\n");
337 
338   ReadableStream* readable = Stream()->Readable();
339   auto* reader =
340       readable->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
341   ScriptPromiseTester read_tester(
342       script_state, reader->read(script_state, ASSERT_NO_EXCEPTION));
343   read_tester.WaitUntilSettled();
344   EXPECT_TRUE(read_tester.IsRejected());
345   EXPECT_TRUE(IsTypeError(script_state, read_tester.Value(), kMessage));
346   ScriptPromiseTester write_tester(script_state,
347                                    ScriptPromise::Cast(script_state, promise));
348   write_tester.WaitUntilSettled();
349   EXPECT_TRUE(write_tester.IsRejected());
350   EXPECT_TRUE(IsTypeError(script_state, write_tester.Value(), kMessage));
351 }
352 
TEST_F(TransformStreamTest,ThrowFromFlush)353 TEST_F(TransformStreamTest, ThrowFromFlush) {
354   static constexpr char kMessage[] = "errorInFlush";
355   class ThrowFromFlushTransformer final : public TestTransformer {
356    public:
357     explicit ThrowFromFlushTransformer(ScriptState* script_state)
358         : TestTransformer(script_state) {}
359 
360     void FlushVoid(TransformStreamDefaultController*,
361                    ExceptionState& exception_state) override {
362       exception_state.ThrowTypeError(kMessage);
363     }
364   };
365   V8TestingScope scope;
366   auto* script_state = scope.GetScriptState();
367   Init(MakeGarbageCollected<ThrowFromFlushTransformer>(scope.GetScriptState()),
368        script_state, ASSERT_NO_EXCEPTION);
369 
370   CopyReadableAndWritableToGlobal(scope);
371 
372   ScriptValue promise =
373       EvalWithPrintingError(&scope,
374                             "const writer = writable.getWriter();\n"
375                             "writer.close();\n");
376 
377   ReadableStream* readable = Stream()->Readable();
378   auto* reader =
379       readable->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
380   ScriptPromiseTester read_tester(
381       script_state, reader->read(script_state, ASSERT_NO_EXCEPTION));
382   read_tester.WaitUntilSettled();
383   EXPECT_TRUE(read_tester.IsRejected());
384   EXPECT_TRUE(IsTypeError(script_state, read_tester.Value(), kMessage));
385   ScriptPromiseTester write_tester(script_state,
386                                    ScriptPromise::Cast(script_state, promise));
387   write_tester.WaitUntilSettled();
388   EXPECT_TRUE(write_tester.IsRejected());
389   EXPECT_TRUE(IsTypeError(script_state, write_tester.Value(), kMessage));
390 }
391 
TEST_F(TransformStreamTest,CreateFromReadableWritablePair)392 TEST_F(TransformStreamTest, CreateFromReadableWritablePair) {
393   V8TestingScope scope;
394   ReadableStream* readable =
395       ReadableStream::Create(scope.GetScriptState(), ASSERT_NO_EXCEPTION);
396   WritableStream* writable =
397       WritableStream::Create(scope.GetScriptState(), ASSERT_NO_EXCEPTION);
398   TransformStream transform(readable, writable);
399   EXPECT_EQ(readable, transform.Readable());
400   EXPECT_EQ(writable, transform.Writable());
401 }
402 
TEST_F(TransformStreamTest,WaitInTransform)403 TEST_F(TransformStreamTest, WaitInTransform) {
404   class WaitInTransformTransformer final : public TestTransformer {
405    public:
406     explicit WaitInTransformTransformer(ScriptState* script_state)
407         : TestTransformer(script_state),
408           transform_promise_resolver_(
409               MakeGarbageCollected<ScriptPromiseResolver>(script_state)) {}
410 
411     ScriptPromise Transform(v8::Local<v8::Value>,
412                             TransformStreamDefaultController*,
413                             ExceptionState&) override {
414       return transform_promise_resolver_->Promise();
415     }
416 
417     void FlushVoid(TransformStreamDefaultController*,
418                    ExceptionState&) override {
419       flush_called_ = true;
420     }
421 
422     void ResolvePromise() { transform_promise_resolver_->Resolve(); }
423     bool FlushCalled() const { return flush_called_; }
424 
425     void Trace(Visitor* visitor) const override {
426       visitor->Trace(transform_promise_resolver_);
427       TestTransformer::Trace(visitor);
428     }
429 
430    private:
431     const Member<ScriptPromiseResolver> transform_promise_resolver_;
432     bool flush_called_ = false;
433   };
434 
435   V8TestingScope scope;
436   auto* script_state = scope.GetScriptState();
437   auto* transformer =
438       MakeGarbageCollected<WaitInTransformTransformer>(script_state);
439   Init(transformer, script_state, ASSERT_NO_EXCEPTION);
440   CopyReadableAndWritableToGlobal(scope);
441 
442   ScriptValue promise =
443       EvalWithPrintingError(&scope,
444                             "const writer = writable.getWriter();\n"
445                             "const promise = writer.write('a');\n"
446                             "writer.close();\n"
447                             "promise;\n");
448   // Need to read to relieve backpressure.
449   Stream()
450       ->Readable()
451       ->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION)
452       ->read(script_state, ASSERT_NO_EXCEPTION);
453 
454   ScriptPromiseTester write_tester(script_state,
455                                    ScriptPromise::Cast(script_state, promise));
456 
457   // Give Transform() the opportunity to be called.
458   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
459 
460   EXPECT_FALSE(write_tester.IsFulfilled());
461   EXPECT_FALSE(transformer->FlushCalled());
462 
463   transformer->ResolvePromise();
464 
465   write_tester.WaitUntilSettled();
466   EXPECT_TRUE(write_tester.IsFulfilled());
467   EXPECT_TRUE(transformer->FlushCalled());
468 }
469 
TEST_F(TransformStreamTest,WaitInFlush)470 TEST_F(TransformStreamTest, WaitInFlush) {
471   class WaitInFlushTransformer final : public TestTransformer {
472    public:
473     explicit WaitInFlushTransformer(ScriptState* script_state)
474         : TestTransformer(script_state),
475           flush_promise_resolver_(
476               MakeGarbageCollected<ScriptPromiseResolver>(script_state)) {}
477 
478     ScriptPromise Flush(TransformStreamDefaultController*,
479                         ExceptionState&) override {
480       return flush_promise_resolver_->Promise();
481     }
482 
483     void ResolvePromise() { flush_promise_resolver_->Resolve(); }
484 
485     void Trace(Visitor* visitor) const override {
486       visitor->Trace(flush_promise_resolver_);
487       TestTransformer::Trace(visitor);
488     }
489 
490    private:
491     const Member<ScriptPromiseResolver> flush_promise_resolver_;
492   };
493 
494   V8TestingScope scope;
495   auto* script_state = scope.GetScriptState();
496   auto* transformer =
497       MakeGarbageCollected<WaitInFlushTransformer>(script_state);
498   Init(transformer, script_state, ASSERT_NO_EXCEPTION);
499   CopyReadableAndWritableToGlobal(scope);
500 
501   ScriptValue promise =
502       EvalWithPrintingError(&scope,
503                             "const writer = writable.getWriter();\n"
504                             "writer.close();\n");
505 
506   // Need to read to relieve backpressure.
507   Stream()
508       ->Readable()
509       ->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION)
510       ->read(script_state, ASSERT_NO_EXCEPTION);
511 
512   ScriptPromiseTester close_tester(script_state,
513                                    ScriptPromise::Cast(script_state, promise));
514 
515   // Give Flush() the opportunity to be called.
516   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
517 
518   EXPECT_FALSE(close_tester.IsFulfilled());
519   transformer->ResolvePromise();
520   close_tester.WaitUntilSettled();
521   EXPECT_TRUE(close_tester.IsFulfilled());
522 }
523 
524 }  // namespace
525 }  // namespace blink
526