mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
aec5e5666e
- bug 1083930 - cpu spin during large h2/spdy upload r=hurley (0949388a6a)
- Bug 1241906 - Spdy deadlock on suspended channel r=hurley (f40e9677d5)
- Bug 1247205 - dont loop on http2 softerror r=dragana (2ab3cb69ef)
- Bug 1246778 - dont loop in nshttpconnection during shutdown r=dragana (0677b9d34b)
- Bug 1201037 - only send "HTTP pings" on seemingly idle connections, r=mcmanus (134198bc79)
- Bug 1174899 - discarded spdy data with fin attributed to wrong stream r=bagder (f3b801c734)
- Bug 1236170 - Make Http2Session::UncompressAndDiscard push-aware. r=mcmanus (e71634e853)
- Bug 1240025 - incorrect close state on pushed stream r=hurley (eb2832177a)
- Bug 1227931 - init Http2Stream::mReceivedData in the constructor. r=nwgh (44f1d8e897)
- Bug 241788 - net_FilterURIString should filter \r\n\t from the entire URL r=honzab (734d9b8cae)
- Bug 1259459 - h2 0 length options puts end-stream on headers r=hurley (31ac211a9b)
- Bug 1174899 - fixup log format strings for spdy/h2 r=bagder (967c9ff71e)
- Bug 1211694 - dataLength has been added twice. r=mcmanus (6773981db3)
- cleanup (26517f5de0)
- Bug 1247998 - Let nsHttpChannel::AsyncOpen* throw after nsHttpHandler has been shutdown, r=mcmanus (90bb2364be)
- Bug 1231512 - Allow nsIHttpChannel.redirectTo() work also on an open channel, r=jduell (198fb72816)
- Bug 1242472 - Properly propagate mTopWindowURI through redirects. r=francois/ckerschb (1d27a15770)
- Bug 1133873 - some spdy logs r=hurley (cd95cfed5a)
- bug 1215724 - enable brotli on spdy r=hurley (83cca72fa5)
- Bug 137852 - Add a new working HTTP authentication identity to the begining of the session cache list. r=honzab (f670349771)
- Bug 1245414, part 1 - Delete the mfbt/decimal/LICENSE* files since upstream now just uses inline comments. r=Waldo (15bb211e14)
- Bug 1245414, part 2 - Update mfbt/decimal/update.sh to reflect Blink's switch from svn to git, and the different files we now pull. r=Waldo (4dd0b5916a)
- Bug 1245414, part 3 - Overwrite mfbt/decimal/Decimal.* with vanilla upstream copies. r=Waldo (98f7ba4711)
- Bug 1245414, part 4 - Update mfbt/decimal/zero-serialization.patch. r =Waldo (055e1354a7)
- Bug 1245414, part 5 - Update mfbt/decimal/comparison-with-nan.patch. r=Waldo (583e0f3e76)
- Bug 1245414, part 6 - Update mfbt/decimal/mfbt-abi-markers.patch. r=Waldo (148b1ac08b)
- Bug 1245414, part 7 - Update mfbt/decimal/to-moz-dependencies.patch. r=Waldo (2e2a6a33d7)
- Bug 1245414, part 8 - Remove mfbt/decimal/floor-ceiling.patch now that the issue is fixed upstream. r=Waldo (84fc02c068)
- Bug 1245414, part 9 - Disable mfbt/decimal/fix-wshadow-warnings.patch. r=cpeterson (4476d04c5d)
- Bug 1245414, part 10 - Apply the Mozilla patches via mfbt/decimal/update.sh. r=Waldo (1f95ef5524)
- Bug 1247082 - Suppress rendering of nsBackdropFrame for VR content r=dholbert (0ffeae4267)
- Bug 1206545 - Initialize AccessibleCaretEventHub in nsCanvasFrame. r=roc (687d4997fb)
- Bug 591737 - Add SummaryFrame. r=bz (1b750bfeb8)
- Bug 1165893 - Fix rounding issue in nsDisplaySelectionOverlay::Paint. r=mattwoodro (9994cc983a)
- Bug 1245450 - Only setup AutoSaveRestorePerspectiveIndex for the descendants of the element with perspective. r=roc (fe8a350417)
- Bug 1243282 - Wrap items having clips with a separator. r=mattwoodrow (915737e3d0)
- Bug 1223232 - Use GetUsedBorder() instead of the computed border value when calculating CB size. r=roc (f4c05b30c7)
- Bug 1223232 - Crashtest. (394e112818)
- Bug 1230665 - Make anonymous flex/grid items non-tabbable and non-focusable. r=roc (0d3f70e672)
- Bug 1142295 - Closing descriptor when GECKO_DISPLAY_REFLOW_RULES_FILE is setted. r=erahm (664ae6ba0a)
- minor change (b914bd2602)
- Bug 1237754 part 1 - [css-grid][css-align] Make 'align/justify-content:normal' behave as 'stretch' for Grid containers. r=dholbert (09a9a09629)
- Bug 1237754 part 2 - [css-grid][css-align] Test updates to account for new default behavior for 'align/justify-content'. (5e62e837ff)
- minor of Bug 1141931 part 2 (a12f5b430e)
- Bu 974309: Fixes the IsEditable() logic for table cells. r=ehsan (2a3caa932f)
- Bug 1238137 - Telemetry pings for main thread keyboard-driven scroll input methods. r=ehsan (e9c07427f9)
- Bug 1238137 - Telemetry pings for main thread scrolling to bring the caret into view after moving it in response to keyboard input. r=ehsan (834bc12b7a)
- Bug 1246405 - Declare mTextRun earlier to avoid alignment spill on 64-bit architectures. r=roc (7ba93b72c9)
- Fixing bug 440486. Work around a Windows XP fax dialog bug. r=rstrong. (a59409acd6)
- Bug 1240911 - Prevent SerializedStructuredCloneBuffer from escaping into the heap. r=amarchesini (2c0b7c474b)
- Bug 1240985 - Hold off processing some messages during timeout (r=dvander) (10f6f6d7a2)
- Bug 1146471 - Release thread asserts for IPC (r=dvander) (f94d0ee09a)
- Bug 1240985 - Fix bug where mAwaitingSyncReply can be overwritten in Send after Cancel (r=dvander) (7b95acdca6)
- Bug 1193861: Log to the process log when launching a sandboxed process on Windows. r=billm (0ad1afd0d0)
- Bug 1233061 - add override declarations for MessagePumpForNonMainUIThreads; r=billm (94b9a5bfe9)
- Bug 1172467: Fix an IPC channel file descriptor leak from Nuwa to the child process. r=khuey (908601ed0e)
- Bug 1240985 - Check WasTransactionCanceled after timeout (and avoid timing out) (r=dvander) (33aade0a92)
- Bug 1237458 - Use MOZ_RELEASE_ASSERT for IPC assertions (r=jld) (cb0f058205)
- Bug 1247429 - Warn instead of error if shmem deallocated before IPDL sends it. r=nical (3c94d99b21)
- Bug 1175999 - Deallocate mach SharedMemory properly. r=blassey. (542649b570)
- Bug 1188186 - Fix leak of FDs in |CreateTransport|. r=bds (a40b9a0c58)
- Bg 1240607 - Force CreateWindow hooks to be detours. r=jmathies (895d1c21c4)
- Bug 1209464: Fix missing neutered window region in MessageChannel::WaitForInterruptNotify. Regression from bug 1189709; r=jimm (204256880b)
- Bug 1229825 - Make GIF deinterlacer respect the frame rect bounds. r=tn (904f6bd9b7)
- Bug 1242093 - Fix assertion in Downscaler::ClearRow. r=njn (63ffe82e99)
- Bug 1235859 - Add FrameSize to non-skia downscaler. r=edwin (e7474630e0)
- Bug 1237709: During RasterImage error-handling cleanup, set UniquePtr mAnim to null instead of using reset(), to avoid leaking. r=dholbert (b064f9c20d)
- Bug 1235605 - Use CheckedInt in Deinterlacer and make its buffer allocation fallible. r=tn (f6f3858c65)
- cleanup (f02aa9441e)
- Bug 1242778: Add MOZ_COUNT_CTOR & MOZ_COUNT_DTOR calls to track leaks of imagelib's FrameAnimator class. r=tn (b1aa366694)
- Bug 1241728. Add crashtest. (17d80a3387)
- Bug 1241729. Add crashtest. (bd6d7337d7)
- Bug 1241728. Limit the size of images that we will downscale from to 1048576 pixels. r=edwin (ad38a82aad)
- Bug 1218782 - use fallible allocations in Downscaler.cpp; r=seth (b22caa1121)
- Bug 1224979. Check if we compute usable filters for the downscaler, and if not put the downscaler in error state so it's not used. r=edwin (8fb59463ef)
- Bug 1235297 - Annotate intentional switch fallthroughs to suppress -Wimplicit-fallthrough warnings in image/decoders/. r=tn (094c37c0fe)
- Bug 1238558 (part 1) - Add Decoder::BeforeFinishInternal(). r=tnikkel. (c7922054d6)
- Bug 1238558 (part 2) - Add a test. r=tnikkel. (7e09caf47f)
- Bug 1238551 (part 2) - Add a test. r=tn. (f548a2cb97)
- Bug 1238551 (part 1) - Reject BITMAPV3INFOHEADER BMP images. r=tn. (c4c8f95cb3)
- Bug 1240629. Don't buffer image file data that we are never going to look at in the gap between the header and the pixel data for BMP files. r=njn (f580910cd3)
- Bug 1237171 - Improve a case where ICO and BMP files disagree on an image size. r=tn. (615db65802)
- Bug 1220021 (part 1) - Don't treat 0RGB ICO files as transparent. r=seth. (b97298285f)
- Bug 1220021 (part 2) - Add four reftests. r=seth. (b1e7b58a98)
- Bug 1163856 (Part 2) - Fix tests that depended on image load event timing. r=tn (4304c676a0)
- Bug 1207958 - Fix heuristic for choosing which ICO sub-image to render - r=tn (3d4db5a033)
- Bug 987625 - Conditionally define MOZ_PNG_MAX_DIMENSION. r=jrmuizel (859bae490c)
- Bug 75077 - Interpolate interlaced PNG images instead of libpng blocky display. r=seth (bc17b43fa6)
- fix side-effect of 1219405 (6536821e18)
- Bug 1245845, part 1 - Stop Moz2D Path::CopyToBuilder/TransformedCopyToBuilder implicitly converting the Path's FillRule. r=Bas (ecc552f359)
- Bug 1245845, part 2 - Remove code that is now useless from gfxContext::EnsurePath. r=Bas (2430be2837)
- Bug 1237448 - Moz2Dify two functions in gfxSurfaceDrawable. r=roc. (bb768302c5)
- Bug 1231888 (follow-up) - Simplify CurrentSurface(). r=jrmuizel. (303cea98f3)
- Bug 1247380: Only copy the background if we can succesfully get a snapshot. r=jrmuizel (13b64445e9)
- Bug 1228507 - Initialize mBlendOpacity. r=Bas (b301a2c9f4)
- Bug 1238846 (part 2) - Remove gfxContext::mOriginalDT, which is unused. r=mattwoodrow. (a5b0f948b7)
- Bug 1240819 - cleanup dead branches in gfxXlibNativeRender.cpp. r=jrmuizel (57bbec6693)
- Bug 1234950 - When advancing APZ animations, use the next vsync timestamp instead of the current one, since that is what will be composited. r=mstange (421829d459)
- Bug 1021845 - Don't skip checkerboarding layers during compositing, even if the layer's visible region is empty. r=botond (6cf1497019)
- Bug 1230149 - check bigImgIter to see if it's not null. r=jmuizelaar (aeef579f9f)
- Bug 1248325 - Update BufferTextureHost::GetAsSurface() r=nical (39a8b3ca71)
- reapply per misspatch Bug 1200595 - Consolidate the TextureClient's destruction logic (68966e4dc3)
- Bug 1249245 - Add missing header gfxPrefs.h to GrallocTextureClient.cpp. r=cyu (676669eb01)
- Bug 1245057: Refer to |gfx::IntPoint| in |GrallocTextureHostOGL::SetCropRect|, r=sotaro (99e572f3f6)
- Bug 1240867 - Fix non-unified build bustage in OGLShaderProgram.cpp. r=nical (0071f08285)
- Bug 1238015 - Make sure PTexture actors are destroyed after all messages referring to them are sent. r=sotaro (250f99b4a4)
- Bug 1220895 - Add layerviewer for layer tree & display list visualization NPOTB. r=botond (fa211145a1)
- Bug 1213464 - ImageBridgeChild and CompositorChild should delete their Transport. r=billm (a37a0dbdfd)
- Bug 1234343 (part 1) - Make GfxMemoryImageReporter::sAmount signed. r=Bas. (18f0cb61ec)
- Bug 1234343 (part 2) - Add a missing GfxMemoryImageReporter::DidAlloc() call. r=Bas. (69df7f3674)
- Bug 1245249 - Check actor state before calling Send__delete__(); r=luke (65716a5915)
- Bug 1221418 - A better cleanup method for AsmJSCache::ChildRunnable, r=janv (5c8c023b9d)
- Bug 1235657 - Session storage needs to handle origin attributes correctly - part 1 - createOriginAttributesWithUserContextId, r=huseby (f2df8109ef)
- Bug 1245954 - Console StartTimer/StopTimer and IncrementCounter should run in the owning thread, r=bz (64f73d7759)
- Bug 1245957 - Adding assertions in Console about in which thread is running what, r=bz (291ee70e2d)
- Bug 1248022 - ConsoleEvent.styles can be a sequence of nullable strings, r=bz (b94ec79ac0)
- Bug 1245242 - Normalize to unit vector for DOMMatrix.rotateAxisAngleSelf. r=roc (3a9e684b4d)
- Bug 1236329. Back out the patch for bug 492933 (revision d8012b35413b) because it's not web-compatible in practice. r=smaug (f6540d84c3)
- mTarget can be null in CanvasRenderingContext2D::ClearRect(), return early if so. (13e8a4e26a)
2113 lines
54 KiB
C++
2113 lines
54 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/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
|
|
|
|
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
|
|
{ }
|
|
|
|
void
|
|
Initialize(JSContext* aCx, Console::MethodName aName,
|
|
const nsAString& aString,
|
|
const Sequence<JS::Value>& aArguments,
|
|
Console* aConsole)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aConsole);
|
|
|
|
aConsole->RegisterConsoleCallData(this);
|
|
mConsole = aConsole;
|
|
|
|
mMethodName = aName;
|
|
mMethodString = aString;
|
|
|
|
for (uint32_t i = 0; i < aArguments.Length(); ++i) {
|
|
if (!mCopiedArguments.AppendElement(aArguments[i])) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
SetIDs(uint64_t aOuterID, uint64_t aInnerID)
|
|
{
|
|
MOZ_ASSERT(mIDType == eUnknown);
|
|
|
|
mOuterIDNumber = aOuterID;
|
|
mInnerIDNumber = aInnerID;
|
|
mIDType = eNumber;
|
|
}
|
|
|
|
void
|
|
SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
|
|
{
|
|
MOZ_ASSERT(mIDType == eUnknown);
|
|
|
|
mOuterIDString = aOuterID;
|
|
mInnerIDString = aInnerID;
|
|
mIDType = eString;
|
|
}
|
|
|
|
void
|
|
CleanupJSObjects()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
mCopiedArguments.Clear();
|
|
|
|
if (mConsole) {
|
|
mConsole->UnregisterConsoleCallData(this);
|
|
mConsole = nullptr;
|
|
}
|
|
}
|
|
|
|
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])
|
|
}
|
|
}
|
|
|
|
void
|
|
AssertIsOnOwningThread() const
|
|
{
|
|
MOZ_ASSERT(mOwningThread);
|
|
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
|
|
}
|
|
|
|
// 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 on the main-thread 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 on the main-thread 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 on the main-thread by 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;
|
|
|
|
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;
|
|
|
|
#ifdef DEBUG
|
|
PRThread* mOwningThread;
|
|
#endif
|
|
|
|
private:
|
|
~ConsoleCallData()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
CleanupJSObjects();
|
|
}
|
|
|
|
RefPtr<Console> mConsole;
|
|
};
|
|
|
|
// 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(JS::Handle<JSObject*> aGlobal)
|
|
{
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
JSContext* cx = mWorkerPrivate->GetJSContext();
|
|
|
|
if (!PreDispatch(cx, aGlobal)) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mWorkerPrivate->AddFeature(cx, this))) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
|
|
return true;
|
|
}
|
|
|
|
virtual bool Notify(JSContext* aCx, 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;
|
|
}
|
|
|
|
void
|
|
PostDispatch()
|
|
{
|
|
class ConsoleReleaseRunnable final : public MainThreadWorkerControlRunnable
|
|
{
|
|
RefPtr<ConsoleRunnable> mRunnable;
|
|
|
|
public:
|
|
ConsoleReleaseRunnable(WorkerPrivate* aWorkerPrivate,
|
|
ConsoleRunnable* aRunnable)
|
|
: MainThreadWorkerControlRunnable(aWorkerPrivate)
|
|
, mRunnable(aRunnable)
|
|
{
|
|
MOZ_ASSERT(aRunnable);
|
|
}
|
|
|
|
virtual bool
|
|
WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
|
|
{
|
|
MOZ_ASSERT(aWorkerPrivate);
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
mRunnable->ReleaseData();
|
|
mRunnable->mConsole = nullptr;
|
|
|
|
aWorkerPrivate->RemoveFeature(aCx, mRunnable);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
~ConsoleReleaseRunnable()
|
|
{}
|
|
};
|
|
|
|
RefPtr<WorkerControlRunnable> runnable =
|
|
new ConsoleReleaseRunnable(mWorkerPrivate, this);
|
|
runnable->Dispatch(nullptr);
|
|
}
|
|
|
|
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, JS::Handle<JSObject*> aGlobal) = 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 (!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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
{ }
|
|
|
|
private:
|
|
bool
|
|
PreDispatch(JSContext* aCx, JS::Handle<JSObject*> aGlobal) override
|
|
{
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
ClearException ce(aCx);
|
|
JSAutoCompartment ac(aCx, aGlobal);
|
|
|
|
JS::Rooted<JSObject*> arguments(aCx,
|
|
JS_NewArrayObject(aCx, mCallData->mCopiedArguments.Length()));
|
|
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 (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
|
|
|
|
if (!Write(aCx, value)) {
|
|
return false;
|
|
}
|
|
|
|
mCallData->CleanupJSObjects();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
|
|
nsPIDOMWindow* aInnerWindow) override
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mCallData->mCopiedArguments.IsEmpty());
|
|
|
|
// The windows have to run in parallel.
|
|
MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
|
|
|
|
if (aOuterWindow) {
|
|
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
|
|
} else {
|
|
ConsoleStackEntry frame;
|
|
if (mCallData->mTopStackFrame) {
|
|
frame = *mCallData->mTopStackFrame;
|
|
}
|
|
|
|
nsString id = 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);
|
|
}
|
|
|
|
// 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
|
|
{
|
|
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);
|
|
|
|
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
|
|
mConsole->ProcessCallData(mCallData, global, 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, JS::Handle<JSObject*> aGlobal) override
|
|
{
|
|
ClearException ce(aCx);
|
|
|
|
JS::Rooted<JSObject*> global(aCx, aGlobal);
|
|
if (!global) {
|
|
return false;
|
|
}
|
|
|
|
JSAutoCompartment ac(aCx, global);
|
|
|
|
JS::Rooted<JSObject*> arguments(aCx,
|
|
JS_NewArrayObject(aCx, mArguments.Length()));
|
|
if (!arguments) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> arg(aCx);
|
|
for (uint32_t i = 0; i < mArguments.Length(); ++i) {
|
|
arg = mArguments[i];
|
|
if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
|
|
|
|
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_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_SCRIPT_OBJECTS
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
|
|
for (uint32_t i = 0; i < tmp->mConsoleCallDataArray.Length(); ++i) {
|
|
tmp->mConsoleCallDataArray[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
|
|
|
|
Console::Console(nsPIDOMWindow* aWindow)
|
|
: mWindow(aWindow)
|
|
#ifdef DEBUG
|
|
, mOwningThread(PR_GetCurrentThread())
|
|
#endif
|
|
, mOuterID(0)
|
|
, mInnerID(0)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
if (NS_IsMainThread()) {
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->AddObserver(this, "inner-window-destroyed", true);
|
|
}
|
|
}
|
|
|
|
mozilla::HoldJSObjects(this);
|
|
}
|
|
|
|
Console::~Console()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mConsoleCallDataArray.IsEmpty());
|
|
|
|
if (!NS_IsMainThread()) {
|
|
if (mStorage) {
|
|
NS_ReleaseOnMainThread(mStorage.forget());
|
|
}
|
|
|
|
if (mSandbox) {
|
|
NS_ReleaseOnMainThread(mSandbox.forget());
|
|
}
|
|
}
|
|
|
|
mozilla::DropJSObjects(this);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Console::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (strcmp(aTopic, "inner-window-destroyed")) {
|
|
return NS_OK;
|
|
}
|
|
|
|
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) {
|
|
nsCOMPtr<nsIObserverService> obs =
|
|
do_GetService("@mozilla.org/observer-service;1");
|
|
if (obs) {
|
|
obs->RemoveObserver(this, "inner-window-destroyed");
|
|
}
|
|
|
|
mTimerRegistry.Clear();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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")
|
|
|
|
void
|
|
Console::Trace(JSContext* aCx)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
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();
|
|
|
|
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();
|
|
|
|
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();
|
|
|
|
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();
|
|
ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
|
|
}
|
|
|
|
void
|
|
Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
|
|
}
|
|
|
|
void
|
|
Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
|
const Sequence<JS::Value>& aData)
|
|
{
|
|
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(global);
|
|
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 =
|
|
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)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
if (!aCondition) {
|
|
Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
|
|
}
|
|
}
|
|
|
|
METHOD(Count, "count")
|
|
|
|
void
|
|
Console::NoopMethod()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
// Nothing to do.
|
|
}
|
|
|
|
static
|
|
nsresult
|
|
StackFrameToStackEntry(nsIStackFrame* aStackFrame,
|
|
ConsoleStackEntry& aStackEntry,
|
|
uint32_t aLanguage)
|
|
{
|
|
MOZ_ASSERT(aStackFrame);
|
|
|
|
nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
int32_t lineNumber;
|
|
rv = aStackFrame->GetLineNumber(&lineNumber);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aStackEntry.mLineNumber = lineNumber;
|
|
|
|
int32_t columnNumber;
|
|
rv = aStackFrame->GetColumnNumber(&columnNumber);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aStackEntry.mColumnNumber = columnNumber;
|
|
|
|
rv = aStackFrame->GetName(aStackEntry.mFunctionName);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsString cause;
|
|
rv = aStackFrame->GetAsyncCause(cause);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!cause.IsEmpty()) {
|
|
aStackEntry.mAsyncCause.Construct(cause);
|
|
}
|
|
|
|
aStackEntry.mLanguage = aLanguage;
|
|
return NS_OK;
|
|
}
|
|
|
|
static
|
|
nsresult
|
|
ReifyStack(nsIStackFrame* aStack, nsTArray<ConsoleStackEntry>& aRefiedStack)
|
|
{
|
|
nsCOMPtr<nsIStackFrame> stack(aStack);
|
|
|
|
while (stack) {
|
|
uint32_t language;
|
|
nsresult rv = stack->GetLanguage(&language);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (language == nsIProgrammingLanguage::JAVASCRIPT) {
|
|
ConsoleStackEntry& data = *aRefiedStack.AppendElement();
|
|
rv = StackFrameToStackEntry(stack, data, language);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<nsIStackFrame> caller;
|
|
rv = stack->GetCaller(getter_AddRefs(caller));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!caller) {
|
|
rv = stack->GetAsyncCaller(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();
|
|
|
|
RefPtr<ConsoleCallData> callData(new ConsoleCallData());
|
|
|
|
ClearException ce(aCx);
|
|
|
|
callData->Initialize(aCx, aMethodName, aMethodString, aData, this);
|
|
|
|
if (mWindow) {
|
|
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
|
|
if (!webNav) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
|
|
MOZ_ASSERT(loadContext);
|
|
|
|
loadContext->GetUsePrivateBrowsing(&callData->mPrivate);
|
|
}
|
|
|
|
uint32_t maxDepth = ShouldIncludeStackTrace(aMethodName) ?
|
|
DEFAULT_MAX_STACKTRACE_DEPTH : 1;
|
|
nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
|
|
|
|
if (!stack) {
|
|
return;
|
|
}
|
|
|
|
// Walk up to the first JS stack frame and save it if we find it.
|
|
do {
|
|
uint32_t language;
|
|
nsresult rv = stack->GetLanguage(&language);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
if (language == nsIProgrammingLanguage::JAVASCRIPT) {
|
|
callData->mTopStackFrame.emplace();
|
|
nsresult rv = StackFrameToStackEntry(stack,
|
|
*callData->mTopStackFrame,
|
|
language);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsIStackFrame> caller;
|
|
rv = stack->GetCaller(getter_AddRefs(caller));
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
stack.swap(caller);
|
|
} while (stack);
|
|
|
|
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(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);
|
|
}
|
|
|
|
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
|
|
if (NS_IsMainThread()) {
|
|
callData->SetIDs(mOuterID, mInnerID);
|
|
ProcessCallData(callData, global, aData);
|
|
return;
|
|
}
|
|
|
|
RefPtr<ConsoleCallDataRunnable> runnable =
|
|
new ConsoleCallDataRunnable(this, callData);
|
|
runnable->Dispatch(global);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
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(stack, reifiedStack);
|
|
if (NS_FAILED(rv)) {
|
|
Throw(aCx, rv);
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> stackVal(aCx);
|
|
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(ConsoleCallData* aData, JS::Handle<JSObject*> aGlobal,
|
|
const Sequence<JS::Value>& aArguments)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aData);
|
|
|
|
ConsoleStackEntry frame;
|
|
if (aData->mTopStackFrame) {
|
|
frame = *aData->mTopStackFrame;
|
|
}
|
|
|
|
AutoSafeJSContext cx;
|
|
ClearException ce(cx);
|
|
RootedDictionary<ConsoleEvent> event(cx);
|
|
|
|
JSAutoCompartment ac(cx, aGlobal);
|
|
|
|
event.mID.Construct();
|
|
event.mInnerID.Construct();
|
|
|
|
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
|
|
if (aData->mIDType == ConsoleCallData::eString) {
|
|
event.mID.Value().SetAsString() = aData->mOuterIDString;
|
|
event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
|
|
} else {
|
|
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
|
|
event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
|
|
event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
|
|
}
|
|
|
|
event.mLevel = aData->mMethodString;
|
|
event.mFilename = frame.mFilename;
|
|
|
|
nsCOMPtr<nsIURI> filenameURI;
|
|
nsAutoCString pass;
|
|
if (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 (!ProcessArguments(cx, aArguments, event.mArguments.Value(),
|
|
event.mStyles.Value())) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
event.mArguments.Construct();
|
|
if (!ArgumentsToValueList(aArguments, event.mArguments.Value())) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (aData->mMethodName == MethodGroup ||
|
|
aData->mMethodName == MethodGroupCollapsed ||
|
|
aData->mMethodName == MethodGroupEnd) {
|
|
ComposeGroupName(cx, aArguments, event.mGroupName);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
|
|
event.mTimer = CreateStartTimerValue(cx, aData->mStartTimerLabel,
|
|
aData->mStartTimerValue,
|
|
aData->mStartTimerStatus);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
|
|
event.mTimer = CreateStopTimerValue(cx, aData->mStopTimerLabel,
|
|
aData->mStopTimerDuration,
|
|
aData->mStopTimerStatus);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodCount) {
|
|
event.mCounter = CreateCounterValue(cx, aData->mCountLabel,
|
|
aData->mCountValue);
|
|
}
|
|
|
|
// 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.
|
|
JSAutoCompartment ac2(cx, xpc::PrivilegedJunkScope());
|
|
|
|
JS::Rooted<JS::Value> eventValue(cx);
|
|
if (!ToJSValue(cx, event, &eventValue)) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
|
|
MOZ_ASSERT(eventObj);
|
|
|
|
if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
|
|
return;
|
|
}
|
|
|
|
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(cx);
|
|
if (!ToJSValue(cx, *aData->mReifiedStack, &stacktrace) ||
|
|
!JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace,
|
|
JSPROP_ENUMERATE)) {
|
|
return;
|
|
}
|
|
} else {
|
|
JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
|
|
"stacktrace");
|
|
if (!fun) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> funObj(cx, 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(cx);
|
|
nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack,
|
|
&stackVal);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
|
|
js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
|
|
JS::PrivateValue(aData->mStack.get()));
|
|
|
|
if (!JS_DefineProperty(cx, eventObj, "stacktrace",
|
|
JS::UndefinedHandleValue,
|
|
JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER |
|
|
JSPROP_SETTER,
|
|
JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
|
|
nullptr)) {
|
|
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 (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
|
|
NS_WARNING("Failed to record a console event.");
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
|
|
bool
|
|
FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &aOutput)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (!aOutput.IsEmpty()) {
|
|
JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
|
|
aOutput.get(),
|
|
aOutput.Length()));
|
|
if (!str) {
|
|
return false;
|
|
}
|
|
|
|
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
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString string;
|
|
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 (!FlushOutput(aCx, aSequence, output)) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> v(aCx);
|
|
if (index < aData.Length()) {
|
|
v = aData[index++];
|
|
}
|
|
|
|
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 (!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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
int32_t diff = aSequence.Length() - aStyles.Length();
|
|
if (diff > 0) {
|
|
for (int32_t i = 0; i < diff; i++) {
|
|
if (!aStyles.AppendElement(NullString(), fallible)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsAutoJSString string;
|
|
if (!string.init(aCx, jsString)) {
|
|
return false;
|
|
}
|
|
|
|
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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString v;
|
|
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 (!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 (!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 (!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 (!aSequence.AppendElement(aData[index], fallible)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
|
|
int32_t aMantissa, char aCh) const
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
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
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
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 (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> name(aCx, aName);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
|
|
if (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString label;
|
|
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
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString key;
|
|
if (!key.init(aCx, jsString)) {
|
|
return false;
|
|
}
|
|
|
|
DOMHighResTimeStamp entry;
|
|
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
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
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
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
for (uint32_t i = 0; i < aData.Length(); ++i) {
|
|
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
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
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::RegisterConsoleCallData(ConsoleCallData* aData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
MOZ_ASSERT(!mConsoleCallDataArray.Contains(aData));
|
|
mConsoleCallDataArray.AppendElement(aData);
|
|
}
|
|
|
|
void
|
|
Console::UnregisterConsoleCallData(ConsoleCallData* aData)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
MOZ_ASSERT(mConsoleCallDataArray.Contains(aData));
|
|
mConsoleCallDataArray.RemoveElement(aData);
|
|
}
|
|
|
|
void
|
|
Console::AssertIsOnOwningThread() const
|
|
{
|
|
MOZ_ASSERT(mOwningThread);
|
|
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|