MediaRecorder: Adding BlobEvent and connecting it in MediaRecorderHandler

Note: MediaRecorder-basic-video.html is not reconnected yet 
due to missing infra (See http://crbug.com/532509 and
this is ref'd in TestExpectations [1])

BUG=262211
TEST= https://rawgit.com/Miguelao/demos/master/mediarecorder.html
(video of it at [2] -- sorry, just for @google accounts).


[1] https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/LayoutTests/TestExpectations&sq=package:chromium&type=cs&q=TestExpectations%20MEdiaRecorder&l=409
[2] https://drive.google.com/open?id=0BwgFm5xOT0yCU3hScVh4WjIzZzQ

Review URL: https://codereview.chromium.org/1354863002

git-svn-id: svn://svn.chromium.org/blink/trunk@202646 bbb929c8-8fbe-4397-9dbb-9b2b20218538
diff --git a/LayoutTests/fast/mediarecorder/BlobEvent-basic.html b/LayoutTests/fast/mediarecorder/BlobEvent-basic.html
new file mode 100644
index 0000000..64e9f80
--- /dev/null
+++ b/LayoutTests/fast/mediarecorder/BlobEvent-basic.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src=../../resources/testharness.js></script>
+<script src=../../resources/testharnessreport.js></script>
+<script>
+
+test(function() {
+  var array = new Uint8Array([0x70, 0x71, 0x72, 0x73]);
+  var blob = new Blob([array]);
+  var blobEvent = new BlobEvent('BlobEvent', {data : blob});
+
+  var reader = new FileReader();
+  reader.addEventListener("loadend", function() {
+    // |reader.result| contains the contents of blob as an ArrayBuffer.
+    var outputArray = new Uint8Array(reader.result);
+    assert_equals(array.length, outputArray.length);
+    for (var index in outputArray) {
+      assert_equals(array[index], outputArray[index]);
+    }
+  });
+  reader.readAsArrayBuffer(blob);
+
+}, 'check BlobEvent creation and content management');
+
+
+</script>
diff --git a/LayoutTests/fast/mediarecorder/MediaRecorder-basic-video.html b/LayoutTests/fast/mediarecorder/MediaRecorder-basic-video.html
index fff0eea..7021d36 100644
--- a/LayoutTests/fast/mediarecorder/MediaRecorder-basic-video.html
+++ b/LayoutTests/fast/mediarecorder/MediaRecorder-basic-video.html
@@ -4,21 +4,35 @@
 <script>
 
 var test = async_test('checks the video-only MediaRecorder API.');
+var recorder;
 
 recorderOnDataAvailable = test.step_func(function(event) {
-    assert_greater_than(event.data.size, 0, 'Recorded data size should be > 0');
-    assert_equals(recorder.state, "recording");
+    if (event) {
+      assert_greater_than(event.data.size, 0, 'Recorded data size should be > 0');
+      assert_equals(recorder.state, "recording");
+    } else {
+      assert_equals(recorder.state, "inactive");
+    }
 
     // TODO(mcasas): Let the test record for a while.
     // TODO(mcasas): Consider storing the recorded data and playing it back.
 
+    recorder.onstop = recorderOnStopExpected;
+    recorder.stop();
+});
+
+recorderOnStopExpected = test.step_func(function() {
     test.done();
 });
 
-recorderOnStop = test.step_func(function() {
+recorderOnStopUnexpected = test.step_func(function() {
     assert_unreached('Recording stopped.');
 });
 
+recorderOnError = test.step_func(function() {
+    assert_unreached('Recording error.');
+});
+
 gotStream = test.step_func(function(stream) {
     assert_equals(stream.getAudioTracks().length, 0);
     assert_equals(stream.getVideoTracks().length, 1);
@@ -32,7 +46,8 @@
 
     assert_equals(recorder.state, "inactive");
     recorder.ondataavailable = recorderOnDataAvailable;
-    recorder.onstop = recorderOnStop;
+    recorder.onstop = recorderOnStopUnexpected;
+    recorder.onerror = recorderOnError;
     recorder.start();
 
     assert_equals(recorder.state, "recording");
diff --git a/LayoutTests/webexposed/global-interface-listing-expected.txt b/LayoutTests/webexposed/global-interface-listing-expected.txt
index 7c427ca..34490e4 100644
--- a/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -286,6 +286,9 @@
     method close
     method constructor
     method slice
+interface BlobEvent
+    getter data
+    method constructor
 interface BluetoothDevice
     getter deviceClass
     getter instanceID
diff --git a/Source/modules/mediarecorder/BlobEvent.cpp b/Source/modules/mediarecorder/BlobEvent.cpp
new file mode 100644
index 0000000..f94509c
--- /dev/null
+++ b/Source/modules/mediarecorder/BlobEvent.cpp
@@ -0,0 +1,55 @@
+// Copyright 2015 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.
+
+#include "config.h"
+
+#include "modules/mediarecorder/BlobEvent.h"
+
+#include "modules/mediarecorder/BlobEventInit.h"
+
+namespace blink {
+
+// static
+PassRefPtrWillBeRawPtr<BlobEvent> BlobEvent::create()
+{
+    return adoptRefWillBeNoop(new BlobEvent);
+}
+
+// static
+PassRefPtrWillBeRawPtr<BlobEvent> BlobEvent::create(const AtomicString& type, const BlobEventInit& initializer)
+{
+    return adoptRefWillBeNoop(new BlobEvent(type, initializer));
+}
+
+// static
+PassRefPtrWillBeRawPtr<BlobEvent> BlobEvent::create(const AtomicString& type, Blob* blob)
+{
+    return adoptRefWillBeNoop(new BlobEvent(type, blob));
+}
+
+const AtomicString& BlobEvent::interfaceName() const
+{
+    return EventNames::BlobEvent;
+}
+
+DEFINE_TRACE(BlobEvent)
+{
+    visitor->trace(m_blob);
+    Event::trace(visitor);
+}
+
+BlobEvent::BlobEvent(const AtomicString& type, const BlobEventInit& initializer)
+    : Event(type, initializer)
+{
+    if (initializer.hasBlob())
+        m_blob = initializer.blob();
+}
+
+BlobEvent::BlobEvent(const AtomicString& type, Blob* blob)
+    : Event(type, false /* canBubble */, false /* cancelable */)
+    , m_blob(blob)
+{
+}
+
+} // namespace blink
diff --git a/Source/modules/mediarecorder/BlobEvent.h b/Source/modules/mediarecorder/BlobEvent.h
new file mode 100644
index 0000000..a28fec4
--- /dev/null
+++ b/Source/modules/mediarecorder/BlobEvent.h
@@ -0,0 +1,44 @@
+// Copyright 2015 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.
+
+#ifndef BlobEvent_h
+#define BlobEvent_h
+
+#include "core/fileapi/Blob.h"
+#include "modules/EventModules.h"
+#include "modules/ModulesExport.h"
+#include "wtf/text/AtomicString.h"
+
+namespace blink {
+
+class Blob;
+class BlobEventInit;
+
+class MODULES_EXPORT BlobEvent final : public Event {
+    DEFINE_WRAPPERTYPEINFO();
+public:
+    ~BlobEvent() override {}
+
+    static PassRefPtrWillBeRawPtr<BlobEvent> create();
+    static PassRefPtrWillBeRawPtr<BlobEvent> create(const AtomicString& type, const BlobEventInit& initializer);
+    static PassRefPtrWillBeRawPtr<BlobEvent> create(const AtomicString& type, Blob*);
+
+    Blob* data() const { return m_blob.get(); }
+
+    // Event
+    const AtomicString& interfaceName() const final;
+
+    DECLARE_VIRTUAL_TRACE();
+
+private:
+    BlobEvent() {}
+    BlobEvent(const AtomicString& type, const BlobEventInit& initializer);
+    BlobEvent(const AtomicString& type, Blob*);
+
+    PersistentWillBeMember<Blob> m_blob;
+};
+
+} // namespace blink
+
+#endif // BlobEvent_h
diff --git a/Source/modules/mediarecorder/BlobEvent.idl b/Source/modules/mediarecorder/BlobEvent.idl
new file mode 100644
index 0000000..08edd28
--- /dev/null
+++ b/Source/modules/mediarecorder/BlobEvent.idl
@@ -0,0 +1,15 @@
+// Copyright 2015 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.
+
+// https://w3c.github.io/mediacapture-record/MediaRecorder.html#blob-event
+
+[
+    // This constructor is yet to be added to the specification, see:
+    // https://github.com/w3c/mediacapture-record/issues/11
+    Constructor(DOMString type, optional BlobEventInit eventInit),
+    RuntimeEnabled=MediaRecorder
+]
+interface BlobEvent : Event {
+    readonly attribute Blob data;
+};
diff --git a/Source/modules/mediarecorder/BlobEventInit.idl b/Source/modules/mediarecorder/BlobEventInit.idl
new file mode 100644
index 0000000..5ffbe06
--- /dev/null
+++ b/Source/modules/mediarecorder/BlobEventInit.idl
@@ -0,0 +1,7 @@
+// Copyright 2015 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.
+
+dictionary BlobEventInit : EventInit {
+    Blob? blob = null;
+};
diff --git a/Source/modules/mediarecorder/MediaRecorder.cpp b/Source/modules/mediarecorder/MediaRecorder.cpp
index 3183ca6..bd0f537 100644
--- a/Source/modules/mediarecorder/MediaRecorder.cpp
+++ b/Source/modules/mediarecorder/MediaRecorder.cpp
@@ -9,6 +9,7 @@
 #include "core/fileapi/Blob.h"
 #include "modules/EventModules.h"
 #include "modules/EventTargetModules.h"
+#include "modules/mediarecorder/BlobEvent.h"
 #include "modules/mediarecorder/MediaRecorderErrorEvent.h"
 #include "platform/NotImplemented.h"
 #include "platform/blob/BlobData.h"
@@ -152,7 +153,7 @@
         return;
     }
 
-    createBlobEvent(BlobData::create());
+    createBlobEvent(nullptr);
 }
 
 String MediaRecorder::canRecordMimeType(const String& mimeType)
@@ -212,10 +213,9 @@
 
     // TODO(mcasas): Act as |m_ignoredMutedMedia| instructs if |m_stream| track(s) is in muted() state.
     // TODO(mcasas): Use |lastInSlice| to indicate to JS that recording is done.
-
     OwnPtr<BlobData> blobData = BlobData::create();
     blobData->appendBytes(data, length);
-    createBlobEvent(blobData.release());
+    createBlobEvent(Blob::create(BlobDataHandle::create(blobData.release(), length)));
 }
 
 void MediaRecorder::failOutOfMemory(const WebString& message)
@@ -245,10 +245,10 @@
         stopRecording();
 }
 
-void MediaRecorder::createBlobEvent(PassOwnPtr<BlobData> blobData)
+void MediaRecorder::createBlobEvent(Blob* blob)
 {
-    // TODO(mcasas): Launch a BlobEvent when that class is landed, but also see https://github.com/w3c/mediacapture-record/issues/17.
-    notImplemented();
+    // TODO(mcasas): Consider launching an Event with a TypedArray inside, see https://github.com/w3c/mediacapture-record/issues/17.
+    scheduleDispatchEvent(BlobEvent::create(EventTypeNames::dataavailable, blob));
 }
 
 void MediaRecorder::stopRecording()
@@ -258,7 +258,7 @@
 
     m_recorderHandler->stop();
 
-    createBlobEvent(BlobData::create());
+    createBlobEvent(nullptr);
 
     scheduleDispatchEvent(Event::create(EventTypeNames::stop));
 }
diff --git a/Source/modules/mediarecorder/MediaRecorder.h b/Source/modules/mediarecorder/MediaRecorder.h
index 028515b..80ae6db 100644
--- a/Source/modules/mediarecorder/MediaRecorder.h
+++ b/Source/modules/mediarecorder/MediaRecorder.h
@@ -16,7 +16,7 @@
 
 namespace blink {
 
-class BlobData;
+class Blob;
 class ExceptionState;
 
 class MODULES_EXPORT MediaRecorder final
@@ -81,7 +81,7 @@
 private:
     MediaRecorder(ExecutionContext*, MediaStream*, const String& mimeType, ExceptionState&);
 
-    void createBlobEvent(PassOwnPtr<BlobData> blobData);
+    void createBlobEvent(Blob*);
 
     void stopRecording();
     void scheduleDispatchEvent(PassRefPtrWillBeRawPtr<Event>);
diff --git a/Source/modules/mediarecorder/MediaRecorder.idl b/Source/modules/mediarecorder/MediaRecorder.idl
index 3b360ee..92004b4 100644
--- a/Source/modules/mediarecorder/MediaRecorder.idl
+++ b/Source/modules/mediarecorder/MediaRecorder.idl
@@ -10,6 +10,7 @@
     GarbageCollected,
     ActiveDOMObject,
     // TODO(mcasas): consider changing |mimeType| to a dictionary with said key, see https://github.com/w3c/mediacapture-record/issues/19
+    // TODO(mcasas): Remove [TreatUndefinedAs], see https://github.com/w3c/mediacapture-record/issues/7
     Constructor(MediaStream stream, [TreatUndefinedAs=Missing] optional DOMString mimeType),
     ConstructorCallWith=ExecutionContext,
     RaisesException=Constructor,
diff --git a/Source/modules/modules.gypi b/Source/modules/modules.gypi
index 855af39..bf24044 100644
--- a/Source/modules/modules.gypi
+++ b/Source/modules/modules.gypi
@@ -105,6 +105,7 @@
       'indexeddb/IDBRequest.idl',
       'indexeddb/IDBTransaction.idl',
       'indexeddb/IDBVersionChangeEvent.idl',
+      'mediarecorder/BlobEvent.idl',
       'mediarecorder/MediaRecorder.idl',
       'mediarecorder/MediaRecorderErrorEvent.idl',
       'mediasession/MediaSession.idl',
@@ -395,6 +396,7 @@
       'gamepad/GamepadEvent.idl',
       'geofencing/GeofencingEvent.idl',
       'indexeddb/IDBVersionChangeEvent.idl',
+      'mediarecorder/BlobEvent.idl',
       'mediarecorder/MediaRecorderErrorEvent.idl',
       'mediastream/MediaStreamEvent.idl',
       'mediastream/MediaStreamTrackEvent.idl',
@@ -451,6 +453,7 @@
       'indexeddb/IDBIndexParameters.idl',
       'indexeddb/IDBObjectStoreParameters.idl',
       'indexeddb/IDBVersionChangeEventInit.idl',
+      'mediarecorder/BlobEventInit.idl',
       'mediastream/MediaStreamConstraints.idl',
       'mediastream/MediaStreamEventInit.idl',
       'mediastream/RTCDTMFToneChangeEventInit.idl',
@@ -562,6 +565,8 @@
       '<(blink_modules_output_dir)/indexeddb/IDBObjectStoreParameters.h',
       '<(blink_modules_output_dir)/indexeddb/IDBVersionChangeEventInit.cpp',
       '<(blink_modules_output_dir)/indexeddb/IDBVersionChangeEventInit.h',
+      '<(blink_modules_output_dir)/mediarecorder/BlobEventInit.cpp',
+      '<(blink_modules_output_dir)/mediarecorder/BlobEventInit.h',
       '<(blink_modules_output_dir)/mediastream/MediaStreamConstraints.cpp',
       '<(blink_modules_output_dir)/mediastream/MediaStreamConstraints.h',
       '<(blink_modules_output_dir)/mediastream/MediaStreamEventInit.cpp',
@@ -1086,6 +1091,8 @@
       'indexeddb/WebIDBDatabaseCallbacksImpl.h',
       'indexeddb/WorkerGlobalScopeIndexedDatabase.cpp',
       'indexeddb/WorkerGlobalScopeIndexedDatabase.h',
+      'mediarecorder/BlobEvent.cpp',
+      'mediarecorder/BlobEvent.h',
       'mediarecorder/MediaRecorder.cpp',
       'mediarecorder/MediaRecorder.h',
       'mediarecorder/MediaRecorderErrorEvent.cpp',