/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* 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/. */

/* High level class and public functions implementation. */

#include "mozilla/Assertions.h"
#include "mozilla/Base64.h"
#include "mozilla/Likely.h"
#include "mozilla/Unused.h"

#include "xpcprivate.h"
#include "XPCWrapper.h"
#include "jsfriendapi.h"
#include "nsJSEnvironment.h"
#include "nsThreadUtils.h"
#include "nsDOMJSUtils.h"

#include "WrapperFactory.h"
#include "AccessCheck.h"

#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/Promise.h"

#include "nsDOMMutationObserver.h"
#include "nsICycleCollectorListener.h"
#include "mozilla/XPTInterfaceInfoManager.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsScriptSecurityManager.h"
#include "nsIPermissionManager.h"
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "nsScriptError.h"
#include "jsfriendapi.h"

using namespace mozilla;
using namespace mozilla::dom;
using namespace xpc;
using namespace JS;

NS_IMPL_ISUPPORTS(nsXPConnect, nsIXPConnect)

nsXPConnect* nsXPConnect::gSelf = nullptr;
bool         nsXPConnect::gOnceAliveNowDead = false;

// Global cache of the default script security manager (QI'd to
// nsIScriptSecurityManager) and the system principal.
nsIScriptSecurityManager* nsXPConnect::gScriptSecurityManager = nullptr;
nsIPrincipal* nsXPConnect::gSystemPrincipal = nullptr;

const char XPC_CONTEXT_STACK_CONTRACTID[] = "@mozilla.org/js/xpc/ContextStack;1";
const char XPC_EXCEPTION_CONTRACTID[]     = "@mozilla.org/js/xpc/Exception;1";
const char XPC_CONSOLE_CONTRACTID[]       = "@mozilla.org/consoleservice;1";
const char XPC_SCRIPT_ERROR_CONTRACTID[]  = "@mozilla.org/scripterror;1";
const char XPC_ID_CONTRACTID[]            = "@mozilla.org/js/xpc/ID;1";
const char XPC_XPCONNECT_CONTRACTID[]     = "@mozilla.org/js/xpc/XPConnect;1";

/***************************************************************************/

nsXPConnect::nsXPConnect()
    :   mContext(nullptr),
        mShuttingDown(false)
{
    mContext = XPCJSContext::newXPCJSContext();
    if (!mContext) {
        NS_RUNTIMEABORT("Couldn't create XPCJSContext.");
    }
}

nsXPConnect::~nsXPConnect()
{
    mContext->DeleteSingletonScopes();

    // In order to clean up everything properly, we need to GC twice: once now,
    // to clean anything that can go away on its own (like the Junk Scope, which
    // we unrooted above), and once after forcing a bunch of shutdown in
    // XPConnect, to clean the stuff we forcibly disconnected. The forced
    // shutdown code defaults to leaking in a number of situations, so we can't
    // get by with only the second GC. :-(
    mContext->GarbageCollect(JS::gcreason::XPCONNECT_SHUTDOWN);

    mShuttingDown = true;
    XPCWrappedNativeScope::SystemIsBeingShutDown();

    // The above causes us to clean up a bunch of XPConnect data structures,
    // after which point we need to GC to clean everything up. We need to do
    // this before deleting the XPCJSContext, because doing so destroys the
    // maps that our finalize callback depends on.
    mContext->GarbageCollect(JS::gcreason::XPCONNECT_SHUTDOWN);

    NS_RELEASE(gSystemPrincipal);
    gScriptSecurityManager = nullptr;

    // shutdown the logging system
    XPC_LOG_FINISH();

    delete mContext;

    gSelf = nullptr;
    gOnceAliveNowDead = true;
}

// static
void
nsXPConnect::InitStatics()
{
    gSelf = new nsXPConnect();
    gOnceAliveNowDead = false;
    if (!gSelf->mContext) {
        NS_RUNTIMEABORT("Couldn't create XPCJSContext.");
    }

    // Initial extra ref to keep the singleton alive
    // balanced by explicit call to ReleaseXPConnectSingleton()
    NS_ADDREF(gSelf);

    // Fire up the SSM.
    nsScriptSecurityManager::InitStatics();
    gScriptSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
    gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal);
    MOZ_RELEASE_ASSERT(gSystemPrincipal);

    if (!JS::InitSelfHostedCode(gSelf->mContext->Context()))
        MOZ_CRASH("InitSelfHostedCode failed");
    if (!gSelf->mContext->JSContextInitialized(gSelf->mContext->Context()))
        MOZ_CRASH("JSContextInitialized failed");

    // Initialize our singleton scopes.
    gSelf->mContext->InitSingletonScopes();
}

nsXPConnect*
nsXPConnect::GetSingleton()
{
    nsXPConnect* xpc = nsXPConnect::XPConnect();
    NS_IF_ADDREF(xpc);
    return xpc;
}

// static
void
nsXPConnect::ReleaseXPConnectSingleton()
{
    nsXPConnect* xpc = gSelf;
    if (xpc) {
        nsrefcnt cnt;
        NS_RELEASE2(xpc, cnt);
    }
}

// static
XPCJSContext*
nsXPConnect::GetContextInstance()
{
    nsXPConnect* xpc = XPConnect();
    return xpc->GetContext();
}

// static
bool
nsXPConnect::IsISupportsDescendant(nsIInterfaceInfo* info)
{
    bool found = false;
    if (info)
        info->HasAncestor(&NS_GET_IID(nsISupports), &found);
    return found;
}

void
xpc::ErrorBase::Init(JSErrorBase* aReport)
{
    if (!aReport->filename) {
        mFileName.SetIsVoid(true);
    } else {
        mFileName.AssignWithConversion(aReport->filename);
    }

    mLineNumber = aReport->lineno;
    mColumn = aReport->column;
}

void
xpc::ErrorNote::Init(JSErrorNotes::Note* aNote)
{
    xpc::ErrorBase::Init(aNote);

    ErrorNoteToMessageString(aNote, mErrorMsg);
}

void
xpc::ErrorReport::Init(JSErrorReport* aReport, const char* aToStringResult,
                       bool aIsChrome, uint64_t aWindowID)
{
    xpc::ErrorBase::Init(aReport);
    mCategory = aIsChrome ? NS_LITERAL_CSTRING("chrome javascript")
                          : NS_LITERAL_CSTRING("content javascript");
    mWindowID = aWindowID;

    ErrorReportToMessageString(aReport, mErrorMsg);
    if (mErrorMsg.IsEmpty() && aToStringResult) {
        AppendUTF8toUTF16(aToStringResult, mErrorMsg);
    }

    mSourceLine.Assign(aReport->linebuf(), aReport->linebufLength());
    const JSErrorFormatString* efs = js::GetErrorMessage(nullptr, aReport->errorNumber);

    if (efs == nullptr) {
        mErrorMsgName.AssignASCII("");
    } else {
        mErrorMsgName.AssignASCII(efs->name);
    }

    mFlags = aReport->flags;
    mIsMuted = aReport->isMuted;

    if (aReport->notes) {
        if (!mNotes.SetLength(aReport->notes->length(), fallible)) {
            return;
        }

        size_t i = 0;
        for (auto&& note : *aReport->notes) {
            mNotes.ElementAt(i).Init(note.get());
            i++;
        }
    }
}

void
xpc::ErrorReport::Init(JSContext* aCx, mozilla::dom::Exception* aException,
                       bool aIsChrome, uint64_t aWindowID)
{
    mCategory = aIsChrome ? NS_LITERAL_CSTRING("chrome javascript")
                          : NS_LITERAL_CSTRING("content javascript");
    mWindowID = aWindowID;

    aException->GetErrorMessage(mErrorMsg);

    aException->GetFilename(aCx, mFileName);
    if (mFileName.IsEmpty()) {
      mFileName.SetIsVoid(true);
    }
    aException->GetLineNumber(aCx, &mLineNumber);
    aException->GetColumnNumber(&mColumn);

    mFlags = JSREPORT_EXCEPTION;
}

static LazyLogModule gJSDiagnostics("JSDiagnostics");

void
xpc::ErrorBase::AppendErrorDetailsTo(nsCString& error)
{
    error.Append(NS_LossyConvertUTF16toASCII(mFileName));
    error.AppendLiteral(", line ");
    error.AppendInt(mLineNumber, 10);
    error.AppendLiteral(": ");
    error.Append(NS_LossyConvertUTF16toASCII(mErrorMsg));
}

void
xpc::ErrorNote::LogToStderr()
{
    if (!nsContentUtils::DOMWindowDumpEnabled()) {
        return;
    }

    nsAutoCString error;
    error.AssignLiteral("JavaScript note: ");
    AppendErrorDetailsTo(error);

    fprintf(stderr, "%s\n", error.get());
    fflush(stderr);
}

void
xpc::ErrorReport::LogToStderr()
{
    if (!nsContentUtils::DOMWindowDumpEnabled()) {
        return;
    }

    nsAutoCString error;
    error.AssignLiteral("JavaScript ");
    if (JSREPORT_IS_STRICT(mFlags)) {
        error.AppendLiteral("strict ");
    }
    if (JSREPORT_IS_WARNING(mFlags)) {
        error.AppendLiteral("warning: ");
    } else {
        error.AppendLiteral("error: ");
    }
    AppendErrorDetailsTo(error);

    fprintf(stderr, "%s\n", error.get());
    fflush(stderr);

    for (size_t i = 0, len = mNotes.Length(); i < len; i++) {
        ErrorNote& note = mNotes[i];
        note.LogToStderr();
    }
}

void
xpc::ErrorReport::LogToConsole()
{
  LogToConsoleWithStack(nullptr);
}
void
xpc::ErrorReport::LogToConsoleWithStack(JS::HandleObject aStack)
{
    LogToStderr();

    MOZ_LOG(gJSDiagnostics,
            JSREPORT_IS_WARNING(mFlags) ? LogLevel::Warning : LogLevel::Error,
            ("file %s, line %u\n%s", NS_LossyConvertUTF16toASCII(mFileName).get(),
             mLineNumber, NS_LossyConvertUTF16toASCII(mErrorMsg).get()));

    // Log to the console. We do this last so that we can simply return if
    // there's no console service without affecting the other reporting
    // mechanisms.
    nsCOMPtr<nsIConsoleService> consoleService =
      do_GetService(NS_CONSOLESERVICE_CONTRACTID);
    NS_ENSURE_TRUE_VOID(consoleService);

    RefPtr<nsScriptErrorBase> errorObject;
    if (mWindowID && aStack) {
      // Only set stack on messages related to a document
      // As we cache messages in the console service,
      // we have to ensure not leaking them after the related
      // context is destroyed and we only track document lifecycle for now.
      errorObject = new nsScriptErrorWithStack(aStack);
    } else {
      errorObject = new nsScriptError();
    }
    errorObject->SetErrorMessageName(mErrorMsgName);

    nsresult rv = errorObject->InitWithWindowID(mErrorMsg, mFileName, mSourceLine,
                                                mLineNumber, mColumn, mFlags,
                                                mCategory, mWindowID);
    NS_ENSURE_SUCCESS_VOID(rv);

    for (size_t i = 0, len = mNotes.Length(); i < len; i++) {
        ErrorNote& note = mNotes[i];

        nsScriptErrorNote* noteObject = new nsScriptErrorNote();
        noteObject->Init(note.mErrorMsg, note.mFileName,
                         note.mLineNumber, note.mColumn);
        errorObject->AddNote(noteObject);
    }

    consoleService->LogMessage(errorObject);

}

/* static */
void
xpc::ErrorNote::ErrorNoteToMessageString(JSErrorNotes::Note* aNote,
                                         nsAString& aString)
{
    aString.Truncate();
    if (aNote->message()) {
        aString.Append(NS_ConvertUTF8toUTF16(aNote->message().c_str()));
    }
}

/* static */
void
xpc::ErrorReport::ErrorReportToMessageString(JSErrorReport* aReport,
                                             nsAString& aString)
{
    aString.Truncate();
    if (aReport->message()) {
        JSFlatString* name = js::GetErrorTypeName(CycleCollectedJSContext::Get()->Context(), aReport->exnType);
        if (name) {
            AssignJSFlatString(aString, name);
            aString.AppendLiteral(": ");
        }
        aString.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
    }
}

/***************************************************************************/


nsresult
nsXPConnect::GetInfoForIID(const nsIID * aIID, nsIInterfaceInfo** info)
{
  return XPTInterfaceInfoManager::GetSingleton()->GetInfoForIID(aIID, info);
}

nsresult
nsXPConnect::GetInfoForName(const char * name, nsIInterfaceInfo** info)
{
  nsresult rv = XPTInterfaceInfoManager::GetSingleton()->GetInfoForName(name, info);
  return NS_FAILED(rv) ? NS_OK : NS_ERROR_NO_INTERFACE;
}

NS_IMETHODIMP
nsXPConnect::GarbageCollect(uint32_t reason)
{
    GetContext()->GarbageCollect(reason);
    return NS_OK;
}

void
xpc_MarkInCCGeneration(nsISupports* aVariant, uint32_t aGeneration)
{
    nsCOMPtr<XPCVariant> variant = do_QueryInterface(aVariant);
    if (variant) {
        variant->SetCCGeneration(aGeneration);
        variant->GetJSVal(); // Unmarks gray JSObject.
        XPCVariant* weak = variant.get();
        variant = nullptr;
        if (weak->IsPurple()) {
          weak->RemovePurple();
        }
    }
}

void
xpc_TryUnmarkWrappedGrayObject(nsISupports* aWrappedJS)
{
    // QIing to nsIXPConnectWrappedJSUnmarkGray may have side effects!
    nsCOMPtr<nsIXPConnectWrappedJSUnmarkGray> wjsug =
      do_QueryInterface(aWrappedJS);
    Unused << wjsug;
    MOZ_ASSERT(!wjsug, "One should never be able to QI to "
                       "nsIXPConnectWrappedJSUnmarkGray successfully!");
}

/***************************************************************************/
/***************************************************************************/
// nsIXPConnect interface methods...

template<typename T>
static inline T UnexpectedFailure(T rv)
{
    NS_ERROR("This is not supposed to fail!");
    return rv;
}

void
xpc::TraceXPCGlobal(JSTracer* trc, JSObject* obj)
{
    if (js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL)
        mozilla::dom::TraceProtoAndIfaceCache(trc, obj);

    // We might be called from a GC during the creation of a global, before we've
    // been able to set up the compartment private or the XPCWrappedNativeScope,
    // so we need to null-check those.
    xpc::CompartmentPrivate* compartmentPrivate = xpc::CompartmentPrivate::Get(obj);
    if (compartmentPrivate && compartmentPrivate->scope)
        compartmentPrivate->scope->TraceInside(trc);
}


namespace xpc {

JSObject*
CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal,
                   JS::CompartmentOptions& aOptions)
{
    MOZ_ASSERT(NS_IsMainThread(), "using a principal off the main thread?");
    MOZ_ASSERT(principal);

    MOZ_RELEASE_ASSERT(principal != nsContentUtils::GetNullSubjectPrincipal(),
                       "The null subject principal is getting inherited - fix that!");

    RootedObject global(cx,
                        JS_NewGlobalObject(cx, clasp, nsJSPrincipals::get(principal),
                                           JS::DontFireOnNewGlobalHook, aOptions));
    if (!global)
        return nullptr;
    JSAutoCompartment ac(cx, global);

    // The constructor automatically attaches the scope to the compartment private
    // of |global|.
    (void) new XPCWrappedNativeScope(cx, global);

    if (clasp->flags & JSCLASS_DOM_GLOBAL) {
#ifdef DEBUG
        // Verify that the right trace hook is called. Note that this doesn't
        // work right for wrapped globals, since the tracing situation there is
        // more complicated. Manual inspection shows that they do the right
        // thing.  Also note that we only check this for JSCLASS_DOM_GLOBAL
        // classes because xpc::TraceXPCGlobal won't call
        // TraceProtoAndIfaceCache unless that flag is set.
        if (!((const js::Class*)clasp)->isWrappedNative())
        {
            VerifyTraceProtoAndIfaceCacheCalledTracer trc(cx);
            TraceChildren(&trc, GCCellPtr(global.get()));
            MOZ_ASSERT(trc.ok, "Trace hook on global needs to call TraceXPCGlobal for XPConnect compartments.");
        }
#endif

        const char* className = clasp->name;
        AllocateProtoAndIfaceCache(global,
                                   (strcmp(className, "Window") == 0 ||
                                    strcmp(className, "ChromeWindow") == 0)
                                   ? ProtoAndIfaceCache::WindowLike
                                   : ProtoAndIfaceCache::NonWindowLike);
    }

    return global;
}

void
InitGlobalObjectOptions(JS::CompartmentOptions& aOptions,
                        nsIPrincipal* aPrincipal)
{
    bool shouldDiscardSystemSource = ShouldDiscardSystemSource();
    bool extraWarningsForSystemJS = ExtraWarningsForSystemJS();

    bool isSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);

    if (isSystem) {
        // Make sure [SecureContext] APIs are visible:
        aOptions.creationOptions().setSecureContext(true);
    }

    if (shouldDiscardSystemSource) {
        bool discardSource = isSystem;

        aOptions.behaviors().setDiscardSource(discardSource);
    }

    if (extraWarningsForSystemJS) {
        if (isSystem)
            aOptions.behaviors().extraWarningsOverride().set(true);
    }
}

bool
InitGlobalObject(JSContext* aJSContext, JS::Handle<JSObject*> aGlobal, uint32_t aFlags)
{
    // Immediately enter the global's compartment so that everything we create
    // ends up there.
    JSAutoCompartment ac(aJSContext, aGlobal);

    // Stuff coming through this path always ends up as a DOM global.
    MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL);

    if (!(aFlags & nsIXPConnect::OMIT_COMPONENTS_OBJECT)) {
        // XPCCallContext gives us an active request needed to save/restore.
        if (!CompartmentPrivate::Get(aGlobal)->scope->AttachComponentsObject(aJSContext) ||
            !XPCNativeWrapper::AttachNewConstructorObject(aJSContext, aGlobal)) {
            return UnexpectedFailure(false);
        }
    }

    if (!(aFlags & nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK))
        JS_FireOnNewGlobalObject(aJSContext, aGlobal);

    return true;
}

} // namespace xpc

NS_IMETHODIMP
nsXPConnect::InitClassesWithNewWrappedGlobal(JSContext * aJSContext,
                                             nsISupports* aCOMObj,
                                             nsIPrincipal * aPrincipal,
                                             uint32_t aFlags,
                                             JS::CompartmentOptions& aOptions,
                                             nsIXPConnectJSObjectHolder** _retval)
{
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aCOMObj, "bad param");
    MOZ_ASSERT(_retval, "bad param");

    // We pass null for the 'extra' pointer during global object creation, so
    // we need to have a principal.
    MOZ_ASSERT(aPrincipal);

    InitGlobalObjectOptions(aOptions, aPrincipal);

    // Call into XPCWrappedNative to make a new global object, scope, and global
    // prototype.
    xpcObjectHelper helper(aCOMObj);
    MOZ_ASSERT(helper.GetScriptableFlags() & nsIXPCScriptable::IS_GLOBAL_OBJECT);
    RefPtr<XPCWrappedNative> wrappedGlobal;
    nsresult rv =
        XPCWrappedNative::WrapNewGlobal(helper, aPrincipal,
                                        aFlags & nsIXPConnect::INIT_JS_STANDARD_CLASSES,
                                        aOptions, getter_AddRefs(wrappedGlobal));
    NS_ENSURE_SUCCESS(rv, rv);

    // Grab a copy of the global and enter its compartment.
    RootedObject global(aJSContext, wrappedGlobal->GetFlatJSObject());
    MOZ_ASSERT(JS_IsGlobalObject(global));

    if (!InitGlobalObject(aJSContext, global, aFlags))
        return UnexpectedFailure(NS_ERROR_FAILURE);

    wrappedGlobal.forget(_retval);
    return NS_OK;
}

static nsresult
NativeInterface2JSObject(HandleObject aScope,
                         nsISupports* aCOMObj,
                         nsWrapperCache* aCache,
                         const nsIID * aIID,
                         bool aAllowWrapping,
                         MutableHandleValue aVal,
                         nsIXPConnectJSObjectHolder** aHolder)
{
    AutoJSContext cx;
    JSAutoCompartment ac(cx, aScope);

    nsresult rv;
    xpcObjectHelper helper(aCOMObj, aCache);
    if (!XPCConvert::NativeInterface2JSObject(aVal, aHolder, helper, aIID,
                                              aAllowWrapping, &rv))
        return rv;

    MOZ_ASSERT(aAllowWrapping || !xpc::WrapperFactory::IsXrayWrapper(&aVal.toObject()),
               "Shouldn't be returning a xray wrapper here");

    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::WrapNative(JSContext * aJSContext,
                        JSObject * aScopeArg,
                        nsISupports* aCOMObj,
                        const nsIID & aIID,
                        JSObject** aRetVal)
{
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aScopeArg, "bad param");
    MOZ_ASSERT(aCOMObj, "bad param");

    RootedObject aScope(aJSContext, aScopeArg);
    RootedValue v(aJSContext);
    nsresult rv = NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
                                           true, &v, nullptr);
    if (NS_FAILED(rv))
        return rv;

    if (!v.isObjectOrNull())
        return NS_ERROR_FAILURE;

    *aRetVal = v.toObjectOrNull();
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::WrapNativeHolder(JSContext * aJSContext,
                              JSObject * aScopeArg,
                              nsISupports* aCOMObj,
                              const nsIID & aIID,
                              nsIXPConnectJSObjectHolder **aHolder)
{
    MOZ_ASSERT(aHolder, "bad param");
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aScopeArg, "bad param");
    MOZ_ASSERT(aCOMObj, "bad param");

    RootedObject aScope(aJSContext, aScopeArg);
    RootedValue v(aJSContext);
    return NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
                                    true, &v, aHolder);
}

NS_IMETHODIMP
nsXPConnect::WrapNativeToJSVal(JSContext* aJSContext,
                               JSObject* aScopeArg,
                               nsISupports* aCOMObj,
                               nsWrapperCache* aCache,
                               const nsIID* aIID,
                               bool aAllowWrapping,
                               MutableHandleValue aVal)
{
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aScopeArg, "bad param");
    MOZ_ASSERT(aCOMObj, "bad param");

    RootedObject aScope(aJSContext, aScopeArg);
    return NativeInterface2JSObject(aScope, aCOMObj, aCache, aIID,
                                    aAllowWrapping, aVal, nullptr);
}

NS_IMETHODIMP
nsXPConnect::WrapJS(JSContext * aJSContext,
                    JSObject * aJSObjArg,
                    const nsIID & aIID,
                    void * *result)
{
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aJSObjArg, "bad param");
    MOZ_ASSERT(result, "bad param");

    *result = nullptr;

    RootedObject aJSObj(aJSContext, aJSObjArg);
    JSAutoCompartment ac(aJSContext, aJSObj);

    nsresult rv = NS_ERROR_UNEXPECTED;
    if (!XPCConvert::JSObject2NativeInterface(result, aJSObj,
                                              &aIID, nullptr, &rv))
        return rv;
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::JSValToVariant(JSContext* cx,
                            HandleValue aJSVal,
                            nsIVariant** aResult)
{
    NS_PRECONDITION(aResult, "bad param");

    RefPtr<XPCVariant> variant = XPCVariant::newVariant(cx, aJSVal);
    variant.forget(aResult);
    NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);

    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::WrapJSAggregatedToNative(nsISupports* aOuter,
                                      JSContext* aJSContext,
                                      JSObject* aJSObjArg,
                                      const nsIID& aIID,
                                      void** result)
{
    MOZ_ASSERT(aOuter, "bad param");
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aJSObjArg, "bad param");
    MOZ_ASSERT(result, "bad param");

    *result = nullptr;

    RootedObject aJSObj(aJSContext, aJSObjArg);
    nsresult rv;
    if (!XPCConvert::JSObject2NativeInterface(result, aJSObj,
                                              &aIID, aOuter, &rv))
        return rv;
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::GetWrappedNativeOfJSObject(JSContext * aJSContext,
                                        JSObject * aJSObjArg,
                                        nsIXPConnectWrappedNative** _retval)
{
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aJSObjArg, "bad param");
    MOZ_ASSERT(_retval, "bad param");

    RootedObject aJSObj(aJSContext, aJSObjArg);
    aJSObj = js::CheckedUnwrap(aJSObj, /* stopAtWindowProxy = */ false);
    if (!aJSObj || !IS_WN_REFLECTOR(aJSObj)) {
        *_retval = nullptr;
        return NS_ERROR_FAILURE;
    }

    RefPtr<XPCWrappedNative> temp = XPCWrappedNative::Get(aJSObj);
    temp.forget(_retval);
    return NS_OK;
}

already_AddRefed<nsISupports>
xpc::UnwrapReflectorToISupports(JSObject* reflector)
{
    // Unwrap security wrappers, if allowed.
    reflector = js::CheckedUnwrap(reflector, /* stopAtWindowProxy = */ false);
    if (!reflector)
        return nullptr;

    // Try XPCWrappedNatives.
    if (IS_WN_REFLECTOR(reflector)) {
        XPCWrappedNative* wn = XPCWrappedNative::Get(reflector);
        if (!wn)
            return nullptr;
        nsCOMPtr<nsISupports> native = wn->Native();
        return native.forget();
    }

    // Try DOM objects.  This QI without taking a ref first is safe, because
    // this if non-null our thing will definitely be a DOM object, and we know
    // their QI to nsISupports doesn't do anything weird.
    nsCOMPtr<nsISupports> canonical =
        do_QueryInterface(mozilla::dom::UnwrapDOMObjectToISupports(reflector));
    return canonical.forget();
}

NS_IMETHODIMP
nsXPConnect::GetWrappedNativeOfNativeObject(JSContext * aJSContext,
                                            JSObject * aScopeArg,
                                            nsISupports* aCOMObj,
                                            const nsIID & aIID,
                                            nsIXPConnectWrappedNative** _retval)
{
    MOZ_ASSERT(aJSContext, "bad param");
    MOZ_ASSERT(aScopeArg, "bad param");
    MOZ_ASSERT(aCOMObj, "bad param");
    MOZ_ASSERT(_retval, "bad param");

    *_retval = nullptr;

    RootedObject aScope(aJSContext, aScopeArg);

    XPCWrappedNativeScope* scope = ObjectScope(aScope);
    if (!scope)
        return UnexpectedFailure(NS_ERROR_FAILURE);

    RefPtr<XPCNativeInterface> iface =
        XPCNativeInterface::GetNewOrUsed(&aIID);
    if (!iface)
        return NS_ERROR_FAILURE;

    XPCWrappedNative* wrapper;

    nsresult rv = XPCWrappedNative::GetUsedOnly(aCOMObj, scope, iface, &wrapper);
    if (NS_FAILED(rv))
        return NS_ERROR_FAILURE;
    *_retval = static_cast<nsIXPConnectWrappedNative*>(wrapper);
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::GetCurrentJSStack(nsIStackFrame * *aCurrentJSStack)
{
    MOZ_ASSERT(aCurrentJSStack, "bad param");

    nsCOMPtr<nsIStackFrame> currentStack = dom::GetCurrentJSStack();
    currentStack.forget(aCurrentJSStack);

    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::GetCurrentNativeCallContext(nsAXPCNativeCallContext * *aCurrentNativeCallContext)
{
    MOZ_ASSERT(aCurrentNativeCallContext, "bad param");

    *aCurrentNativeCallContext = XPCJSContext::Get()->GetCallContext();
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::SetFunctionThisTranslator(const nsIID & aIID,
                                       nsIXPCFunctionThisTranslator* aTranslator)
{
    XPCJSContext* cx = GetContext();
    IID2ThisTranslatorMap* map = cx->GetThisTranslatorMap();
    map->Add(aIID, aTranslator);
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::CreateSandbox(JSContext* cx, nsIPrincipal* principal,
                           JSObject** _retval)
{
    *_retval = nullptr;

    RootedValue rval(cx);
    SandboxOptions options;
    nsresult rv = CreateSandboxObject(cx, &rval, principal, options);
    MOZ_ASSERT(NS_FAILED(rv) || !rval.isPrimitive(),
               "Bad return value from xpc_CreateSandboxObject()!");

    if (NS_SUCCEEDED(rv) && !rval.isPrimitive()) {
        *_retval = rval.toObjectOrNull();
    }

    return rv;
}

NS_IMETHODIMP
nsXPConnect::EvalInSandboxObject(const nsAString& source, const char* filename,
                                 JSContext* cx, JSObject* sandboxArg,
                                 int32_t jsVersion,
                                 MutableHandleValue rval)
{
#ifdef DEBUG
    {
        const char *version = JS_VersionToString(JSVersion(jsVersion));
        MOZ_ASSERT(version && strcmp(version, "unknown") != 0, "Illegal JS version passed");
    }
#endif
    if (!sandboxArg)
        return NS_ERROR_INVALID_ARG;

    RootedObject sandbox(cx, sandboxArg);
    nsCString filenameStr;
    if (filename) {
        filenameStr.Assign(filename);
    } else {
        filenameStr = NS_LITERAL_CSTRING("x-bogus://XPConnect/Sandbox");
    }
    return EvalInSandbox(cx, sandbox, source, filenameStr, 1,
                         JSVersion(jsVersion), rval);
}

NS_IMETHODIMP
nsXPConnect::GetWrappedNativePrototype(JSContext* aJSContext,
                                       JSObject* aScopeArg,
                                       nsIClassInfo* aClassInfo,
                                       JSObject** aRetVal)
{
    RootedObject aScope(aJSContext, aScopeArg);
    JSAutoCompartment ac(aJSContext, aScope);

    XPCWrappedNativeScope* scope = ObjectScope(aScope);
    if (!scope)
        return UnexpectedFailure(NS_ERROR_FAILURE);

    XPCNativeScriptableCreateInfo sciProto;
    XPCWrappedNative::GatherProtoScriptableCreateInfo(aClassInfo, sciProto);

    AutoMarkingWrappedNativeProtoPtr proto(aJSContext);
    proto = XPCWrappedNativeProto::GetNewOrUsed(scope, aClassInfo, &sciProto);
    if (!proto)
        return UnexpectedFailure(NS_ERROR_FAILURE);

    JSObject* protoObj = proto->GetJSProtoObject();
    if (!protoObj)
        return UnexpectedFailure(NS_ERROR_FAILURE);

    *aRetVal = protoObj;

    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::DebugDump(int16_t depth)
{
#ifdef DEBUG
    depth-- ;
    XPC_LOG_ALWAYS(("nsXPConnect @ %x with mRefCnt = %d", this, mRefCnt.get()));
    XPC_LOG_INDENT();
        XPC_LOG_ALWAYS(("gSelf @ %x", gSelf));
        XPC_LOG_ALWAYS(("gOnceAliveNowDead is %d", (int)gOnceAliveNowDead));
        if (mContext) {
            if (depth)
                mContext->DebugDump(depth);
            else
                XPC_LOG_ALWAYS(("XPCJSContext @ %x", mContext));
        } else
            XPC_LOG_ALWAYS(("mContext is null"));
        XPCWrappedNativeScope::DebugDumpAllScopes(depth);
    XPC_LOG_OUTDENT();
#endif
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::DebugDumpObject(nsISupports* p, int16_t depth)
{
#ifdef DEBUG
    if (!depth)
        return NS_OK;
    if (!p) {
        XPC_LOG_ALWAYS(("*** Cound not dump object with NULL address"));
        return NS_OK;
    }

    nsCOMPtr<nsIXPConnect> xpc;
    nsCOMPtr<nsIXPCWrappedJSClass> wjsc;
    nsCOMPtr<nsIXPConnectWrappedNative> wn;
    nsCOMPtr<nsIXPConnectWrappedJS> wjs;

    if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnect),
                                       getter_AddRefs(xpc)))) {
        XPC_LOG_ALWAYS(("Dumping a nsIXPConnect..."));
        xpc->DebugDump(depth);
    } else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPCWrappedJSClass),
                                              getter_AddRefs(wjsc)))) {
        XPC_LOG_ALWAYS(("Dumping a nsIXPCWrappedJSClass..."));
        wjsc->DebugDump(depth);
    } else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnectWrappedNative),
                                              getter_AddRefs(wn)))) {
        XPC_LOG_ALWAYS(("Dumping a nsIXPConnectWrappedNative..."));
        wn->DebugDump(depth);
    } else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnectWrappedJS),
                                              getter_AddRefs(wjs)))) {
        XPC_LOG_ALWAYS(("Dumping a nsIXPConnectWrappedJS..."));
        wjs->DebugDump(depth);
    } else {
        XPC_LOG_ALWAYS(("*** Could not dump the nsISupports @ %x", p));
    }
#endif
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::DebugDumpJSStack(bool showArgs,
                              bool showLocals,
                              bool showThisProps)
{
    xpc_DumpJSStack(showArgs, showLocals, showThisProps);

    return NS_OK;
}

char*
nsXPConnect::DebugPrintJSStack(bool showArgs,
                               bool showLocals,
                               bool showThisProps)
{
    JSContext* cx = nsContentUtils::GetCurrentJSContext();
    if (!cx)
        printf("there is no JSContext on the nsIThreadJSContextStack!\n");
    else
        return xpc_PrintJSStack(cx, showArgs, showLocals, showThisProps);

    return nullptr;
}

NS_IMETHODIMP
nsXPConnect::VariantToJS(JSContext* ctx, JSObject* scopeArg, nsIVariant* value,
                         MutableHandleValue _retval)
{
    NS_PRECONDITION(ctx, "bad param");
    NS_PRECONDITION(scopeArg, "bad param");
    NS_PRECONDITION(value, "bad param");

    RootedObject scope(ctx, scopeArg);
    MOZ_ASSERT(js::IsObjectInContextCompartment(scope, ctx));

    nsresult rv = NS_OK;
    if (!XPCVariant::VariantDataToJS(value, &rv, _retval)) {
        if (NS_FAILED(rv))
            return rv;

        return NS_ERROR_FAILURE;
    }
    return NS_OK;
}

NS_IMETHODIMP
nsXPConnect::JSToVariant(JSContext* ctx, HandleValue value, nsIVariant** _retval)
{
    NS_PRECONDITION(ctx, "bad param");
    NS_PRECONDITION(_retval, "bad param");

    RefPtr<XPCVariant> variant = XPCVariant::newVariant(ctx, value);
    variant.forget(_retval);
    if (!(*_retval))
        return NS_ERROR_FAILURE;

    return NS_OK;
}

nsIPrincipal*
nsXPConnect::GetPrincipal(JSObject* obj, bool allowShortCircuit) const
{
    MOZ_ASSERT(IS_WN_REFLECTOR(obj), "What kind of wrapper is this?");

    XPCWrappedNative* xpcWrapper = XPCWrappedNative::Get(obj);
    if (xpcWrapper) {
        if (allowShortCircuit) {
            nsIPrincipal* result = xpcWrapper->GetObjectPrincipal();
            if (result) {
                return result;
            }
        }

        // If not, check if it points to an nsIScriptObjectPrincipal
        nsCOMPtr<nsIScriptObjectPrincipal> objPrin =
            do_QueryInterface(xpcWrapper->Native());
        if (objPrin) {
            nsIPrincipal* result = objPrin->GetPrincipal();
            if (result) {
                return result;
            }
        }
    }

    return nullptr;
}

namespace xpc {

bool
Base64Encode(JSContext* cx, HandleValue val, MutableHandleValue out)
{
    MOZ_ASSERT(cx);

    nsAutoCString encodedString;
    if (!ConvertJSValueToByteString(cx, val, false, encodedString)) {
        return false;
    }

    nsAutoCString result;
    if (NS_FAILED(mozilla::Base64Encode(encodedString, result))) {
        JS_ReportErrorASCII(cx, "Failed to encode base64 data!");
        return false;
    }

    JSString* str = JS_NewStringCopyN(cx, result.get(), result.Length());
    if (!str)
        return false;

    out.setString(str);
    return true;
}

bool
Base64Decode(JSContext* cx, HandleValue val, MutableHandleValue out)
{
    MOZ_ASSERT(cx);

    nsAutoCString encodedString;
    if (!ConvertJSValueToByteString(cx, val, false, encodedString)) {
        return false;
    }

    nsAutoCString result;
    if (NS_FAILED(mozilla::Base64Decode(encodedString, result))) {
        JS_ReportErrorASCII(cx, "Failed to decode base64 string!");
        return false;
    }

    JSString* str = JS_NewStringCopyN(cx, result.get(), result.Length());
    if (!str)
        return false;

    out.setString(str);
    return true;
}

void
SetLocationForGlobal(JSObject* global, const nsACString& location)
{
    MOZ_ASSERT(global);
    CompartmentPrivate::Get(global)->SetLocation(location);
}

void
SetLocationForGlobal(JSObject* global, nsIURI* locationURI)
{
    MOZ_ASSERT(global);
    CompartmentPrivate::Get(global)->SetLocationURI(locationURI);
}

} // namespace xpc

NS_IMETHODIMP
nsXPConnect::NotifyDidPaint()
{
    JS::NotifyDidPaint(GetContext()->Context());
    return NS_OK;
}

static nsresult
WriteScriptOrFunction(nsIObjectOutputStream* stream, JSContext* cx,
                      JSScript* scriptArg, HandleObject functionObj)
{
    // Exactly one of script or functionObj must be given
    MOZ_ASSERT(!scriptArg != !functionObj);

    RootedScript script(cx, scriptArg);
    if (!script) {
        RootedFunction fun(cx, JS_GetObjectFunction(functionObj));
        script.set(JS_GetFunctionScript(cx, fun));
    }

    uint8_t flags = 0; // We don't have flags anymore.
    nsresult rv = stream->Write8(flags);
    if (NS_FAILED(rv))
        return rv;


    TranscodeBuffer buffer;
    TranscodeResult code;
    {
        if (functionObj)
            code = EncodeInterpretedFunction(cx, buffer, functionObj);
        else
            code = EncodeScript(cx, buffer, script);
    }

    if (code != TranscodeResult_Ok) {
        if ((code & TranscodeResult_Failure) != 0)
            return NS_ERROR_FAILURE;
        MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
        JS_ClearPendingException(cx);
        return NS_ERROR_OUT_OF_MEMORY;
    }

    size_t size = buffer.length();
    if (size > UINT32_MAX)
        return NS_ERROR_FAILURE;
    rv = stream->Write32(size);
    if (NS_SUCCEEDED(rv))
        rv = stream->WriteBytes(reinterpret_cast<char*>(buffer.begin()), size);

    return rv;
}

static nsresult
ReadScriptOrFunction(nsIObjectInputStream* stream, JSContext* cx,
                     JSScript** scriptp, JSObject** functionObjp)
{
    // Exactly one of script or functionObj must be given
    MOZ_ASSERT(!scriptp != !functionObjp);

    uint8_t flags;
    nsresult rv = stream->Read8(&flags);
    if (NS_FAILED(rv))
        return rv;

    // We don't serialize mutedError-ness of scripts, which is fine as long as
    // we only serialize system and XUL-y things. We can detect this by checking
    // where the caller wants us to deserialize.
    MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome() ||
                       CurrentGlobalOrNull(cx) == xpc::CompilationScope());

    uint32_t size;
    rv = stream->Read32(&size);
    if (NS_FAILED(rv))
        return rv;

    char* data;
    rv = stream->ReadBytes(size, &data);
    if (NS_FAILED(rv))
        return rv;

    TranscodeBuffer buffer;
    buffer.replaceRawBuffer(reinterpret_cast<uint8_t*>(data), size);

    {
        TranscodeResult code;
        if (scriptp) {
            Rooted<JSScript*> script(cx);
            code = DecodeScript(cx, buffer, &script);
            if (code == TranscodeResult_Ok)
                *scriptp = script.get();
        } else {
            Rooted<JSFunction*> funobj(cx);
            code = DecodeInterpretedFunction(cx, buffer, &funobj);
            if (code == TranscodeResult_Ok)
                *functionObjp = JS_GetFunctionObject(funobj.get());
        }

        if (code != TranscodeResult_Ok) {
            if ((code & TranscodeResult_Failure) != 0)
                return NS_ERROR_FAILURE;
            MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
            JS_ClearPendingException(cx);
            return NS_ERROR_OUT_OF_MEMORY;
        }
    }

    return rv;
}

NS_IMETHODIMP
nsXPConnect::WriteScript(nsIObjectOutputStream* stream, JSContext* cx, JSScript* script)
{
    return WriteScriptOrFunction(stream, cx, script, nullptr);
}

NS_IMETHODIMP
nsXPConnect::ReadScript(nsIObjectInputStream* stream, JSContext* cx, JSScript** scriptp)
{
    return ReadScriptOrFunction(stream, cx, scriptp, nullptr);
}

NS_IMETHODIMP
nsXPConnect::WriteFunction(nsIObjectOutputStream* stream, JSContext* cx, JSObject* functionObjArg)
{
    RootedObject functionObj(cx, functionObjArg);
    return WriteScriptOrFunction(stream, cx, nullptr, functionObj);
}

NS_IMETHODIMP
nsXPConnect::ReadFunction(nsIObjectInputStream* stream, JSContext* cx, JSObject** functionObjp)
{
    return ReadScriptOrFunction(stream, cx, nullptr, functionObjp);
}

/* These are here to be callable from a debugger */
extern "C" {
JS_EXPORT_API(void) DumpJSStack()
{
    xpc_DumpJSStack(true, true, false);
}

JS_EXPORT_API(char*) PrintJSStack()
{
    nsresult rv;
    nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
    return (NS_SUCCEEDED(rv) && xpc) ?
        xpc->DebugPrintJSStack(true, true, false) :
        nullptr;
}

JS_EXPORT_API(void) DumpCompleteHeap()
{
    nsCOMPtr<nsICycleCollectorListener> listener =
      do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
    if (!listener) {
      NS_WARNING("Failed to create CC logger");
      return;
    }

    nsCOMPtr<nsICycleCollectorListener> alltracesListener;
    listener->AllTraces(getter_AddRefs(alltracesListener));
    if (!alltracesListener) {
      NS_WARNING("Failed to get all traces logger");
      return;
    }

    nsJSContext::CycleCollectNow(alltracesListener);
}

} // extern "C"

namespace xpc {

bool
Atob(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (!args.length())
        return true;

    return xpc::Base64Decode(cx, args[0], args.rval());
}

bool
Btoa(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (!args.length())
        return true;

    return xpc::Base64Encode(cx, args[0], args.rval());
}

bool
IsXrayWrapper(JSObject* obj)
{
    return WrapperFactory::IsXrayWrapper(obj);
}

JSAddonId*
NewAddonId(JSContext* cx, const nsACString& id)
{
    JS::RootedString str(cx, JS_NewStringCopyN(cx, id.BeginReading(), id.Length()));
    if (!str)
        return nullptr;
    return JS::NewAddonId(cx, str);
}

bool
SetAddonInterposition(const nsACString& addonIdStr, nsIAddonInterposition* interposition)
{
    JSAddonId* addonId;
    // We enter the junk scope just to allocate a string, which actually will go
    // in the system zone.
    AutoJSAPI jsapi;
    if (!jsapi.Init(xpc::PrivilegedJunkScope()))
        return false;
    addonId = NewAddonId(jsapi.cx(), addonIdStr);
    if (!addonId)
        return false;
    return XPCWrappedNativeScope::SetAddonInterposition(jsapi.cx(), addonId, interposition);
}

bool
AllowCPOWsInAddon(const nsACString& addonIdStr, bool allow)
{
    JSAddonId* addonId;
    // We enter the junk scope just to allocate a string, which actually will go
    // in the system zone.
    AutoJSAPI jsapi;
    if (!jsapi.Init(xpc::PrivilegedJunkScope()))
        return false;
    addonId = NewAddonId(jsapi.cx(), addonIdStr);
    if (!addonId)
        return false;
    return XPCWrappedNativeScope::AllowCPOWsInAddon(jsapi.cx(), addonId, allow);
}

} // namespace xpc

namespace mozilla {
namespace dom {

bool
IsChromeOrXBL(JSContext* cx, JSObject* /* unused */)
{
    MOZ_ASSERT(NS_IsMainThread());
    JSCompartment* c = js::GetContextCompartment(cx);

    // For remote XUL, we run XBL in the XUL scope. Given that we care about
    // compat and not security for remote XUL, we just always claim to be XBL.
    //
    // Note that, for performance, we don't check AllowXULXBLForPrincipal here,
    // and instead rely on the fact that AllowContentXBLScope() only returns false in
    // remote XUL situations.
    return AccessCheck::isChrome(c) || IsContentXBLScope(c) || !AllowContentXBLScope(c);
}

namespace workers {
extern bool IsCurrentThreadRunningChromeWorker();
} // namespace workers

bool
ThreadSafeIsChromeOrXBL(JSContext* cx, JSObject* obj)
{
    if (NS_IsMainThread()) {
        return IsChromeOrXBL(cx, obj);
    }
    return workers::IsCurrentThreadRunningChromeWorker();
}

} // namespace dom
} // namespace mozilla