Bug 965860 - patch 2 - ConsoleAPI written in C++, r=bz

This commit is contained in:
Andrea Marchesini 2014-02-27 23:39:00 +00:00
parent 07f3b7d454
commit 80210d8dab
24 changed files with 1171 additions and 591 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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");

View File

@ -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
View 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
View 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 */

View File

@ -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]);

View File

@ -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}

View File

@ -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',

View File

@ -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)

View File

@ -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;

View File

@ -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]

View 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>

View File

@ -245,6 +245,10 @@ DOMInterfaces = {
'nativeType': 'mozilla::dom::CompositionEvent',
},
'Console': {
'implicitJSContext': [ 'trace', 'time', 'timeEnd' ],
},
'ConvolverNode': {
'implicitJSContext': [ 'buffer' ],
'resultNotAddRefed': [ 'buffer' ],

View File

@ -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
View 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";
};

View File

@ -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;

View File

@ -55,6 +55,7 @@ WEBIDL_FILES = [
'CommandEvent.webidl',
'Comment.webidl',
'CompositionEvent.webidl',
'Console.webidl',
'Contacts.webidl',
'ConvolverNode.webidl',
'Coordinates.webidl',

View File

@ -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"],

View File

@ -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)

View File

@ -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

View File

@ -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;