Bug 1127885 - Console API should display blobs when used in workers, r=smaug
☠☠ backed out by 0674270dedae ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Sat, 31 Jan 2015 19:12:00 +0100
changeset 226930 030744f8ef5a88772ebede81f0a75e5aef5ee398
parent 226929 37cbadfe1bc172eb91ffc237baea56f37107aac7
child 226931 012da03f66554f14fc9cd33433c7123d6b948e94
push id54951
push useramarchesini@mozilla.com
push dateSat, 31 Jan 2015 18:12:21 +0000
treeherdermozilla-inbound@030744f8ef5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1127885
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1127885 - Console API should display blobs when used in workers, r=smaug
dom/base/Console.cpp
dom/workers/test/mochitest.ini
dom/workers/test/test_consoleAndBlobs.html
dom/workers/test/worker_consoleAndBlobs.js
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -1,17 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Console.h"
 #include "mozilla/dom/ConsoleBinding.h"
 
+#include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/File.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/Maybe.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDocument.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsGlobalWindow.h"
 #include "nsJSUtils.h"
 #include "nsPerformance.h"
@@ -36,90 +38,124 @@
 
 // The maximum allowed number of concurrent counters per page.
 #define MAX_PAGE_COUNTERS 10000
 
 // The maximum stacktrace depth when populating the stacktrace array used for
 // console.trace().
 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
 
-// This tag is used in the Structured Clone Algorithm to move js values from
+// This tags are used in the Structured Clone Algorithm to move js values from
 // worker thread to main thread
-#define CONSOLE_TAG JS_SCTAG_USER_MIN
+#define CONSOLE_TAG_STRING JS_SCTAG_USER_MIN
+#define CONSOLE_TAG_BLOB   JS_SCTAG_USER_MIN + 1
 
 using namespace mozilla::dom::exceptions;
 using namespace mozilla::dom::workers;
 
 namespace mozilla {
 namespace dom {
 
+struct
+ConsoleStructuredCloneData
+{
+  nsCOMPtr<nsISupports> mParent;
+  nsTArray<nsString> mStrings;
+  nsTArray<nsRefPtr<FileImpl>> mFiles;
+};
+
 /**
  * Console API in workers uses the Structured Clone Algorithm to move any value
  * from the worker thread to the main-thread. Some object cannot be moved and,
  * in these cases, we convert them to strings.
  * It's not the best, but at least we are able to show something.
  */
 
 // This method is called by the Structured Clone Algorithm when some data has
 // to be read.
 static JSObject*
 ConsoleStructuredCloneCallbacksRead(JSContext* aCx,
                                     JSStructuredCloneReader* /* unused */,
-                                    uint32_t aTag, uint32_t aData,
+                                    uint32_t aTag, uint32_t aIndex,
                                     void* aClosure)
 {
   AssertIsOnMainThread();
+  ConsoleStructuredCloneData* data =
+    static_cast<ConsoleStructuredCloneData*>(aClosure);
+  MOZ_ASSERT(data);
+  MOZ_ASSERT(data->mParent);
 
-  if (aTag != CONSOLE_TAG) {
-    return nullptr;
+  if (aTag == CONSOLE_TAG_STRING) {
+    MOZ_ASSERT(data->mStrings.Length() > aIndex);
+
+    JS::Rooted<JS::Value> value(aCx);
+    if (!xpc::StringToJsval(aCx, data->mStrings.ElementAt(aIndex), &value)) {
+      return nullptr;
+    }
+
+    JS::Rooted<JSObject*> obj(aCx);
+    if (!JS_ValueToObject(aCx, value, &obj)) {
+      return nullptr;
+    }
+
+    return obj;
   }
 
-  nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
-  MOZ_ASSERT(strings->Length() > aData);
+  if (aTag == CONSOLE_TAG_BLOB) {
+    MOZ_ASSERT(data->mFiles.Length() > aIndex);
 
-  JS::Rooted<JS::Value> value(aCx);
-  if (!xpc::StringToJsval(aCx, strings->ElementAt(aData), &value)) {
-    return nullptr;
+    nsRefPtr<File> file =
+      new File(data->mParent, data->mFiles.ElementAt(aIndex));
+    return file->WrapObject(aCx);
   }
 
-  JS::Rooted<JSObject*> obj(aCx);
-  if (!JS_ValueToObject(aCx, value, &obj)) {
-    return nullptr;
-  }
-
-  return obj;
+  MOZ_CRASH("No other tags are supported.");
+  return nullptr;
 }
 
 // This method is called by the Structured Clone Algorithm when some data has
 // to be written.
 static bool
 ConsoleStructuredCloneCallbacksWrite(JSContext* aCx,
                                      JSStructuredCloneWriter* aWriter,
                                      JS::Handle<JSObject*> aObj,
                                      void* aClosure)
 {
+  ConsoleStructuredCloneData* data =
+    static_cast<ConsoleStructuredCloneData*>(aClosure);
+  MOZ_ASSERT(data);
+
+  nsRefPtr<File> file;
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, file)) &&
+      file->Impl()->MayBeClonedToOtherThreads()) {
+    if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB, data->mFiles.Length())) {
+      return false;
+    }
+
+    data->mFiles.AppendElement(file->Impl());
+    return true;
+  }
+
   JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
   if (!jsString) {
     return false;
   }
 
   nsAutoJSString string;
   if (!string.init(aCx, jsString)) {
     return false;
   }
 
-  nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
-
-  if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG, strings->Length())) {
+  if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_STRING,
+                          data->mStrings.Length())) {
     return false;
   }
 
-  strings->AppendElement(string);
-
+  data->mStrings.AppendElement(string);
   return true;
 }
 
 static void
 ConsoleStructuredCloneCallbacksError(JSContext* /* aCx */,
                                      uint32_t /* aErrorId */)
 {
   NS_WARNING("Failed to clone data for the Console API in workers.");
@@ -409,17 +445,17 @@ private:
       arg = mCallData->mArguments[i];
       if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
         return false;
       }
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
-    if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
+    if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mData)) {
       return false;
     }
 
     mCallData->CleanupJSObjects();
     return true;
   }
 
   void
@@ -446,28 +482,33 @@ private:
         id = NS_LITERAL_STRING("ServiceWorker");
       } else {
         id = NS_LITERAL_STRING("Worker");
       }
 
       mCallData->SetIDs(id, frame.mFilename);
     }
 
+    // Now we could have the correct window (if we are not window-less).
+    mData.mParent = aInnerWindow;
+
     ProcessCallData(aCx);
     mCallData->CleanupJSObjects();
+
+    mData.mParent = nullptr;
   }
 
 private:
   void
   ProcessCallData(JSContext* aCx)
   {
     ClearException ce(aCx);
 
     JS::Rooted<JS::Value> argumentsValue(aCx);
-    if (!mArguments.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
+    if (!mArguments.read(aCx, &argumentsValue, &gConsoleCallbacks, &mData)) {
       return;
     }
 
     MOZ_ASSERT(argumentsValue.isObject());
     JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
     MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
 
     uint32_t length;
@@ -489,17 +530,17 @@ private:
 
     mCallData->mGlobal = JS::CurrentGlobalOrNull(aCx);
     mConsole->ProcessCallData(mCallData);
   }
 
   ConsoleCallData* mCallData;
 
   JSAutoStructuredCloneBuffer mArguments;
-  nsTArray<nsString> mStrings;
+  ConsoleStructuredCloneData mData;
 };
 
 // This runnable calls ProfileMethod() on the console on the main-thread.
 class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable
 {
 public:
   ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
                          const Sequence<JS::Value>& aArguments)
@@ -534,31 +575,37 @@ private:
       arg = mArguments[i];
       if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
         return false;
       }
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
-    if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
+    if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mData)) {
       return false;
     }
 
     return true;
   }
 
   void
   RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
              nsPIDOMWindow* aInnerWindow) MOZ_OVERRIDE
   {
     ClearException ce(aCx);
 
+    // Now we could have the correct window (if we are not window-less).
+    mData.mParent = aInnerWindow;
+
     JS::Rooted<JS::Value> argumentsValue(aCx);
-    if (!mBuffer.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
+    bool ok = mBuffer.read(aCx, &argumentsValue, &gConsoleCallbacks, &mData);
+    mData.mParent = nullptr;
+
+    if (!ok) {
       return;
     }
 
     MOZ_ASSERT(argumentsValue.isObject());
     JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
     MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
 
     uint32_t length;
@@ -580,17 +627,17 @@ private:
 
     mConsole->ProfileMethod(aCx, mAction, arguments);
   }
 
   nsString mAction;
   Sequence<JS::Value> mArguments;
 
   JSAutoStructuredCloneBuffer mBuffer;
-  nsTArray<nsString> mStrings;
+  ConsoleStructuredCloneData mData;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
 
 // We don't need to traverse/unlink mStorage and mSanbox because they are not
 // CCed objects and they are only used on the main thread, even when this
 // Console object is used on workers.
 
--- a/dom/workers/test/mochitest.ini
+++ b/dom/workers/test/mochitest.ini
@@ -92,16 +92,17 @@ support-files =
   test_worker_interfaces.js
   test_worker_performance_now.js
   worker_driver.js
   worker_wrapper.js
   bug1060621_worker.js
   bug1062920_worker.js
   webSocket_sharedWorker.js
   bug1104064_worker.js
+  worker_consoleAndBlobs.js
 
 [test_404.html]
 [test_atob.html]
 [test_blobConstructor.html]
 [test_blobWorkers.html]
 [test_bug1002702.html]
 [test_bug949946.html]
 [test_bug1010784.html]
@@ -192,8 +193,9 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_websocket_loadgroup.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #bug 982828
 [test_bug1062920.html]
 [test_bug978260.html]
 [test_webSocket_sharedWorker.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #bug 982828
 [test_websocket_pref.html]
 [test_bug1104064.html]
+[test_consoleAndBlobs.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_consoleAndBlobs.html
@@ -0,0 +1,41 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Test for console API and blobs</title>
+    <script src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+  </head>
+  <body>
+    <script type="text/javascript">
+
+  function consoleListener() {
+    SpecialPowers.addObserver(this, "console-api-log-event", false);
+  }
+
+  var order = 0;
+  consoleListener.prototype  = {
+    observe: function(aSubject, aTopic, aData) {
+      ok(true, "Something has been received");
+      is(aTopic, "console-api-log-event");
+      SpecialPowers.removeObserver(this, "console-api-log-event");
+
+      var obj = aSubject.wrappedJSObject;
+      is(obj.arguments[0].size, 3, "The size is correct");
+      is(obj.arguments[0].type, 'foo/bar', "The type is correct");
+      SimpleTest.finish();
+    }
+  }
+
+  var cl = new consoleListener();
+
+  new Worker('worker_consoleAndBlobs.js');
+  SimpleTest.waitForExplicitFinish();
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/worker_consoleAndBlobs.js
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+var b = new Blob(['123'], { type: 'foo/bar'});
+console.log(b);