Bug 1058644 - Console API in ServiceWorkers. r=khuey

This commit is contained in:
Andrea Marchesini 2015-01-06 10:45:00 -05:00
parent 340c51ec0d
commit 1db122563c
8 changed files with 422 additions and 266 deletions

View File

@ -29,6 +29,7 @@
#include "nsIServiceManager.h"
#include "nsISupportsPrimitives.h"
#include "nsIWebNavigation.h"
#include "nsIXPConnect.h"
// The maximum allowed number of concurrent timers per page.
#define MAX_PAGE_TIMERS 10000
@ -40,13 +41,6 @@
// console.trace().
#define DEFAULT_MAX_STACKTRACE_DEPTH 200
// The console API methods are async and their action is executed later. This
// delay tells how much later.
#define CALL_DELAY 15 // milliseconds
// 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
@ -137,22 +131,17 @@ static const JSStructuredCloneCallbacks gConsoleCallbacks = {
ConsoleStructuredCloneCallbacksError
};
class ConsoleCallData MOZ_FINAL : public LinkedListElement<ConsoleCallData>
class ConsoleCallData MOZ_FINAL
{
public:
ConsoleCallData()
: mMethodName(Console::MethodLog)
, mPrivate(false)
, mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
, mMonotonicTimer(0)
{
MOZ_COUNT_CTOR(ConsoleCallData);
}
~ConsoleCallData()
{
MOZ_COUNT_DTOR(ConsoleCallData);
}
, mIDType(eUnknown)
, mOuterIDNumber(0)
, mInnerIDNumber(0)
{ }
void
Initialize(JSContext* aCx, Console::MethodName aName,
@ -167,6 +156,33 @@ public:
}
}
void
SetIDs(uint64_t aOuterID, uint64_t aInnerID)
{
MOZ_ASSERT(mIDType == eUnknown);
mOuterIDNumber = aOuterID;
mInnerIDNumber = aInnerID;
mIDType = eNumber;
}
void
SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
{
MOZ_ASSERT(mIDType == eUnknown);
mOuterIDString = aOuterID;
mInnerIDString = aInnerID;
mIDType = eString;
}
void
CleanupJSObjects()
{
mArguments.Clear();
mGlobal = nullptr;
}
JS::Heap<JSObject*> mGlobal;
Console::MethodName mMethodName;
@ -174,6 +190,24 @@ public:
int64_t mTimeStamp;
DOMHighResTimeStamp mMonotonicTimer;
// The concept of outerID and innerID is misleading because when a
// ConsoleCallData is created from a window, these are the window IDs, but
// when the object is created from a SharedWorker, a ServiceWorker or a
// subworker of a ChromeWorker these IDs are the type of worker and the
// filename of the callee.
// In Console.jsm the ID is 'jsm'.
enum {
eString,
eNumber,
eUnknown
} mIDType;
uint64_t mOuterIDNumber;
nsString mOuterIDString;
uint64_t mInnerIDNumber;
nsString mInnerIDString;
nsString mMethodString;
nsTArray<JS::Heap<JS::Value>> mArguments;
@ -209,8 +243,9 @@ private:
class ConsoleRunnable : public nsRunnable
{
public:
ConsoleRunnable()
ConsoleRunnable(Console* aConsole)
: mWorkerPrivate(GetCurrentThreadWorkerPrivate())
, mConsole(aConsole)
{
MOZ_ASSERT(mWorkerPrivate);
}
@ -248,7 +283,18 @@ private:
{
AssertIsOnMainThread();
RunConsole();
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
nsPIDOMWindow* window = wp->GetWindow();
if (!window) {
RunWindowless();
} else {
RunWithWindow(window);
}
nsRefPtr<MainThreadStopSyncLoopRunnable> response =
new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
@ -261,15 +307,70 @@ private:
return NS_OK;
}
void
RunWithWindow(nsPIDOMWindow* aWindow)
{
AutoJSAPI jsapi;
MOZ_ASSERT(aWindow);
nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(aWindow);
if (NS_WARN_IF(!jsapi.Init(win))) {
return;
}
MOZ_ASSERT(aWindow->IsInnerWindow());
nsPIDOMWindow* outerWindow = aWindow->GetOuterWindow();
MOZ_ASSERT(outerWindow);
RunConsole(jsapi.cx(), outerWindow, aWindow);
}
void
RunWindowless()
{
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
MOZ_ASSERT(!wp->GetWindow());
AutoSafeJSContext cx;
nsCOMPtr<nsIXPConnectJSObjectHolder> sandbox =
mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal());
if (NS_WARN_IF(!sandbox)) {
return;
}
JS::Rooted<JSObject*> global(cx, sandbox->GetJSObject());
if (NS_WARN_IF(!global)) {
return;
}
// The CreateSandbox call returns a proxy to the actual sandbox object. We
// don't need a proxy here.
global = js::UncheckedUnwrap(global);
JSAutoCompartment ac(cx, global);
RunConsole(cx, nullptr, nullptr);
}
protected:
virtual bool
PreDispatch(JSContext* aCx) = 0;
virtual void
RunConsole() = 0;
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
nsPIDOMWindow* aInnerWindow) = 0;
WorkerPrivate* mWorkerPrivate;
// Raw pointer because this method is async and this object is kept alive by
// the caller.
Console* mConsole;
private:
nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
};
@ -279,15 +380,21 @@ private:
class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable
{
public:
explicit ConsoleCallDataRunnable(ConsoleCallData* aCallData)
: mCallData(aCallData)
{
}
ConsoleCallDataRunnable(Console* aConsole,
ConsoleCallData* aCallData)
: ConsoleRunnable(aConsole)
, mCallData(aCallData)
{ }
private:
~ConsoleCallDataRunnable()
{ }
bool
PreDispatch(JSContext* aCx) MOZ_OVERRIDE
{
mWorkerPrivate->AssertIsOnWorkerThread();
ClearException ce(aCx);
JSAutoCompartment ac(aCx, mCallData->mGlobal);
@ -311,58 +418,67 @@ private:
return false;
}
mCallData->mArguments.Clear();
mCallData->mGlobal = nullptr;
mCallData->CleanupJSObjects();
return true;
}
void
RunConsole() MOZ_OVERRIDE
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
nsPIDOMWindow* aInnerWindow) MOZ_OVERRIDE
{
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
MOZ_ASSERT(NS_IsMainThread());
// The windows have to run in parallel.
MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
if (aOuterWindow) {
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
} else {
ConsoleStackEntry frame;
if (mCallData->mTopStackFrame) {
frame = *mCallData->mTopStackFrame;
}
nsString id;
if (mWorkerPrivate->IsSharedWorker()) {
id = NS_LITERAL_STRING("SharedWorker");
} else if (mWorkerPrivate->IsServiceWorker()) {
id = NS_LITERAL_STRING("ServiceWorker");
} else {
id = NS_LITERAL_STRING("Worker");
}
mCallData->SetIDs(id, frame.mFilename);
}
nsPIDOMWindow* window = wp->GetWindow();
NS_ENSURE_TRUE_VOID(window);
ProcessCallData(aCx);
mCallData->CleanupJSObjects();
}
nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window);
NS_ENSURE_TRUE_VOID(win);
private:
void
ProcessCallData(JSContext* aCx)
{
ClearException ce(aCx);
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(win))) {
return;
}
JSContext* cx = jsapi.cx();
ClearException ce(cx);
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)) {
JS::Rooted<JS::Value> argumentsValue(aCx);
if (!mArguments.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
return;
}
MOZ_ASSERT(argumentsValue.isObject());
JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
uint32_t length;
if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
return;
}
for (uint32_t i = 0; i < length; ++i) {
JS::Rooted<JS::Value> value(cx);
JS::Rooted<JS::Value> value(aCx);
if (!JS_GetElement(cx, argumentsObj, i, &value)) {
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
return;
}
@ -371,12 +487,11 @@ private:
MOZ_ASSERT(mCallData->mArguments.Length() == length);
mCallData->mGlobal = JS::CurrentGlobalOrNull(cx);
console->AppendCallData(mCallData.forget());
mCallData->mGlobal = JS::CurrentGlobalOrNull(aCx);
mConsole->ProcessCallData(mCallData);
}
private:
nsAutoPtr<ConsoleCallData> mCallData;
ConsoleCallData* mCallData;
JSAutoStructuredCloneBuffer mArguments;
nsTArray<nsString> mStrings;
@ -386,11 +501,13 @@ private:
class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable
{
public:
ConsoleProfileRunnable(const nsAString& aAction,
ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
const Sequence<JS::Value>& aArguments)
: mAction(aAction)
: ConsoleRunnable(aConsole)
, mAction(aAction)
, mArguments(aArguments)
{
MOZ_ASSERT(aConsole);
}
private:
@ -430,64 +547,40 @@ private:
}
void
RunConsole() MOZ_OVERRIDE
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
nsPIDOMWindow* aInnerWindow) MOZ_OVERRIDE
{
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
ClearException ce(aCx);
nsPIDOMWindow* window = wp->GetWindow();
NS_ENSURE_TRUE_VOID(window);
nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window);
NS_ENSURE_TRUE_VOID(win);
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(win))) {
return;
}
JSContext* cx = jsapi.cx();
ClearException ce(cx);
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)) {
JS::Rooted<JS::Value> argumentsValue(aCx);
if (!mBuffer.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
return;
}
MOZ_ASSERT(argumentsValue.isObject());
JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
uint32_t length;
if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
return;
}
Sequence<JS::Value> arguments;
for (uint32_t i = 0; i < length; ++i) {
JS::Rooted<JS::Value> value(cx);
JS::Rooted<JS::Value> value(aCx);
if (!JS_GetElement(cx, argumentsObj, i, &value)) {
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
return;
}
arguments.AppendElement(value);
}
console->ProfileMethod(cx, mAction, arguments);
mConsole->ProfileMethod(aCx, mAction, arguments);
}
private:
nsString mAction;
Sequence<JS::Value> mArguments;
@ -497,37 +590,22 @@ private:
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.
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
tmp->ClearConsoleData();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
for (ConsoleCallData* data = tmp->mQueuedCalls.getFirst(); data != nullptr;
data = data->getNext()) {
if (data->mGlobal) {
aCallbacks.Trace(&data->mGlobal, "data->mGlobal", aClosure);
}
for (uint32_t i = 0; i < data->mArguments.Length(); ++i) {
aCallbacks.Trace(&data->mArguments[i], "data->mArguments[i]", aClosure);
}
}
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
@ -535,9 +613,8 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
Console::Console(nsPIDOMWindow* aWindow)
@ -566,6 +643,23 @@ Console::Console(nsPIDOMWindow* aWindow)
Console::~Console()
{
if (!NS_IsMainThread()) {
nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread));
if (mStorage) {
nsIConsoleAPIStorage* storage;
mStorage.forget(&storage);
NS_ProxyRelease(mainThread, storage, false);
}
if (mSandbox) {
nsIXPConnectJSObjectHolder* sandbox;
mSandbox.forget(&sandbox);
NS_ProxyRelease(mainThread, sandbox, false);
}
}
mozilla::DropJSObjects(this);
}
@ -593,13 +687,7 @@ Console::Observe(nsISupports* aSubject, const char* aTopic,
obs->RemoveObserver(this, "inner-window-destroyed");
}
ClearConsoleData();
mTimerRegistry.Clear();
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
}
return NS_OK;
@ -685,7 +773,7 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
if (!NS_IsMainThread()) {
// Here we are in a worker thread.
nsRefPtr<ConsoleProfileRunnable> runnable =
new ConsoleProfileRunnable(aAction, aData);
new ConsoleProfileRunnable(this, aAction, aData);
runnable->Dispatch();
return;
}
@ -843,43 +931,11 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
const nsAString& aMethodString,
const Sequence<JS::Value>& aData)
{
// This RAII class removes the last element of the mQueuedCalls if something
// goes wrong.
class RAII {
public:
explicit RAII(LinkedList<ConsoleCallData>& aList)
: mList(aList)
, mUnfinished(true)
{
}
~RAII()
{
if (mUnfinished) {
ConsoleCallData* data = mList.popLast();
MOZ_ASSERT(data);
delete data;
}
}
void
Finished()
{
mUnfinished = false;
}
private:
LinkedList<ConsoleCallData>& mList;
bool mUnfinished;
};
ConsoleCallData* callData = new ConsoleCallData();
mQueuedCalls.insertBack(callData);
nsAutoPtr<ConsoleCallData> callData(new ConsoleCallData());
ClearException ce(aCx);
callData->Initialize(aCx, aMethodName, aMethodString, aData);
RAII raii(mQueuedCalls);
if (mWindow) {
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
@ -989,62 +1045,17 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
}
}
// The operation is completed. RAII class has to be disabled.
raii.Finished();
if (!NS_IsMainThread()) {
// Here we are in a worker thread. The ConsoleCallData has to been removed
// from the list and it will be deleted by the ConsoleCallDataRunnable or
// by the Main-Thread Console object.
mQueuedCalls.popLast();
nsRefPtr<ConsoleCallDataRunnable> runnable =
new ConsoleCallDataRunnable(callData);
runnable->Dispatch();
if (NS_IsMainThread()) {
callData->SetIDs(mOuterID, mInnerID);
ProcessCallData(callData);
return;
}
if (!mTimer) {
mTimer = do_CreateInstance("@mozilla.org/timer;1");
mTimer->InitWithCallback(this, CALL_DELAY,
nsITimer::TYPE_REPEATING_SLACK);
}
}
void
Console::AppendCallData(ConsoleCallData* aCallData)
{
mQueuedCalls.insertBack(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)
{
MOZ_ASSERT(!mQueuedCalls.isEmpty());
for (uint32_t i = 0; i < MESSAGES_IN_INTERVAL; ++i) {
ConsoleCallData* data = mQueuedCalls.popFirst();
if (!data) {
break;
}
ProcessCallData(data);
delete data;
}
if (mQueuedCalls.isEmpty() && mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
return NS_OK;
// Note: we can pass the reference of callData because this runnable calls
// ProcessCallData() synchronously.
nsRefPtr<ConsoleCallDataRunnable> runnable =
new ConsoleCallDataRunnable(this, callData);
runnable->Dispatch();
}
// We store information to lazily compute the stack in the reserved slots of
@ -1111,13 +1122,15 @@ Console::ProcessCallData(ConsoleCallData* aData)
event.mID.Construct();
event.mInnerID.Construct();
if (mWindow) {
event.mID.Value().SetAsUnsignedLong() = mOuterID;
event.mInnerID.Value().SetAsUnsignedLong() = mInnerID;
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
if (aData->mIDType == ConsoleCallData::eString) {
event.mID.Value().SetAsString() = aData->mOuterIDString;
event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
} else {
// If we are in a JSM, the window doesn't exist.
event.mID.Value().SetAsString() = NS_LITERAL_STRING("jsm");
event.mInnerID.Value().SetAsString() = frame.mFilename;
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
event.mID.Value().SetAsUnsignedLong() = aData->mOuterIDNumber;
event.mInnerID.Value().SetAsUnsignedLong() = aData->mInnerIDNumber;
}
event.mLevel = aData->mMethodString;
@ -1243,29 +1256,21 @@ Console::ProcessCallData(ConsoleCallData* aData)
return;
}
nsAutoString innerID;
innerID.AppendInt(mInnerID);
nsAutoString innerID, outerID;
if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
if (aData->mIDType == ConsoleCallData::eString) {
outerID = aData->mOuterIDString;
innerID = aData->mInnerIDString;
} else {
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
outerID.AppendInt(aData->mOuterIDNumber);
innerID.AppendInt(aData->mInnerIDNumber);
}
if (NS_FAILED(mStorage->RecordPendingEvent(innerID, outerID, eventValue))) {
NS_WARNING("Failed to record a console event.");
}
nsXPConnect* xpc = nsXPConnect::XPConnect();
nsCOMPtr<nsISupports> wrapper;
const nsIID& iid = NS_GET_IID(nsISupports);
if (NS_FAILED(xpc->WrapJS(cx, eventObj, iid, getter_AddRefs(wrapper)))) {
return;
}
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
if (obs) {
nsAutoString outerID;
outerID.AppendInt(mOuterID);
obs->NotifyObservers(wrapper, "console-api-log-event", outerID.get());
}
}
void
@ -1689,14 +1694,6 @@ Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
return value;
}
void
Console::ClearConsoleData()
{
while (ConsoleCallData* data = mQueuedCalls.popFirst()) {
delete data;
}
}
bool
Console::ShouldIncludeStackTrace(MethodName aMethodName)
{
@ -1711,5 +1708,24 @@ Console::ShouldIncludeStackTrace(MethodName aMethodName)
}
}
nsIXPConnectJSObjectHolder*
Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mSandbox) {
nsIXPConnect* xpc = nsContentUtils::XPConnect();
MOZ_ASSERT(xpc, "This should never be null!");
nsresult rv = xpc->CreateSandbox(aCx, aPrincipal,
getter_AddRefs(mSandbox));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
}
return mSandbox;
}
} // namespace dom
} // namespace mozilla

View File

@ -12,12 +12,12 @@
#include "nsDataHashtable.h"
#include "nsHashKeys.h"
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsWrapperCache.h"
#include "nsDOMNavigationTiming.h"
#include "nsPIDOMWindow.h"
class nsIConsoleAPIStorage;
class nsIXPConnectJSObjectHolder;
namespace mozilla {
namespace dom {
@ -25,17 +25,14 @@ namespace dom {
class ConsoleCallData;
struct ConsoleStackEntry;
class Console MOZ_FINAL : public nsITimerCallback
, public nsIObserver
class Console MOZ_FINAL : public nsIObserver
, public nsWrapperCache
{
~Console();
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Console,
nsITimerCallback)
NS_DECL_NSITIMERCALLBACK
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Console)
NS_DECL_NSIOBSERVER
explicit Console(nsPIDOMWindow* aWindow);
@ -131,9 +128,6 @@ private:
Method(JSContext* aCx, MethodName aName, const nsAString& aString,
const Sequence<JS::Value>& aData);
void
AppendCallData(ConsoleCallData* aData);
void
ProcessCallData(ConsoleCallData* aData);
@ -191,17 +185,16 @@ private:
IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
const nsTArray<JS::Heap<JS::Value>>& aArguments);
void
ClearConsoleData();
bool
ShouldIncludeStackTrace(MethodName aMethodName);
nsCOMPtr<nsPIDOMWindow> mWindow;
nsCOMPtr<nsITimer> mTimer;
nsCOMPtr<nsIConsoleAPIStorage> mStorage;
nsIXPConnectJSObjectHolder*
GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal);
nsCOMPtr<nsPIDOMWindow> mWindow;
nsCOMPtr<nsIConsoleAPIStorage> mStorage;
nsCOMPtr<nsIXPConnectJSObjectHolder> mSandbox;
LinkedList<ConsoleCallData> mQueuedCalls;
nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
@ -209,6 +202,7 @@ private:
uint64_t mInnerID;
friend class ConsoleCallData;
friend class ConsoleRunnable;
friend class ConsoleCallDataRunnable;
friend class ConsoleProfileRunnable;
};

View File

@ -11,9 +11,18 @@ let Cc = Components.classes;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// The console API events have to be scheduled when stored using
// |recordPendingEvent|.
const CALL_DELAY = 15 // milliseconds
// This constant tells how many messages to process in a single timer execution.
const MESSAGES_IN_INTERVAL = 1500
const STORAGE_MAX_EVENTS = 200;
var _consoleStorage = new Map();
var _consolePendingStorage = new Map();
var _timer;
const CONSOLEAPISTORAGE_CID = Components.ID('{96cf7855-dfa9-4c6d-8276-f9705b4890f2}');
@ -114,7 +123,7 @@ ConsoleAPIStorageService.prototype = {
* @param object aEvent
* A JavaScript object you want to store.
*/
recordEvent: function CS_recordEvent(aId, aEvent)
recordEvent: function CS_recordEvent(aId, aOuterId, aEvent)
{
if (!_consoleStorage.has(aId)) {
_consoleStorage.set(aId, []);
@ -128,9 +137,55 @@ ConsoleAPIStorageService.prototype = {
storage.shift();
}
Services.obs.notifyObservers(aEvent, "console-api-log-event", aOuterId);
Services.obs.notifyObservers(aEvent, "console-storage-cache-event", aId);
},
/**
* Similar to recordEvent, but these events are scheduled and stored any
* CALL_DELAY millisecs.
*/
recordPendingEvent: function CS_recordPendingEvent(aId, aOuterId, aEvent)
{
if (!_consolePendingStorage.has(aId)) {
_consolePendingStorage.set(aId, []);
}
let storage = _consolePendingStorage.get(aId);
storage.push({ outerId: aOuterId, event: aEvent });
if (!_timer) {
_timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
}
let self = this;
_timer.initWithCallback(function() { self.flushPendingEvents(); },
CALL_DELAY, Ci.nsITimer.TYPE_REPEATING_SLACK);
},
/**
* Processes the pending event queue.
*/
flushPendingEvents: function CS_flushPendingEvents()
{
for (let [id, objs] of _consolePendingStorage) {
for (let i = 0; i < objs.length && i < MESSAGES_IN_INTERVAL; ++i) {
this.recordEvent(id, objs[i].outerId, objs[i].event);
}
if (objs.length <= MESSAGES_IN_INTERVAL) {
_consolePendingStorage.delete(id);
} else {
_consolePendingStorage.set(id, objs.splice(MESSAGES_IN_INTERVAL));
}
}
if (_timer && _consolePendingStorage.size == 0) {
_timer.cancel();
_timer = null;
}
},
/**
* Clear storage data for the given window.
*

View File

@ -5,7 +5,7 @@
#include "nsISupports.idl"
[scriptable, uuid(6701600a-17ca-417e-98f9-4ceb175dd15d)]
[scriptable, uuid(cce39123-585e-411b-9edd-2513f7cf7e47)]
interface nsIConsoleAPIStorage : nsISupports
{
/**
@ -27,10 +27,29 @@ interface nsIConsoleAPIStorage : nsISupports
* @param string aId
* The ID of the inner window for which the event occurred or "jsm" for
* messages logged from JavaScript modules..
* @param string aOuterId
* This ID is used as 3rd parameters for the console-api-log-event
* notification.
* @param object aEvent
* A JavaScript object you want to store.
*/
void recordEvent(in DOMString aId, in jsval aEvent);
void recordEvent(in DOMString aId, in DOMString aOuterId, in jsval aEvent);
/**
* Similar to recordEvent() but these events will be collected
* and dispatched with a timer in order to avoid flooding the devtools
* webconsole.
*
* @param string aId
* The ID of the inner window for which the event occurred or "jsm" for
* messages logged from JavaScript modules..
* @param string aOuterId
* This ID is used as 3rd parameters for the console-api-log-event
* notification.
* @param object aEvent
* A JavaScript object you want to store.
*/
void recordPendingEvent(in DOMString aId, in DOMString aOuterId, in jsval aEvent);
/**
* Clear storage data for the given window.

View File

@ -51,6 +51,7 @@ support-files =
relativeLoad_worker.js
relativeLoad_worker2.js
rvals_worker.js
sharedWorker_console.js
sharedWorker_sharedWorker.js
simpleThread_worker.js
suspend_iframe.html
@ -115,6 +116,7 @@ support-files =
[test_closeOnGC.html]
[test_console.html]
[test_consoleReplaceable.html]
[test_consoleSharedWorkers.html]
[test_contentWorker.html]
[test_csp.html]
skip-if = (toolkit == 'gonk' && debug) #debug-only failure

View File

@ -0,0 +1,11 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
onconnect = function(evt) {
console.profile("Hello profiling from a SharedWorker!");
console.log("Hello world from a SharedWorker!");
evt.ports[0].postMessage('ok!');
}

View File

@ -0,0 +1,58 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Test for console API in SharedWorker</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);
SpecialPowers.addObserver(this, "console-api-profiler", false);
}
var order = 0;
consoleListener.prototype = {
observe: function(aSubject, aTopic, aData) {
ok(true, "Something has been received");
if (aTopic == "console-api-profiler") {
var obj = aSubject.wrappedJSObject;
is (obj.arguments[0], "Hello profiling from a SharedWorker!", "A message from a SharedWorker \\o/");
is (order++, 0, "First a profiler message.");
SpecialPowers.removeObserver(this, "console-api-profiler");
return;
}
if (aTopic == "console-api-log-event") {
var obj = aSubject.wrappedJSObject;
is (obj.arguments[0], "Hello world from a SharedWorker!", "A message from a SharedWorker \\o/");
is (aData, "SharedWorker", "The ID is SharedWorker");
is (order++, 1, "Then a log message.");
SpecialPowers.removeObserver(this, "console-api-log-event");
SimpleTest.finish();
return;
}
}
}
var cl = new consoleListener();
SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() {
new SharedWorker('sharedWorker_console.js');
});
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@ -559,10 +559,11 @@ function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {})
break;
}
Services.obs.notifyObservers(consoleEvent, "console-api-log-event", null);
let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
.getService(Ci.nsIConsoleAPIStorage);
ConsoleAPIStorage.recordEvent("jsm", consoleEvent);
if (ConsoleAPIStorage) {
ConsoleAPIStorage.recordEvent("jsm", null, consoleEvent);
}
}
/**