mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
Bug 779048 part 1. Implement a parent class for C++ reflections of callback functions in WebIDL. r=bholley,smaug
This commit is contained in:
parent
63fb55a443
commit
05e4e3b75a
@ -1650,6 +1650,11 @@ nsScriptSecurityManager::CheckFunctionAccess(JSContext *aCx, void *aFunObj,
|
||||
if (!result)
|
||||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
|
||||
if (!aTargetObj) {
|
||||
// We're done here
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get origin of subject and object and compare.
|
||||
*/
|
||||
|
@ -3615,13 +3615,8 @@ nsJSContext::DropScriptObject(void* aScriptObject)
|
||||
void
|
||||
nsJSContext::ReportPendingException()
|
||||
{
|
||||
// set aside the frame chain, since it has nothing to do with the
|
||||
// exception we're reporting.
|
||||
if (mIsInitialized && ::JS_IsExceptionPending(mContext)) {
|
||||
bool saved = ::JS_SaveFrameChain(mContext);
|
||||
::JS_ReportPendingException(mContext);
|
||||
if (saved)
|
||||
::JS_RestoreFrameChain(mContext);
|
||||
if (mIsInitialized) {
|
||||
nsJSUtils::ReportPendingException(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,8 +218,10 @@ private:
|
||||
JSContext *mContext;
|
||||
bool mActive;
|
||||
|
||||
protected:
|
||||
// Public so we can use it from CallbackFunction
|
||||
public:
|
||||
struct TerminationFuncHolder;
|
||||
protected:
|
||||
friend struct TerminationFuncHolder;
|
||||
|
||||
struct TerminationFuncClosure
|
||||
@ -242,6 +244,8 @@ protected:
|
||||
TerminationFuncClosure* mNext;
|
||||
};
|
||||
|
||||
// Public so we can use it from CallbackFunction
|
||||
public:
|
||||
struct TerminationFuncHolder
|
||||
{
|
||||
TerminationFuncHolder(nsJSContext* aContext)
|
||||
@ -269,7 +273,8 @@ protected:
|
||||
nsJSContext* mContext;
|
||||
TerminationFuncClosure* mTerminations;
|
||||
};
|
||||
|
||||
|
||||
protected:
|
||||
TerminationFuncClosure* mTerminations;
|
||||
|
||||
private:
|
||||
|
@ -133,3 +133,14 @@ nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext)
|
||||
return innerWindowID;
|
||||
}
|
||||
|
||||
void
|
||||
nsJSUtils::ReportPendingException(JSContext *aContext)
|
||||
{
|
||||
if (JS_IsExceptionPending(aContext)) {
|
||||
bool saved = JS_SaveFrameChain(aContext);
|
||||
JS_ReportPendingException(aContext);
|
||||
if (saved) {
|
||||
JS_RestoreFrameChain(aContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,13 @@ public:
|
||||
* @returns uint64_t the inner window ID.
|
||||
*/
|
||||
static uint64_t GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext);
|
||||
|
||||
/**
|
||||
* Report a pending exception on aContext, if any. Note that this
|
||||
* can be called when the context has a JS stack. If that's the
|
||||
* case, the stack will be set aside before reporting the exception.
|
||||
*/
|
||||
static void ReportPendingException(JSContext *aContext);
|
||||
};
|
||||
|
||||
|
||||
|
156
dom/bindings/CallbackFunction.cpp
Normal file
156
dom/bindings/CallbackFunction.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/CallbackFunction.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "nsIScriptGlobalObject.h"
|
||||
#include "nsIXPConnect.h"
|
||||
#include "nsIScriptContext.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackFunction)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackFunction)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackFunction)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackFunction)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackFunction)
|
||||
tmp->DropCallback();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackFunction)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackFunction)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallable)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
CallbackFunction::CallSetup::CallSetup(JSObject* const aCallable)
|
||||
: mCx(nullptr)
|
||||
{
|
||||
xpc_UnmarkGrayObject(aCallable);
|
||||
|
||||
// We need to produce a useful JSContext here. Ideally one that the callable
|
||||
// is in some sense associated with, so that we can sort of treat it as a
|
||||
// "script entry point".
|
||||
|
||||
// First, find the real underlying callable.
|
||||
JSObject* realCallable = js::UnwrapObject(aCallable);
|
||||
|
||||
// Now get the nsIScriptGlobalObject for this callable.
|
||||
JSContext* cx = nullptr;
|
||||
nsIScriptContext* ctx = nullptr;
|
||||
nsIScriptGlobalObject* sgo = nsJSUtils::GetStaticScriptGlobal(realCallable);
|
||||
if (sgo) {
|
||||
// Make sure that if this is a window it's the current inner, since the
|
||||
// nsIScriptContext and hence JSContext are associated with the outer
|
||||
// window. Which means that if someone holds on to a function from a
|
||||
// now-unloaded document we'd have the new document as the script entry
|
||||
// point...
|
||||
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(sgo);
|
||||
if (win) {
|
||||
MOZ_ASSERT(win->IsInnerWindow());
|
||||
nsPIDOMWindow* outer = win->GetOuterWindow();
|
||||
if (!outer || win != outer->GetCurrentInnerWindow()) {
|
||||
// Just bail out from here
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if not a window at all, just press on
|
||||
|
||||
ctx = sgo->GetContext();
|
||||
if (ctx) {
|
||||
// We don't check whether scripts are enabled on ctx, because
|
||||
// CheckFunctionAccess will do that anyway... and because we ignore them
|
||||
// being disabled if the callee is system.
|
||||
cx = ctx->GetNativeContext();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cx) {
|
||||
// We didn't manage to hunt down a script global to work with. Just fall
|
||||
// back on using the safe context.
|
||||
cx = nsContentUtils::GetSafeJSContext();
|
||||
}
|
||||
|
||||
// Victory! We have a JSContext. Now do the things we need a JSContext for.
|
||||
mAr.construct(cx);
|
||||
|
||||
// Make sure our JSContext is pushed on the stack.
|
||||
if (!mCxPusher.Push(cx, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// After this point we guarantee calling ScriptEvaluated() if we
|
||||
// have an nsIScriptContext.
|
||||
// XXXbz Why, if, say CheckFunctionAccess fails? I know that's how
|
||||
// nsJSContext::CallEventHandler works, but is it required?
|
||||
// FIXME: Bug 807369.
|
||||
mCtx = ctx;
|
||||
|
||||
// Check that it's ok to run this callback at all.
|
||||
// FIXME: Bug 807371: we want a less silly check here.
|
||||
// Make sure to unwrap aCallable before passing it in, because
|
||||
// getting principals from wrappers is silly.
|
||||
nsresult rv = nsContentUtils::GetSecurityManager()->
|
||||
CheckFunctionAccess(cx, js::UnwrapObject(aCallable), nullptr);
|
||||
|
||||
// Construct a termination func holder even if we're not planning to
|
||||
// run any script. We need this because we're going to call
|
||||
// ScriptEvaluated even if we don't run the script... See XXX
|
||||
// comment above.
|
||||
if (ctx) {
|
||||
mTerminationFuncHolder.construct(static_cast<nsJSContext*>(ctx));
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
// Security check failed. We're done here.
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter the compartment of our callable, so we can actually call it.
|
||||
mAc.construct(cx, aCallable);
|
||||
|
||||
// And now we're ready to go.
|
||||
mCx = cx;
|
||||
}
|
||||
|
||||
CallbackFunction::CallSetup::~CallSetup()
|
||||
{
|
||||
// First things first: if we have a JSContext, report any pending
|
||||
// errors on it.
|
||||
if (mCx) {
|
||||
nsJSUtils::ReportPendingException(mCx);
|
||||
}
|
||||
|
||||
// If we have an mCtx, we need to call ScriptEvaluated() on it. But we have
|
||||
// to do that after we pop the JSContext stack (see bug 295983). And to get
|
||||
// our nesting right we have to destroy our JSAutoCompartment first. But be
|
||||
// careful: it might not have been constructed at all!
|
||||
mAc.destroyIfConstructed();
|
||||
|
||||
// XXXbz For that matter why do we need to manually call ScriptEvaluated at
|
||||
// all? nsCxPusher::Pop will do that nowadays if !mScriptIsRunning, so the
|
||||
// concerns from bug 295983 don't seem relevant anymore. Do we want to make
|
||||
// sure it's still called when !mScriptIsRunning? I guess play it safe for
|
||||
// now and do what CallEventHandler did, which is call always.
|
||||
|
||||
// Popping an nsCxPusher is safe even if it never got pushed.
|
||||
mCxPusher.Pop();
|
||||
|
||||
if (mCtx) {
|
||||
mCtx->ScriptEvaluated(true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
142
dom/bindings/CallbackFunction.h
Normal file
142
dom/bindings/CallbackFunction.h
Normal file
@ -0,0 +1,142 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* A common base class for representing WebIDL callback function types in C++.
|
||||
*
|
||||
* This class implements common functionality like lifetime
|
||||
* management, initialization with the callable, and setup of the call
|
||||
* environment. Subclasses corresponding to particular callback
|
||||
* function types should provide a Call() method that actually does
|
||||
* the call.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "jsapi.h"
|
||||
#include "jswrapper.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Util.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "nsJSEnvironment.h"
|
||||
#include "xpcpublic.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class CallbackFunction : public nsISupports
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackFunction)
|
||||
|
||||
/**
|
||||
* Create a CallbackFunction. aCallable is the callable we're wrapping.
|
||||
* aOwner is the object that will be receiving this CallbackFunction as a
|
||||
* method argument, if any. We need this so we can store our callable in the
|
||||
* same compartment as our owner. If *aInited is set to false, an exception
|
||||
* has been thrown.
|
||||
*/
|
||||
CallbackFunction(JSContext* cx, JSObject* aOwner, JSObject* aCallable,
|
||||
bool* aInited)
|
||||
: mCallable(nullptr)
|
||||
{
|
||||
MOZ_ASSERT(JS_ObjectIsCallable(cx, aCallable));
|
||||
// If aOwner is not null, enter the compartment of aOwner's
|
||||
// underlying object.
|
||||
if (aOwner) {
|
||||
aOwner = js::UnwrapObject(aOwner);
|
||||
JSAutoCompartment ac(cx, aOwner);
|
||||
if (!JS_WrapObject(cx, &aCallable)) {
|
||||
*aInited = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Set mCallable before we hold, on the off chance that a GC could somehow
|
||||
// happen in there... (which would be pretty odd, granted).
|
||||
mCallable = aCallable;
|
||||
NS_HOLD_JS_OBJECTS(this, CallbackFunction);
|
||||
*aInited = true;
|
||||
}
|
||||
|
||||
virtual ~CallbackFunction()
|
||||
{
|
||||
DropCallback();
|
||||
}
|
||||
|
||||
JSObject* Callable() const
|
||||
{
|
||||
xpc_UnmarkGrayObject(mCallable);
|
||||
return mCallable;
|
||||
}
|
||||
|
||||
protected:
|
||||
void DropCallback()
|
||||
{
|
||||
if (mCallable) {
|
||||
NS_DROP_JS_OBJECTS(this, CallbackFunction);
|
||||
mCallable = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JSObject* mCallable;
|
||||
|
||||
class NS_STACK_CLASS CallSetup
|
||||
{
|
||||
/**
|
||||
* A class that performs whatever setup we need to safely make a
|
||||
* call while this class is on the stack, After the constructor
|
||||
* returns, the call is safe to make if GetContext() returns
|
||||
* non-null.
|
||||
*/
|
||||
public:
|
||||
CallSetup(JSObject* const aCallable);
|
||||
~CallSetup();
|
||||
|
||||
JSContext* GetContext() const
|
||||
{
|
||||
return mCx;
|
||||
}
|
||||
|
||||
private:
|
||||
// We better not get copy-constructed
|
||||
CallSetup(const CallSetup&) MOZ_DELETE;
|
||||
|
||||
// Members which can go away whenever
|
||||
JSContext* mCx;
|
||||
nsCOMPtr<nsIScriptContext> mCtx;
|
||||
|
||||
// And now members whose construction/destruction order we need to control.
|
||||
|
||||
// Put our nsAutoMicrotask first, so it gets destroyed after everything else
|
||||
// is gone
|
||||
nsAutoMicroTask mMt;
|
||||
|
||||
// Can't construct an XPCAutoRequest until we have a JSContext, so
|
||||
// this needs to be a Maybe.
|
||||
Maybe<XPCAutoRequest> mAr;
|
||||
|
||||
// Can't construct a TerminationFuncHolder without an nsJSContext. But we
|
||||
// generally want its destructor to come after the destructor of mCxPusher.
|
||||
Maybe<nsJSContext::TerminationFuncHolder> mTerminationFuncHolder;
|
||||
|
||||
nsCxPusher mCxPusher;
|
||||
|
||||
// Can't construct a JSAutoCompartment without a JSContext either. Also,
|
||||
// Put mAc after mCxPusher so that we exit the compartment before we pop the
|
||||
// JSContext. Though in practice we'll often manually order those two
|
||||
// things.
|
||||
Maybe<JSAutoCompartment> mAc;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
@ -45,6 +45,7 @@ CPPSRCS = \
|
||||
$(linked_binding_cpp_files) \
|
||||
$(filter %.cpp, $(globalgen_targets)) \
|
||||
BindingUtils.cpp \
|
||||
CallbackFunction.cpp \
|
||||
DOMJSProxyHandler.cpp \
|
||||
$(NULL)
|
||||
|
||||
@ -56,6 +57,7 @@ EXPORTS_mozilla = \
|
||||
|
||||
EXPORTS_$(binding_include_path) = \
|
||||
BindingUtils.h \
|
||||
CallbackFunction.h \
|
||||
DOMJSClass.h \
|
||||
DOMJSProxyHandler.h \
|
||||
Errors.msg \
|
||||
|
Loading…
Reference in New Issue
Block a user