mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 965860 - patch 2 - ConsoleAPI written in C++, r=bz
This commit is contained in:
parent
07f3b7d454
commit
80210d8dab
@ -61,9 +61,6 @@ function PlainTextConsole(print) {
|
||||
});
|
||||
|
||||
// We defined the `__exposedProps__` in our console chrome object.
|
||||
// Although it seems redundant, because we use `createObjectIn` too, in
|
||||
// worker.js, we are following what `ConsoleAPI` does. See:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#132
|
||||
//
|
||||
// Meanwhile we're investigating with the platform team if `__exposedProps__`
|
||||
// are needed, or are just a left-over.
|
||||
|
@ -199,11 +199,7 @@ const WorkerSandbox = Class({
|
||||
if (!getTabForContentWindow(window)) {
|
||||
let win = getUnsafeWindow(window);
|
||||
|
||||
// export our chrome console to content window, using the same approach
|
||||
// of `ConsoleAPI`:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#150
|
||||
//
|
||||
// and described here:
|
||||
// export our chrome console to content window, as described here:
|
||||
// https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
|
||||
let con = Cu.createObjectIn(win);
|
||||
|
||||
|
@ -278,11 +278,7 @@ const WorkerSandbox = EventEmitter.compose({
|
||||
if (!getTabForContentWindow(window)) {
|
||||
let win = window.wrappedJSObject ? window.wrappedJSObject : window;
|
||||
|
||||
// export our chrome console to content window, using the same approach
|
||||
// of `ConsoleAPI`:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#150
|
||||
//
|
||||
// and described here:
|
||||
// export our chrome console to content window as described here:
|
||||
// https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
|
||||
let con = Cu.createObjectIn(win);
|
||||
|
||||
|
@ -339,7 +339,6 @@
|
||||
|
||||
; JavaScript components
|
||||
@BINPATH@/components/ConsoleAPI.manifest
|
||||
@BINPATH@/components/ConsoleAPI.js
|
||||
@BINPATH@/components/ConsoleAPIStorage.js
|
||||
@BINPATH@/components/BrowserElementParent.manifest
|
||||
@BINPATH@/components/BrowserElementParent.js
|
||||
|
@ -33,10 +33,10 @@ function test() {
|
||||
|
||||
// The expected stack trace object.
|
||||
let stacktrace = [
|
||||
{ filename: TEST_URI, lineNumber: 9, functionName: "window.foobar585956c", language: 2 },
|
||||
{ filename: TEST_URI, lineNumber: 14, functionName: "foobar585956b", language: 2 },
|
||||
{ filename: TEST_URI, lineNumber: 18, functionName: "foobar585956a", language: 2 },
|
||||
{ filename: TEST_URI, lineNumber: 21, functionName: null, language: 2 }
|
||||
{ filename: TEST_URI, functionName: "window.foobar585956c", language: 2, lineNumber: 9 },
|
||||
{ filename: TEST_URI, functionName: "foobar585956b", language: 2, lineNumber: 14 },
|
||||
{ filename: TEST_URI, functionName: "foobar585956a", language: 2, lineNumber: 18 },
|
||||
{ filename: TEST_URI, functionName: "", language: 2, lineNumber: 21 }
|
||||
];
|
||||
|
||||
ok(obj._stacktrace, "found stacktrace object");
|
||||
|
@ -346,7 +346,6 @@
|
||||
|
||||
; JavaScript components
|
||||
@BINPATH@/components/ConsoleAPI.manifest
|
||||
@BINPATH@/components/ConsoleAPI.js
|
||||
@BINPATH@/components/ConsoleAPIStorage.js
|
||||
@BINPATH@/components/BrowserElementParent.manifest
|
||||
@BINPATH@/components/BrowserElementParent.js
|
||||
|
831
dom/base/Console.cpp
Normal file
831
dom/base/Console.cpp
Normal file
@ -0,0 +1,831 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/Console.h"
|
||||
#include "mozilla/dom/ConsoleBinding.h"
|
||||
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsDocument.h"
|
||||
#include "nsDOMNavigationTiming.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsPerformance.h"
|
||||
|
||||
#include "nsIConsoleAPIStorage.h"
|
||||
#include "nsIDOMWindowUtils.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsILoadContext.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsIWebNavigation.h"
|
||||
|
||||
// The maximum allowed number of concurrent timers per page.
|
||||
#define MAX_PAGE_TIMERS 10000
|
||||
|
||||
// The maximum stacktrace depth when populating the stacktrace array used for
|
||||
// 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
|
||||
|
||||
using namespace mozilla::dom::exceptions;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class ConsoleCallData
|
||||
{
|
||||
public:
|
||||
ConsoleCallData()
|
||||
: mMethodName(Console::MethodLog)
|
||||
, mPrivate(false)
|
||||
, mTimeStamp(JS_Now())
|
||||
, mMonotonicTimer(0)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Initialize(JSContext* aCx, Console::MethodName aName,
|
||||
const nsAString& aString, const Sequence<JS::Value>& aArguments)
|
||||
{
|
||||
mGlobal = JS::CurrentGlobalOrNull(aCx);
|
||||
mMethodName = aName;
|
||||
mMethodString = aString;
|
||||
|
||||
for (uint32_t i = 0; i < aArguments.Length(); ++i) {
|
||||
mArguments.AppendElement(aArguments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
JS::Heap<JSObject*> mGlobal;
|
||||
|
||||
Console::MethodName mMethodName;
|
||||
bool mPrivate;
|
||||
int64_t mTimeStamp;
|
||||
DOMHighResTimeStamp mMonotonicTimer;
|
||||
|
||||
nsString mMethodString;
|
||||
nsTArray<JS::Heap<JS::Value>> mArguments;
|
||||
Sequence<ConsoleStackEntry> mStack;
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
|
||||
|
||||
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->mQueuedCalls.Clear();
|
||||
|
||||
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 (uint32_t i = 0; i < tmp->mQueuedCalls.Length(); ++i) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mQueuedCalls[i].mGlobal);
|
||||
|
||||
for (uint32_t j = 0; j < tmp->mQueuedCalls[i].mArguments.Length(); ++j) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mQueuedCalls[i].mArguments[j]);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
|
||||
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(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
Console::Console(nsPIDOMWindow* aWindow)
|
||||
: mWindow(aWindow)
|
||||
, mOuterID(0)
|
||||
, mInnerID(0)
|
||||
{
|
||||
if (mWindow) {
|
||||
MOZ_ASSERT(mWindow->IsInnerWindow());
|
||||
mInnerID = mWindow->WindowID();
|
||||
|
||||
nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow();
|
||||
MOZ_ASSERT(outerWindow);
|
||||
mOuterID = outerWindow->WindowID();
|
||||
}
|
||||
|
||||
SetIsDOMBinding();
|
||||
mozilla::HoldJSObjects(this);
|
||||
}
|
||||
|
||||
Console::~Console()
|
||||
{
|
||||
mozilla::DropJSObjects(this);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
Console::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
|
||||
{
|
||||
return ConsoleBinding::Wrap(aCx, aScope, this);
|
||||
}
|
||||
|
||||
#define METHOD(name, string) \
|
||||
void \
|
||||
Console::name(JSContext* aCx, const Sequence<JS::Value>& aData) \
|
||||
{ \
|
||||
Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \
|
||||
}
|
||||
|
||||
METHOD(Log, "log")
|
||||
METHOD(Info, "info")
|
||||
METHOD(Warn, "warn")
|
||||
METHOD(Error, "error")
|
||||
METHOD(Exception, "exception")
|
||||
METHOD(Debug, "debug")
|
||||
|
||||
void
|
||||
Console::Trace(JSContext* aCx)
|
||||
{
|
||||
const Sequence<JS::Value> data;
|
||||
Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data);
|
||||
}
|
||||
|
||||
// Displays an interactive listing of all the properties of an object.
|
||||
METHOD(Dir, "dir");
|
||||
|
||||
METHOD(Group, "group")
|
||||
METHOD(GroupCollapsed, "groupCollapsed")
|
||||
METHOD(GroupEnd, "groupEnd")
|
||||
|
||||
void
|
||||
Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
|
||||
{
|
||||
Sequence<JS::Value> data;
|
||||
SequenceRooter<JS::Value> rooter(aCx, &data);
|
||||
|
||||
if (!aTime.isUndefined()) {
|
||||
data.AppendElement(aTime);
|
||||
}
|
||||
|
||||
Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
|
||||
}
|
||||
|
||||
void
|
||||
Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
|
||||
{
|
||||
Sequence<JS::Value> data;
|
||||
SequenceRooter<JS::Value> rooter(aCx, &data);
|
||||
|
||||
if (!aTime.isUndefined()) {
|
||||
data.AppendElement(aTime);
|
||||
}
|
||||
|
||||
Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
|
||||
}
|
||||
|
||||
void
|
||||
Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
||||
const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
RootedDictionary<ConsoleProfileEvent> event(aCx);
|
||||
event.mAction = aAction;
|
||||
|
||||
event.mArguments.Construct();
|
||||
Sequence<JS::Value>& sequence = event.mArguments.Value();
|
||||
|
||||
for (uint32_t i = 0; i < aData.Length(); ++i) {
|
||||
sequence.AppendElement(aData[i]);
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> eventValue(aCx);
|
||||
if (!event.ToObject(aCx, JS::NullPtr(), &eventValue)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
|
||||
MOZ_ASSERT(eventObj);
|
||||
|
||||
if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
|
||||
nullptr, nullptr, JSPROP_ENUMERATE)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
nsXPConnect* xpc = nsXPConnect::XPConnect();
|
||||
nsCOMPtr<nsISupports> wrapper;
|
||||
const nsIID& iid = NS_GET_IID(nsISupports);
|
||||
|
||||
if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs =
|
||||
do_GetService("@mozilla.org/observer-service;1");
|
||||
if (obs) {
|
||||
obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Console::Assert(JSContext* aCx, bool aCondition,
|
||||
const Sequence<JS::Value>& aData)
|
||||
{
|
||||
if (!aCondition) {
|
||||
Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Console::__noSuchMethod__()
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
// Queue a call to a console method. See the CALL_DELAY constant.
|
||||
void
|
||||
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:
|
||||
RAII(nsTArray<ConsoleCallData>& aArray)
|
||||
: mArray(aArray)
|
||||
, mUnfinished(true)
|
||||
{
|
||||
}
|
||||
|
||||
~RAII()
|
||||
{
|
||||
if (mUnfinished) {
|
||||
mArray.RemoveElementAt(mArray.Length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Finished()
|
||||
{
|
||||
mUnfinished = false;
|
||||
}
|
||||
|
||||
private:
|
||||
nsTArray<ConsoleCallData>& mArray;
|
||||
bool mUnfinished;
|
||||
};
|
||||
|
||||
ConsoleCallData& callData = *mQueuedCalls.AppendElement();
|
||||
callData.Initialize(aCx, aMethodName, aMethodString, aData);
|
||||
RAII raii(mQueuedCalls);
|
||||
|
||||
if (mWindow) {
|
||||
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
|
||||
if (!webNav) {
|
||||
Throw(aCx, NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
|
||||
MOZ_ASSERT(loadContext);
|
||||
|
||||
loadContext->GetUsePrivateBrowsing(&callData.mPrivate);
|
||||
}
|
||||
|
||||
uint32_t maxDepth = aMethodName == MethodTrace ?
|
||||
DEFAULT_MAX_STACKTRACE_DEPTH : 1;
|
||||
nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
|
||||
|
||||
if (!stack) {
|
||||
Throw(aCx, NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
// nsIStackFrame is not thread-safe so we take what we need and we store in
|
||||
// an array of ConsoleStackEntry objects.
|
||||
do {
|
||||
uint32_t language;
|
||||
nsresult rv = stack->GetLanguage(&language);
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
if (language == nsIProgrammingLanguage::JAVASCRIPT ||
|
||||
language == nsIProgrammingLanguage::JAVASCRIPT2) {
|
||||
ConsoleStackEntry& data = *callData.mStack.AppendElement();
|
||||
|
||||
nsCString string;
|
||||
rv = stack->GetFilename(string);
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
CopyUTF8toUTF16(string, data.mFilename);
|
||||
|
||||
int32_t lineNumber;
|
||||
rv = stack->GetLineNumber(&lineNumber);
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
data.mLineNumber = lineNumber;
|
||||
|
||||
rv = stack->GetName(string);
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
CopyUTF8toUTF16(string, data.mFunctionName);
|
||||
|
||||
data.mLanguage = language;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIStackFrame> caller;
|
||||
rv = stack->GetCaller(getter_AddRefs(caller));
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
stack.swap(caller);
|
||||
} while (stack);
|
||||
|
||||
// Monotonic timer for 'time' and 'timeEnd'
|
||||
if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd) && mWindow) {
|
||||
nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get());
|
||||
MOZ_ASSERT(win);
|
||||
|
||||
ErrorResult rv;
|
||||
nsRefPtr<nsPerformance> performance = win->GetPerformance(rv);
|
||||
if (rv.Failed()) {
|
||||
Throw(aCx, rv.ErrorCode());
|
||||
return;
|
||||
}
|
||||
|
||||
callData.mMonotonicTimer = performance->Now();
|
||||
}
|
||||
|
||||
// The operation is completed. RAII class has to be disabled.
|
||||
raii.Finished();
|
||||
|
||||
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());
|
||||
|
||||
uint32_t i = 0;
|
||||
for (; i < MESSAGES_IN_INTERVAL && i < mQueuedCalls.Length();
|
||||
++i) {
|
||||
ProcessCallData(mQueuedCalls[i]);
|
||||
}
|
||||
|
||||
mQueuedCalls.RemoveElementsAt(0, i);
|
||||
|
||||
if (mQueuedCalls.IsEmpty()) {
|
||||
mTimer->Cancel();
|
||||
mTimer = nullptr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
Console::ProcessCallData(ConsoleCallData& aData)
|
||||
{
|
||||
ConsoleStackEntry frame;
|
||||
if (!aData.mStack.IsEmpty()) {
|
||||
frame = aData.mStack[0];
|
||||
}
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
ClearException ce(cx);
|
||||
RootedDictionary<ConsoleEvent> event(cx);
|
||||
|
||||
JSAutoCompartment ac(cx, aData.mGlobal);
|
||||
|
||||
event.mID.Construct();
|
||||
event.mInnerID.Construct();
|
||||
if (mWindow) {
|
||||
event.mID.Value().SetAsUnsignedLong() = mOuterID;
|
||||
event.mInnerID.Value().SetAsUnsignedLong() = mInnerID;
|
||||
} 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;
|
||||
}
|
||||
|
||||
event.mLevel = aData.mMethodString;
|
||||
event.mFilename = frame.mFilename;
|
||||
event.mLineNumber = frame.mLineNumber;
|
||||
event.mFunctionName = frame.mFunctionName;
|
||||
event.mTimeStamp = aData.mTimeStamp;
|
||||
event.mPrivate = aData.mPrivate;
|
||||
|
||||
switch (aData.mMethodName) {
|
||||
case MethodLog:
|
||||
case MethodInfo:
|
||||
case MethodWarn:
|
||||
case MethodError:
|
||||
case MethodException:
|
||||
case MethodDebug:
|
||||
case MethodAssert:
|
||||
event.mArguments.Construct();
|
||||
ProcessArguments(cx, aData.mArguments, event.mArguments.Value());
|
||||
break;
|
||||
|
||||
default:
|
||||
event.mArguments.Construct();
|
||||
ArgumentsToValueList(aData.mArguments, event.mArguments.Value());
|
||||
}
|
||||
|
||||
if (aData.mMethodName == MethodTrace) {
|
||||
event.mStacktrace.Construct();
|
||||
event.mStacktrace.Value().SwapElements(aData.mStack);
|
||||
}
|
||||
|
||||
else if (aData.mMethodName == MethodGroup ||
|
||||
aData.mMethodName == MethodGroupCollapsed ||
|
||||
aData.mMethodName == MethodGroupEnd) {
|
||||
ComposeGroupName(cx, aData.mArguments, event.mGroupName);
|
||||
}
|
||||
|
||||
else if (aData.mMethodName == MethodTime && !aData.mArguments.IsEmpty()) {
|
||||
event.mTimer = StartTimer(cx, aData.mArguments[0], aData.mMonotonicTimer);
|
||||
}
|
||||
|
||||
else if (aData.mMethodName == MethodTimeEnd && !aData.mArguments.IsEmpty()) {
|
||||
event.mTimer = StopTimer(cx, aData.mArguments[0], aData.mMonotonicTimer);
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> eventValue(cx);
|
||||
if (!event.ToObject(cx, JS::NullPtr(), &eventValue)) {
|
||||
Throw(cx, NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
|
||||
MOZ_ASSERT(eventObj);
|
||||
|
||||
if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue,
|
||||
nullptr, nullptr, JSPROP_ENUMERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mStorage) {
|
||||
mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
|
||||
}
|
||||
|
||||
if (!mStorage) {
|
||||
NS_WARNING("Failed to get the ConsoleAPIStorage service.");
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString innerID;
|
||||
innerID.AppendInt(mInnerID);
|
||||
|
||||
if (NS_FAILED(mStorage->RecordEvent(innerID, 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
|
||||
Console::ProcessArguments(JSContext* aCx,
|
||||
const nsTArray<JS::Heap<JS::Value>>& aData,
|
||||
Sequence<JS::Value>& aSequence)
|
||||
{
|
||||
if (aData.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aData.Length() == 1 || !aData[0].isString()) {
|
||||
ArgumentsToValueList(aData, aSequence);
|
||||
return;
|
||||
}
|
||||
|
||||
SequenceRooter<JS::Value> rooter(aCx, &aSequence);
|
||||
|
||||
JS::Rooted<JS::Value> format(aCx, aData[0]);
|
||||
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
|
||||
if (!jsString) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDependentJSString string;
|
||||
if (!string.init(aCx, jsString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsString::const_iterator start, end;
|
||||
string.BeginReading(start);
|
||||
string.EndReading(end);
|
||||
|
||||
nsString output;
|
||||
uint32_t index = 1;
|
||||
|
||||
while (start != end) {
|
||||
if (*start != '%') {
|
||||
output.Append(*start);
|
||||
++start;
|
||||
continue;
|
||||
}
|
||||
|
||||
++start;
|
||||
|
||||
if (*start == '%') {
|
||||
output.Append(*start);
|
||||
++start;
|
||||
continue;
|
||||
}
|
||||
|
||||
char ch = *start;
|
||||
++start;
|
||||
|
||||
switch (ch) {
|
||||
case 'o':
|
||||
{
|
||||
if (!output.IsEmpty()) {
|
||||
JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
|
||||
output.get(),
|
||||
output.Length()));
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
aSequence.AppendElement(JS::StringValue(str));
|
||||
output.Truncate();
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> v(aCx);
|
||||
if (index < aData.Length()) {
|
||||
v = aData[index++];
|
||||
}
|
||||
|
||||
aSequence.AppendElement(v);
|
||||
break;
|
||||
}
|
||||
|
||||
case 's':
|
||||
if (index < aData.Length()) {
|
||||
JS::Rooted<JS::Value> value(aCx, aData[index++]);
|
||||
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
|
||||
if (!jsString) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDependentJSString v;
|
||||
if (!v.init(aCx, jsString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
output.Append(v);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
case 'i':
|
||||
if (index < aData.Length()) {
|
||||
JS::Rooted<JS::Value> value(aCx, aData[index++]);
|
||||
|
||||
int32_t v;
|
||||
if (!JS::ToInt32(aCx, value, &v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
output.AppendPrintf("%d", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
if (index < aData.Length()) {
|
||||
JS::Rooted<JS::Value> value(aCx, aData[index++]);
|
||||
|
||||
double v;
|
||||
if (!JS::ToNumber(aCx, value, &v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
output.AppendPrintf("%f", v);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
output.Append(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!output.IsEmpty()) {
|
||||
JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, output.get(),
|
||||
output.Length()));
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
aSequence.AppendElement(JS::StringValue(str));
|
||||
}
|
||||
|
||||
// The rest of the array, if unused by the format string.
|
||||
for (; index < aData.Length(); ++index) {
|
||||
aSequence.AppendElement(aData[index]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Console::ComposeGroupName(JSContext* aCx,
|
||||
const nsTArray<JS::Heap<JS::Value>>& aData,
|
||||
nsAString& aName)
|
||||
{
|
||||
for (uint32_t i = 0; i < aData.Length(); ++i) {
|
||||
if (i != 0) {
|
||||
aName.AppendASCII(" ");
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx, aData[i]);
|
||||
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
|
||||
if (!jsString) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDependentJSString string;
|
||||
if (!string.init(aCx, jsString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
aName.Append(string);
|
||||
}
|
||||
}
|
||||
|
||||
JS::Value
|
||||
Console::StartTimer(JSContext* aCx, const JS::Value& aName,
|
||||
DOMHighResTimeStamp aTimestamp)
|
||||
{
|
||||
if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
|
||||
RootedDictionary<ConsoleTimerError> error(aCx);
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (!error.ToObject(aCx, JS::NullPtr(), &value)) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
RootedDictionary<ConsoleTimerStart> timer(aCx);
|
||||
|
||||
JS::Rooted<JS::Value> name(aCx, aName);
|
||||
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
|
||||
if (!jsString) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
nsDependentJSString key;
|
||||
if (!key.init(aCx, jsString)) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
timer.mName = key;
|
||||
|
||||
DOMHighResTimeStamp entry;
|
||||
if (!mTimerRegistry.Get(key, &entry)) {
|
||||
mTimerRegistry.Put(key, aTimestamp);
|
||||
} else {
|
||||
aTimestamp = entry;
|
||||
}
|
||||
|
||||
timer.mStarted = aTimestamp;
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (!timer.ToObject(aCx, JS::NullPtr(), &value)) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
JS::Value
|
||||
Console::StopTimer(JSContext* aCx, const JS::Value& aName,
|
||||
DOMHighResTimeStamp aTimestamp)
|
||||
{
|
||||
JS::Rooted<JS::Value> name(aCx, aName);
|
||||
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
|
||||
if (!jsString) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
nsDependentJSString key;
|
||||
if (!key.init(aCx, jsString)) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
DOMHighResTimeStamp entry;
|
||||
if (!mTimerRegistry.Get(key, &entry)) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
mTimerRegistry.Remove(key);
|
||||
|
||||
RootedDictionary<ConsoleTimerEnd> timer(aCx);
|
||||
timer.mName = key;
|
||||
timer.mDuration = aTimestamp - entry;
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (!timer.ToObject(aCx, JS::NullPtr(), &value)) {
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void
|
||||
Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
|
||||
Sequence<JS::Value>& aSequence)
|
||||
{
|
||||
for (uint32_t i = 0; i < aData.Length(); ++i) {
|
||||
aSequence.AppendElement(aData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
180
dom/base/Console.h
Normal file
180
dom/base/Console.h
Normal file
@ -0,0 +1,180 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_Console_h
|
||||
#define mozilla_dom_Console_h
|
||||
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/UnionConversions.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsIConsoleAPIStorage;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class ConsoleCallData;
|
||||
|
||||
class Console MOZ_FINAL : public nsITimerCallback
|
||||
, public nsWrapperCache
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Console)
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
Console(nsPIDOMWindow* aWindow);
|
||||
~Console();
|
||||
|
||||
// WebIDL methods
|
||||
nsISupports* GetParentObject() const
|
||||
{
|
||||
return mWindow;
|
||||
}
|
||||
|
||||
virtual JSObject*
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
|
||||
|
||||
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, const Sequence<JS::Value>& aData);
|
||||
|
||||
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, const JS::Handle<JS::Value> aTime);
|
||||
|
||||
void
|
||||
TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime);
|
||||
|
||||
void
|
||||
Profile(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void
|
||||
ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
|
||||
|
||||
void
|
||||
__noSuchMethod__();
|
||||
|
||||
private:
|
||||
enum MethodName
|
||||
{
|
||||
MethodLog,
|
||||
MethodInfo,
|
||||
MethodWarn,
|
||||
MethodError,
|
||||
MethodException,
|
||||
MethodDebug,
|
||||
MethodTrace,
|
||||
MethodDir,
|
||||
MethodGroup,
|
||||
MethodGroupCollapsed,
|
||||
MethodGroupEnd,
|
||||
MethodTime,
|
||||
MethodTimeEnd,
|
||||
MethodAssert
|
||||
};
|
||||
|
||||
void
|
||||
Method(JSContext* aCx, MethodName aName, const nsAString& aString,
|
||||
const Sequence<JS::Value>& aData);
|
||||
|
||||
void
|
||||
ProcessCallData(ConsoleCallData& aData);
|
||||
|
||||
// If the first JS::Value of the array is a string, this method uses it to
|
||||
// format a string. The supported sequences are:
|
||||
// %s - string
|
||||
// %d,%i - integer
|
||||
// %f - double
|
||||
// %o - a JS object.
|
||||
// The output is an array where any object is a separated item, the rest is
|
||||
// unified in a format string.
|
||||
// Example if the input is:
|
||||
// "string: %s, integer: %d, object: %o, double: %d", 's', 1, window, 0.9
|
||||
// The output will be:
|
||||
// [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
|
||||
void
|
||||
ProcessArguments(JSContext* aCx, const nsTArray<JS::Heap<JS::Value>>& aData,
|
||||
Sequence<JS::Value>& aSequence);
|
||||
|
||||
// Stringify and Concat all the JS::Value in a single string using ' ' as
|
||||
// separator.
|
||||
void
|
||||
ComposeGroupName(JSContext* aCx, const nsTArray<JS::Heap<JS::Value>>& aData,
|
||||
nsAString& aName);
|
||||
|
||||
JS::Value
|
||||
StartTimer(JSContext* aCx, const JS::Value& aName,
|
||||
DOMHighResTimeStamp aTimestamp);
|
||||
|
||||
JS::Value
|
||||
StopTimer(JSContext* aCx, const JS::Value& aName,
|
||||
DOMHighResTimeStamp aTimestamp);
|
||||
|
||||
// The method populates a Sequence from an array of JS::Value.
|
||||
void
|
||||
ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
|
||||
Sequence<JS::Value>& aSequence);
|
||||
|
||||
void
|
||||
ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
||||
const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> mWindow;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
nsCOMPtr<nsIConsoleAPIStorage> mStorage;
|
||||
|
||||
nsTArray<ConsoleCallData> mQueuedCalls;
|
||||
nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
|
||||
|
||||
uint64_t mOuterID;
|
||||
uint64_t mInnerID;
|
||||
|
||||
friend class ConsoleCallData;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif /* mozilla_dom_Console_h */
|
@ -1,563 +0,0 @@
|
||||
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
|
||||
/* vim: set ft=javascript 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/. */
|
||||
|
||||
let Cu = Components.utils;
|
||||
let Ci = Components.interfaces;
|
||||
let Cc = Components.classes;
|
||||
|
||||
// The maximum allowed number of concurrent timers per page.
|
||||
const MAX_PAGE_TIMERS = 10000;
|
||||
|
||||
// The maximum allowed number of concurrent counters per page.
|
||||
const MAX_PAGE_COUNTERS = 10000;
|
||||
|
||||
// The regular expression used to parse %s/%d and other placeholders for
|
||||
// variables in strings that need to be interpolated.
|
||||
const ARGUMENT_PATTERN = /%\d*\.?\d*([osdif])\b/g;
|
||||
|
||||
// The maximum stacktrace depth when populating the stacktrace array used for
|
||||
// console.trace().
|
||||
const DEFAULT_MAX_STACKTRACE_DEPTH = 200;
|
||||
|
||||
// The console API methods are async and their action is executed later. This
|
||||
// delay tells how much later.
|
||||
const CALL_DELAY = 15; // milliseconds
|
||||
|
||||
// This constant tells how many messages to process in a single timer execution.
|
||||
const MESSAGES_IN_INTERVAL = 1500;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ConsoleAPIStorage",
|
||||
"@mozilla.org/consoleAPI-storage;1",
|
||||
"nsIConsoleAPIStorage");
|
||||
|
||||
/**
|
||||
* The window.console API implementation. One instance is lazily created for
|
||||
* every inner window, when the window.console object is accessed.
|
||||
*/
|
||||
function ConsoleAPI() {}
|
||||
ConsoleAPI.prototype = {
|
||||
|
||||
classID: Components.ID("{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsIObserver]),
|
||||
|
||||
_timerInitialized: false,
|
||||
_queuedCalls: null,
|
||||
_window: null,
|
||||
_innerID: null,
|
||||
_outerID: null,
|
||||
_windowDestroyed: false,
|
||||
_timer: null,
|
||||
|
||||
// nsIDOMGlobalPropertyInitializer
|
||||
init: function CA_init(aWindow) {
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", true);
|
||||
|
||||
try {
|
||||
let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
this._outerID = windowUtils.outerWindowID;
|
||||
this._innerID = windowUtils.currentInnerWindowID;
|
||||
}
|
||||
catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
let chromeObject = {
|
||||
// window.console API
|
||||
log: function CA_log() {
|
||||
self.queueCall("log", arguments);
|
||||
},
|
||||
info: function CA_info() {
|
||||
self.queueCall("info", arguments);
|
||||
},
|
||||
warn: function CA_warn() {
|
||||
self.queueCall("warn", arguments);
|
||||
},
|
||||
error: function CA_error() {
|
||||
self.queueCall("error", arguments);
|
||||
},
|
||||
exception: function CA_exception() {
|
||||
self.queueCall("exception", arguments);
|
||||
},
|
||||
debug: function CA_debug() {
|
||||
self.queueCall("debug", arguments);
|
||||
},
|
||||
trace: function CA_trace() {
|
||||
self.queueCall("trace", arguments);
|
||||
},
|
||||
// Displays an interactive listing of all the properties of an object.
|
||||
dir: function CA_dir() {
|
||||
self.queueCall("dir", arguments);
|
||||
},
|
||||
group: function CA_group() {
|
||||
self.queueCall("group", arguments);
|
||||
},
|
||||
groupCollapsed: function CA_groupCollapsed() {
|
||||
self.queueCall("groupCollapsed", arguments);
|
||||
},
|
||||
groupEnd: function CA_groupEnd() {
|
||||
self.queueCall("groupEnd", arguments);
|
||||
},
|
||||
time: function CA_time() {
|
||||
self.queueCall("time", arguments);
|
||||
},
|
||||
timeEnd: function CA_timeEnd() {
|
||||
self.queueCall("timeEnd", arguments);
|
||||
},
|
||||
profile: function CA_profile() {
|
||||
// Send a notification picked up by the profiler if installed.
|
||||
// This must happen right away otherwise we will miss samples
|
||||
let consoleEvent = {
|
||||
action: "profile",
|
||||
arguments: arguments
|
||||
};
|
||||
consoleEvent.wrappedJSObject = consoleEvent;
|
||||
Services.obs.notifyObservers(consoleEvent, "console-api-profiler",
|
||||
null);
|
||||
},
|
||||
profileEnd: function CA_profileEnd() {
|
||||
// Send a notification picked up by the profiler if installed.
|
||||
// This must happen right away otherwise we will miss samples
|
||||
let consoleEvent = {
|
||||
action: "profileEnd",
|
||||
arguments: arguments
|
||||
};
|
||||
consoleEvent.wrappedJSObject = consoleEvent;
|
||||
Services.obs.notifyObservers(consoleEvent, "console-api-profiler",
|
||||
null);
|
||||
},
|
||||
assert: function CA_assert() {
|
||||
let args = Array.prototype.slice.call(arguments);
|
||||
if(!args.shift()) {
|
||||
self.queueCall("assert", args);
|
||||
}
|
||||
},
|
||||
count: function CA_count() {
|
||||
self.queueCall("count", arguments);
|
||||
},
|
||||
__exposedProps__: {
|
||||
log: "r",
|
||||
info: "r",
|
||||
warn: "r",
|
||||
error: "r",
|
||||
exception: "r",
|
||||
debug: "r",
|
||||
trace: "r",
|
||||
dir: "r",
|
||||
group: "r",
|
||||
groupCollapsed: "r",
|
||||
groupEnd: "r",
|
||||
time: "r",
|
||||
timeEnd: "r",
|
||||
profile: "r",
|
||||
profileEnd: "r",
|
||||
assert: "r",
|
||||
count: "r"
|
||||
}
|
||||
};
|
||||
|
||||
// We need to return an actual content object here, instead of a wrapped
|
||||
// chrome object. This allows things like console.log.bind() to work.
|
||||
let contentObj = Cu.createObjectIn(aWindow);
|
||||
function genPropDesc(fun) {
|
||||
return { enumerable: true, configurable: true, writable: true,
|
||||
value: chromeObject[fun].bind(chromeObject) };
|
||||
}
|
||||
const properties = {
|
||||
log: genPropDesc('log'),
|
||||
info: genPropDesc('info'),
|
||||
warn: genPropDesc('warn'),
|
||||
error: genPropDesc('error'),
|
||||
exception: genPropDesc('exception'),
|
||||
debug: genPropDesc('debug'),
|
||||
trace: genPropDesc('trace'),
|
||||
dir: genPropDesc('dir'),
|
||||
group: genPropDesc('group'),
|
||||
groupCollapsed: genPropDesc('groupCollapsed'),
|
||||
groupEnd: genPropDesc('groupEnd'),
|
||||
time: genPropDesc('time'),
|
||||
timeEnd: genPropDesc('timeEnd'),
|
||||
profile: genPropDesc('profile'),
|
||||
profileEnd: genPropDesc('profileEnd'),
|
||||
assert: genPropDesc('assert'),
|
||||
count: genPropDesc('count'),
|
||||
__noSuchMethod__: { enumerable: true, configurable: true, writable: true,
|
||||
value: function() {} },
|
||||
__mozillaConsole__: { value: true }
|
||||
};
|
||||
|
||||
Object.defineProperties(contentObj, properties);
|
||||
Cu.makeObjectPropsNormal(contentObj);
|
||||
|
||||
this._queuedCalls = [];
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._window = Cu.getWeakReference(aWindow);
|
||||
this.timerRegistry = new Map();
|
||||
this.counterRegistry = new Map();
|
||||
|
||||
return contentObj;
|
||||
},
|
||||
|
||||
observe: function CA_observe(aSubject, aTopic, aData)
|
||||
{
|
||||
if (aTopic == "inner-window-destroyed") {
|
||||
let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (innerWindowID == this._innerID) {
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed");
|
||||
this._windowDestroyed = true;
|
||||
if (!this._timerInitialized) {
|
||||
this.timerRegistry.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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, aStack = null)
|
||||
{
|
||||
let window = this._window.get();
|
||||
let metaForCall = {
|
||||
private: PrivateBrowsingUtils.isWindowPrivate(window),
|
||||
timeStamp: Date.now(),
|
||||
stack: (aStack ? aStack : this.getStackTrace(aMethod != "trace" ? 1 : null)),
|
||||
};
|
||||
|
||||
if (aMethod == "time" || aMethod == "timeEnd") {
|
||||
metaForCall.monotonicTimer = window.performance.now();
|
||||
}
|
||||
|
||||
this._queuedCalls.push([aMethod, aArguments, metaForCall]);
|
||||
|
||||
if (!this._timerInitialized) {
|
||||
this._timer.initWithCallback(this._timerCallback.bind(this), CALL_DELAY,
|
||||
Ci.nsITimer.TYPE_REPEATING_SLACK);
|
||||
this._timerInitialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Timer callback used to process each of the queued calls.
|
||||
* @private
|
||||
*/
|
||||
_timerCallback: function CA__timerCallback()
|
||||
{
|
||||
this._queuedCalls.splice(0, MESSAGES_IN_INTERVAL)
|
||||
.forEach(this._processQueuedCall, this);
|
||||
|
||||
if (!this._queuedCalls.length) {
|
||||
this._timerInitialized = false;
|
||||
this._timer.cancel();
|
||||
|
||||
if (this._windowDestroyed) {
|
||||
ConsoleAPIStorage.clearEvents(this._innerID);
|
||||
this.timerRegistry.clear();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Process a queued call to a console method.
|
||||
*
|
||||
* @private
|
||||
* @param array aCall
|
||||
* Array that holds information about the queued call.
|
||||
*/
|
||||
_processQueuedCall: function CA__processQueuedItem(aCall)
|
||||
{
|
||||
let [method, args, meta] = aCall;
|
||||
|
||||
let frame;
|
||||
if (meta.stack.length) {
|
||||
frame = meta.stack[0];
|
||||
} else {
|
||||
frame = {
|
||||
filename: "",
|
||||
lineNumber: 0,
|
||||
functionName: "",
|
||||
};
|
||||
}
|
||||
|
||||
let consoleEvent = {
|
||||
ID: this._outerID,
|
||||
innerID: this._innerID,
|
||||
level: method,
|
||||
filename: frame.filename,
|
||||
lineNumber: frame.lineNumber,
|
||||
functionName: frame.functionName,
|
||||
timeStamp: meta.timeStamp,
|
||||
arguments: args,
|
||||
private: meta.private,
|
||||
};
|
||||
|
||||
switch (method) {
|
||||
case "log":
|
||||
case "info":
|
||||
case "warn":
|
||||
case "error":
|
||||
case "exception":
|
||||
case "debug":
|
||||
case "assert":
|
||||
consoleEvent.arguments = this.processArguments(args);
|
||||
break;
|
||||
case "trace":
|
||||
consoleEvent.stacktrace = meta.stack;
|
||||
break;
|
||||
case "group":
|
||||
case "groupCollapsed":
|
||||
case "groupEnd":
|
||||
try {
|
||||
consoleEvent.groupName = Array.prototype.join.call(args, " ");
|
||||
}
|
||||
catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
Cu.reportError(ex.stack);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "dir":
|
||||
break;
|
||||
case "time":
|
||||
consoleEvent.timer = this.startTimer(args[0], meta.monotonicTimer);
|
||||
break;
|
||||
case "timeEnd":
|
||||
consoleEvent.timer = this.stopTimer(args[0], meta.monotonicTimer);
|
||||
break;
|
||||
case "count":
|
||||
consoleEvent.counter = this.increaseCounter(frame, args[0]);
|
||||
break;
|
||||
default:
|
||||
// unknown console API method!
|
||||
return;
|
||||
}
|
||||
|
||||
this.notifyObservers(method, consoleEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify all observers of any console API call.
|
||||
*
|
||||
* @param string aLevel
|
||||
* The message level.
|
||||
* @param object aConsoleEvent
|
||||
* The console event object to send to observers for the given console
|
||||
* API call.
|
||||
*/
|
||||
notifyObservers: function CA_notifyObservers(aLevel, aConsoleEvent)
|
||||
{
|
||||
aConsoleEvent.wrappedJSObject = aConsoleEvent;
|
||||
ConsoleAPIStorage.recordEvent(this._innerID, aConsoleEvent);
|
||||
Services.obs.notifyObservers(aConsoleEvent, "console-api-log-event",
|
||||
this._outerID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the console API call arguments in order to perform printf-like
|
||||
* string substitution.
|
||||
*
|
||||
* TODO: object substitution should take into account width and precision
|
||||
* qualifiers (bug 685813).
|
||||
*
|
||||
* @param mixed aArguments
|
||||
* The arguments given to the console API call.
|
||||
**/
|
||||
processArguments: function CA_processArguments(aArguments) {
|
||||
if (aArguments.length < 2 || typeof aArguments[0] != "string") {
|
||||
return aArguments;
|
||||
}
|
||||
|
||||
let args = Array.prototype.slice.call(aArguments);
|
||||
let format = args.shift();
|
||||
let splitter = "%" + format.length + Date.now() + "%";
|
||||
let objects = [];
|
||||
|
||||
// Format specification regular expression.
|
||||
let processed = format.replace(ARGUMENT_PATTERN, function CA_PA_substitute(match, submatch) {
|
||||
switch (submatch) {
|
||||
case "o":
|
||||
objects.push(args.shift());
|
||||
return splitter;
|
||||
case "s":
|
||||
return String(args.shift());
|
||||
case "d":
|
||||
case "i":
|
||||
return parseInt(args.shift());
|
||||
case "f":
|
||||
return parseFloat(args.shift());
|
||||
default:
|
||||
return submatch;
|
||||
};
|
||||
});
|
||||
|
||||
let result = [];
|
||||
let processedArray = processed.split(splitter);
|
||||
processedArray.forEach(function(aValue, aIndex) {
|
||||
if (aValue !== "") {
|
||||
result.push(aValue);
|
||||
}
|
||||
if (objects[aIndex]) {
|
||||
result.push(objects[aIndex]);
|
||||
}
|
||||
});
|
||||
|
||||
return result.concat(args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Build the stacktrace array for the console.trace() call.
|
||||
*
|
||||
* @param number [aMaxDepth=DEFAULT_MAX_STACKTRACE_DEPTH]
|
||||
* Optional maximum stacktrace depth.
|
||||
* @return array
|
||||
* Each element is a stack frame that holds the following properties:
|
||||
* filename, lineNumber, functionName and language.
|
||||
*/
|
||||
getStackTrace: function CA_getStackTrace(aMaxDepth) {
|
||||
if (!aMaxDepth) {
|
||||
aMaxDepth = DEFAULT_MAX_STACKTRACE_DEPTH;
|
||||
}
|
||||
|
||||
let stack = [];
|
||||
let frame = Components.stack.caller.caller;
|
||||
while (frame = frame.caller) {
|
||||
if (frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT ||
|
||||
frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT2) {
|
||||
stack.push({
|
||||
filename: frame.filename,
|
||||
lineNumber: frame.lineNumber,
|
||||
functionName: frame.name,
|
||||
language: frame.language,
|
||||
});
|
||||
if (stack.length == aMaxDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
},
|
||||
|
||||
/*
|
||||
* A registry of started timers.
|
||||
* @type Map
|
||||
*/
|
||||
timerRegistry: null,
|
||||
|
||||
/**
|
||||
* Create a new timer by recording the current time under the specified name.
|
||||
*
|
||||
* @param string aName
|
||||
* The name of the timer.
|
||||
* @param number aTimestamp
|
||||
* A monotonic strictly-increasing timing value that tells when the
|
||||
* timer was started.
|
||||
* @return object
|
||||
* The name property holds the timer name and the started property
|
||||
* holds the time the timer was started. In case of error, it returns
|
||||
* an object with the single property "error" that contains the key
|
||||
* for retrieving the localized error message.
|
||||
**/
|
||||
startTimer: function CA_startTimer(aName, aTimestamp) {
|
||||
if (!aName) {
|
||||
return;
|
||||
}
|
||||
if (this.timerRegistry.size > MAX_PAGE_TIMERS - 1) {
|
||||
return { error: "maxTimersExceeded" };
|
||||
}
|
||||
let key = aName.toString();
|
||||
if (!this.timerRegistry.has(key)) {
|
||||
this.timerRegistry.set(key, aTimestamp);
|
||||
}
|
||||
return { name: aName, started: this.timerRegistry.get(key) };
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the timer with the specified name and retrieve the elapsed time.
|
||||
*
|
||||
* @param string aName
|
||||
* The name of the timer.
|
||||
* @param number aTimestamp
|
||||
* A monotonic strictly-increasing timing value that tells when the
|
||||
* timer was stopped.
|
||||
* @return object
|
||||
* The name property holds the timer name and the duration property
|
||||
* holds the number of milliseconds since the timer was started.
|
||||
**/
|
||||
stopTimer: function CA_stopTimer(aName, aTimestamp) {
|
||||
if (!aName) {
|
||||
return;
|
||||
}
|
||||
let key = aName.toString();
|
||||
if (!this.timerRegistry.has(key)) {
|
||||
return;
|
||||
}
|
||||
let duration = aTimestamp - this.timerRegistry.get(key);
|
||||
this.timerRegistry.delete(key);
|
||||
return { name: aName, duration: duration };
|
||||
},
|
||||
|
||||
/*
|
||||
* A registry of counsole.count() counters.
|
||||
* @type Map
|
||||
*/
|
||||
counterRegistry: null,
|
||||
|
||||
/**
|
||||
* Increases the given counter by one or creates a new counter if the label
|
||||
* is not known so far.
|
||||
*
|
||||
* @param object aFrame
|
||||
* The current stack frame to extract the filename and linenumber
|
||||
* from the console.count() invocation.
|
||||
* @param string aLabel
|
||||
* The label of the counter. If no label is provided, the script url
|
||||
* and line number is used for associating the counters
|
||||
* @return object
|
||||
* The label property holds the counters label and the count property
|
||||
* holds the current count.
|
||||
**/
|
||||
increaseCounter: function CA_increaseCounter(aFrame, aLabel) {
|
||||
let key = null, label = null;
|
||||
try {
|
||||
label = key = aLabel ? aLabel + "" : "";
|
||||
} catch (ex) { }
|
||||
if (!key) {
|
||||
key = aFrame.filename + ":" + aFrame.lineNumber;
|
||||
}
|
||||
let counter = null;
|
||||
if (!this.counterRegistry.has(key)) {
|
||||
if (this.counterRegistry.size > MAX_PAGE_COUNTERS - 1) {
|
||||
return { error: "maxCountersExceeded" };
|
||||
}
|
||||
counter = { label: label, count: 1 };
|
||||
this.counterRegistry.set(key, counter);
|
||||
} else {
|
||||
counter = this.counterRegistry.get(key);
|
||||
counter.count += 1;
|
||||
}
|
||||
return { label: counter.label, count: counter.count };
|
||||
}
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ConsoleAPI]);
|
@ -1,5 +1,2 @@
|
||||
component {b49c18f8-3379-4fc0-8c90-d7772c1a9ff3} ConsoleAPI.js
|
||||
contract @mozilla.org/console-api;1 {b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}
|
||||
category JavaScript-global-property console @mozilla.org/console-api;1
|
||||
component {96cf7855-dfa9-4c6d-8276-f9705b4890f2} ConsoleAPIStorage.js
|
||||
contract @mozilla.org/consoleAPI-storage;1 {96cf7855-dfa9-4c6d-8276-f9705b4890f2}
|
||||
|
@ -50,6 +50,7 @@ EXPORTS += [
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'BarProps.h',
|
||||
'Console.h',
|
||||
'DOMCursor.h',
|
||||
'DOMError.h',
|
||||
'DOMException.h',
|
||||
@ -68,6 +69,7 @@ EXPORTS.mozilla.dom += [
|
||||
UNIFIED_SOURCES += [
|
||||
'BarProps.cpp',
|
||||
'CompositionStringSynthesizer.cpp',
|
||||
'Console.cpp',
|
||||
'Crypto.cpp',
|
||||
'DOMCursor.cpp',
|
||||
'DOMError.cpp',
|
||||
@ -118,7 +120,6 @@ SOURCES += [
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'ConsoleAPI.js',
|
||||
'ConsoleAPI.manifest',
|
||||
'ConsoleAPIStorage.js',
|
||||
'SiteSpecificUserAgent.js',
|
||||
|
@ -207,6 +207,7 @@
|
||||
#include "TimeChangeObserver.h"
|
||||
#include "mozilla/dom/AudioContext.h"
|
||||
#include "mozilla/dom/BrowserElementDictionariesBinding.h"
|
||||
#include "mozilla/dom/Console.h"
|
||||
#include "mozilla/dom/FunctionBinding.h"
|
||||
#include "mozilla/dom/WindowBinding.h"
|
||||
#include "nsITabChild.h"
|
||||
@ -1439,6 +1440,8 @@ nsGlobalWindow::CleanUp()
|
||||
mApplicationCache = nullptr;
|
||||
mIndexedDB = nullptr;
|
||||
|
||||
mConsole = nullptr;
|
||||
|
||||
mPerformance = nullptr;
|
||||
|
||||
#ifdef MOZ_WEBSPEECH
|
||||
@ -1753,6 +1756,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
|
||||
@ -1810,6 +1814,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -13360,6 +13365,27 @@ nsGlobalWindow::SetHasAudioAvailableEventListeners()
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGlobalWindow::GetConsole(nsISupports** aConsole)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<Console> console = GetConsole(rv);
|
||||
console.forget(aConsole);
|
||||
return rv.ErrorCode();
|
||||
}
|
||||
|
||||
Console*
|
||||
nsGlobalWindow::GetConsole(ErrorResult& aRv)
|
||||
{
|
||||
FORWARD_TO_INNER_OR_THROW(GetConsole, (aRv), aRv, nullptr);
|
||||
|
||||
if (!mConsole) {
|
||||
mConsole = new Console(this);
|
||||
}
|
||||
|
||||
return mConsole;
|
||||
}
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
void
|
||||
nsGlobalWindow::EnableNetworkEvent(uint32_t aType)
|
||||
|
@ -106,6 +106,7 @@ namespace mozilla {
|
||||
class Selection;
|
||||
namespace dom {
|
||||
class BarProp;
|
||||
class Console;
|
||||
class Function;
|
||||
class Gamepad;
|
||||
class MediaQueryList;
|
||||
@ -820,6 +821,8 @@ public:
|
||||
mozilla::dom::Navigator* GetNavigator(mozilla::ErrorResult& aError);
|
||||
nsIDOMOfflineResourceList* GetApplicationCache(mozilla::ErrorResult& aError);
|
||||
|
||||
mozilla::dom::Console* GetConsole(mozilla::ErrorResult& aRv);
|
||||
|
||||
protected:
|
||||
bool AlertOrConfirm(bool aAlert, const nsAString& aMessage,
|
||||
mozilla::ErrorResult& aError);
|
||||
@ -1454,6 +1457,7 @@ protected:
|
||||
nsString mDefaultStatus;
|
||||
nsGlobalWindowObserver* mObserver; // Inner windows only.
|
||||
nsCOMPtr<nsIDOMCrypto> mCrypto;
|
||||
nsRefPtr<mozilla::dom::Console> mConsole;
|
||||
|
||||
nsCOMPtr<nsIDOMStorage> mLocalStorage;
|
||||
nsCOMPtr<nsIDOMStorage> mSessionStorage;
|
||||
|
@ -10,6 +10,7 @@ support-files =
|
||||
[test_appname_override.html]
|
||||
[test_bug913761.html]
|
||||
[test_clearTimeoutIntervalNoArg.html]
|
||||
[test_consoleEmptyStack.html]
|
||||
[test_constructor-assignment.html]
|
||||
[test_constructor.html]
|
||||
[test_document.all_unqualified.html]
|
||||
|
27
dom/base/test/test_consoleEmptyStack.html
Normal file
27
dom/base/test/test_consoleEmptyStack.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test for empty stack in console</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
window.setTimeout(console.log.bind(console), 0, "xyz");
|
||||
|
||||
window.addEventListener("fake", console.log.bind(console, "xyz"));
|
||||
|
||||
window.addEventListener("fake", function() {
|
||||
ok(true, "Still alive");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
window.dispatchEvent(new Event("fake"));
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -245,6 +245,10 @@ DOMInterfaces = {
|
||||
'nativeType': 'mozilla::dom::CompositionEvent',
|
||||
},
|
||||
|
||||
'Console': {
|
||||
'implicitJSContext': [ 'trace', 'time', 'timeEnd' ],
|
||||
},
|
||||
|
||||
'ConvolverNode': {
|
||||
'implicitJSContext': [ 'buffer' ],
|
||||
'resultNotAddRefed': [ 'buffer' ],
|
||||
|
@ -24,7 +24,7 @@ interface nsIVariant;
|
||||
* @see <http://www.whatwg.org/html/#window>
|
||||
*/
|
||||
|
||||
[scriptable, uuid(97b6784b-ab12-4f79-8422-d7868a4cc7dc)]
|
||||
[scriptable, uuid(8c115ab3-cf96-492c-850c-3b18056b45e2)]
|
||||
interface nsIDOMWindow : nsISupports
|
||||
{
|
||||
// the current browsing context
|
||||
@ -504,6 +504,11 @@ interface nsIDOMWindow : nsISupports
|
||||
|
||||
[implicit_jscontext] attribute jsval onmouseenter;
|
||||
[implicit_jscontext] attribute jsval onmouseleave;
|
||||
|
||||
/**
|
||||
* Console API
|
||||
*/
|
||||
readonly attribute nsISupports console;
|
||||
};
|
||||
|
||||
[scriptable, uuid(2146c906-57f7-486c-a1b4-8cdb57ef577f)]
|
||||
|
75
dom/webidl/Console.webidl
Normal file
75
dom/webidl/Console.webidl
Normal file
@ -0,0 +1,75 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
[ChromeOnly]
|
||||
interface Console {
|
||||
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);
|
||||
|
||||
[Throws]
|
||||
void profile(any... data);
|
||||
|
||||
[Throws]
|
||||
void profileEnd(any... data);
|
||||
|
||||
void assert(boolean condition, any... data);
|
||||
void ___noSuchMethod__();
|
||||
};
|
||||
|
||||
// This is used to propagate console events to the observers.
|
||||
dictionary ConsoleEvent {
|
||||
(unsigned long or DOMString) ID;
|
||||
(unsigned long or DOMString) innerID;
|
||||
DOMString level = "";
|
||||
DOMString filename = "";
|
||||
unsigned long lineNumber = 0;
|
||||
DOMString functionName = "";
|
||||
double timeStamp = 0;
|
||||
sequence<any> arguments;
|
||||
boolean private = false;
|
||||
sequence<ConsoleStackEntry> stacktrace;
|
||||
DOMString groupName = "";
|
||||
any timer = null;
|
||||
};
|
||||
|
||||
// Event for profile operations
|
||||
dictionary ConsoleProfileEvent {
|
||||
DOMString action = "";
|
||||
sequence<any> arguments;
|
||||
};
|
||||
|
||||
// This dictionary is used to manage stack trace data.
|
||||
dictionary ConsoleStackEntry {
|
||||
DOMString filename = "";
|
||||
unsigned long lineNumber = 0;
|
||||
DOMString functionName = "";
|
||||
unsigned long language = 0;
|
||||
};
|
||||
|
||||
dictionary ConsoleTimerStart {
|
||||
DOMString name = "";
|
||||
double started = 0;
|
||||
};
|
||||
|
||||
dictionary ConsoleTimerEnd {
|
||||
DOMString name = "";
|
||||
double duration = 0;
|
||||
};
|
||||
|
||||
dictionary ConsoleTimerError {
|
||||
DOMString error = "maxTimersExceeded";
|
||||
};
|
@ -342,6 +342,13 @@ Window implements TouchEventHandlers;
|
||||
|
||||
Window implements OnErrorEventHandlerForWindow;
|
||||
|
||||
// ConsoleAPI
|
||||
partial interface Window {
|
||||
[Replaceable, GetterThrows]
|
||||
readonly attribute Console console;
|
||||
};
|
||||
|
||||
|
||||
[ChromeOnly] interface ChromeWindow {
|
||||
[Func="nsGlobalWindow::IsChromeWindow"]
|
||||
const unsigned short STATE_MAXIMIZED = 1;
|
||||
|
@ -55,6 +55,7 @@ WEBIDL_FILES = [
|
||||
'CommandEvent.webidl',
|
||||
'Comment.webidl',
|
||||
'CompositionEvent.webidl',
|
||||
'Console.webidl',
|
||||
'Contacts.webidl',
|
||||
'ConvolverNode.webidl',
|
||||
'Coordinates.webidl',
|
||||
|
@ -107,7 +107,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
|
||||
["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"],
|
||||
#endif
|
||||
["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
|
||||
["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
|
||||
["FindHelper", ["FindInPage:Find", "FindInPage:Prev", "FindInPage:Next", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
|
||||
["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
|
||||
["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
|
||||
|
@ -43,7 +43,6 @@ chrome.jar:
|
||||
content/InputWidgetHelper.js (content/InputWidgetHelper.js)
|
||||
content/WebrtcUI.js (content/WebrtcUI.js)
|
||||
content/MemoryObserver.js (content/MemoryObserver.js)
|
||||
content/ConsoleAPI.js (content/ConsoleAPI.js)
|
||||
content/PluginHelper.js (content/PluginHelper.js)
|
||||
content/OfflineApps.js (content/OfflineApps.js)
|
||||
content/MasterPassword.js (content/MasterPassword.js)
|
||||
|
@ -280,7 +280,6 @@
|
||||
|
||||
; JavaScript components
|
||||
@BINPATH@/components/ConsoleAPI.manifest
|
||||
@BINPATH@/components/ConsoleAPI.js
|
||||
@BINPATH@/components/ConsoleAPIStorage.js
|
||||
@BINPATH@/components/ContactManager.js
|
||||
@BINPATH@/components/ContactManager.manifest
|
||||
|
@ -929,7 +929,7 @@ BrowserTabActor.prototype = {
|
||||
let isNative = false;
|
||||
try {
|
||||
let console = aWindow.wrappedJSObject.console;
|
||||
isNative = "__mozillaConsole__" in console;
|
||||
isNative = console instanceof aWindow.Console;
|
||||
}
|
||||
catch (ex) { }
|
||||
return isNative;
|
||||
|
Loading…
Reference in New Issue
Block a user