Bug 965860 - patch 7 - Console API in workers, r=khuey

This commit is contained in:
Andrea Marchesini 2014-02-27 23:39:30 +00:00
Родитель dc67186f99
Коммит f90b5edc71
13 изменённых файлов: 423 добавлений и 168 удалений

Просмотреть файл

@ -13,6 +13,9 @@
#include "nsGlobalWindow.h"
#include "nsJSUtils.h"
#include "nsPerformance.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "xpcprivate.h"
#include "nsIConsoleAPIStorage.h"
#include "nsIDOMWindowUtils.h"
@ -39,11 +42,96 @@
// This constant tells how many messages to process in a single timer execution.
#define MESSAGES_IN_INTERVAL 1500
// This tag is used in the Structured Clone Algorithm to move js values from
// worker thread to main thread
#define CONSOLE_TAG JS_SCTAG_USER_MIN
using namespace mozilla::dom::exceptions;
using namespace mozilla::dom::workers;
namespace mozilla {
namespace dom {
/**
* 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,
void* aClosure)
{
AssertIsOnMainThread();
if (aTag != CONSOLE_TAG) {
return nullptr;
}
nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
MOZ_ASSERT(strings->Length() <= aData);
JS::Rooted<JS::Value> value(aCx);
if (!xpc::StringToJsval(aCx, strings->ElementAt(aData), &value)) {
return nullptr;
}
JS::Rooted<JSObject*> obj(aCx);
if (!JS_ValueToObject(aCx, value, &obj)) {
return nullptr;
}
return obj;
}
// 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)
{
JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
if (!jsString) {
return false;
}
nsDependentJSString string;
if (!string.init(aCx, jsString)) {
return false;
}
nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG, strings->Length())) {
return false;
}
strings->AppendElement(string);
return true;
}
static void
ConsoleStructuredCloneCallbacksError(JSContext* /* aCx */,
uint32_t /* aErrorId */)
{
NS_WARNING("Failed to clone data for the Console API in workers.");
}
JSStructuredCloneCallbacks gConsoleCallbacks = {
ConsoleStructuredCloneCallbacksRead,
ConsoleStructuredCloneCallbacksWrite,
ConsoleStructuredCloneCallbacksError
};
class ConsoleCallData
{
public:
@ -98,6 +186,292 @@ private:
JSContext* mCx;
};
class ConsoleRunnable : public nsRunnable
{
public:
ConsoleRunnable()
: mWorkerPrivate(GetCurrentThreadWorkerPrivate())
{
MOZ_ASSERT(mWorkerPrivate);
}
virtual
~ConsoleRunnable()
{
}
bool
Dispatch()
{
mWorkerPrivate->AssertIsOnWorkerThread();
JSContext* cx = mWorkerPrivate->GetJSContext();
if (!PreDispatch(cx)) {
return false;
}
AutoSyncLoopHolder syncLoop(mWorkerPrivate);
mSyncLoopTarget = syncLoop.EventTarget();
if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
JS_ReportError(cx,
"Failed to dispatch to main thread for the Console API!");
return false;
}
return syncLoop.Run();
}
private:
NS_IMETHOD Run()
{
AssertIsOnMainThread();
RunConsole();
nsRefPtr<MainThreadStopSyncLoopRunnable> response =
new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
mSyncLoopTarget.forget(),
true);
if (!response->Dispatch(nullptr)) {
NS_WARNING("Failed to dispatch response!");
}
return NS_OK;
}
protected:
virtual bool
PreDispatch(JSContext* aCx) = 0;
virtual void
RunConsole() = 0;
WorkerPrivate* mWorkerPrivate;
private:
nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
};
// This runnable appends a CallData object into the Console queue running on
// the main-thread.
class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable
{
public:
ConsoleCallDataRunnable(const ConsoleCallData& aCallData)
: mCallData(aCallData)
{
}
private:
bool
PreDispatch(JSContext* aCx) MOZ_OVERRIDE
{
ClearException ce(aCx);
JSAutoCompartment ac(aCx, mCallData.mGlobal);
JS::Rooted<JSObject*> arguments(aCx,
JS_NewArrayObject(aCx, mCallData.mArguments.Length()));
if (!arguments) {
return false;
}
for (uint32_t i = 0; i < mCallData.mArguments.Length(); ++i) {
if (!JS_DefineElement(aCx, arguments, i, mCallData.mArguments[i],
nullptr, nullptr, JSPROP_ENUMERATE)) {
return false;
}
}
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
return false;
}
mCallData.mArguments.Clear();
mCallData.mGlobal = nullptr;
return true;
}
void
RunConsole() MOZ_OVERRIDE
{
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
AutoPushJSContext cx(wp->ParentJSContext());
ClearException ce(cx);
nsPIDOMWindow* window = wp->GetWindow();
NS_ENSURE_TRUE_VOID(window);
nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window);
NS_ENSURE_TRUE_VOID(win);
ErrorResult error;
nsRefPtr<Console> console = win->GetConsole(error);
if (error.Failed()) {
NS_WARNING("Failed to get console from the window.");
return;
}
JS::Rooted<JS::Value> argumentsValue(cx);
if (!mArguments.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
return;
}
MOZ_ASSERT(argumentsValue.isObject());
JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
uint32_t length;
if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
return;
}
for (uint32_t i = 0; i < length; ++i) {
JS::Rooted<JS::Value> value(cx);
if (!JS_GetElement(cx, argumentsObj, i, &value)) {
return;
}
mCallData.mArguments.AppendElement(value);
}
MOZ_ASSERT(mCallData.mArguments.Length() == length);
mCallData.mGlobal = JS::CurrentGlobalOrNull(cx);
console->AppendCallData(mCallData);
}
private:
ConsoleCallData mCallData;
JSAutoStructuredCloneBuffer mArguments;
nsTArray<nsString> mStrings;
};
// This runnable calls ProfileMethod() on the console on the main-thread.
class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable
{
public:
ConsoleProfileRunnable(const nsAString& aAction,
const Sequence<JS::Value>& aArguments)
: mAction(aAction)
, mArguments(aArguments)
{
}
private:
bool
PreDispatch(JSContext* aCx) MOZ_OVERRIDE
{
ClearException ce(aCx);
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
if (!global) {
return false;
}
JSAutoCompartment ac(aCx, global);
JS::Rooted<JSObject*> arguments(aCx,
JS_NewArrayObject(aCx, mArguments.Length()));
if (!arguments) {
return false;
}
for (uint32_t i = 0; i < mArguments.Length(); ++i) {
if (!JS_DefineElement(aCx, arguments, i, mArguments[i], nullptr, nullptr,
JSPROP_ENUMERATE)) {
return false;
}
}
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
return false;
}
return true;
}
void
RunConsole() MOZ_OVERRIDE
{
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
AutoPushJSContext cx(wp->ParentJSContext());
ClearException ce(cx);
JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
NS_ENSURE_TRUE_VOID(global);
JSAutoCompartment ac(cx, global);
nsPIDOMWindow* window = wp->GetWindow();
NS_ENSURE_TRUE_VOID(window);
nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window);
NS_ENSURE_TRUE_VOID(win);
ErrorResult error;
nsRefPtr<Console> console = win->GetConsole(error);
if (error.Failed()) {
NS_WARNING("Failed to get console from the window.");
return;
}
JS::Rooted<JS::Value> argumentsValue(cx);
if (!mBuffer.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
return;
}
MOZ_ASSERT(argumentsValue.isObject());
JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
uint32_t length;
if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
return;
}
Sequence<JS::Value> arguments;
for (uint32_t i = 0; i < length; ++i) {
JS::Rooted<JS::Value> value(cx);
if (!JS_GetElement(cx, argumentsObj, i, &value)) {
return;
}
arguments.AppendElement(value);
}
console->ProfileMethod(cx, mAction, arguments, error);
if (error.Failed()) {
NS_WARNING("Failed to call call profile() method to the ConsoleAPI.");
}
}
private:
nsString mAction;
Sequence<JS::Value> mArguments;
JSAutoStructuredCloneBuffer mBuffer;
nsTArray<nsString> mStrings;
};
NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
@ -285,6 +659,14 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
const Sequence<JS::Value>& aData,
ErrorResult& aRv)
{
if (!NS_IsMainThread()) {
// Here we are in a worker thread.
nsRefPtr<ConsoleProfileRunnable> runnable =
new ConsoleProfileRunnable(aAction, aData);
runnable->Dispatch();
return;
}
RootedDictionary<ConsoleProfileEvent> event(aCx);
event.mAction = aAction;
@ -471,6 +853,14 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
callData.mMonotonicTimer = performance->Now();
}
if (!NS_IsMainThread()) {
// Here we are in a worker thread.
nsRefPtr<ConsoleCallDataRunnable> runnable =
new ConsoleCallDataRunnable(callData);
runnable->Dispatch();
return;
}
// The operation is completed. RAII class has to be disabled.
raii.Finished();
@ -481,6 +871,18 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
}
}
void
Console::AppendCallData(const ConsoleCallData& aCallData)
{
mQueuedCalls.AppendElement(aCallData);
if (!mTimer) {
mTimer = do_CreateInstance("@mozilla.org/timer;1");
mTimer->InitWithCallback(this, CALL_DELAY,
nsITimer::TYPE_REPEATING_SLACK);
}
}
// Timer callback used to process each of the queued calls.
NS_IMETHODIMP
Console::Notify(nsITimer *timer)

Просмотреть файл

@ -127,6 +127,9 @@ private:
Method(JSContext* aCx, MethodName aName, const nsAString& aString,
const Sequence<JS::Value>& aData);
void
AppendCallData(const ConsoleCallData& aData);
void
ProcessCallData(ConsoleCallData& aData);
@ -190,6 +193,8 @@ private:
uint64_t mInnerID;
friend class ConsoleCallData;
friend class ConsoleCallDataRunnable;
friend class ConsoleProfileRunnable;
};
} // dom namespace

Просмотреть файл

@ -145,6 +145,7 @@ LOCAL_INCLUDES += [
'../src/geolocation',
'../src/storage',
'../time',
'../workers',
'../xbl',
'/content/base/src',
'/content/html/document/src',

Просмотреть файл

@ -1519,12 +1519,7 @@ DOMInterfaces = {
'implicitJSContext': [
'close', 'importScripts',
],
},
'WorkerConsole': {
'headerFile': 'mozilla/dom/workers/bindings/Console.h',
'workers': True,
'implicitJSContext': [ 'trace', 'time', 'timeEnd' ],
'binaryNames': { 'console': 'getConsole', },
},
'WorkerLocation': {

Просмотреть файл

@ -11775,7 +11775,7 @@ struct PrototypeTraits;
curr = CGWrapper(curr, post='\n')
headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h", "XPCWrapper.h"])
headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h"])
curr = CGHeaders([], [], [], [], headers, [], 'UnionConversions', curr)
# Add include guards.

Просмотреть файл

@ -1,35 +0,0 @@
/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
interface WorkerConsole {
void log(any... data);
void info(any... data);
void warn(any... data);
void error(any... data);
void _exception(any... data);
void debug(any... data);
void trace();
void dir(any data);
void group(any... data);
void groupCollapsed(any... data);
void groupEnd(any... data);
void time(any time);
void timeEnd(any time);
void profile(any... data);
void profileEnd(any... data);
void assert(boolean condition, any... data);
void ___noSuchMethod__();
};
// This dictionary is used internally to send the stack trace from the worker to
// the main thread Console API implementation.
dictionary WorkerConsoleStack {
DOMString filename = "";
unsigned long lineNumber = 0;
DOMString functionName = "";
unsigned long language = 0;
};

Просмотреть файл

@ -16,7 +16,7 @@ interface WorkerGlobalScope : EventTarget {
readonly attribute WorkerGlobalScope self;
[Replaceable]
readonly attribute WorkerConsole console;
readonly attribute Console console;
readonly attribute WorkerLocation location;

Просмотреть файл

@ -435,7 +435,6 @@ WEBIDL_FILES = [
'WheelEvent.webidl',
'WifiOptions.webidl',
'Worker.webidl',
'WorkerConsole.webidl',
'WorkerGlobalScope.webidl',
'WorkerLocation.webidl',
'WorkerNavigator.webidl',

Просмотреть файл

@ -1,112 +0,0 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* 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/. */
#ifndef mozilla_dom_workers_Console_h
#define mozilla_dom_workers_Console_h
#include "Workers.h"
#include "WorkerPrivate.h"
#include "nsWrapperCache.h"
BEGIN_WORKERS_NAMESPACE
class ConsoleProxy;
class ConsoleStackData;
class WorkerConsole MOZ_FINAL : public nsWrapperCache
{
WorkerConsole();
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WorkerConsole)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WorkerConsole)
static already_AddRefed<WorkerConsole>
Create();
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
nsISupports* GetParentObject() const
{
return nullptr;
}
~WorkerConsole();
ConsoleProxy*
GetProxy() const
{
return mProxy;
}
void
SetProxy(ConsoleProxy* aProxy);
// WebIDL methods
void
Log(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Info(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Warn(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Error(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Exception(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Debug(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Trace(JSContext* aCx);
void
Dir(JSContext* aCx, JS::Handle<JS::Value> aValue);
void
Group(JSContext* aCx, const Sequence<JS::Value>& aData);
void
GroupCollapsed(JSContext* aCx, const Sequence<JS::Value>& aData);
void
GroupEnd(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Time(JSContext* aCx, JS::Handle<JS::Value> aTimer);
void
TimeEnd(JSContext* aCx, JS::Handle<JS::Value> aTimer);
void
Profile(JSContext* aCx, const Sequence<JS::Value>& aData);
void
ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData);
void
Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
void
__noSuchMethod__();
private:
void
Method(JSContext* aCx, const char* aMethodName,
const Sequence<JS::Value>& aData, uint32_t aMaxStackDepth);
nsRefPtr<ConsoleProxy> mProxy;
};
END_WORKERS_NAMESPACE
#endif // mozilla_dom_workers_Console_h

Просмотреть файл

@ -11,6 +11,7 @@
#include "jsapi.h"
#include "js/OldDebugAPI.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ConsoleBinding.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/EventBinding.h"
#include "mozilla/dom/EventHandlerBinding.h"
@ -58,7 +59,8 @@ WorkerPrivate::RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
}
// Init other paris-bindings.
if (!DOMExceptionBinding::GetConstructorObject(aCx, aGlobal) ||
if (!ConsoleBinding::GetConstructorObject(aCx, aGlobal) ||
!DOMExceptionBinding::GetConstructorObject(aCx, aGlobal) ||
!EventBinding::GetConstructorObject(aCx, aGlobal) ||
!FileReaderSyncBinding_workers::GetConstructorObject(aCx, aGlobal) ||
!ImageDataBinding::GetConstructorObject(aCx, aGlobal) ||

Просмотреть файл

@ -10,12 +10,12 @@
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h"
#include "mozilla/dom/SharedWorkerGlobalScopeBinding.h"
#include "mozilla/dom/Console.h"
#ifdef ANDROID
#include <android/log.h>
#endif
#include "Console.h"
#include "Location.h"
#include "Navigator.h"
#include "Principal.h"
@ -77,17 +77,17 @@ WorkerGlobalScope::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
MOZ_CRASH("We should never get here!");
}
WorkerConsole*
WorkerGlobalScope::Console()
already_AddRefed<Console>
WorkerGlobalScope::GetConsole()
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (!mConsole) {
mConsole = WorkerConsole::Create();
mConsole = new Console(nullptr);
MOZ_ASSERT(mConsole);
}
return mConsole;
return mConsole.forget();
}
already_AddRefed<WorkerLocation>

Просмотреть файл

@ -12,6 +12,7 @@
namespace mozilla {
namespace dom {
class Console;
class Function;
} // namespace dom
@ -20,14 +21,13 @@ class Function;
BEGIN_WORKERS_NAMESPACE
class WorkerPrivate;
class WorkerConsole;
class WorkerLocation;
class WorkerNavigator;
class WorkerGlobalScope : public nsDOMEventTargetHelper,
public nsIGlobalObject
{
nsRefPtr<WorkerConsole> mConsole;
nsRefPtr<Console> mConsole;
nsRefPtr<WorkerLocation> mLocation;
nsRefPtr<WorkerNavigator> mNavigator;
@ -60,8 +60,8 @@ public:
return nsRefPtr<WorkerGlobalScope>(this).forget();
}
WorkerConsole*
Console();
already_AddRefed<Console>
GetConsole();
already_AddRefed<WorkerLocation>
Location();

Просмотреть файл

@ -19,7 +19,6 @@ EXPORTS.mozilla.dom.workers += [
# Stuff needed for the bindings, not really public though.
EXPORTS.mozilla.dom.workers.bindings += [
'Console.h',
'FileReaderSync.h',
'Location.h',
'MessagePort.h',
@ -33,7 +32,6 @@ EXPORTS.mozilla.dom.workers.bindings += [
SOURCES += [
'ChromeWorkerScope.cpp',
'Console.cpp',
'File.cpp',
'FileReaderSync.cpp',
'Location.cpp',