Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsError.h"
#include "nsJSEnvironment.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsPIDOMWindow.h"
#include "nsDOMCID.h"
#include "nsIXPConnect.h"
#include "nsCOMPtr.h"
#include "nsISupportsPrimitives.h"
#include "nsReadableUtils.h"
#include "nsDOMJSUtils.h"
#include "nsJSUtils.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsPresContext.h"
#include "nsIConsoleService.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIObserverService.h"
#include "nsITimer.h"
#include "nsAtom.h"
#include "nsContentUtils.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/HoldDropJSObjects.h"
#include "nsIContent.h"
#include "nsCycleCollector.h"
#include "nsXPCOMCIDInternal.h"
#include "nsServiceManagerUtils.h"
#include "nsTextFormatter.h"
#ifdef XP_WIN
# include <process.h>
# define getpid _getpid
#else
# include <unistd.h> // for getpid()
#endif
#include "xpcpublic.h"
#include "jsapi.h"
#include "js/Array.h" // JS::NewArrayObject
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "js/PropertySpec.h"
#include "js/SliceBudget.h"
#include "js/Wrapper.h"
#include "nsIArray.h"
#include "CCGCScheduler.h"
#include "WrapperFactory.h"
#include "nsGlobalWindowInner.h"
#include "nsGlobalWindowOuter.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/CycleCollectorStats.h"
#include "mozilla/MainThreadIdlePeriod.h"
#include "mozilla/PresShell.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_javascript.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/FetchUtil.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SerializedStackHolder.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "nsRefreshDriver.h"
#include "nsJSPrincipals.h"
#include "AccessCheck.h"
#include "mozilla/Logging.h"
#include "prthread.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "nsCycleCollectionNoteRootCallback.h"
#include "nsViewManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#if defined(MOZ_MEMORY)
# include "mozmemory.h"
#endif
using namespace mozilla;
using namespace mozilla::dom;
// Thank you Microsoft!
#ifdef CompareString
# undef CompareString
#endif
static JS::GCSliceCallback sPrevGCSliceCallback;
static bool sIncrementalCC = false;
static bool sIsInitialized;
static bool sShuttingDown;
static CCGCScheduler* sScheduler = nullptr;
static std::aligned_storage_t<sizeof(*sScheduler)> sSchedulerStorage;
// Cache a pointer to the main thread's statistics struct.
static CycleCollectorStats* sCCStats = nullptr;
static const char* ProcessNameForCollectorLog() {
return XRE_GetProcessType() == GeckoProcessType_Default ? "default"
: "content";
}
namespace xpc {
// This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC
// Exceptions, and arbitrary values that were associated with a stack by the
// JS engine when they were thrown, as specified by exceptionStack.
//
// Note that the returned stackObj and stackGlobal are _not_ wrapped into the
// compartment of exceptionValue.
void FindExceptionStackForConsoleReport(
nsPIDOMWindowInner* win, JS::Handle<JS::Value> exceptionValue,
JS::Handle<JSObject*> exceptionStack, JS::MutableHandle<JSObject*> stackObj,
JS::MutableHandle<JSObject*> stackGlobal) {
stackObj.set(nullptr);
stackGlobal.set(nullptr);
if (!exceptionValue.isObject()) {
// Use the stack provided by the JS engine, if available. This will not be
// a wrapper.
if (exceptionStack) {
stackObj.set(exceptionStack);
stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
}
return;
}
if (win && win->AsGlobal()->IsDying()) {
// Pretend like we have no stack, so we don't end up keeping the global
// alive via the stack.
return;
}
JS::RootingContext* rcx = RootingCx();
JS::Rooted<JSObject*> exceptionObject(rcx, &exceptionValue.toObject());
if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) {
// At this point we know exceptionObject is a possibly-wrapped
// js::ErrorObject that has excStack as stack. excStack might also be a CCW,
// but excStack must be same-compartment with the unwrapped ErrorObject.
// Return the ErrorObject's global as stackGlobal. This matches what we do
// in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal
// are same-compartment.
JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject);
stackObj.set(excStack);
stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException));
return;
}
// It is not a JS Exception, try DOM Exception.
RefPtr<Exception> exception;
UNWRAP_OBJECT(DOMException, exceptionObject, exception);
if (!exception) {
// Not a DOM Exception, try XPC Exception.
UNWRAP_OBJECT(Exception, exceptionObject, exception);
if (!exception) {
// As above, use the stack provided by the JS engine, if available.
if (exceptionStack) {
stackObj.set(exceptionStack);
stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
}
return;
}
}
nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
if (!stack) {
return;
}
JS::Rooted<JS::Value> value(rcx);
stack->GetNativeSavedFrame(&value);
if (value.isObject()) {
stackObj.set(&value.toObject());
MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj));
return;
}
}
} /* namespace xpc */
static TimeDuration GetCollectionTimeDelta() {
static TimeStamp sFirstCollectionTime;
TimeStamp now = TimeStamp::Now();
if (sFirstCollectionTime) {
return now - sFirstCollectionTime;
}
sFirstCollectionTime = now;
return TimeDuration();
}
class nsJSEnvironmentObserver final : public nsIObserver {
~nsJSEnvironmentObserver() = default;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
};
NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver)
NS_IMETHODIMP
nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
if (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
if (sShuttingDown) {
// Don't GC/CC if we're already shutting down.
return NS_OK;
}
nsDependentString data(aData);
if (data.EqualsLiteral("low-memory-ongoing")) {
// Don't GC/CC if we are in an ongoing low-memory state since its very
// slow and it likely won't help us anyway.
return NS_OK;
}
if (data.EqualsLiteral("heap-minimize")) {
// heap-minimize notifiers expect this to run synchronously
nsJSContext::DoLowMemoryGC();
return NS_OK;
}
if (data.EqualsLiteral("low-memory")) {
nsJSContext::SetLowMemoryState(true);
}
// Asynchronously GC.
nsJSContext::LowMemoryGC();
}
} else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) {
nsJSContext::SetLowMemoryState(false);
} else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
sScheduler->UserIsInactive();
} else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
sScheduler->UserIsActive();
} else if (!nsCRT::strcmp(aTopic, "quit-application") ||
!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
!nsCRT::strcmp(aTopic, "content-child-will-shutdown")) {
sShuttingDown = true;
sScheduler->Shutdown();
}
return NS_OK;
}
/****************************************************************
************************** AutoFree ****************************
****************************************************************/
class AutoFree {
public:
explicit AutoFree(void* aPtr) : mPtr(aPtr) {}
~AutoFree() {
if (mPtr) free(mPtr);
}
void Invalidate() { mPtr = nullptr; }
private:
void* mPtr;
};
// A utility function for script languages to call. Although it looks small,
// the use of nsIDocShell and nsPresContext triggers a huge number of
// dependencies that most languages would not otherwise need.
// XXXmarkh - This function is mis-placed!
bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal,
const ErrorEventInit& aErrorEventInit,
nsEventStatus* aStatus) {
bool called = false;
nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobal));
nsIDocShell* docShell = win ? win->GetDocShell() : nullptr;
if (docShell) {
RefPtr<nsPresContext> presContext = docShell->GetPresContext();
static int32_t errorDepth; // Recursion prevention
++errorDepth;
if (errorDepth < 2) {
// Dispatch() must be synchronous for the recursion block
// (errorDepth) to work.
RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
nsGlobalWindowInner::Cast(win), u"error"_ns, aErrorEventInit);
event->SetTrusted(true);
// MOZ_KnownLive due to bug 1506441
EventDispatcher::DispatchDOMEvent(
MOZ_KnownLive(nsGlobalWindowInner::Cast(win)), nullptr, event,
presContext, aStatus);
called = true;
}
--errorDepth;
}
return called;
}
class ScriptErrorEvent : public Runnable {
public:
ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx,
xpc::ErrorReport* aReport, JS::Handle<JS::Value> aError,
JS::Handle<JSObject*> aErrorStack)
: mozilla::Runnable("ScriptErrorEvent"),
mWindow(aWindow),
mReport(aReport),
mError(aRootingCx, aError),
mErrorStack(aRootingCx, aErrorStack) {}
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
nsEventStatus status = nsEventStatus_eIgnore;
nsCOMPtr<nsPIDOMWindowInner> win = mWindow;
MOZ_ASSERT(win);
MOZ_ASSERT(NS_IsMainThread());
// First, notify the DOM that we have a script error, but only if
// our window is still the current inner.
JS::RootingContext* rootingCx = RootingCx();
if (win->IsCurrentInnerWindow() && win->GetDocShell() &&
!sHandlingScriptError) {
AutoRestore<bool> recursionGuard(sHandlingScriptError);
sHandlingScriptError = true;
RefPtr<nsPresContext> presContext = win->GetDocShell()->GetPresContext();
RootedDictionary<ErrorEventInit> init(rootingCx);
init.mCancelable = true;
init.mFilename = mReport->mFileName;
init.mBubbles = true;
constexpr auto xoriginMsg = u"Script error."_ns;
if (!mReport->mIsMuted) {
init.mMessage = mReport->mErrorMsg;
init.mLineno = mReport->mLineNumber;
init.mColno = mReport->mColumn;
init.mError = mError;
} else {
NS_WARNING("Not same origin error!");
init.mMessage = xoriginMsg;
init.mLineno = 0;
}
RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
nsGlobalWindowInner::Cast(win), u"error"_ns, init);
event->SetTrusted(true);
// MOZ_KnownLive due to bug 1506441
EventDispatcher::DispatchDOMEvent(
MOZ_KnownLive(nsGlobalWindowInner::Cast(win)), nullptr, event,
presContext, &status);
}
if (status != nsEventStatus_eConsumeNoDefault) {
JS::Rooted<JSObject*> stack(rootingCx);
JS::Rooted<JSObject*> stackGlobal(rootingCx);
xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
&stackGlobal);
JS::Rooted<Maybe<JS::Value>> exception(rootingCx, Some(mError));
nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win);
mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal);
}
return NS_OK;
}
private:
nsCOMPtr<nsPIDOMWindowInner> mWindow;
RefPtr<xpc::ErrorReport> mReport;
JS::PersistentRooted<JS::Value> mError;
JS::PersistentRooted<JSObject*> mErrorStack;
static bool sHandlingScriptError;
};
bool ScriptErrorEvent::sHandlingScriptError = false;
// This temporarily lives here to avoid code churn. It will go away entirely
// soon.
namespace xpc {
void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
JS::RootingContext* rootingCx,
xpc::ErrorReport* xpcReport,
JS::Handle<JS::Value> exception,
JS::Handle<JSObject*> exceptionStack) {
nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
win, rootingCx, xpcReport, exception, exceptionStack));
}
} /* namespace xpc */
#ifdef DEBUG
// A couple of useful functions to call when you're debugging.
nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
return xpc::WindowOrNull(obj);
}
template <typename T>
void PrintWinURI(T* win) {
if (!win) {
printf("No window passed in.\n");
return;
}
nsCOMPtr<Document> doc = win->GetExtantDoc();
if (!doc) {
printf("No document in the window.\n");
return;
}
nsIURI* uri = doc->GetDocumentURI();
if (!uri) {
printf("Document doesn't have a URI.\n");
return;
}
printf("%s\n", uri->GetSpecOrDefault().get());
}
void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
template <typename T>
void PrintWinCodebase(T* win) {
if (!win) {
printf("No window passed in.\n");
return;
}
nsIPrincipal* prin = win->GetPrincipal();
if (!prin) {
printf("Window doesn't have principals.\n");
return;
}
if (prin->IsSystemPrincipal()) {
printf("No URI, it's the system principal.\n");
return;
}
nsCString spec;
prin->GetAsciiSpec(spec);
printf("%s\n", spec.get());
}
void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) {
return PrintWinCodebase(aWin);
}
void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) {
return PrintWinCodebase(aWin);
}
void DumpString(const nsAString& str) {
printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
}
#endif
nsJSContext::nsJSContext(bool aGCOnDestruction,
nsIScriptGlobalObject* aGlobalObject)
: mWindowProxy(nullptr),
mGCOnDestruction(aGCOnDestruction),
mGlobalObjectRef(aGlobalObject) {
EnsureStatics();
mProcessingScriptTag = false;
HoldJSObjects(this);
}
nsJSContext::~nsJSContext() {
mGlobalObjectRef = nullptr;
Destroy();
}
void nsJSContext::Destroy() {
if (mGCOnDestruction) {
sScheduler->PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy);
}
DropJSObjects(this);
}
// QueryInterface implementation for nsJSContext
NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext)
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext)
tmp->mGCOnDestruction = false;
tmp->mWindowProxy = nullptr;
tmp->Destroy();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
#ifdef DEBUG
bool AtomIsEventHandlerName(nsAtom* aName) {
const char16_t* name = aName->GetUTF16String();
const char16_t* cp;
char16_t c;
for (cp = name; *cp != '\0'; ++cp) {
c = *cp;
if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
}
return true;
}
#endif
nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
// Note: this could probably be simplified somewhat more; see bug 974327
// comments 1 and 3.
if (!mWindowProxy) {
return nullptr;
}
MOZ_ASSERT(mGlobalObjectRef);
return mGlobalObjectRef;
}
nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
const char* aPropName, nsISupports* aArgs) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
JS::RootedVector<JS::Value> args(cx);
JS::Rooted<JSObject*> global(cx, GetWindowProxy());
nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
NS_ENSURE_SUCCESS(rv, rv);
// got the arguments, now attach them.
for (uint32_t i = 0; i < args.length(); ++i) {
if (!JS_WrapValue(cx, args[i])) {
return NS_ERROR_FAILURE;
}
}
JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, args));
if (!array) {
return NS_ERROR_FAILURE;
}
return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
: NS_ERROR_FAILURE;
}
nsresult nsJSContext::ConvertSupportsTojsvals(
JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
JS::MutableHandleVector<JS::Value> aArgsOut) {
nsresult rv = NS_OK;
// If the array implements nsIJSArgArray, copy the contents and return.
nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
if (fastArray) {
uint32_t argc;
JS::Value* argv;
rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
rv = NS_ERROR_OUT_OF_MEMORY;
}
return rv;
}
// Take the slower path converting each item.
// Handle only nsIArray and nsIVariant. nsIArray is only needed for
// SetProperty('arguments', ...);
nsIXPConnect* xpc = nsContentUtils::XPConnect();
NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
if (!aArgs) return NS_OK;
uint32_t argCount;
// This general purpose function may need to convert an arg array
// (window.arguments, event-handler args) and a generic property.
nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
if (argsArray) {
rv = argsArray->GetLength(&argCount);
NS_ENSURE_SUCCESS(rv, rv);
if (argCount == 0) return NS_OK;
} else {
argCount = 1; // the nsISupports which is not an array
}
// Use the caller's auto guards to release and unroot.
if (!aArgsOut.resize(argCount)) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (argsArray) {
for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
nsCOMPtr<nsISupports> arg;
JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
getter_AddRefs(arg));
if (!arg) {
thisVal.setNull();
continue;
}
nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
if (variant != nullptr) {
rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
} else {
// And finally, support the nsISupportsPrimitives supplied
// by the AppShell. It generally will pass only strings, but
// as we have code for handling all, we may as well use it.
rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
if (rv == NS_ERROR_NO_INTERFACE) {
// something else - probably an event object or similar -
// just wrap it.
#ifdef DEBUG
// but first, check its not another nsISupportsPrimitive, as
// these are now deprecated for use with script contexts.
nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
NS_ASSERTION(prim == nullptr,
"Don't pass nsISupportsPrimitives - use nsIVariant!");
#endif
JSAutoRealm ar(aCx, aScope);
rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
}
}
}
} else {
nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
if (variant) {
rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
} else {
NS_ERROR("Not an array, not an interface?");
rv = NS_ERROR_UNEXPECTED;
}
}
return rv;
}
// This really should go into xpconnect somewhere...
nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
nsISupports* aArg,
JS::Value* aArgv) {
MOZ_ASSERT(aArg, "Empty arg");
nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
uint16_t type;
argPrimitive->GetType(&type);
switch (type) {
case nsISupportsPrimitive::TYPE_CSTRING: {
nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
nsAutoCString data;
p->GetData(data);
JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
aArgv->setString(str);
break;
}
case nsISupportsPrimitive::TYPE_STRING: {
nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
nsAutoString data;
p->GetData(data);
// cast is probably safe since wchar_t and char16_t are expected
// to be equivalent; both unsigned 16-bit entities
JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
aArgv->setString(str);
break;
}
case nsISupportsPrimitive::TYPE_PRBOOL: {
nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
bool data;
p->GetData(&data);
aArgv->setBoolean(data);
break;
}
case nsISupportsPrimitive::TYPE_PRUINT8: {
nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
uint8_t data;
p->GetData(&data);
aArgv->setInt32(data);
break;
}
case nsISupportsPrimitive::TYPE_PRUINT16: {
nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
uint16_t data;
p->GetData(&data);
aArgv->setInt32(data);
break;
}
case nsISupportsPrimitive::TYPE_PRUINT32: {
nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
uint32_t data;
p->GetData(&data);
aArgv->setInt32(data);
break;
}
case nsISupportsPrimitive::TYPE_CHAR: {
nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
char data;
p->GetData(&data);
JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
aArgv->setString(str);
break;
}
case nsISupportsPrimitive::TYPE_PRINT16: {
nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
int16_t data;
p->GetData(&data);
aArgv->setInt32(data);
break;
}
case nsISupportsPrimitive::TYPE_PRINT32: {
nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
int32_t data;
p->GetData(&data);
aArgv->setInt32(data);
break;
}
case nsISupportsPrimitive::TYPE_FLOAT: {
nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
float data;
p->GetData(&data);
*aArgv = ::JS_NumberValue(data);
break;
}
case nsISupportsPrimitive::TYPE_DOUBLE: {
nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
double data;
p->GetData(&data);
*aArgv = ::JS_NumberValue(data);
break;
}
case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsISupports> data;
nsIID* iid = nullptr;
p->GetData(getter_AddRefs(data));
p->GetDataIID(&iid);
NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
AutoFree iidGuard(iid); // Free iid upon destruction.
JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
JS::Rooted<JS::Value> v(aCx);
JSAutoRealm ar(aCx, scope);
nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
NS_ENSURE_SUCCESS(rv, rv);
*aArgv = v;
break;
}
case nsISupportsPrimitive::TYPE_ID:
case nsISupportsPrimitive::TYPE_PRUINT64:
case nsISupportsPrimitive::TYPE_PRINT64:
case nsISupportsPrimitive::TYPE_PRTIME: {
NS_WARNING("Unsupported primitive type used");
aArgv->setNull();
break;
}
default: {
NS_WARNING("Unknown primitive type used");
aArgv->setNull();
break;
}
}
return NS_OK;
}
nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
JSAutoRealm ar(cx, aGlobalObj);
return NS_OK;
}
bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
void nsJSContext::SetProcessingScriptTag(bool aFlag) {
mProcessingScriptTag = aFlag;
}
// static
void nsJSContext::SetLowMemoryState(bool aState) {
JSContext* cx = danger::GetJSContext();
JS::SetLowMemoryState(cx, aState);
}
static void GarbageCollectImpl(JS::GCReason aReason,
nsJSContext::IsShrinking aShrinking,
const JS::SliceBudget& aBudget) {
AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
"nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason));
bool wantIncremental = !aBudget.isUnlimited();
// We use danger::GetJSContext() since AutoJSAPI will assert if the current
// thread's context is null (such as during shutdown).
JSContext* cx = danger::GetJSContext();
if (!nsContentUtils::XPConnect() || !cx) {
return;
}
if (sScheduler->InIncrementalGC() && wantIncremental) {
// We're in the middle of incremental GC. Do another slice.
JS::PrepareForIncrementalGC(cx);
JS::IncrementalGCSlice(cx, aReason, aBudget);
return;
}
JS::GCOptions options = aShrinking == nsJSContext::ShrinkingGC
? JS::GCOptions::Shrink
: JS::GCOptions::Normal;
if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) {
sScheduler->SetNeedsFullGC();
}
if (sScheduler->NeedsFullGC()) {
JS::PrepareForFullGC(cx);
}
if (wantIncremental) {
// Incremental GC slices will be triggered by the GC Runner. If one doesn't
// already exist, create it in the GC_SLICE_END callback for the first
// slice being executed here.
JS::StartIncrementalGC(cx, options, aReason, aBudget);
} else {
JS::NonIncrementalGC(cx, options, aReason);
}
}
// static
void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
IsShrinking aShrinking) {
GarbageCollectImpl(aReason, aShrinking, JS::SliceBudget::unlimited());
}
// static
void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason,
IsShrinking aShrinking,
JS::SliceBudget& aBudget) {
AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental GC", GCCC);
GarbageCollectImpl(aReason, aShrinking, aBudget);
}
static void FinishAnyIncrementalGC() {
AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);
if (sScheduler->InIncrementalGC()) {
AutoJSAPI jsapi;
jsapi.Init();
// We're in the middle of an incremental GC, so finish it.
JS::PrepareForIncrementalGC(jsapi.cx());
JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED);
}
}
static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) {
TimeStamp startTimeStamp = TimeStamp::Now();
FinishAnyIncrementalGC();
JS::SliceBudget budget =
sScheduler->ComputeForgetSkippableBudget(startTimeStamp, aDeadline);
bool earlyForgetSkippable = sScheduler->IsEarlyForgetSkippable();
nsCycleCollector_forgetSkippable(startTimeStamp, budget, !aDeadline.IsNull(),
aRemoveChildless, earlyForgetSkippable);
TimeStamp now = TimeStamp::Now();
sScheduler->NoteForgetSkippableComplete(now,
nsCycleCollector_suspectedCount());
TimeDuration duration = now - startTimeStamp;
if (duration.ToSeconds()) {
TimeDuration idleDuration;
if (!aDeadline.IsNull()) {
if (aDeadline < now) {
// This slice overflowed the idle period.
if (aDeadline > startTimeStamp) {
idleDuration = aDeadline - startTimeStamp;
}
} else {
idleDuration = duration;
}
}
uint32_t percent =
uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent);
}
}
static void MaybeLogStats(const CycleCollectorResults& aResults,
uint32_t aCleanups) {
if (!StaticPrefs::javascript_options_mem_log() && !sCCStats->mFile) {
return;
}
TimeDuration delta = GetCollectionTimeDelta();
nsCString mergeMsg;
if (aResults.mMergedZones) {
mergeMsg.AssignLiteral(" merged");
}
nsCString gcMsg;
if (aResults.mForcedGC) {
gcMsg.AssignLiteral(", forced a GC");
}
const char16_t* kFmt =
u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, "
u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu "
u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: "
u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu";
nsString msg;
nsTextFormatter::ssprintf(
msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC,
ProcessNameForCollectorLog(), getpid(),
sCCStats->mMaxSliceTime.ToMilliseconds(),
sCCStats->mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices,
sCCStats->mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
mergeMsg.get(), aResults.mFreedRefCounted, aResults.mFreedGCed,
sScheduler->mCCollectedWaitingForGC,
sScheduler->mCCollectedZonesWaitingForGC,
sScheduler->mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
sCCStats->mForgetSkippableBeforeCC,
sCCStats->mMinForgetSkippableTime.ToMilliseconds(),
sCCStats->mMaxForgetSkippableTime.ToMilliseconds(),
sCCStats->mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
sCCStats->mTotalForgetSkippableTime.ToMilliseconds(),
sCCStats->mMaxSkippableDuration.ToMilliseconds(),
sCCStats->mRemovedPurples);
if (StaticPrefs::javascript_options_mem_log()) {
nsCOMPtr<nsIConsoleService> cs =
do_GetService(