Bug 860941 - Separate the handling of |dialogArguments| and |arguments|, and use IDL for the |dialogArguments| getter. r=jst

This patch is bigger than I'd like it to be, but there are a lot of interlocked
dependencies and I eventually decided it was easier to just lump it together.

The semantics of |showModalDialog|/|window.dialogArguments| (an web-exposed
HTML5 feature) and |openDialog|/|window.arguments| (a XUL-proprietary feature)
are quite different. The former is essentially a security-checked JSVal, while
the latter gets converted into an array. We handled them together in the old
world, which led to a lot of confusion and muddled semantics. This patch
separates them.

This patch also eschews the roundabout resolve hook for dialogArguments in favor
of returning them directly from the XPIDL getter. This better matches the
behavior in the spec, especially because it allows dialogArguments to live on
the outer as they're supposed to, rather than the first inner that happens to
end up in the docshell. All in all, this should make this all very
straightforward to convert WebIDL when the time comes.

The current spec on the origin checks here is pretty fictional, so I've filed
https://www.w3.org/Bugs/Public/show_bug.cgi?id=21932 to fix it. This patch
should more or less preserve the current security behavior.
This commit is contained in:
Bobby Holley 2013-05-17 10:43:19 -07:00
parent e81a69d26b
commit e00d8aa943
7 changed files with 124 additions and 110 deletions

View File

@ -1005,7 +1005,6 @@ jsid nsDOMClassInfo::sToolbar_id = JSID_VOID;
jsid nsDOMClassInfo::sLocationbar_id = JSID_VOID;
jsid nsDOMClassInfo::sPersonalbar_id = JSID_VOID;
jsid nsDOMClassInfo::sStatusbar_id = JSID_VOID;
jsid nsDOMClassInfo::sDialogArguments_id = JSID_VOID;
jsid nsDOMClassInfo::sControllers_id = JSID_VOID;
jsid nsDOMClassInfo::sLength_id = JSID_VOID;
jsid nsDOMClassInfo::sScrollX_id = JSID_VOID;
@ -1265,7 +1264,6 @@ nsDOMClassInfo::DefineStaticJSVals(JSContext *cx)
SET_JSID_TO_STRING(sLocationbar_id, cx, "locationbar");
SET_JSID_TO_STRING(sPersonalbar_id, cx, "personalbar");
SET_JSID_TO_STRING(sStatusbar_id, cx, "statusbar");
SET_JSID_TO_STRING(sDialogArguments_id, cx, "dialogArguments");
SET_JSID_TO_STRING(sControllers_id, cx, "controllers");
SET_JSID_TO_STRING(sLength_id, cx, "length");
SET_JSID_TO_STRING(sScrollX_id, cx, "scrollX");
@ -2973,7 +2971,6 @@ nsDOMClassInfo::ShutDown()
sLocationbar_id = JSID_VOID;
sPersonalbar_id = JSID_VOID;
sStatusbar_id = JSID_VOID;
sDialogArguments_id = JSID_VOID;
sControllers_id = JSID_VOID;
sLength_id = JSID_VOID;
sScrollX_id = JSID_VOID;
@ -5082,23 +5079,6 @@ nsWindowSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
return NS_OK;
}
if (sDialogArguments_id == id && win->IsModalContentWindow()) {
nsCOMPtr<nsIArray> args;
((nsGlobalModalWindow *)win)->GetDialogArguments(getter_AddRefs(args));
nsIScriptContext *script_cx = win->GetContext();
if (script_cx) {
// Make nsJSContext::SetProperty()'s magic argument array
// handling happen.
rv = script_cx->SetProperty(obj, "dialogArguments", args);
NS_ENSURE_SUCCESS(rv, rv);
*objp = obj;
}
return NS_OK;
}
}
rv = nsDOMGenericSH::NewResolve(wrapper, cx, obj, id, flags, objp,

View File

@ -535,6 +535,16 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindow *aOuterWindow)
nsPIDOMWindow::~nsPIDOMWindow() {}
// DialogValueHolder CC goop.
NS_IMPL_CYCLE_COLLECTION_1(DialogValueHolder, mValue)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DialogValueHolder)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(DialogValueHolder)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DialogValueHolder)
//*****************************************************************************
// nsOuterWindowProxy: Outer Window Proxy
//*****************************************************************************
@ -1402,7 +1412,7 @@ nsGlobalWindow::CleanUp(bool aIgnoreModalDialog)
mInnerWindowHolder = nullptr;
mArguments = nullptr;
mArgumentsOrigin = nullptr;
mDialogArguments = nullptr;
CleanupCachedXBLHandlers(this);
@ -1630,6 +1640,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDialogArguments)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
@ -1676,6 +1687,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
@ -2514,11 +2526,7 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
if (mArguments) {
newInnerWindow->DefineArgumentsProperty(mArguments);
newInnerWindow->mArguments = mArguments;
newInnerWindow->mArgumentsOrigin = mArgumentsOrigin;
mArguments = nullptr;
mArgumentsOrigin = nullptr;
}
// Give the new inner window our chrome event handler (since it
@ -3139,50 +3147,50 @@ nsGlobalWindow::SetScriptsEnabled(bool aEnabled, bool aFireTimeouts)
}
nsresult
nsGlobalWindow::SetArguments(nsIArray *aArguments, nsIPrincipal *aOrigin)
nsGlobalWindow::SetArguments(nsIArray *aArguments)
{
FORWARD_TO_OUTER(SetArguments, (aArguments, aOrigin),
FORWARD_TO_OUTER(SetArguments, (aArguments),
NS_ERROR_NOT_INITIALIZED);
nsresult rv;
// Hold on to the arguments so that we can re-set them once the next
// document is loaded.
mArguments = aArguments;
mArgumentsOrigin = aOrigin;
// Historically, we've used the same machinery to handle openDialog arguments
// (exposed via window.arguments) and showModalDialog arguments (exposed via
// window.dialogArguments), even though the former is XUL-only and uses an XPCOM
// array while the latter is web-exposed and uses an arbitrary JS value.
// Moreover, per-spec |dialogArguments| is a property of the browsing context
// (outer), whereas |arguments| lives on the inner.
//
// We've now mostly separated them, but the difference is still opaque to
// nsWindowWatcher (the caller of SetArguments in this little back-and-forth
// embedding waltz we do here).
//
// So we need to demultiplex the two cases here.
nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
if (mIsModalContentWindow && currentInner) {
// SetArguments() is being called on a modal content window that
// already has an inner window. This can happen when loading
// javascript: URIs as modal content dialogs. In this case, we'll
// set up the dialog window, both inner and outer, before we call
// SetArguments() on the window, so to deal with that, make sure
// here that the arguments are propagated to the inner window.
currentInner->mArguments = aArguments;
currentInner->mArgumentsOrigin = aOrigin;
if (mIsModalContentWindow) {
// nsWindowWatcher blindly converts the original nsISupports into an array
// of length 1. We need to recover it, and then cast it back to the concrete
// object we know it to be.
nsCOMPtr<nsISupports> supports = do_QueryElementAt(aArguments, 0, &rv);
NS_ENSURE_SUCCESS(rv, rv);
mDialogArguments = static_cast<DialogValueHolder*>(supports.get());
} else {
mArguments = aArguments;
rv = currentInner->DefineArgumentsProperty(aArguments);
NS_ENSURE_SUCCESS(rv, rv);
}
return currentInner ?
currentInner->DefineArgumentsProperty(aArguments) : NS_OK;
return NS_OK;
}
nsresult
nsGlobalWindow::DefineArgumentsProperty(nsIArray *aArguments)
{
MOZ_ASSERT(!mIsModalContentWindow); // Handled separately.
nsIScriptContext *ctx = GetOuterWindowInternal()->mContext;
NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED);
AutoPushJSContext cx(ctx->GetNativeContext());
NS_ENSURE_TRUE(cx, NS_ERROR_NOT_INITIALIZED);
if (mIsModalContentWindow) {
// Modal content windows don't have an "arguments" property, they
// have a "dialogArguments" property which is handled
// separately. See nsWindowSH::NewResolve().
return NS_OK;
}
JS::Rooted<JSObject*> obj(cx, mJSObject);
return GetContextInternal()->SetProperty(obj, "arguments", aArguments);
}
@ -7642,6 +7650,9 @@ nsGlobalWindow::ShowModalDialog(const nsAString& aURI, nsIVariant *aArgs,
if (Preferences::GetBool("dom.disable_window_showModalDialog", false))
return NS_ERROR_NOT_AVAILABLE;
nsRefPtr<DialogValueHolder> argHolder =
new DialogValueHolder(nsContentUtils::GetSubjectPrincipal(), aArgs);
// Before bringing up the window/dialog, unsuppress painting and flush
// pending reflows.
EnsureReflowFlushAndPaint();
@ -7671,8 +7682,8 @@ nsGlobalWindow::ShowModalDialog(const nsAString& aURI, nsIVariant *aArgs,
true, // aCalledNoScript
true, // aDoJSFixups
true, // aNavigate
nullptr, aArgs, // args
GetPrincipal(), // aCalleePrincipal
nullptr, argHolder, // args
GetPrincipal(), // aCalleePrincipal
nullptr, // aJSCallerContext
getter_AddRefs(dlgWin));
nsContentUtils::SetMicroTaskLevel(oldMicroTaskLevel);
@ -11616,21 +11627,14 @@ NS_IMPL_RELEASE_INHERITED(nsGlobalModalWindow, nsGlobalWindow)
NS_IMETHODIMP
nsGlobalModalWindow::GetDialogArguments(nsIArray **aArguments)
nsGlobalModalWindow::GetDialogArguments(nsIVariant **aArguments)
{
FORWARD_TO_INNER_MODAL_CONTENT_WINDOW(GetDialogArguments, (aArguments),
FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetDialogArguments, (aArguments),
NS_ERROR_NOT_INITIALIZED);
bool subsumes = false;
nsIPrincipal *self = GetPrincipal();
if (self && NS_SUCCEEDED(self->Subsumes(mArgumentsOrigin, &subsumes)) &&
subsumes) {
NS_IF_ADDREF(*aArguments = mArguments);
} else {
*aArguments = nullptr;
}
return NS_OK;
// This does an internal origin check, and returns undefined if the subject
// does not subsumes the origin of the arguments.
return mDialogArguments->Get(nsContentUtils::GetSubjectPrincipal(), aArguments);
}
NS_IMETHODIMP

View File

@ -234,6 +234,50 @@ struct IdleObserverHolder
}
};
static inline already_AddRefed<nsIVariant>
CreateVoidVariant()
{
nsCOMPtr<nsIWritableVariant> writable =
do_CreateInstance(NS_VARIANT_CONTRACTID);
writable->SetAsVoid();
return writable.forget();
}
// Helper class to manage modal dialog arguments and all their quirks.
//
// Given our clunky embedding APIs, modal dialog arguments need to be passed
// as an nsISupports parameter to WindowWatcher, get stuck inside an array of
// length 1, and then passed back to the newly-created dialog.
//
// However, we need to track both the caller-passed value as well as the
// caller's, so that we can do an origin check (even for primitives) when the
// value is accessed. This class encapsulates that magic.
class DialogValueHolder : public nsISupports
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(DialogValueHolder)
DialogValueHolder(nsIPrincipal* aSubject, nsIVariant* aValue)
: mOrigin(aSubject)
, mValue(aValue) {}
nsresult Get(nsIPrincipal* aSubject, nsIVariant** aResult)
{
nsCOMPtr<nsIVariant> result;
if (aSubject->Subsumes(mOrigin)) {
result = mValue;
} else {
result = CreateVoidVariant();
}
result.forget(aResult);
return NS_OK;
}
virtual ~DialogValueHolder() {}
private:
nsCOMPtr<nsIPrincipal> mOrigin;
nsCOMPtr<nsIVariant> mValue;
};
//*****************************************************************************
// nsGlobalWindow: Global Object for Scripting
//*****************************************************************************
@ -571,7 +615,7 @@ public:
virtual void DisableNetworkEvent(uint32_t aType);
#endif // MOZ_B2G
virtual nsresult SetArguments(nsIArray *aArguments, nsIPrincipal *aOrigin);
virtual nsresult SetArguments(nsIArray *aArguments);
static bool DOMWindowDumpEnabled();
@ -1113,8 +1157,13 @@ protected:
nsCOMPtr<nsIScriptContext> mContext;
nsWeakPtr mOpener;
nsCOMPtr<nsIControllers> mControllers;
// For |window.arguments|, via |openDialog|.
nsCOMPtr<nsIArray> mArguments;
nsCOMPtr<nsIPrincipal> mArgumentsOrigin;
// For |window.dialogArguments|, via |showModalDialog|.
nsRefPtr<DialogValueHolder> mDialogArguments;
nsRefPtr<Navigator> mNavigator;
nsRefPtr<nsScreen> mScreen;
nsRefPtr<nsDOMWindowList> mFrames;

View File

@ -1677,27 +1677,17 @@ nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget, const char* aPropName, n
ConvertSupportsTojsvals(aArgs, global, &argc, &argv, tempStorage);
NS_ENSURE_SUCCESS(rv, rv);
JS::Value vargs;
// got the arguments, now attach them.
// window.dialogArguments is supposed to be an array if a JS array
// was passed to showModalDialog(), deal with that here.
if (strcmp(aPropName, "dialogArguments") == 0 && argc <= 1) {
vargs = argc ? argv[0] : JSVAL_VOID;
} else {
for (uint32_t i = 0; i < argc; ++i) {
if (!JS_WrapValue(mContext, &argv[i])) {
return NS_ERROR_FAILURE;
}
for (uint32_t i = 0; i < argc; ++i) {
if (!JS_WrapValue(mContext, &argv[i])) {
return NS_ERROR_FAILURE;
}
JSObject *args = ::JS_NewArrayObject(mContext, argc, argv);
vargs = OBJECT_TO_JSVAL(args);
}
// Make sure to use JS_DefineProperty here so that we can override
// readonly XPConnect properties here as well (read dialogArguments).
JSObject *args = ::JS_NewArrayObject(mContext, argc, argv);
JS::Value vargs = OBJECT_TO_JSVAL(args);
return JS_DefineProperty(mContext, aTarget, aPropName, vargs, NULL, NULL, 0)
? NS_OK
: NS_ERROR_FAILURE;

View File

@ -58,8 +58,8 @@ class AudioContext;
}
#define NS_PIDOMWINDOW_IID \
{ 0x81fe131f, 0x57c9, 0x4992, \
{ 0xa7, 0xad, 0x82, 0x67, 0x3f, 0xc4, 0xe2, 0x53 } }
{ 0x7202842a, 0x0e24, 0x46dc, \
{ 0xb2, 0x25, 0xd2, 0x9d, 0x28, 0xda, 0x87, 0xd8 } }
class nsPIDOMWindow : public nsIDOMWindowInternal
{
@ -603,11 +603,14 @@ public:
/**
* Set a arguments for this window. This will be set on the window
* right away (if there's an existing document) and it will also be
* installed on the window when the next document is loaded. Each
* language impl is responsible for converting to an array of args
* as appropriate for that language.
* installed on the window when the next document is loaded.
*
* This function serves double-duty for passing both |arguments| and
* |dialogArguments| back from nsWindowWatcher to nsGlobalWindow. For the
* latter, the array is an array of length 0 whose only element is a
* DialogArgumentsHolder representing the JS value passed to showModalDialog.
*/
virtual nsresult SetArguments(nsIArray *aArguments, nsIPrincipal *aOrigin) = 0;
virtual nsresult SetArguments(nsIArray *aArguments) = 0;
/**
* NOTE! This function *will* be called on multiple threads so the

View File

@ -8,14 +8,15 @@
interface nsIVariant;
interface nsIArray;
[scriptable, uuid(51aebd45-b979-4ec6-9d11-3a3fd3d5d59e)]
[scriptable, uuid(3f4cb2d0-5f7e-44a9-9f4f-370945f8db08)]
interface nsIDOMModalContentWindow : nsISupports
{
/**
* Readonly attribute containing an array of arguments that was
* passed to the code that opened this modal content window.
* Readonly attribute containing an arbitrary JS value passed by the
* code that opened the modal content window. A security check is
* performed at access time, per spec.
*/
readonly attribute nsIArray dialogArguments;
readonly attribute nsIVariant dialogArguments;
/**
* The return value that will be returned to the function that

View File

@ -519,20 +519,7 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent,
nsCOMPtr<nsIScriptSecurityManager>
sm(do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID));
NS_ENSURE_TRUE(sm, NS_ERROR_FAILURE);
// Remember who's calling us. This code used to assume a null
// subject principal if it failed to get the principal, but that's
// just not safe, so bail on errors here.
nsCOMPtr<nsIPrincipal> callerPrincipal;
rv = sm->GetSubjectPrincipal(getter_AddRefs(callerPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
bool isCallerChrome = true;
if (callerPrincipal) {
rv = sm->IsSystemPrincipal(callerPrincipal, &isCallerChrome);
NS_ENSURE_SUCCESS(rv, rv);
}
bool isCallerChrome = nsContentUtils::IsCallerChrome();
JSContext *cx = GetJSContextFromWindow(aParent);
@ -746,7 +733,7 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent,
nsCOMPtr<nsPIDOMWindow> piwin(do_QueryInterface(*_retval));
NS_ENSURE_TRUE(piwin, NS_ERROR_UNEXPECTED);
rv = piwin->SetArguments(argv, callerPrincipal);
rv = piwin->SetArguments(argv);
NS_ENSURE_SUCCESS(rv, rv);
}