mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1058644 - Console API in ServiceWorkers. r=khuey
This commit is contained in:
parent
340c51ec0d
commit
1db122563c
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
11
dom/workers/test/sharedWorker_console.js
Normal file
11
dom/workers/test/sharedWorker_console.js
Normal 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!');
|
||||
}
|
58
dom/workers/test/test_consoleSharedWorkers.html
Normal file
58
dom/workers/test/test_consoleSharedWorkers.html
Normal 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>
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user