mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
76fdf7909d
- Bug 1222098 - Devicemotion event timestamp should return values from Android sensor API and not Gecko. r:smaug (0fb05341d5)
- Bug 1266701 - some header missing in dom/events, r=jwatt (59a8a30af3)
- namespace (c5796648b6)
- Bug 1243555: Remove unnecessary nsDocShell static_cast in EventListenerManager::HandleEventInternal(). r=smaug (21c559122c)
- Bug 1254629 - Let query events fail when content root is wrong; r=masayuki (64454249aa)
- Bug 1224233 - fix crashy usage of IMENotificationSender::Run using on-screen keyboard on Windows, r=smaug (ddcdf13ad0)
- Bug 1252058 IMEContentObserver::IMENotificationSender shouldn't post notifications when IMEContentObserver which is the owner of it stopped observing contents r=smaug (9f4a14f13a)
- Bug 1259671 part.1 Rename InternalScrollPortEvent::orientType to InternalScrollPortEvent::OrientType r=smaug (eaefa4472f)
- Bug 1259671 part.2 Rename InternalScrollPortEvent::vertical to InternalScrollPortEvent::eVertical r=smaug (91bcdcd1df)
- Bug 1259671 part.3 Rename InternalScrollPortEvent::horizontal to InternalScrollPortEvent::eHorizontal r=smaug (f3c206dc4c)
- Bug 1259671 part.4 Rename InternalScrollPortEvent::both to InternalScrollPortEvent::eBoth r=smaug (ac34dc95c1)
- Bug 1259671 part.5 Rename InternalScrollPortEvent::orient to InternalScrollPortEvent::mOrient r=smaug (6736862c75)
- Bug 1262324 (part 1) - Remove "locale.all" prefix from Unix charsets. r=emk. (b337137c3d)
- Bug 1255655 - Const-ify dom encodings and similar arrays. r=baku. (93d79e84a7)
- Bug 1262324 (part 2) - Introduce nsUConvProp. r=emk. (bad497dee6)
- Bug 1257877 - Remove UTF-16 support from TextEncoder. r=hsivonen (ad647b12a3)
- Bug 1120813 - Add support for the MS932 label of Shift_JIS. r=emk, r=Ms2ger (8a35fd93cf)
- Bug 1250930 - Use correct global when creating a key in GenerateAsymmetricKeyTask r=bz (ba65e0ae34)
- Bug 842818 - Allow calling WebCryptoTask::DispatchWithPromise() from workers r=keeler (e99301ba3e)
- Bug 1251390. Make timer queries available at the appropriate time. r=jgilbert (f1a0dcf7d3)
- Bug 1259696 - Check read buffer mode when doing CopyTexImage. r=jgilbert (fe51211b56)
- Bug 1241042 - Get correct frag varying from angle validator. r=jmuizelaar (29a4298a7e)
- Bug 1244611 - "Using named uniform buffer objects in the fragment shader fails". r=jgilbert (2e05e31d09)
- Bug 1263018 - Only update active prog info if linking active prog. - r=jrmuizel (1603ebdd8c)
- Bug 1257593 - Handle webgl FramebufferTexture2D() with an unbound texture. r=jgilbert (4d75981cb7)
- Bug 1257593 followup, fix bustage from believing a reviewer and then sending a different patch to try (7766b0cdb4)
- Bug 1258061 - Clarify FramebufferTexture2D handling for tex2D and cubeMap; r=jgilbert (0635d9412a)
- Bug 1259449 - Require GLFeature::sync for WebGL's disjoint_timer_query. - r=jrmuizel (b6017134a3)
- Bug 1247804 - Enable seamless cubemaps where available. - r=jrmuizel (e7fd3ec5ed)
- Bug 1247977: More information when we hit the OpenGL error in FakeBlackTexture. r=jgilbert (f6f2c82e33)
- Bug 1255655 - Const-ify sExtensionNames. r=mattwoodrow. (c0c60b34c7)
- Bug 1262757 - Use StaticRefPtr for the global context in GLContextProviderWGL, r=jrmuizel (0cc29a3385)
- Bug 1259811 - Require FBO support for GLContexts. - r=jrmuizel (ec3c1a6045)
- Bug 1234441 - Allow malformed ESSL version string. - r=jrmuizel (8fda1569ab)
- Bug 1199923 - Work around Intel Linux driver lying about max texture size - r=jgilbert (546b7dfe6a)
- Bug 1262265 - Cleanup GLContext symbol init. - r=jrmuizel (8da8ce647a)
- Bug 1232334 - [1.2] Only set context on successful attach. r=snorp (ca29c322bf)
- Bug 1261320 - Check DataSurface is vaild before using, r=milan (be9aebba47)
- Bug 1245868 - repalce pass by value with pass by pointer in IsHeadRequest. r=jst (f9d7f6185f)
- fix tests (3ccf9d1715)
- Bug 1232941 - Register Observer and listen to NS_XPCOM_SHUTDOWN_OBSERVER_ID for GATT, r=shuang (aa367807fa)
- Bug 1239979: Init and uninit all Bluetooth profile managers, r=shuang (78c1ebbd13)
- Bug 1239979: Uninitialized Bluetooth profile managers explictly to release refs, r=shuang (83b5389539)
- Bug 1262630. Replace workers::GetGlobalObjectForGlobal with xpc::NativeGlobal. r=khuey (9f639580be)
- Bug 1255817 part 6. Fix up some comments in CallbackObject that refer to things that no longer exist. r=bholley (575bf90c98)
- Bug 1259545. Remove the JS_SaveFrameChain bits in CallbackObject error reporting, since they are no longer needed. r=bholley (16181ddb38)
- Bug 1260511 part 2. Change XPConnect's handling of exceptions thrown from JS components so that if an nsresult integer is thrown we convert it into an actual exception object before handing it out to content instead of propagating out the numeric value. r=khuey (dc02854e3f)
- Bug 1260511 part 3. In dom::Throw, ignore the pending xpconnect exception if we were given a non-default message string (because we don't want to lose that string). Also, make sure to always clear the pending xpconnect exception there. r=khuey (978a1e9132)
- Bug 1250106 - Correctly set charging status and remaining time when battery level is rounded to 1.0 r=bz (662e18648c)
- Bug 1253641 - DOMException's CC participant should traverse mData. r=khuey. (0a48e3f8e7)
- Bug 1261115 - when Console is running in the main thread the existence of mWindow should always be ensured, r=smaug (caa1efd087)
- Bug 1257208 - Use the nsTextNode concrete type in several places in DirectionalityUtils.cpp instead of nsINode and nsIContent; r=peterv (69529fd8d4)
- Bug 1260982 - BlobFileImpl::GetType() should work also in workers, r=smaug (4b01d269bc)
- Bug 1262104 - Remove a non-used CTOR for BlobImplFile, r=ehsan (37ee0ec6d4)
- Bug 1259477 - Port test_document_register.html to mochitest-plain so that it can be turned on in e10s mode; r=mrbkap (411c220cda)
- Bug 1222128 - Turn test_bug1011748.html into a browser mochitest to make it run properly in e10s mode r=bzbarsky (45403d3d15)
- Bug 1259588 - new File("") throws TypeError exception, r=baku (05f6e7292d)
- Bug 1264710 - Catch IDB exceptions in IndexedDBHelper. r=fabrice (0d3c860a89)
- Bug 1263553 - Move MultipartBlobImpl into mozilla::dom namespace, r=smaug (e286c6cfe1)
- Bug 1263551 - Remove unused method in MultipartBlobImpl, r=smaug (54c2da9a12)
- Bug 1252687 - get rid of static nsStrings in PerformanceObserver.cpp r=bz (319f2697d4)
- Bug 1148535 - Check if the density descriptor in srcset consists of a valid floating-point number. r=jdm (79ac8d8dea)
- Bug 1257742 - Part 1: Follow the update-source-set rule to append default source into source set; r=jdm (853c69cc8c)
- Bug 1257742 - Part 2: Allow both width and static density candidates showing in same selector; r=jdm (4f1e00225d)
- Bug 1257742 - Part 3: Support using floating point in sizes descriptor; r=jdm (c1a7e36bb3)
- Bug 1257742 - Part 4: Update web-platform test expectation; r=jdm (e41044b88e)
- Bug 1262942 - Remove unnecessary warning message in ResponsiveImageSelector; r=jdm (ffb757204d)
- Bug 1158412 - Remove assertion for document prescontex and add crash test; r=jdm (947ccdfbfc)
- Bug 1237633 - Part 1: Percentages are not allowed in a <source-size-value>. r=jdm (614b560097)
- Bug 1237633 - Part 2: Avoid fatal assertion when a responsive image's size specifier is invalid. r=johns, r=jdm (4e90829d97)
- Bug 495546 - Add crashtest. (c2765ecbf4)
- fix some tests (d9b393b168)
- Bug 1256419. Null-check our nsDOMWindowList before trying to get its length. r=smaug (6c14430e5d)
- Bug 1162775: Make contentAreaDropListener use dataTransfer.files to get the files dropped. r=smaug (d2850f2008)
- Bug 1220679 - replace AutoSafeJSContext with AutoJSAPI. r=bz. (64538bdd44)
- reinstantiate assert, present in up to esr68 (221cec538a)
- Bug 1209329 - Improve comments about about: URIs in nsContentUtils::InternalStorageAllowedForPrincipal, r=bholley (71152e5639)
- Bug 1246250 - Deal with failure to create a blob actor. r=khuey (102686ac28)
- Bug 1265902 - part 1 - be more efficient when using nsContentUtils::GetSurfaceData(); r=mccr8 (ba2a52abd3)
- Bug 1258857 - Add empty items to an IPC transferable object for every flavor of the source object that did not have any data associated to it r=enndeakin (0a02b61566)
- Bug 1265902 - part 2 - don't construct unnecessary string temporaries in TransferablesToIPCTransferables; r=mccr8 (aec10c8fc6)
- remove unknown blob handling, not found in Tycho-dev repo, nor esr60 or TFF (eb6a24720a)
- Bug 1155486 - Convert nsDOMAttributeMap::mLocalName to void* to ensure that we can never dereference it; r=baku (34e2864340)
- Bug 1250926 - Remove unused SCRIPTABLE_FLAGS defines from nsDOMClassInfo; r=peterv (76917fb76b)
- No bug. Helper tool to partially autogenerate portions of the release notes. (3818e5534b)
- Bug 1203423 - Move call to AddClone outside nsMutationReceiver constructor; r=smaug (35c94ad785)
- Bug 1254096 - Update CaretPositionFromPoint() for type=number, r=ehsan (df31edca8f)
- Bug 1265771 P1 Only store active documents in the global observer list. r=bz (01502e91e5)
- Bug 1265771 P2 Expand navigate-window.https.html wpt test to cover uncontrolled windows. r=bz (3333906720)
- Bug 1265771 P3 Expand browser_force_refresh.js to verify Clients.matchAll() behavior on refresh. r=bz (01394ec8f1)
- stop hiding things for _LIBCPP_VERSION (5de86e8bbf)
2415 lines
64 KiB
C++
2415 lines
64 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=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/. */
|
|
|
|
#include "mozilla/dom/Console.h"
|
|
#include "mozilla/dom/ConsoleBinding.h"
|
|
|
|
#include "mozilla/dom/BlobBinding.h"
|
|
#include "mozilla/dom/Exceptions.h"
|
|
#include "mozilla/dom/File.h"
|
|
#include "mozilla/dom/FunctionBinding.h"
|
|
#include "mozilla/dom/StructuredCloneHolder.h"
|
|
#include "mozilla/dom/ToJSValue.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsDocument.h"
|
|
#include "nsDOMNavigationTiming.h"
|
|
#include "nsGlobalWindow.h"
|
|
#include "nsJSUtils.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPerformance.h"
|
|
#include "ScriptSettings.h"
|
|
#include "WorkerPrivate.h"
|
|
#include "WorkerRunnable.h"
|
|
#include "xpcprivate.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDocShell.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "mozilla/ConsoleTimelineMarker.h"
|
|
#include "mozilla/TimestampTimelineMarker.h"
|
|
|
|
#include "nsIConsoleAPIStorage.h"
|
|
#include "nsIDOMWindowUtils.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsIProgrammingLanguage.h"
|
|
#include "nsISensitiveInfoHiddenURI.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsIWebNavigation.h"
|
|
#include "nsIXPConnect.h"
|
|
|
|
// The maximum allowed number of concurrent timers per page.
|
|
#define MAX_PAGE_TIMERS 10000
|
|
|
|
// The maximum allowed number of concurrent counters per page.
|
|
#define MAX_PAGE_COUNTERS 10000
|
|
|
|
// The maximum stacktrace depth when populating the stacktrace array used for
|
|
// console.trace().
|
|
#define DEFAULT_MAX_STACKTRACE_DEPTH 200
|
|
|
|
// This tags are used in the Structured Clone Algorithm to move js values from
|
|
// worker thread to main thread
|
|
#define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN
|
|
|
|
// This value is taken from ConsoleAPIStorage.js
|
|
#define STORAGE_MAX_EVENTS 1000
|
|
|
|
using namespace mozilla::dom::exceptions;
|
|
using namespace mozilla::dom::workers;
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
struct
|
|
ConsoleStructuredCloneData
|
|
{
|
|
nsCOMPtr<nsISupports> mParent;
|
|
nsTArray<RefPtr<BlobImpl>> mBlobs;
|
|
};
|
|
|
|
/**
|
|
* Console API in workers uses the Structured Clone Algorithm to move any value
|
|
* from the worker thread to the main-thread. Some object cannot be moved and,
|
|
* in these cases, we convert them to strings.
|
|
* It's not the best, but at least we are able to show something.
|
|
*/
|
|
|
|
class ConsoleCallData final
|
|
{
|
|
public:
|
|
NS_INLINE_DECL_REFCOUNTING(ConsoleCallData)
|
|
|
|
ConsoleCallData()
|
|
: mMethodName(Console::MethodLog)
|
|
, mPrivate(false)
|
|
, mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
|
|
, mStartTimerValue(0)
|
|
, mStartTimerStatus(false)
|
|
, mStopTimerDuration(0)
|
|
, mStopTimerStatus(false)
|
|
, mCountValue(MAX_PAGE_COUNTERS)
|
|
, mIDType(eUnknown)
|
|
, mOuterIDNumber(0)
|
|
, mInnerIDNumber(0)
|
|
#ifdef DEBUG
|
|
, mOwningThread(PR_GetCurrentThread())
|
|
#endif
|
|
{}
|
|
|
|
bool
|
|
Initialize(JSContext* aCx, Console::MethodName aName,
|
|
const nsAString& aString,
|
|
const Sequence<JS::Value>& aArguments,
|
|
Console* aConsole)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aConsole);
|
|
|
|
// We must be registered before doing any JS operation otherwise it can
|
|
// happen that mCopiedArguments are not correctly traced.
|
|
aConsole->StoreCallData(this);
|
|
|
|
mMethodName = aName;
|
|
mMethodString = aString;
|
|
|
|
mGlobal = JS::CurrentGlobalOrNull(aCx);
|
|
|
|
for (uint32_t i = 0; i < aArguments.Length(); ++i) {
|
|
if (NS_WARN_IF(!mCopiedArguments.AppendElement(aArguments[i]))) {
|
|
aConsole->UnstoreCallData(this);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
SetIDs(uint64_t aOuterID, uint64_t aInnerID)
|
|
{
|
|
MOZ_ASSERT(mIDType == eUnknown);
|
|
|
|
mOuterIDNumber = aOuterID;
|
|
mInnerIDNumber = aInnerID;
|
|
mIDType = eNumber;
|
|
}
|
|
|
|
void
|
|
SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
|
|
{
|
|
MOZ_ASSERT(mIDType == eUnknown);
|
|
|
|
mOuterIDString = aOuterID;
|
|
mInnerIDString = aInnerID;
|
|
mIDType = eString;
|
|
}
|
|
|
|
void
|
|
SetOriginAttributes(const OriginAttributes& aOriginAttributes)
|
|
{
|
|
mOriginAttributes = aOriginAttributes;
|
|
}
|
|
|
|
bool
|
|
PopulateArgumentsSequence(Sequence<JS::Value>& aSequence) const
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
|
|
if (NS_WARN_IF(!aSequence.AppendElement(mCopiedArguments[i],
|
|
fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Trace(const TraceCallbacks& aCallbacks, void* aClosure)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
ConsoleCallData* tmp = this;
|
|
for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCopiedArguments[i])
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal);
|
|
}
|
|
|
|
void
|
|
AssertIsOnOwningThread() const
|
|
{
|
|
MOZ_ASSERT(mOwningThread);
|
|
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
|
|
}
|
|
|
|
JS::Heap<JSObject*> mGlobal;
|
|
|
|
// This is a copy of the arguments we received from the DOM bindings. Console
|
|
// object traces them because this ConsoleCallData calls
|
|
// RegisterConsoleCallData() in the Initialize().
|
|
nsTArray<JS::Heap<JS::Value>> mCopiedArguments;
|
|
|
|
Console::MethodName mMethodName;
|
|
bool mPrivate;
|
|
int64_t mTimeStamp;
|
|
|
|
// These values are set in the owning thread and they contain the timestamp of
|
|
// when the new timer has started, the name of it and the status of the
|
|
// creation of it. If status is false, something went wrong. User
|
|
// DOMHighResTimeStamp instead mozilla::TimeStamp because we use
|
|
// monotonicTimer from Performance.now();
|
|
// They will be set on the owning thread and never touched again on that
|
|
// thread. They will be used in order to create a ConsoleTimerStart dictionary
|
|
// when console.time() is used.
|
|
DOMHighResTimeStamp mStartTimerValue;
|
|
nsString mStartTimerLabel;
|
|
bool mStartTimerStatus;
|
|
|
|
// These values are set in the owning thread and they contain the duration,
|
|
// the name and the status of the StopTimer method. If status is false,
|
|
// something went wrong. They will be set on the owning thread and never
|
|
// touched again on that thread. They will be used in order to create a
|
|
// ConsoleTimerEnd dictionary. This members are set when
|
|
// console.timeEnd() is called.
|
|
double mStopTimerDuration;
|
|
nsString mStopTimerLabel;
|
|
bool mStopTimerStatus;
|
|
|
|
// These 2 values are set by IncreaseCounter on the owning thread and they are
|
|
// used CreateCounterValue. These members are set when console.count() is
|
|
// called.
|
|
nsString mCountLabel;
|
|
uint32_t mCountValue;
|
|
|
|
// The concept of outerID and innerID is misleading because when a
|
|
// ConsoleCallData is created from a window, these are the window IDs, but
|
|
// when the object is created from a SharedWorker, a ServiceWorker or a
|
|
// subworker of a ChromeWorker these IDs are the type of worker and the
|
|
// filename of the callee.
|
|
// In Console.jsm the ID is 'jsm'.
|
|
enum {
|
|
eString,
|
|
eNumber,
|
|
eUnknown
|
|
} mIDType;
|
|
|
|
uint64_t mOuterIDNumber;
|
|
nsString mOuterIDString;
|
|
|
|
uint64_t mInnerIDNumber;
|
|
nsString mInnerIDString;
|
|
|
|
OriginAttributes mOriginAttributes;
|
|
|
|
nsString mMethodString;
|
|
|
|
// Stack management is complicated, because we want to do it as
|
|
// lazily as possible. Therefore, we have the following behavior:
|
|
// 1) mTopStackFrame is initialized whenever we have any JS on the stack
|
|
// 2) mReifiedStack is initialized if we're created in a worker.
|
|
// 3) mStack is set (possibly to null if there is no JS on the stack) if
|
|
// we're created on main thread.
|
|
Maybe<ConsoleStackEntry> mTopStackFrame;
|
|
Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
|
|
nsCOMPtr<nsIStackFrame> mStack;
|
|
|
|
// mStatus is about the lifetime of this object. Console must take care of
|
|
// keep it alive or not following this enumeration.
|
|
enum {
|
|
// If the object is created but it is owned by some runnable, this is its
|
|
// status. It can be deleted at any time.
|
|
eUnused,
|
|
|
|
// When a runnable takes ownership of a ConsoleCallData and send it to
|
|
// different thread, this is its status. Console cannot delete it at this
|
|
// time.
|
|
eInUse,
|
|
|
|
// When a runnable owns this ConsoleCallData, we can't delete it directly.
|
|
// instead, we mark it with this new status and we move it in
|
|
// mCallDataStoragePending list in order to keep it alive an trace it
|
|
// correctly. Once the runnable finishs its task, it will delete this object
|
|
// calling ReleaseCallData().
|
|
eToBeDeleted
|
|
} mStatus;
|
|
|
|
#ifdef DEBUG
|
|
PRThread* mOwningThread;
|
|
#endif
|
|
|
|
private:
|
|
~ConsoleCallData()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus != eInUse);
|
|
}
|
|
};
|
|
|
|
// This class is used to clear any exception at the end of this method.
|
|
class ClearException
|
|
{
|
|
public:
|
|
explicit ClearException(JSContext* aCx)
|
|
: mCx(aCx)
|
|
{
|
|
}
|
|
|
|
~ClearException()
|
|
{
|
|
JS_ClearPendingException(mCx);
|
|
}
|
|
|
|
private:
|
|
JSContext* mCx;
|
|
};
|
|
|
|
class ConsoleRunnable : public nsRunnable
|
|
, public WorkerFeature
|
|
, public StructuredCloneHolderBase
|
|
{
|
|
public:
|
|
explicit ConsoleRunnable(Console* aConsole)
|
|
: mWorkerPrivate(GetCurrentThreadWorkerPrivate())
|
|
, mConsole(aConsole)
|
|
{
|
|
MOZ_ASSERT(mWorkerPrivate);
|
|
}
|
|
|
|
virtual
|
|
~ConsoleRunnable()
|
|
{
|
|
// Clear the StructuredCloneHolderBase class.
|
|
Clear();
|
|
}
|
|
|
|
bool
|
|
Dispatch(JSContext* aCx)
|
|
{
|
|
if (!DispatchInternal(aCx)) {
|
|
ReleaseData();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool Notify(workers::Status aStatus) override
|
|
{
|
|
// We don't care about the notification. We just want to keep the
|
|
// mWorkerPrivate alive.
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
NS_IMETHOD
|
|
Run() override
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
// Walk up to our containing page
|
|
WorkerPrivate* wp = mWorkerPrivate;
|
|
while (wp->GetParent()) {
|
|
wp = wp->GetParent();
|
|
}
|
|
|
|
nsPIDOMWindow* window = wp->GetWindow();
|
|
if (!window) {
|
|
RunWindowless();
|
|
} else {
|
|
RunWithWindow(window);
|
|
}
|
|
|
|
PostDispatch();
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
DispatchInternal(JSContext* aCx)
|
|
{
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
if (NS_WARN_IF(!PreDispatch(aCx))) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(this)))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PostDispatch()
|
|
{
|
|
class ConsoleReleaseRunnable final : public MainThreadWorkerControlRunnable
|
|
{
|
|
RefPtr<ConsoleRunnable> mRunnable;
|
|
|
|
public:
|
|
ConsoleReleaseRunnable(WorkerPrivate* aWorkerPrivate,
|
|
ConsoleRunnable* aRunnable)
|
|
: MainThreadWorkerControlRunnable(aWorkerPrivate)
|
|
, mRunnable(aRunnable)
|
|
{
|
|
MOZ_ASSERT(aRunnable);
|
|
}
|
|
|
|
// If something goes wrong, we still need to release the ConsoleCallData
|
|
// object. For this reason we have a custom Cancel method.
|
|
nsresult
|
|
Cancel() override
|
|
{
|
|
mRunnable->ReleaseData();
|
|
mRunnable->mConsole = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
virtual bool
|
|
WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
|
|
{
|
|
MOZ_ASSERT(aWorkerPrivate);
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
Cancel();
|
|
|
|
aWorkerPrivate->RemoveFeature(mRunnable);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
~ConsoleReleaseRunnable()
|
|
{}
|
|
};
|
|
|
|
RefPtr<WorkerControlRunnable> runnable =
|
|
new ConsoleReleaseRunnable(mWorkerPrivate, this);
|
|
NS_WARN_IF(!runnable->Dispatch());
|
|
}
|
|
|
|
void
|
|
RunWithWindow(nsPIDOMWindow* aWindow)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
AutoJSAPI jsapi;
|
|
MOZ_ASSERT(aWindow);
|
|
|
|
RefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(aWindow);
|
|
if (NS_WARN_IF(!jsapi.Init(win))) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aWindow->IsInnerWindow());
|
|
nsPIDOMWindow* outerWindow = aWindow->GetOuterWindow();
|
|
if (NS_WARN_IF(!outerWindow)) {
|
|
return;
|
|
}
|
|
|
|
RunConsole(jsapi.cx(), outerWindow, aWindow);
|
|
}
|
|
|
|
void
|
|
RunWindowless()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
WorkerPrivate* wp = mWorkerPrivate;
|
|
while (wp->GetParent()) {
|
|
wp = wp->GetParent();
|
|
}
|
|
|
|
MOZ_ASSERT(!wp->GetWindow());
|
|
|
|
AutoSafeJSContext cx;
|
|
|
|
JS::Rooted<JSObject*> global(cx, mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal()));
|
|
if (NS_WARN_IF(!global)) {
|
|
return;
|
|
}
|
|
|
|
// The CreateSandbox call returns a proxy to the actual sandbox object. We
|
|
// don't need a proxy here.
|
|
global = js::UncheckedUnwrap(global);
|
|
|
|
JSAutoCompartment ac(cx, global);
|
|
|
|
RunConsole(cx, nullptr, nullptr);
|
|
}
|
|
|
|
protected:
|
|
// This method is called in the owning thread of the Console object.
|
|
virtual bool
|
|
PreDispatch(JSContext* aCx) = 0;
|
|
|
|
// This method is called in the main-thread.
|
|
virtual void
|
|
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
|
|
nsPIDOMWindow* aInnerWindow) = 0;
|
|
|
|
// This method is called in the owning thread of the Console object.
|
|
virtual void
|
|
ReleaseData() = 0;
|
|
|
|
virtual JSObject* CustomReadHandler(JSContext* aCx,
|
|
JSStructuredCloneReader* aReader,
|
|
uint32_t aTag,
|
|
uint32_t aIndex) override
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (aTag == CONSOLE_TAG_BLOB) {
|
|
MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);
|
|
|
|
JS::Rooted<JS::Value> val(aCx);
|
|
{
|
|
RefPtr<Blob> blob =
|
|
Blob::Create(mClonedData.mParent, mClonedData.mBlobs.ElementAt(aIndex));
|
|
if (!ToJSValue(aCx, blob, &val)) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return &val.toObject();
|
|
}
|
|
|
|
MOZ_CRASH("No other tags are supported.");
|
|
return nullptr;
|
|
}
|
|
|
|
virtual bool CustomWriteHandler(JSContext* aCx,
|
|
JSStructuredCloneWriter* aWriter,
|
|
JS::Handle<JSObject*> aObj) override
|
|
{
|
|
RefPtr<Blob> blob;
|
|
if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) &&
|
|
blob->Impl()->MayBeClonedToOtherThreads()) {
|
|
if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
|
|
mClonedData.mBlobs.Length()))) {
|
|
return false;
|
|
}
|
|
|
|
mClonedData.mBlobs.AppendElement(blob->Impl());
|
|
return true;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
|
|
if (NS_WARN_IF(!jsString)) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
WorkerPrivate* mWorkerPrivate;
|
|
|
|
// This must be released on the worker thread.
|
|
RefPtr<Console> mConsole;
|
|
|
|
ConsoleStructuredCloneData mClonedData;
|
|
};
|
|
|
|
// This runnable appends a CallData object into the Console queue running on
|
|
// the main-thread.
|
|
class ConsoleCallDataRunnable final : public ConsoleRunnable
|
|
{
|
|
public:
|
|
ConsoleCallDataRunnable(Console* aConsole,
|
|
ConsoleCallData* aCallData)
|
|
: ConsoleRunnable(aConsole)
|
|
, mCallData(aCallData)
|
|
{
|
|
MOZ_ASSERT(aCallData);
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
mCallData->AssertIsOnOwningThread();
|
|
}
|
|
|
|
private:
|
|
~ConsoleCallDataRunnable()
|
|
{
|
|
MOZ_ASSERT(!mCallData);
|
|
}
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx) override
|
|
{
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
mCallData->AssertIsOnOwningThread();
|
|
|
|
ClearException ce(aCx);
|
|
|
|
JS::Rooted<JSObject*> arguments(aCx,
|
|
JS_NewArrayObject(aCx, mCallData->mCopiedArguments.Length()));
|
|
if (NS_WARN_IF(!arguments)) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> arg(aCx);
|
|
for (uint32_t i = 0; i < mCallData->mCopiedArguments.Length(); ++i) {
|
|
arg = mCallData->mCopiedArguments[i];
|
|
if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
|
|
JSPROP_ENUMERATE))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
|
|
|
|
if (NS_WARN_IF(!Write(aCx, value))) {
|
|
return false;
|
|
}
|
|
|
|
mCallData->mStatus = ConsoleCallData::eInUse;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
|
|
nsPIDOMWindow* aInnerWindow) override
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
// The windows have to run in parallel.
|
|
MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
|
|
|
|
if (aOuterWindow) {
|
|
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
|
|
|
|
// Save the principal's OriginAttributes in the console event data
|
|
// so that we will be able to filter messages by origin attributes.
|
|
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aInnerWindow);
|
|
if (NS_WARN_IF(!sop)) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
|
|
if (NS_WARN_IF(!principal)) {
|
|
return;
|
|
}
|
|
|
|
mCallData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
|
|
} else {
|
|
ConsoleStackEntry frame;
|
|
if (mCallData->mTopStackFrame) {
|
|
frame = *mCallData->mTopStackFrame;
|
|
}
|
|
|
|
nsString id = frame.mFilename;
|
|
nsString innerID;
|
|
if (mWorkerPrivate->IsSharedWorker()) {
|
|
innerID = NS_LITERAL_STRING("SharedWorker");
|
|
} else if (mWorkerPrivate->IsServiceWorker()) {
|
|
innerID = NS_LITERAL_STRING("ServiceWorker");
|
|
// Use scope as ID so the webconsole can decide if the message should
|
|
// show up per tab
|
|
id.AssignWithConversion(mWorkerPrivate->WorkerName());
|
|
} else {
|
|
innerID = NS_LITERAL_STRING("Worker");
|
|
}
|
|
|
|
mCallData->SetIDs(id, innerID);
|
|
|
|
// Save the principal's OriginAttributes in the console event data
|
|
// so that we will be able to filter messages by origin attributes.
|
|
nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
|
|
if (NS_WARN_IF(!principal)) {
|
|
return;
|
|
}
|
|
|
|
mCallData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
|
|
}
|
|
|
|
// Now we could have the correct window (if we are not window-less).
|
|
mClonedData.mParent = aInnerWindow;
|
|
|
|
ProcessCallData(aCx);
|
|
|
|
mClonedData.mParent = nullptr;
|
|
}
|
|
|
|
virtual void
|
|
ReleaseData() override
|
|
{
|
|
mConsole->AssertIsOnOwningThread();
|
|
|
|
if (mCallData->mStatus == ConsoleCallData::eToBeDeleted) {
|
|
mConsole->ReleaseCallData(mCallData);
|
|
} else {
|
|
MOZ_ASSERT(mCallData->mStatus == ConsoleCallData::eInUse);
|
|
mCallData->mStatus = ConsoleCallData::eUnused;
|
|
}
|
|
|
|
mCallData = nullptr;
|
|
}
|
|
|
|
void
|
|
ProcessCallData(JSContext* aCx)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
ClearException ce(aCx);
|
|
|
|
JS::Rooted<JS::Value> argumentsValue(aCx);
|
|
if (!Read(aCx, &argumentsValue)) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(argumentsValue.isObject());
|
|
|
|
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
|
|
|
|
uint32_t length;
|
|
if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
|
|
return;
|
|
}
|
|
|
|
Sequence<JS::Value> values;
|
|
SequenceRooter<JS::Value> arguments(aCx, &values);
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
|
|
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
|
|
return;
|
|
}
|
|
|
|
if (!values.AppendElement(value, fallible)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(values.Length() == length);
|
|
|
|
mConsole->ProcessCallData(aCx, mCallData, values);
|
|
}
|
|
|
|
RefPtr<ConsoleCallData> mCallData;
|
|
};
|
|
|
|
// This runnable calls ProfileMethod() on the console on the main-thread.
|
|
class ConsoleProfileRunnable final : public ConsoleRunnable
|
|
{
|
|
public:
|
|
ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
|
|
const Sequence<JS::Value>& aArguments)
|
|
: ConsoleRunnable(aConsole)
|
|
, mAction(aAction)
|
|
, mArguments(aArguments)
|
|
{
|
|
MOZ_ASSERT(aConsole);
|
|
}
|
|
|
|
private:
|
|
bool
|
|
PreDispatch(JSContext* aCx) override
|
|
{
|
|
ClearException ce(aCx);
|
|
|
|
JS::Rooted<JSObject*> arguments(aCx,
|
|
JS_NewArrayObject(aCx, mArguments.Length()));
|
|
if (NS_WARN_IF(!arguments)) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> arg(aCx);
|
|
for (uint32_t i = 0; i < mArguments.Length(); ++i) {
|
|
arg = mArguments[i];
|
|
if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
|
|
JSPROP_ENUMERATE))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
|
|
|
|
if (NS_WARN_IF(!Write(aCx, value))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
|
|
nsPIDOMWindow* aInnerWindow) override
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
ClearException ce(aCx);
|
|
|
|
// Now we could have the correct window (if we are not window-less).
|
|
mClonedData.mParent = aInnerWindow;
|
|
|
|
JS::Rooted<JS::Value> argumentsValue(aCx);
|
|
bool ok = Read(aCx, &argumentsValue);
|
|
mClonedData.mParent = nullptr;
|
|
|
|
if (!ok) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(argumentsValue.isObject());
|
|
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
|
|
|
|
uint32_t length;
|
|
if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
|
|
return;
|
|
}
|
|
|
|
Sequence<JS::Value> arguments;
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
|
|
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
|
|
return;
|
|
}
|
|
|
|
if (!arguments.AppendElement(value, fallible)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
mConsole->ProfileMethod(aCx, mAction, arguments);
|
|
}
|
|
|
|
virtual void
|
|
ReleaseData() override
|
|
{}
|
|
|
|
nsString mAction;
|
|
|
|
// This is a reference of the sequence of arguments we receive from the DOM
|
|
// bindings and it's rooted by them. It's only used on the owning thread in
|
|
// PreDispatch().
|
|
const Sequence<JS::Value>& mArguments;
|
|
};
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
|
|
|
|
// We don't need to traverse/unlink mStorage and mSandbox because they are not
|
|
// CCed objects and they are only used on the main thread, even when this
|
|
// Console object is used on workers.
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
|
|
tmp->Shutdown();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
|
|
for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
|
|
tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
|
|
tmp->mCallDataStoragePending[i]->Trace(aCallbacks, aClosure);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
|
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(nsIObserver)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
/* static */ already_AddRefed<Console>
|
|
Console::Create(nsPIDOMWindow* aWindow, ErrorResult& aRv)
|
|
{
|
|
MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
|
|
|
|
RefPtr<Console> console = new Console(aWindow);
|
|
console->Initialize(aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return console.forget();
|
|
}
|
|
|
|
Console::Console(nsPIDOMWindow* aWindow)
|
|
: mWindow(aWindow)
|
|
#ifdef DEBUG
|
|
, mOwningThread(PR_GetCurrentThread())
|
|
#endif
|
|
, mOuterID(0)
|
|
, mInnerID(0)
|
|
, mStatus(eUnknown)
|
|
{
|
|
MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
|
|
|
|
if (mWindow) {
|
|
MOZ_ASSERT(mWindow->IsInnerWindow());
|
|
mInnerID = mWindow->WindowID();
|
|
|
|
// Without outerwindow any console message coming from this object will not
|
|
// shown in the devtools webconsole. But this should be fine because
|
|
// probably we are shutting down, or the window is CCed/GCed.
|
|
nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow();
|
|
if (outerWindow) {
|
|
mOuterID = outerWindow->WindowID();
|
|
}
|
|
}
|
|
|
|
mozilla::HoldJSObjects(this);
|
|
}
|
|
|
|
Console::~Console()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
Shutdown();
|
|
mozilla::DropJSObjects(this);
|
|
}
|
|
|
|
void
|
|
Console::Initialize(ErrorResult& aRv)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eUnknown);
|
|
|
|
if (NS_IsMainThread()) {
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (NS_WARN_IF(!obs)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
aRv = obs->AddObserver(this, "inner-window-destroyed", true);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return;
|
|
}
|
|
|
|
aRv = obs->AddObserver(this, "memory-pressure", true);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
mStatus = eInitialized;
|
|
}
|
|
|
|
void
|
|
Console::Shutdown()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
if (mStatus == eUnknown || mStatus == eShuttingDown) {
|
|
return;
|
|
}
|
|
|
|
if (NS_IsMainThread()) {
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->RemoveObserver(this, "inner-window-destroyed");
|
|
obs->RemoveObserver(this, "memory-pressure");
|
|
}
|
|
}
|
|
|
|
NS_ReleaseOnMainThread(mStorage.forget());
|
|
NS_ReleaseOnMainThread(mSandbox.forget());
|
|
|
|
mTimerRegistry.Clear();
|
|
mCounterRegistry.Clear();
|
|
|
|
mCallDataStorage.Clear();
|
|
mCallDataStoragePending.Clear();
|
|
|
|
mStatus = eShuttingDown;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Console::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (!strcmp(aTopic, "inner-window-destroyed")) {
|
|
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
|
|
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
|
|
|
|
uint64_t innerID;
|
|
nsresult rv = wrapper->GetData(&innerID);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (innerID == mInnerID) {
|
|
Shutdown();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, "memory-pressure")) {
|
|
ClearStorage();
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
Console::ClearStorage()
|
|
{
|
|
mCallDataStorage.Clear();
|
|
}
|
|
|
|
JSObject*
|
|
Console::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return ConsoleBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
#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")
|
|
METHOD(Table, "table")
|
|
METHOD(Clear, "clear")
|
|
|
|
void
|
|
Console::Trace(JSContext* aCx)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
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(Dirxml, "dirxml");
|
|
|
|
METHOD(Group, "group")
|
|
METHOD(GroupCollapsed, "groupCollapsed")
|
|
METHOD(GroupEnd, "groupEnd")
|
|
|
|
void
|
|
Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
Sequence<JS::Value> data;
|
|
SequenceRooter<JS::Value> rooter(aCx, &data);
|
|
|
|
if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
|
|
return;
|
|
}
|
|
|
|
Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
|
|
}
|
|
|
|
void
|
|
Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
Sequence<JS::Value> data;
|
|
SequenceRooter<JS::Value> rooter(aCx, &data);
|
|
|
|
if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
|
|
return;
|
|
}
|
|
|
|
Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
|
|
}
|
|
|
|
void
|
|
Console::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
Sequence<JS::Value> data;
|
|
SequenceRooter<JS::Value> rooter(aCx, &data);
|
|
|
|
if (aData.isString() && !data.AppendElement(aData, fallible)) {
|
|
return;
|
|
}
|
|
|
|
Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
|
|
}
|
|
|
|
void
|
|
Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
|
|
}
|
|
|
|
void
|
|
Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
|
|
}
|
|
|
|
void
|
|
Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
|
const Sequence<JS::Value>& aData)
|
|
{
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
if (!NS_IsMainThread()) {
|
|
// Here we are in a worker thread.
|
|
RefPtr<ConsoleProfileRunnable> runnable =
|
|
new ConsoleProfileRunnable(this, aAction, aData);
|
|
|
|
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
runnable->Dispatch(aCx);
|
|
return;
|
|
}
|
|
|
|
ClearException ce(aCx);
|
|
|
|
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) {
|
|
if (!sequence.AppendElement(aData[i], fallible)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> eventValue(aCx);
|
|
if (!ToJSValue(aCx, event, &eventValue)) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
|
|
MOZ_ASSERT(eventObj);
|
|
|
|
if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
|
|
JSPROP_ENUMERATE)) {
|
|
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)))) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
|
|
}
|
|
}
|
|
|
|
void
|
|
Console::Assert(JSContext* aCx, bool aCondition,
|
|
const Sequence<JS::Value>& aData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
if (!aCondition) {
|
|
Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
|
|
}
|
|
}
|
|
|
|
METHOD(Count, "count")
|
|
|
|
void
|
|
Console::NoopMethod()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
// Nothing to do.
|
|
}
|
|
|
|
static
|
|
nsresult
|
|
StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
|
|
ConsoleStackEntry& aStackEntry)
|
|
{
|
|
MOZ_ASSERT(aStackFrame);
|
|
|
|
nsresult rv = aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
int32_t lineNumber;
|
|
rv = aStackFrame->GetLineNumber(aCx, &lineNumber);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aStackEntry.mLineNumber = lineNumber;
|
|
|
|
int32_t columnNumber;
|
|
rv = aStackFrame->GetColumnNumber(aCx, &columnNumber);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aStackEntry.mColumnNumber = columnNumber;
|
|
|
|
rv = aStackFrame->GetName(aCx, aStackEntry.mFunctionName);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsString cause;
|
|
rv = aStackFrame->GetAsyncCause(aCx, cause);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!cause.IsEmpty()) {
|
|
aStackEntry.mAsyncCause.Construct(cause);
|
|
}
|
|
|
|
aStackEntry.mLanguage = nsIProgrammingLanguage::JAVASCRIPT;
|
|
return NS_OK;
|
|
}
|
|
|
|
static
|
|
nsresult
|
|
ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
|
|
nsTArray<ConsoleStackEntry>& aRefiedStack)
|
|
{
|
|
nsCOMPtr<nsIStackFrame> stack(aStack);
|
|
|
|
while (stack) {
|
|
ConsoleStackEntry& data = *aRefiedStack.AppendElement();
|
|
nsresult rv = StackFrameToStackEntry(aCx, stack, data);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIStackFrame> caller;
|
|
rv = stack->GetCaller(aCx, getter_AddRefs(caller));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!caller) {
|
|
rv = stack->GetAsyncCaller(aCx, getter_AddRefs(caller));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
stack.swap(caller);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mStatus == eInitialized);
|
|
|
|
RefPtr<ConsoleCallData> callData(new ConsoleCallData());
|
|
|
|
ClearException ce(aCx);
|
|
|
|
if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
|
|
aData, this))) {
|
|
return;
|
|
}
|
|
|
|
if (mWindow) {
|
|
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
|
|
if (!webNav) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
|
|
MOZ_ASSERT(loadContext);
|
|
|
|
loadContext->GetUsePrivateBrowsing(&callData->mPrivate);
|
|
|
|
// Save the principal's OriginAttributes in the console event data
|
|
// so that we will be able to filter messages by origin attributes.
|
|
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mWindow);
|
|
if (NS_WARN_IF(!sop)) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
|
|
if (NS_WARN_IF(!principal)) {
|
|
return;
|
|
}
|
|
|
|
callData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
|
|
}
|
|
|
|
uint32_t maxDepth = ShouldIncludeStackTrace(aMethodName) ?
|
|
DEFAULT_MAX_STACKTRACE_DEPTH : 1;
|
|
nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
|
|
|
|
if (stack) {
|
|
callData->mTopStackFrame.emplace();
|
|
nsresult rv = StackFrameToStackEntry(aCx, stack,
|
|
*callData->mTopStackFrame);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (NS_IsMainThread()) {
|
|
callData->mStack = stack;
|
|
} else {
|
|
// nsIStackFrame is not threadsafe, so we need to snapshot it now,
|
|
// before we post our runnable to the main thread.
|
|
callData->mReifiedStack.emplace();
|
|
nsresult rv = ReifyStack(aCx, stack, *callData->mReifiedStack);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
DOMHighResTimeStamp monotonicTimer;
|
|
|
|
// Monotonic timer for 'time' and 'timeEnd'
|
|
if (aMethodName == MethodTime ||
|
|
aMethodName == MethodTimeEnd ||
|
|
aMethodName == MethodTimeStamp) {
|
|
if (mWindow) {
|
|
nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get());
|
|
MOZ_ASSERT(win);
|
|
|
|
RefPtr<nsPerformance> performance = win->GetPerformance();
|
|
if (!performance) {
|
|
return;
|
|
}
|
|
|
|
monotonicTimer = performance->Now();
|
|
|
|
nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
|
|
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
|
|
bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
|
|
|
|
// The 'timeStamp' recordings do not need an argument; use empty string
|
|
// if no arguments passed in.
|
|
if (isTimelineRecording && aMethodName == MethodTimeStamp) {
|
|
JS::Rooted<JS::Value> value(aCx, aData.Length() == 0
|
|
? JS_GetEmptyStringValue(aCx)
|
|
: aData[0]);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
|
|
|
|
nsAutoJSString key;
|
|
if (jsString) {
|
|
key.init(aCx, jsString);
|
|
}
|
|
|
|
timelines->AddMarkerForDocShell(docShell, Move(
|
|
MakeUnique<TimestampTimelineMarker>(key)));
|
|
}
|
|
// For `console.time(foo)` and `console.timeEnd(foo)`.
|
|
else if (isTimelineRecording && aData.Length() == 1) {
|
|
JS::Rooted<JS::Value> value(aCx, aData[0]);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
|
|
|
|
if (jsString) {
|
|
nsAutoJSString key;
|
|
if (key.init(aCx, jsString)) {
|
|
timelines->AddMarkerForDocShell(docShell, Move(
|
|
MakeUnique<ConsoleTimelineMarker>(
|
|
key, aMethodName == MethodTime ? MarkerTracingType::START
|
|
: MarkerTracingType::END)));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
|
MOZ_ASSERT(workerPrivate);
|
|
|
|
TimeDuration duration =
|
|
mozilla::TimeStamp::Now() - workerPrivate->NowBaseTimeStamp();
|
|
|
|
monotonicTimer = duration.ToMilliseconds();
|
|
}
|
|
}
|
|
|
|
if (aMethodName == MethodTime && !aData.IsEmpty()) {
|
|
callData->mStartTimerStatus = StartTimer(aCx, aData[0],
|
|
monotonicTimer,
|
|
callData->mStartTimerLabel,
|
|
&callData->mStartTimerValue);
|
|
}
|
|
|
|
else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
|
|
callData->mStopTimerStatus = StopTimer(aCx, aData[0],
|
|
monotonicTimer,
|
|
callData->mStopTimerLabel,
|
|
&callData->mStopTimerDuration);
|
|
}
|
|
|
|
else if (aMethodName == MethodCount) {
|
|
ConsoleStackEntry frame;
|
|
if (callData->mTopStackFrame) {
|
|
frame = *callData->mTopStackFrame;
|
|
}
|
|
|
|
callData->mCountValue = IncreaseCounter(aCx, frame, aData,
|
|
callData->mCountLabel);
|
|
}
|
|
|
|
if (NS_IsMainThread()) {
|
|
callData->SetIDs(mOuterID, mInnerID);
|
|
ProcessCallData(aCx, callData, aData);
|
|
|
|
// Just because we don't want to expose
|
|
// retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
|
|
// cleanup the mCallDataStorage:
|
|
UnstoreCallData(callData);
|
|
return;
|
|
}
|
|
|
|
// We do this only in workers for now.
|
|
NotifyHandler(aCx, aData, callData);
|
|
|
|
RefPtr<ConsoleCallDataRunnable> runnable =
|
|
new ConsoleCallDataRunnable(this, callData);
|
|
NS_WARN_IF(!runnable->Dispatch(aCx));
|
|
}
|
|
|
|
// We store information to lazily compute the stack in the reserved slots of
|
|
// LazyStackGetter. The first slot always stores a JS object: it's either the
|
|
// JS wrapper of the nsIStackFrame or the actual reified stack representation.
|
|
// The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
|
|
// reified the stack yet, or an UndefinedValue() otherwise.
|
|
enum {
|
|
SLOT_STACKOBJ,
|
|
SLOT_RAW_STACK
|
|
};
|
|
|
|
bool
|
|
LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
|
{
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
JS::Rooted<JSObject*> callee(aCx, &args.callee());
|
|
|
|
JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
|
|
if (v.isUndefined()) {
|
|
// Already reified.
|
|
args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
|
|
return true;
|
|
}
|
|
|
|
nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
|
|
nsTArray<ConsoleStackEntry> reifiedStack;
|
|
nsresult rv = ReifyStack(aCx, stack, reifiedStack);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Throw(aCx, rv);
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> stackVal(aCx);
|
|
if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(stackVal.isObject());
|
|
|
|
js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
|
|
js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
|
|
|
|
args.rval().set(stackVal);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Console::ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
|
|
const Sequence<JS::Value>& aArguments)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aData);
|
|
|
|
JS::Rooted<JS::Value> eventValue(aCx);
|
|
|
|
// We want to create a console event object and pass it to our
|
|
// nsIConsoleAPIStorage implementation. We want to define some accessor
|
|
// properties on this object, and those will need to keep an nsIStackFrame
|
|
// alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
|
|
// further, passing untrusted objects to system code is likely to run afoul of
|
|
// Object Xrays. So we want to wrap in a system-principal scope here. But
|
|
// which one? We could cheat and try to get the underlying JSObject* of
|
|
// mStorage, but that's a bit fragile. Instead, we just use the junk scope,
|
|
// with explicit permission from the XPConnect module owner. If you're
|
|
// tempted to do that anywhere else, talk to said module owner first.
|
|
|
|
// aCx and aArguments are in the same compartment.
|
|
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
|
|
xpc::PrivilegedJunkScope(),
|
|
&eventValue, aData))) {
|
|
return;
|
|
}
|
|
|
|
if (!mStorage) {
|
|
mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
|
|
}
|
|
|
|
if (!mStorage) {
|
|
NS_WARNING("Failed to get the ConsoleAPIStorage service.");
|
|
return;
|
|
}
|
|
|
|
nsAutoString innerID, outerID;
|
|
|
|
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
|
|
if (aData->mIDType == ConsoleCallData::eString) {
|
|
outerID = aData->mOuterIDString;
|
|
innerID = aData->mInnerIDString;
|
|
} else {
|
|
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
|
|
outerID.AppendInt(aData->mOuterIDNumber);
|
|
innerID.AppendInt(aData->mInnerIDNumber);
|
|
}
|
|
|
|
if (aData->mMethodName == MethodClear) {
|
|
nsresult rv = mStorage->ClearEvents(innerID);
|
|
NS_WARN_IF(NS_FAILED(rv));
|
|
}
|
|
|
|
if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
|
|
NS_WARNING("Failed to record a console event.");
|
|
}
|
|
}
|
|
|
|
bool
|
|
Console::PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
|
|
const Sequence<JS::Value>& aArguments,
|
|
JSObject* aTargetScope,
|
|
JS::MutableHandle<JS::Value> aEventValue,
|
|
ConsoleCallData* aData) const
|
|
{
|
|
MOZ_ASSERT(aCx);
|
|
MOZ_ASSERT(aData);
|
|
MOZ_ASSERT(aTargetScope);
|
|
|
|
JS::Rooted<JSObject*> targetScope(aCx, aTargetScope);
|
|
|
|
ConsoleStackEntry frame;
|
|
if (aData->mTopStackFrame) {
|
|
frame = *aData->mTopStackFrame;
|
|
}
|
|
|
|
ClearException ce(aCx);
|
|
RootedDictionary<ConsoleEvent> event(aCx);
|
|
|
|
// Save the principal's OriginAttributes in the console event data
|
|
// so that we will be able to filter messages by origin attributes.
|
|
JS::Rooted<JS::Value> originAttributesValue(aCx);
|
|
if (ToJSValue(aCx, aData->mOriginAttributes, &originAttributesValue)) {
|
|
event.mOriginAttributes = originAttributesValue;
|
|
}
|
|
|
|
event.mID.Construct();
|
|
event.mInnerID.Construct();
|
|
|
|
if (aData->mIDType == ConsoleCallData::eString) {
|
|
event.mID.Value().SetAsString() = aData->mOuterIDString;
|
|
event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
|
|
} else if (aData->mIDType == ConsoleCallData::eNumber) {
|
|
event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
|
|
event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
|
|
} else {
|
|
// aData->mIDType can be eUnknown when we dispatch notifications via
|
|
// mConsoleEventNotifier.
|
|
event.mID.Value().SetAsUnsignedLongLong() = 0;
|
|
event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
|
|
}
|
|
|
|
event.mLevel = aData->mMethodString;
|
|
event.mFilename = frame.mFilename;
|
|
|
|
nsCOMPtr<nsIURI> filenameURI;
|
|
nsAutoCString pass;
|
|
if (NS_IsMainThread() &&
|
|
NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
|
|
NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
|
|
nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
|
|
nsAutoCString spec;
|
|
if (safeURI &&
|
|
NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
|
|
CopyUTF8toUTF16(spec, event.mFilename);
|
|
}
|
|
}
|
|
|
|
event.mLineNumber = frame.mLineNumber;
|
|
event.mColumnNumber = frame.mColumnNumber;
|
|
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();
|
|
event.mStyles.Construct();
|
|
if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
|
|
event.mArguments.Value(),
|
|
event.mStyles.Value()))) {
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
event.mArguments.Construct();
|
|
if (NS_WARN_IF(!ArgumentsToValueList(aArguments,
|
|
event.mArguments.Value()))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (aData->mMethodName == MethodGroup ||
|
|
aData->mMethodName == MethodGroupCollapsed ||
|
|
aData->mMethodName == MethodGroupEnd) {
|
|
ComposeGroupName(aCx, aArguments, event.mGroupName);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
|
|
event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
|
|
aData->mStartTimerValue,
|
|
aData->mStartTimerStatus);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
|
|
event.mTimer = CreateStopTimerValue(aCx, aData->mStopTimerLabel,
|
|
aData->mStopTimerDuration,
|
|
aData->mStopTimerStatus);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodCount) {
|
|
event.mCounter = CreateCounterValue(aCx, aData->mCountLabel,
|
|
aData->mCountValue);
|
|
}
|
|
|
|
JSAutoCompartment ac2(aCx, targetScope);
|
|
|
|
if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
|
|
if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
|
|
JSPROP_ENUMERATE))) {
|
|
return false;
|
|
}
|
|
|
|
if (ShouldIncludeStackTrace(aData->mMethodName)) {
|
|
// Now define the "stacktrace" property on eventObj. There are two cases
|
|
// here. Either we came from a worker and have a reified stack, or we want
|
|
// to define a getter that will lazily reify the stack.
|
|
if (aData->mReifiedStack) {
|
|
JS::Rooted<JS::Value> stacktrace(aCx);
|
|
if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
|
|
NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
|
|
JSPROP_ENUMERATE))) {
|
|
return false;
|
|
}
|
|
} else {
|
|
JSFunction* fun = js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0,
|
|
"stacktrace");
|
|
if (NS_WARN_IF(!fun)) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
|
|
|
|
// We want to store our stack in the function and have it stay alive. But
|
|
// we also need sane access to the C++ nsIStackFrame. So store both a JS
|
|
// wrapper and the raw pointer: the former will keep the latter alive.
|
|
JS::Rooted<JS::Value> stackVal(aCx);
|
|
nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack,
|
|
&stackVal);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
|
|
js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
|
|
JS::PrivateValue(aData->mStack.get()));
|
|
|
|
if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace",
|
|
JS::UndefinedHandleValue,
|
|
JSPROP_ENUMERATE | JSPROP_SHARED |
|
|
JSPROP_GETTER | JSPROP_SETTER,
|
|
JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
|
|
nullptr))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
|
|
bool
|
|
FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &aOutput)
|
|
{
|
|
if (!aOutput.IsEmpty()) {
|
|
JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
|
|
aOutput.get(),
|
|
aOutput.Length()));
|
|
if (NS_WARN_IF(!str)) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
|
|
return false;
|
|
}
|
|
|
|
aOutput.Truncate();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool
|
|
Console::ProcessArguments(JSContext* aCx,
|
|
const Sequence<JS::Value>& aData,
|
|
Sequence<JS::Value>& aSequence,
|
|
Sequence<nsString>& aStyles) const
|
|
{
|
|
if (aData.IsEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
if (aData.Length() == 1 || !aData[0].isString()) {
|
|
return ArgumentsToValueList(aData, aSequence);
|
|
}
|
|
|
|
JS::Rooted<JS::Value> format(aCx, aData[0]);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
|
|
if (NS_WARN_IF(!jsString)) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString string;
|
|
if (NS_WARN_IF(!string.init(aCx, jsString))) {
|
|
return false;
|
|
}
|
|
|
|
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 == end) {
|
|
output.Append('%');
|
|
break;
|
|
}
|
|
|
|
if (*start == '%') {
|
|
output.Append(*start);
|
|
++start;
|
|
continue;
|
|
}
|
|
|
|
nsAutoString tmp;
|
|
tmp.Append('%');
|
|
|
|
int32_t integer = -1;
|
|
int32_t mantissa = -1;
|
|
|
|
// Let's parse %<number>.<number> for %d and %f
|
|
if (*start >= '0' && *start <= '9') {
|
|
integer = 0;
|
|
|
|
do {
|
|
integer = integer * 10 + *start - '0';
|
|
tmp.Append(*start);
|
|
++start;
|
|
} while (*start >= '0' && *start <= '9' && start != end);
|
|
}
|
|
|
|
if (start == end) {
|
|
output.Append(tmp);
|
|
break;
|
|
}
|
|
|
|
if (*start == '.') {
|
|
tmp.Append(*start);
|
|
++start;
|
|
|
|
if (start == end) {
|
|
output.Append(tmp);
|
|
break;
|
|
}
|
|
|
|
// '.' must be followed by a number.
|
|
if (*start < '0' || *start > '9') {
|
|
output.Append(tmp);
|
|
continue;
|
|
}
|
|
|
|
mantissa = 0;
|
|
|
|
do {
|
|
mantissa = mantissa * 10 + *start - '0';
|
|
tmp.Append(*start);
|
|
++start;
|
|
} while (*start >= '0' && *start <= '9' && start != end);
|
|
|
|
if (start == end) {
|
|
output.Append(tmp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
char ch = *start;
|
|
tmp.Append(ch);
|
|
++start;
|
|
|
|
switch (ch) {
|
|
case 'o':
|
|
case 'O':
|
|
{
|
|
if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> v(aCx);
|
|
if (index < aData.Length()) {
|
|
v = aData[index++];
|
|
}
|
|
|
|
if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'c':
|
|
{
|
|
// If there isn't any output but there's already a style, then
|
|
// discard the previous style and use the next one instead.
|
|
if (output.IsEmpty() && !aStyles.IsEmpty()) {
|
|
aStyles.TruncateLength(aStyles.Length() - 1);
|
|
}
|
|
|
|
if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
|
|
return false;
|
|
}
|
|
|
|
if (index < aData.Length()) {
|
|
JS::Rooted<JS::Value> v(aCx, aData[index++]);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
|
|
if (NS_WARN_IF(!jsString)) {
|
|
return false;
|
|
}
|
|
|
|
int32_t diff = aSequence.Length() - aStyles.Length();
|
|
if (diff > 0) {
|
|
for (int32_t i = 0; i < diff; i++) {
|
|
if (NS_WARN_IF(!aStyles.AppendElement(NullString(), fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsAutoJSString string;
|
|
if (NS_WARN_IF(!string.init(aCx, jsString))) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
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 (NS_WARN_IF(!jsString)) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString v;
|
|
if (NS_WARN_IF(!v.init(aCx, jsString))) {
|
|
return false;
|
|
}
|
|
|
|
output.Append(v);
|
|
}
|
|
break;
|
|
|
|
case 'd':
|
|
case 'i':
|
|
if (index < aData.Length()) {
|
|
JS::Rooted<JS::Value> value(aCx, aData[index++]);
|
|
|
|
int32_t v;
|
|
if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
|
|
return false;
|
|
}
|
|
|
|
nsCString format;
|
|
MakeFormatString(format, integer, mantissa, 'd');
|
|
output.AppendPrintf(format.get(), v);
|
|
}
|
|
break;
|
|
|
|
case 'f':
|
|
if (index < aData.Length()) {
|
|
JS::Rooted<JS::Value> value(aCx, aData[index++]);
|
|
|
|
double v;
|
|
if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
|
|
return false;
|
|
}
|
|
|
|
// nspr returns "nan", but we want to expose it as "NaN"
|
|
if (std::isnan(v)) {
|
|
output.AppendFloat(v);
|
|
} else {
|
|
nsCString format;
|
|
MakeFormatString(format, integer, mantissa, 'f');
|
|
output.AppendPrintf(format.get(), v);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
output.Append(tmp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
|
|
return false;
|
|
}
|
|
|
|
// Discard trailing style element if there is no output to apply it to.
|
|
if (aStyles.Length() > aSequence.Length()) {
|
|
aStyles.TruncateLength(aSequence.Length());
|
|
}
|
|
|
|
// The rest of the array, if unused by the format string.
|
|
for (; index < aData.Length(); ++index) {
|
|
if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
|
|
int32_t aMantissa, char aCh) const
|
|
{
|
|
aFormat.Append('%');
|
|
if (aInteger >= 0) {
|
|
aFormat.AppendInt(aInteger);
|
|
}
|
|
|
|
if (aMantissa >= 0) {
|
|
aFormat.Append('.');
|
|
aFormat.AppendInt(aMantissa);
|
|
}
|
|
|
|
aFormat.Append(aCh);
|
|
}
|
|
|
|
void
|
|
Console::ComposeGroupName(JSContext* aCx,
|
|
const Sequence<JS::Value>& aData,
|
|
nsAString& aName) const
|
|
{
|
|
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;
|
|
}
|
|
|
|
nsAutoJSString string;
|
|
if (!string.init(aCx, jsString)) {
|
|
return;
|
|
}
|
|
|
|
aName.Append(string);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Console::StartTimer(JSContext* aCx, const JS::Value& aName,
|
|
DOMHighResTimeStamp aTimestamp,
|
|
nsAString& aTimerLabel,
|
|
DOMHighResTimeStamp* aTimerValue)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aTimerValue);
|
|
|
|
*aTimerValue = 0;
|
|
|
|
if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> name(aCx, aName);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
|
|
if (NS_WARN_IF(!jsString)) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString label;
|
|
if (NS_WARN_IF(!label.init(aCx, jsString))) {
|
|
return false;
|
|
}
|
|
|
|
DOMHighResTimeStamp entry;
|
|
if (!mTimerRegistry.Get(label, &entry)) {
|
|
mTimerRegistry.Put(label, aTimestamp);
|
|
} else {
|
|
aTimestamp = entry;
|
|
}
|
|
|
|
aTimerLabel = label;
|
|
*aTimerValue = aTimestamp;
|
|
return true;
|
|
}
|
|
|
|
JS::Value
|
|
Console::CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
|
|
DOMHighResTimeStamp aTimerValue,
|
|
bool aTimerStatus) const
|
|
{
|
|
if (!aTimerStatus) {
|
|
RootedDictionary<ConsoleTimerError> error(aCx);
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, error, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
RootedDictionary<ConsoleTimerStart> timer(aCx);
|
|
|
|
timer.mName = aTimerLabel;
|
|
timer.mStarted = aTimerValue;
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, timer, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
bool
|
|
Console::StopTimer(JSContext* aCx, const JS::Value& aName,
|
|
DOMHighResTimeStamp aTimestamp,
|
|
nsAString& aTimerLabel,
|
|
double* aTimerDuration)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aTimerDuration);
|
|
|
|
*aTimerDuration = 0;
|
|
|
|
JS::Rooted<JS::Value> name(aCx, aName);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
|
|
if (NS_WARN_IF(!jsString)) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString key;
|
|
if (NS_WARN_IF(!key.init(aCx, jsString))) {
|
|
return false;
|
|
}
|
|
|
|
DOMHighResTimeStamp entry;
|
|
if (NS_WARN_IF(!mTimerRegistry.Get(key, &entry))) {
|
|
return false;
|
|
}
|
|
|
|
mTimerRegistry.Remove(key);
|
|
|
|
aTimerLabel = key;
|
|
*aTimerDuration = aTimestamp - entry;
|
|
return true;
|
|
}
|
|
|
|
JS::Value
|
|
Console::CreateStopTimerValue(JSContext* aCx, const nsAString& aLabel,
|
|
double aDuration, bool aStatus) const
|
|
{
|
|
if (!aStatus) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
RootedDictionary<ConsoleTimerEnd> timer(aCx);
|
|
timer.mName = aLabel;
|
|
timer.mDuration = aDuration;
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, timer, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
bool
|
|
Console::ArgumentsToValueList(const Sequence<JS::Value>& aData,
|
|
Sequence<JS::Value>& aSequence) const
|
|
{
|
|
for (uint32_t i = 0; i < aData.Length(); ++i) {
|
|
if (NS_WARN_IF(!aSequence.AppendElement(aData[i], fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t
|
|
Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
|
|
const Sequence<JS::Value>& aArguments,
|
|
nsAString& aCountLabel)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
ClearException ce(aCx);
|
|
|
|
nsAutoString key;
|
|
nsAutoString label;
|
|
|
|
if (!aArguments.IsEmpty()) {
|
|
JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
|
|
|
|
nsAutoJSString string;
|
|
if (jsString && string.init(aCx, jsString)) {
|
|
label = string;
|
|
key = string;
|
|
}
|
|
}
|
|
|
|
if (key.IsEmpty()) {
|
|
key.Append(aFrame.mFilename);
|
|
key.Append(':');
|
|
key.AppendInt(aFrame.mLineNumber);
|
|
}
|
|
|
|
uint32_t count = 0;
|
|
if (!mCounterRegistry.Get(key, &count) &&
|
|
mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
|
|
return MAX_PAGE_COUNTERS;
|
|
}
|
|
|
|
++count;
|
|
mCounterRegistry.Put(key, count);
|
|
|
|
aCountLabel = label;
|
|
return count;
|
|
}
|
|
|
|
JS::Value
|
|
Console::CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
|
|
uint32_t aCountValue) const
|
|
{
|
|
ClearException ce(aCx);
|
|
|
|
if (aCountValue == MAX_PAGE_COUNTERS) {
|
|
RootedDictionary<ConsoleCounterError> error(aCx);
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, error, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
RootedDictionary<ConsoleCounter> data(aCx);
|
|
data.mLabel = aCountLabel;
|
|
data.mCount = aCountValue;
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, data, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
bool
|
|
Console::ShouldIncludeStackTrace(MethodName aMethodName) const
|
|
{
|
|
switch (aMethodName) {
|
|
case MethodError:
|
|
case MethodException:
|
|
case MethodAssert:
|
|
case MethodTrace:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JSObject*
|
|
Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (!mSandbox) {
|
|
nsIXPConnect* xpc = nsContentUtils::XPConnect();
|
|
MOZ_ASSERT(xpc, "This should never be null!");
|
|
|
|
JS::Rooted<JSObject*> sandbox(aCx);
|
|
nsresult rv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
mSandbox = new JSObjectHolder(aCx, sandbox);
|
|
}
|
|
|
|
return mSandbox->GetJSObject();
|
|
}
|
|
|
|
void
|
|
Console::StoreCallData(ConsoleCallData* aCallData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
MOZ_ASSERT(aCallData);
|
|
MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
|
|
MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
|
|
|
|
mCallDataStorage.AppendElement(aCallData);
|
|
|
|
if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
|
|
RefPtr<ConsoleCallData> callData = mCallDataStorage[0];
|
|
mCallDataStorage.RemoveElementAt(0);
|
|
|
|
MOZ_ASSERT(callData->mStatus != ConsoleCallData::eToBeDeleted);
|
|
|
|
// We cannot delete this object now because we have to trace its JSValues
|
|
// until the pending operation (ConsoleCallDataRunnable) is completed.
|
|
if (callData->mStatus == ConsoleCallData::eInUse) {
|
|
callData->mStatus = ConsoleCallData::eToBeDeleted;
|
|
mCallDataStoragePending.AppendElement(callData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Console::UnstoreCallData(ConsoleCallData* aCallData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
MOZ_ASSERT(aCallData);
|
|
MOZ_ASSERT(mCallDataStorage.Contains(aCallData));
|
|
MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
|
|
|
|
mCallDataStorage.RemoveElement(aCallData);
|
|
}
|
|
|
|
void
|
|
Console::ReleaseCallData(ConsoleCallData* aCallData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aCallData);
|
|
MOZ_ASSERT(aCallData->mStatus == ConsoleCallData::eToBeDeleted);
|
|
MOZ_ASSERT(mCallDataStoragePending.Contains(aCallData));
|
|
|
|
mCallDataStoragePending.RemoveElement(aCallData);
|
|
}
|
|
|
|
void
|
|
Console::NotifyHandler(JSContext* aCx, const Sequence<JS::Value>& aArguments,
|
|
ConsoleCallData* aCallData) const
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(aCallData);
|
|
|
|
if (!mConsoleEventNotifier) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
|
|
// aCx and aArguments are in the same compartment because this method is
|
|
// called directly when a Console.something() runs.
|
|
// mConsoleEventNotifier->Callable() is the scope where value will be sent to.
|
|
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
|
|
mConsoleEventNotifier->Callable(),
|
|
&value,
|
|
aCallData))) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> ignored(aCx);
|
|
mConsoleEventNotifier->Call(value, &ignored);
|
|
}
|
|
|
|
void
|
|
Console::RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
|
|
ErrorResult& aRv)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
// We don't want to expose this functionality to main-thread yet.
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
|
|
for (uint32_t i = 0; i < mCallDataStorage.Length(); ++i) {
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
|
|
JS::Rooted<JSObject*> sequenceScope(aCx, mCallDataStorage[i]->mGlobal);
|
|
JSAutoCompartment ac(aCx, sequenceScope);
|
|
|
|
Sequence<JS::Value> sequence;
|
|
SequenceRooter<JS::Value> arguments(aCx, &sequence);
|
|
|
|
if (!mCallDataStorage[i]->PopulateArgumentsSequence(sequence)) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
|
|
// Here we have aCx and sequence in the same compartment.
|
|
// targetScope is the destination scope and value will be populated in its
|
|
// compartment.
|
|
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, sequence,
|
|
targetScope,
|
|
&value,
|
|
mCallDataStorage[i]))) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
aEvents.AppendElement(value);
|
|
}
|
|
}
|
|
|
|
void
|
|
Console::SetConsoleEventHandler(AnyCallback* aHandler)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
// We don't want to expose this functionality to main-thread yet.
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
mConsoleEventNotifier = aHandler;
|
|
}
|
|
|
|
void
|
|
Console::AssertIsOnOwningThread() const
|
|
{
|
|
MOZ_ASSERT(mOwningThread);
|
|
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|