diff --git a/browser/extensions/pdfjs/content/build/pdf.worker.js b/browser/extensions/pdfjs/content/build/pdf.worker.js index d6628609ff9..440d33a2ed4 100644 --- a/browser/extensions/pdfjs/content/build/pdf.worker.js +++ b/browser/extensions/pdfjs/content/build/pdf.worker.js @@ -34710,41 +34710,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { var consoleTimer = {}; -var workerConsole = { - log: function log() { - var args = Array.prototype.slice.call(arguments); - globalScope.postMessage({ - action: 'console_log', - data: args - }); - }, - - error: function error() { - var args = Array.prototype.slice.call(arguments); - globalScope.postMessage({ - action: 'console_error', - data: args - }); - throw 'pdf.js execution error'; - }, - - time: function time(name) { - consoleTimer[name] = Date.now(); - }, - - timeEnd: function timeEnd(name) { - var time = consoleTimer[name]; - if (!time) { - error('Unkown timer name ' + name); - } - this.log('Timer:', name, Date.now() - time); - } -}; - // Worker thread? if (typeof window === 'undefined') { - globalScope.console = workerConsole; - // Add a logger so we can pass warnings on to the main thread, errors will // throw an exception which will be forwarded on automatically. PDFJS.LogManager.addLogger({ diff --git a/dom/base/ConsoleAPI.js b/dom/base/ConsoleAPI.js index 3e1c1b1ba80..421ef354463 100644 --- a/dom/base/ConsoleAPI.js +++ b/dom/base/ConsoleAPI.js @@ -214,19 +214,22 @@ ConsoleAPI.prototype = { /** * Queue a call to a console method. See the CALL_DELAY constant. + * This method is the entry point for the console.* for workers. * * @param string aMethod * The console method the code has invoked. * @param object aArguments * The arguments passed to the console method. + * @param array aStack + * The stack of the console method. Used by console.* for workers. */ - queueCall: function CA_queueCall(aMethod, aArguments) + queueCall: function CA_queueCall(aMethod, aArguments, aStack = null) { let window = this._window.get(); let metaForCall = { private: PrivateBrowsingUtils.isWindowPrivate(window), timeStamp: Date.now(), - stack: this.getStackTrace(aMethod != "trace" ? 1 : null), + stack: (aStack ? aStack : this.getStackTrace(aMethod != "trace" ? 1 : null)), }; if (aMethod == "time" || aMethod == "timeEnd") { diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 48b15dae4de..9de1d3990c9 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -1528,6 +1528,12 @@ DOMInterfaces = { ], }, +'WorkerConsole': { + 'headerFile': 'mozilla/dom/workers/bindings/Console.h', + 'workers': True, + 'implicitJSContext': [ 'trace', 'time', 'timeEnd' ], +}, + 'WorkerLocation': { 'headerFile': 'mozilla/dom/workers/bindings/Location.h', 'workers': True, diff --git a/dom/bindings/Exceptions.cpp b/dom/bindings/Exceptions.cpp index 53d5a4d76aa..c080c286524 100644 --- a/dom/bindings/Exceptions.cpp +++ b/dom/bindings/Exceptions.cpp @@ -281,7 +281,7 @@ public: virtual ~JSStackFrame(); static already_AddRefed - CreateStack(JSContext* cx); + CreateStack(JSContext* aCx, int32_t aMaxDepth = -1); static already_AddRefed CreateStackFrameLocation(uint32_t aLanguage, const char* aFilename, @@ -495,11 +495,14 @@ NS_IMETHODIMP JSStackFrame::ToString(nsACString& _retval) } /* static */ already_AddRefed -JSStackFrame::CreateStack(JSContext* cx) +JSStackFrame::CreateStack(JSContext* aCx, int32_t aMaxDepth) { static const unsigned MAX_FRAMES = 100; + if (aMaxDepth < 0) { + aMaxDepth = MAX_FRAMES; + } - JS::StackDescription* desc = JS::DescribeStack(cx, MAX_FRAMES); + JS::StackDescription* desc = JS::DescribeStack(aCx, aMaxDepth); if (!desc) { return nullptr; } @@ -530,9 +533,9 @@ JSStackFrame::CreateStackFrameLocation(uint32_t aLanguage, } already_AddRefed -CreateStack(JSContext* cx) +CreateStack(JSContext* aCx, int32_t aMaxDepth) { - return JSStackFrame::CreateStack(cx); + return JSStackFrame::CreateStack(aCx, aMaxDepth); } already_AddRefed diff --git a/dom/bindings/Exceptions.h b/dom/bindings/Exceptions.h index 55c17103e80..cd24d784b62 100644 --- a/dom/bindings/Exceptions.h +++ b/dom/bindings/Exceptions.h @@ -36,8 +36,10 @@ GetCurrentJSStack(); // Internal stuff not intended to be widely used. namespace exceptions { +// aMaxDepth can be used to define a maximal depth for the stack trace. If the +// value is -1, a default maximal depth will be selected. already_AddRefed -CreateStack(JSContext* cx); +CreateStack(JSContext* aCx, int32_t aMaxDepth = -1); already_AddRefed CreateStackFrameLocation(uint32_t aLanguage, diff --git a/dom/webidl/WorkerConsole.webidl b/dom/webidl/WorkerConsole.webidl new file mode 100644 index 00000000000..0b517cb197e --- /dev/null +++ b/dom/webidl/WorkerConsole.webidl @@ -0,0 +1,35 @@ +/* -*- 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(optional any data); + void group(any... data); + void groupCollapsed(any... data); + void groupEnd(any... data); + void time(optional any time); + void timeEnd(optional 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; +}; + diff --git a/dom/webidl/WorkerGlobalScope.webidl b/dom/webidl/WorkerGlobalScope.webidl index f967091fb30..7873d3096fd 100644 --- a/dom/webidl/WorkerGlobalScope.webidl +++ b/dom/webidl/WorkerGlobalScope.webidl @@ -14,6 +14,7 @@ interface WorkerGlobalScope : EventTarget { readonly attribute WorkerGlobalScope self; + readonly attribute WorkerConsole console; readonly attribute WorkerLocation location; void close(); diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 30cd8040dfe..13512539bc4 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -428,6 +428,7 @@ WEBIDL_FILES = [ 'WheelEvent.webidl', 'WifiOptions.webidl', 'Worker.webidl', + 'WorkerConsole.webidl', 'WorkerGlobalScope.webidl', 'WorkerLocation.webidl', 'WorkerNavigator.webidl', diff --git a/dom/workers/Console.cpp b/dom/workers/Console.cpp new file mode 100644 index 00000000000..11c7c3a1488 --- /dev/null +++ b/dom/workers/Console.cpp @@ -0,0 +1,561 @@ +/* -*- 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/. */ + +#include "Console.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/OldDebugAPI.h" + +#include "nsJSUtils.h" +#include "WorkerRunnable.h" +#include "nsComponentManagerUtils.h" +#include "nsIDOMGlobalPropertyInitializer.h" + +#include "mozilla/dom/WorkerConsoleBinding.h" +#include "mozilla/dom/Exceptions.h" + +#define CONSOLE_TAG JS_SCTAG_USER_MIN + +// From dom/base/ConsoleAPI.js +#define DEFAULT_MAX_STACKTRACE_DEPTH 200 + +using namespace mozilla::dom::exceptions; + +BEGIN_WORKERS_NAMESPACE + +class ConsoleProxy +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLProxy) + + bool + Init(JSContext* aCx, nsPIDOMWindow* aWindow) + { + AssertIsOnMainThread(); + + // Console API: + nsCOMPtr cInstance = + do_CreateInstance("@mozilla.org/console-api;1"); + + nsCOMPtr gpi = + do_QueryInterface(cInstance); + NS_ENSURE_TRUE(gpi, false); + + // We don't do anything with the return value. + JS::Rooted prop_val(aCx); + if (NS_FAILED(gpi->Init(aWindow, &prop_val))) { + return false; + } + + mXpcwrappedjs = do_QueryInterface(cInstance); + NS_ENSURE_TRUE(mXpcwrappedjs, false); + + return true; + } + + nsIXPConnectWrappedJS* + GetWrappedJS() const + { + AssertIsOnMainThread(); + return mXpcwrappedjs; + } + + void ReleaseWrappedJS() + { + AssertIsOnMainThread(); + mXpcwrappedjs = nullptr; + } + +private: + nsCOMPtr mXpcwrappedjs; +}; + +/** + * 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* strings = static_cast*>(aClosure); + if (strings->Length() <= aData) { + return nullptr; + } + + JS::Rooted value(aCx); + if (!xpc::StringToJsval(aCx, strings->ElementAt(aData), &value)) { + return nullptr; + } + + JS::Rooted 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 aObj, + void* aClosure) +{ + JS::Rooted value(aCx, JS::ObjectOrNullValue(aObj)); + JS::Rooted jsString(aCx, JS::ToString(aCx, value)); + if (!jsString) { + return false; + } + + nsDependentJSString string; + if (!string.init(aCx, jsString)) { + return false; + } + + nsTArray* strings = static_cast*>(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 ConsoleStackData +{ +public: + ConsoleStackData() + : mLineNumber(0) + {} + + nsCString mFilename; + uint32_t mLineNumber; + nsCString mFunctionName; +}; + +class ConsoleRunnable MOZ_FINAL : public nsRunnable +{ +public: + explicit ConsoleRunnable(WorkerConsole* aConsole, + WorkerPrivate* aWorkerPrivate) + : mConsole(aConsole) + , mWorkerPrivate(aWorkerPrivate) + , mMethod(nullptr) + { + mWorkerPrivate->AssertIsOnWorkerThread(); + } + + bool + Dispatch(JSContext* aCx, + const char* aMethod, + JS::Handle aArguments, + nsTArray& aStackData) + { + mMethod = aMethod; + mStackData.SwapElements(aStackData); + + if (!mArguments.write(aCx, aArguments, &gConsoleCallbacks, &mStrings)) { + JS_ClearPendingException(aCx); + return false; + } + + mWorkerPrivate->AssertIsOnWorkerThread(); + + AutoSyncLoopHolder syncLoop(mWorkerPrivate); + + mSyncLoopTarget = syncLoop.EventTarget(); + + if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { + JS_ReportError(aCx, + "Failed to dispatch to main thread for the " + "Console API (method %s)!", mMethod); + return false; + } + + return syncLoop.Run(); + } + +private: + NS_IMETHOD Run() + { + AssertIsOnMainThread(); + + RunConsole(); + + nsRefPtr response = + new MainThreadStopSyncLoopRunnable(mWorkerPrivate, + mSyncLoopTarget.forget(), + true); + if (!response->Dispatch(nullptr)) { + NS_WARNING("Failed to dispatch response!"); + } + + return NS_OK; + } + + void + RunConsole() + { + // This class is used to clear any exception at the end of this method. + class ClearException + { + public: + ClearException(JSContext* aCx) + : mCx(aCx) + { + } + + ~ClearException() + { + JS_ClearPendingException(mCx); + } + + private: + JSContext* mCx; + }; + + // Walk up to our containing page + WorkerPrivate* wp = mWorkerPrivate; + while (wp->GetParent()) { + wp = wp->GetParent(); + } + + AutoPushJSContext cx(wp->ParentJSContext()); + JSAutoRequest ar(cx); + ClearException ce(cx); + + nsRefPtr proxy = mConsole->GetProxy(); + if (!proxy) { + nsPIDOMWindow* window = wp->GetWindow(); + NS_ENSURE_TRUE_VOID(window); + + proxy = new ConsoleProxy(); + if (!proxy->Init(cx, window)) { + return; + } + + mConsole->SetProxy(proxy); + } + + JS::Rooted consoleObj(cx, proxy->GetWrappedJS()->GetJSObject()); + NS_ENSURE_TRUE_VOID(consoleObj); + + JSAutoCompartment ac(cx, consoleObj); + + // 3 args for the queueCall. + nsDependentCString method(mMethod); + + JS::Rooted methodValue(cx); + if (!ByteStringToJsval(cx, method, &methodValue)) { + return; + } + + JS::Rooted argumentsValue(cx); + if (!mArguments.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) { + return; + } + + JS::Rooted stackValue(cx); + { + JS::Rooted stackObj(cx, + JS_NewArrayObject(cx, mStackData.Length(), nullptr)); + if (!stackObj) { + return; + } + + for (uint32_t i = 0; i < mStackData.Length(); ++i) { + WorkerConsoleStack stack; + + CopyUTF8toUTF16(mStackData[i].mFilename, stack.mFilename); + + CopyUTF8toUTF16(mStackData[i].mFunctionName, stack.mFunctionName); + + stack.mLineNumber = mStackData[i].mLineNumber; + + stack.mLanguage = nsIProgrammingLanguage::JAVASCRIPT; + + JS::Rooted value(cx); + if (!stack.ToObject(cx, JS::NullPtr(), &value)) { + return; + } + + if (!JS_DefineElement(cx, stackObj, i, value, nullptr, nullptr, 0)) { + return; + } + } + + stackValue = JS::ObjectValue(*stackObj); + } + + JS::AutoValueVector argv(cx); + if (!argv.resize(3)) { + return; + } + + argv[0] = methodValue; + argv[1] = argumentsValue; + argv[2] = stackValue; + + JS::Rooted ret(cx); + JS_CallFunctionName(cx, consoleObj, "queueCall", argv.length(), + argv.begin(), ret.address()); + } + + WorkerConsole* mConsole; + WorkerPrivate* mWorkerPrivate; + nsCOMPtr mSyncLoopTarget; + + const char* mMethod; + JSAutoStructuredCloneBuffer mArguments; + nsTArray mStackData; + + nsTArray mStrings; +}; + +class TeardownRunnable : public nsRunnable +{ +public: + TeardownRunnable(ConsoleProxy* aProxy) + : mProxy(aProxy) + { + } + + NS_IMETHOD Run() + { + AssertIsOnMainThread(); + + mProxy->ReleaseWrappedJS(); + mProxy = nullptr; + + return NS_OK; + } + +private: + nsRefPtr mProxy; +}; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerConsole) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WorkerConsole, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WorkerConsole, Release) + +/* static */ already_AddRefed +WorkerConsole::Create() +{ + nsRefPtr console = new WorkerConsole(); + return console.forget(); +} + +JSObject* +WorkerConsole::WrapObject(JSContext* aCx, JS::Handle aScope) +{ + return WorkerConsoleBinding_workers::Wrap(aCx, aScope, this); +} + +WorkerConsole::WorkerConsole() +{ + MOZ_COUNT_CTOR(WorkerConsole); + SetIsDOMBinding(); +} + +WorkerConsole::~WorkerConsole() +{ + MOZ_COUNT_DTOR(WorkerConsole); + + if (mProxy) { + nsRefPtr runnable = new TeardownRunnable(mProxy); + mProxy = nullptr; + + if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_ERROR("Failed to dispatch teardown runnable!"); + } + } +} + +void +WorkerConsole::SetProxy(ConsoleProxy* aProxy) +{ + MOZ_ASSERT(!mProxy); + mProxy = aProxy; +} + +void +WorkerConsole::Method(JSContext* aCx, const char* aMethodName, + const Sequence& aData, + uint32_t aStackLevel) +{ + nsCOMPtr stack = CreateStack(aCx, aStackLevel); + if (!stack) { + return; + } + + // nsIStackFrame is not thread-safe so we take what we need and we store in + // an array of ConsoleStackData objects. + nsTArray stackData; + while (stack) { + ConsoleStackData& data = *stackData.AppendElement(); + + if (NS_FAILED(stack->GetFilename(data.mFilename))) { + return; + } + + int32_t lineNumber; + if (NS_FAILED(stack->GetLineNumber(&lineNumber))) { + return; + } + + data.mLineNumber = lineNumber; + + if (NS_FAILED(stack->GetName(data.mFunctionName))) { + return; + } + + nsCOMPtr caller; + if (NS_FAILED(stack->GetCaller(getter_AddRefs(caller)))) { + return; + } + + stack.swap(caller); + } + + JS::Rooted arguments(aCx, + JS_NewArrayObject(aCx, aData.Length(), nullptr)); + if (!arguments) { + return; + } + + for (uint32_t i = 0; i < aData.Length(); ++i) { + if (!JS_DefineElement(aCx, arguments, i, aData[i], nullptr, nullptr, + JSPROP_ENUMERATE)) { + return; + } + } + JS::Rooted argumentsValue(aCx, JS::ObjectValue(*arguments)); + + WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + MOZ_ASSERT(worker); + + nsRefPtr runnable = new ConsoleRunnable(this, worker); + runnable->Dispatch(aCx, aMethodName, argumentsValue, stackData); +} + +#define METHOD(name, jsName) \ + void \ + WorkerConsole::name(JSContext* aCx, \ + const Sequence& aData) \ + { \ + Method(aCx, jsName, aData, 1); \ + } + +METHOD(Log, "log") +METHOD(Info, "info") +METHOD(Warn, "warn") +METHOD(Error, "error") +METHOD(Exception, "exception") +METHOD(Debug, "debug") + +void +WorkerConsole::Trace(JSContext* aCx) +{ + Sequence data; + Method(aCx, "trace", data, DEFAULT_MAX_STACKTRACE_DEPTH); +} + +void +WorkerConsole::Dir(JSContext* aCx, + const Optional>& aValue) +{ + Sequence data; + + if (aValue.WasPassed()) { + data.AppendElement(aValue.Value()); + } + + Method(aCx, "dir", data, 1); +} + +METHOD(Group, "group") +METHOD(GroupCollapsed, "groupCollapsed") +METHOD(GroupEnd, "groupEnd") + +void +WorkerConsole::Time(JSContext* aCx, + const Optional>& aTimer) +{ + Sequence data; + + if (aTimer.WasPassed()) { + data.AppendElement(aTimer.Value()); + } + + Method(aCx, "time", data, 1); +} + +void +WorkerConsole::TimeEnd(JSContext* aCx, + const Optional>& aTimer) +{ + Sequence data; + + if (aTimer.WasPassed()) { + data.AppendElement(aTimer.Value()); + } + + Method(aCx, "timeEnd", data, 1); +} + +METHOD(Profile, "profile") +METHOD(ProfileEnd, "profileEnd") + +void +WorkerConsole::Assert(JSContext* aCx, bool aCondition, + const Sequence& aData) +{ + if (!aCondition) { + Method(aCx, "assert", aData, 1); + } +} + +void +WorkerConsole::__noSuchMethod__() +{ + // Nothing to do. +} + +#undef METHOD + +END_WORKERS_NAMESPACE diff --git a/dom/workers/Console.h b/dom/workers/Console.h new file mode 100644 index 00000000000..ade635caeaa --- /dev/null +++ b/dom/workers/Console.h @@ -0,0 +1,112 @@ +/* -*- 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 + Create(); + + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle 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& aData); + + void + Info(JSContext* aCx, const Sequence& aData); + + void + Warn(JSContext* aCx, const Sequence& aData); + + void + Error(JSContext* aCx, const Sequence& aData); + + void + Exception(JSContext* aCx, const Sequence& aData); + + void + Debug(JSContext* aCx, const Sequence& aData); + + void + Trace(JSContext* aCx); + + void + Dir(JSContext* aCx, const Optional>& aValue); + + void + Group(JSContext* aCx, const Sequence& aData); + + void + GroupCollapsed(JSContext* aCx, const Sequence& aData); + + void + GroupEnd(JSContext* aCx, const Sequence& aData); + + void + Time(JSContext* aCx, const Optional>& aTimer); + + void + TimeEnd(JSContext* aCx, const Optional>& aTimer); + + void + Profile(JSContext* aCx, const Sequence& aData); + + void + ProfileEnd(JSContext* aCx, const Sequence& aData); + + void + Assert(JSContext* aCx, bool aCondition, const Sequence& aData); + + void + __noSuchMethod__(); + +private: + void + Method(JSContext* aCx, const char* aMethodName, + const Sequence& aData, uint32_t aMaxStackDepth); + + nsRefPtr mProxy; +}; + +END_WORKERS_NAMESPACE + +#endif // mozilla_dom_workers_Console_h diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 0dd7c370c01..84ce2744e32 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -15,6 +15,7 @@ #include #endif +#include "Console.h" #include "Location.h" #include "Navigator.h" #include "Principal.h" @@ -77,6 +78,19 @@ WorkerGlobalScope::WrapObject(JSContext* aCx, JS::Handle aScope) MOZ_CRASH("We should never get here!"); } +WorkerConsole* +WorkerGlobalScope::Console() +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + if (!mConsole) { + mConsole = WorkerConsole::Create(); + MOZ_ASSERT(mConsole); + } + + return mConsole; +} + already_AddRefed WorkerGlobalScope::Location() { diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 0b2f94122bc..fc55c4adcff 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -20,12 +20,14 @@ class Function; BEGIN_WORKERS_NAMESPACE class WorkerPrivate; +class WorkerConsole; class WorkerLocation; class WorkerNavigator; class WorkerGlobalScope : public nsDOMEventTargetHelper, public nsIGlobalObject { + nsRefPtr mConsole; nsRefPtr mLocation; nsRefPtr mNavigator; @@ -58,6 +60,9 @@ public: return nsRefPtr(this).forget(); } + WorkerConsole* + Console(); + already_AddRefed Location(); diff --git a/dom/workers/moz.build b/dom/workers/moz.build index ee9acee1531..542a96008bf 100644 --- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -19,6 +19,7 @@ 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', @@ -32,6 +33,7 @@ EXPORTS.mozilla.dom.workers.bindings += [ SOURCES += [ 'ChromeWorkerScope.cpp', + 'Console.cpp', 'File.cpp', 'FileReaderSync.cpp', 'Location.cpp', diff --git a/dom/workers/test/console_worker.js b/dom/workers/test/console_worker.js new file mode 100644 index 00000000000..1ef9211251c --- /dev/null +++ b/dom/workers/test/console_worker.js @@ -0,0 +1,104 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +onmessage = function(event) { + // TEST: does console exist? + postMessage({event: 'console exists', status: !!console, last : false}); + + postMessage({event: 'trace without function', status: true, last : false}); + + for (var i = 0; i < 10; ++i) { + console.what('1', 123, 321); + } + + for (var i = 0; i < 10; ++i) { + console.log(i, i, i); + } + + function trace1() { + function trace2() { + function trace3() { + console.trace("trace " + i); + } + trace3(); + } + trace2(); + } + trace1(); + + foobar585956c = function(a) { + console.trace(); + return a+"c"; + }; + + function foobar585956b(a) { + return foobar585956c(a+"b"); + } + + function foobar585956a(omg) { + return foobar585956b(omg + "a"); + } + + function foobar646025(omg) { + console.log(omg, "o", "d"); + } + + function startTimer(timer) { + console.time(timer); + } + + function stopTimer(timer) { + console.timeEnd(timer); + } + + function testGroups() { + console.groupCollapsed("a", "group"); + console.group("b", "group"); + console.groupEnd("b", "group"); + } + + foobar585956a('omg'); + foobar646025('omg'); + testGroups(); + startTimer('foo'); + setTimeout(function() { + stopTimer('foo'); + nextSteps(event); + }, 10); +} + +function nextSteps(event) { + + function namelessTimer() { + console.time(); + console.timeEnd(); + } + + namelessTimer(); + + var str = "Test Message." + console.foobar(str); // if this throws, we don't execute following funcs + console.log(str); + console.info(str); + console.warn(str); + console.error(str); + console.exception(str); + console.assert(true, str); + console.assert(false, str); + console.profile(str); + console.profileEnd(str); + postMessage({event: '4 messages', status: true, last : false}); + + // Recursive: + if (event.data == true) { + var worker = new Worker('console_worker.js'); + worker.onmessage = function(event) { + postMessage(event.data); + } + worker.postMessage(false); + } else { + postMessage({event: 'bye bye', status: true, last : true}); + } +} diff --git a/dom/workers/test/mochitest.ini b/dom/workers/test/mochitest.ini index e1117468590..9116f7a8271 100644 --- a/dom/workers/test/mochitest.ini +++ b/dom/workers/test/mochitest.ini @@ -7,6 +7,7 @@ support-files = closeOnGC_worker.js close_worker.js content_worker.js + console_worker.js csp_worker.js errorPropagation_iframe.html errorPropagation_worker.js @@ -20,6 +21,7 @@ support-files = importScripts_worker_imported4.js instanceof_worker.js json_worker.js + jsversion_worker.js loadEncoding_worker.js location_worker.js longThread_worker.js @@ -59,7 +61,6 @@ support-files = xhr_implicit_cancel_worker.js xhr_worker.js url_exceptions_worker.js - jsversion_worker.js urlSearchParams_worker.js subdir/relativeLoad_sub_worker.js subdir/relativeLoad_sub_worker2.js @@ -73,6 +74,7 @@ support-files = [test_clearTimeouts.html] [test_close.html] [test_closeOnGC.html] +[test_console.html] [test_contentWorker.html] [test_csp.html] [test_csp.html^headers^] @@ -85,6 +87,7 @@ support-files = [test_importScripts.html] [test_instanceof.html] [test_json.html] +[test_jsversion.html] [test_loadEncoding.html] [test_loadError.html] [test_location.html] @@ -125,4 +128,3 @@ support-files = skip-if = (os == "win") || (os == "mac") [test_url_exceptions.html] [test_urlSearchParams.html] -[test_jsversion.html] diff --git a/dom/workers/test/test_console.html b/dom/workers/test/test_console.html new file mode 100644 index 00000000000..af82a7de08a --- /dev/null +++ b/dom/workers/test/test_console.html @@ -0,0 +1,44 @@ + + + + + + Test for DOM Worker Console + + + + +

+ +
+
+
+ +