1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "WindowNamedPropertiesHandler.h"
8 #include "mozilla/dom/WindowBinding.h"
9 #include "nsDOMClassInfo.h"
10 #include "nsGlobalWindow.h"
11 #include "nsHTMLDocument.h"
12 #include "nsJSUtils.h"
13 #include "xpcprivate.h"
19 ShouldExposeChildWindow(nsString
& aNameBeingResolved
, nsIDOMWindow
*aChild
)
21 // If we're same-origin with the child, go ahead and expose it.
22 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aChild
);
23 NS_ENSURE_TRUE(sop
, false);
24 if (nsContentUtils::SubjectPrincipal()->Equals(sop
->GetPrincipal())) {
28 // If we're not same-origin, expose it _only_ if the name of the browsing
29 // context matches the 'name' attribute of the frame element in the parent.
30 // The motivations behind this heuristic are worth explaining here.
32 // Historically, all UAs supported global named access to any child browsing
33 // context (that is to say, window.dolske returns a child frame where either
34 // the "name" attribute on the frame element was set to "dolske", or where
35 // the child explicitly set window.name = "dolske").
37 // This is problematic because it allows possibly-malicious and unrelated
38 // cross-origin subframes to pollute the global namespace of their parent in
39 // unpredictable ways (see bug 860494). This is also problematic for browser
40 // engines like Servo that want to run cross-origin script on different
43 // The naive solution here would be to filter out any cross-origin subframes
44 // obtained when doing named lookup in global scope. But that is unlikely to
45 // be web-compatible, since it will break named access for consumers that do
46 // <iframe name="dolske" src="http://cross-origin.com/sadtrombone.html"> and
47 // expect to be able to access the cross-origin subframe via named lookup on
50 // The optimal behavior would be to do the following:
51 // (a) Look for any child browsing context with name="dolske".
52 // (b) If the result is cross-origin, null it out.
53 // (c) If we have null, look for a frame element whose 'name' attribute is
56 // Unfortunately, (c) would require some engineering effort to be performant
57 // in Gecko, and probably in other UAs as well. So we go with a simpler
58 // approximation of the above. This approximation will only break sites that
59 // rely on their cross-origin subframes setting window.name to a known value,
60 // which is unlikely to be very common. And while it does introduce a
61 // dependency on cross-origin state when doing global lookups, it doesn't
62 // allow the child to arbitrarily pollute the parent namespace, and requires
63 // cross-origin communication only in a limited set of cases that can be
64 // computed independently by the parent.
65 nsCOMPtr
<nsPIDOMWindow
> piWin
= do_QueryInterface(aChild
);
66 NS_ENSURE_TRUE(piWin
, false);
67 Element
* e
= piWin
->GetFrameElementInternal();
68 return e
&& e
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::name
,
69 aNameBeingResolved
, eCaseMatters
);
72 static nsGlobalWindow
*
73 GetWindowFromGlobal(JSObject
* aGlobal
)
76 if (NS_SUCCEEDED(UNWRAP_OBJECT(Window
, aGlobal
, win
))) {
79 XPCWrappedNative
* wrapper
= XPCWrappedNative::Get(aGlobal
);
80 nsCOMPtr
<nsPIDOMWindow
> piWin
= do_QueryWrappedNative(wrapper
);
82 return static_cast<nsGlobalWindow
*>(piWin
.get());
86 WindowNamedPropertiesHandler::getOwnPropDescriptor(JSContext
* aCx
,
87 JS::Handle
<JSObject
*> aProxy
,
90 JS::MutableHandle
<JSPropertyDescriptor
> aDesc
)
93 if (!JSID_IS_STRING(aId
)) {
94 // Nothing to do if we're resolving a non-string property.
98 JS::Rooted
<JSObject
*> global(aCx
, JS_GetGlobalForObject(aCx
, aProxy
));
99 if (HasPropertyOnPrototype(aCx
, aProxy
, aId
)) {
104 if (!str
.init(aCx
, JSID_TO_STRING(aId
))) {
108 // Grab the DOM window.
109 nsGlobalWindow
* win
= GetWindowFromGlobal(global
);
110 if (win
->Length() > 0) {
111 nsCOMPtr
<nsIDOMWindow
> childWin
= win
->GetChildWindow(str
);
112 if (childWin
&& ShouldExposeChildWindow(str
, childWin
)) {
113 // We found a subframe of the right name. Shadowing via |var foo| in
114 // global scope is still allowed, since |var| only looks up |own|
115 // properties. But unqualified shadowing will fail, per-spec.
116 JS::Rooted
<JS::Value
> v(aCx
);
117 if (!WrapObject(aCx
, childWin
, &v
)) {
120 aDesc
.object().set(aProxy
);
121 aDesc
.value().set(v
);
122 aDesc
.setAttributes(JSPROP_ENUMERATE
);
127 // The rest of this function is for HTML documents only.
128 nsCOMPtr
<nsIHTMLDocument
> htmlDoc
= do_QueryInterface(win
->GetExtantDoc());
132 nsHTMLDocument
* document
= static_cast<nsHTMLDocument
*>(htmlDoc
.get());
134 Element
* element
= document
->GetElementById(str
);
136 JS::Rooted
<JS::Value
> v(aCx
);
137 if (!WrapObject(aCx
, element
, &v
)) {
140 aDesc
.object().set(aProxy
);
141 aDesc
.value().set(v
);
142 aDesc
.setAttributes(JSPROP_ENUMERATE
);
146 nsWrapperCache
* cache
;
147 nsISupports
* result
= document
->ResolveName(str
, &cache
);
152 JS::Rooted
<JS::Value
> v(aCx
);
153 if (!WrapObject(aCx
, result
, cache
, nullptr, &v
)) {
156 aDesc
.object().set(aProxy
);
157 aDesc
.value().set(v
);
158 aDesc
.setAttributes(JSPROP_ENUMERATE
);
163 WindowNamedPropertiesHandler::defineProperty(JSContext
* aCx
,
164 JS::Handle
<JSObject
*> aProxy
,
165 JS::Handle
<jsid
> aId
,
166 JS::MutableHandle
<JSPropertyDescriptor
> aDesc
) const
169 rv
.ThrowTypeError(MSG_DEFINEPROPERTY_ON_GSP
);
170 rv
.ReportTypeError(aCx
);
175 WindowNamedPropertiesHandler::ownPropNames(JSContext
* aCx
,
176 JS::Handle
<JSObject
*> aProxy
,
178 JS::AutoIdVector
& aProps
) const
180 // Grab the DOM window.
181 nsGlobalWindow
* win
= GetWindowFromGlobal(JS_GetGlobalForObject(aCx
, aProxy
));
182 nsTArray
<nsString
> names
;
183 win
->GetSupportedNames(names
);
184 // Filter out the ones we wouldn't expose from getOwnPropertyDescriptor.
185 // We iterate backwards so we can remove things from the list easily.
186 for (size_t i
= names
.Length(); i
> 0; ) {
187 --i
; // Now we're pointing at the next name we want to look at
188 nsIDOMWindow
* childWin
= win
->GetChildWindow(names
[i
]);
189 if (!childWin
|| !ShouldExposeChildWindow(names
[i
], childWin
)) {
190 names
.RemoveElementAt(i
);
193 if (!AppendNamedPropertyIds(aCx
, aProxy
, names
, false, aProps
)) {
198 nsCOMPtr
<nsIHTMLDocument
> htmlDoc
= do_QueryInterface(win
->GetExtantDoc());
202 nsHTMLDocument
* document
= static_cast<nsHTMLDocument
*>(htmlDoc
.get());
203 document
->GetSupportedNames(flags
, names
);
205 JS::AutoIdVector
docProps(aCx
);
206 if (!AppendNamedPropertyIds(aCx
, aProxy
, names
, false, docProps
)) {
210 return js::AppendUnique(aCx
, aProps
, docProps
);
214 WindowNamedPropertiesHandler::delete_(JSContext
* aCx
,
215 JS::Handle
<JSObject
*> aProxy
,
216 JS::Handle
<jsid
> aId
, bool* aBp
) const
224 WindowNamedPropertiesHandler::Install(JSContext
* aCx
,
225 JS::Handle
<JSObject
*> aProto
)
227 JS::Rooted
<JSObject
*> protoProto(aCx
);
228 if (!::JS_GetPrototype(aCx
, aProto
, &protoProto
)) {
232 // Note: since the scope polluter proxy lives on the window's prototype
233 // chain, it needs a singleton type to avoid polluting type information
234 // for properties on the window.
235 JS::Rooted
<JSObject
*> gsp(aCx
);
236 js::ProxyOptions options
;
237 options
.setSingleton(true);
238 gsp
= js::NewProxyObject(aCx
, WindowNamedPropertiesHandler::getInstance(),
239 JS::NullHandleValue
, protoProto
,
240 js::GetGlobalForObjectCrossCompartment(aProto
),
246 // And then set the prototype of the interface prototype object to be the
247 // global scope polluter.
248 ::JS_SplicePrototype(aCx
, aProto
, gsp
);
252 } // namespace mozilla