1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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/EventTargetBinding.h"
9 #include "mozilla/dom/WindowBinding.h"
10 #include "mozilla/dom/WindowProxyHolder.h"
11 #include "nsContentUtils.h"
12 #include "nsGlobalWindow.h"
13 #include "nsHTMLDocument.h"
14 #include "nsJSUtils.h"
15 #include "xpcprivate.h"
17 namespace mozilla::dom
{
19 static bool ShouldExposeChildWindow(const nsString
& aNameBeingResolved
,
20 BrowsingContext
* aChild
) {
21 Element
* e
= aChild
->GetEmbedderElement();
22 if (e
&& e
->IsInShadowTree()) {
26 // If we're same-origin with the child, go ahead and expose it.
27 nsPIDOMWindowOuter
* child
= aChild
->GetDOMWindow();
28 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(child
);
29 if (sop
&& nsContentUtils::SubjectPrincipal()->Equals(sop
->GetPrincipal())) {
33 // If we're not same-origin, expose it _only_ if the name of the browsing
34 // context matches the 'name' attribute of the frame element in the parent.
35 // The motivations behind this heuristic are worth explaining here.
37 // Historically, all UAs supported global named access to any child browsing
38 // context (that is to say, window.dolske returns a child frame where either
39 // the "name" attribute on the frame element was set to "dolske", or where
40 // the child explicitly set window.name = "dolske").
42 // This is problematic because it allows possibly-malicious and unrelated
43 // cross-origin subframes to pollute the global namespace of their parent in
44 // unpredictable ways (see bug 860494). This is also problematic for browser
45 // engines like Servo that want to run cross-origin script on different
48 // The naive solution here would be to filter out any cross-origin subframes
49 // obtained when doing named lookup in global scope. But that is unlikely to
50 // be web-compatible, since it will break named access for consumers that do
51 // <iframe name="dolske" src="http://cross-origin.com/sadtrombone.html"> and
52 // expect to be able to access the cross-origin subframe via named lookup on
55 // The optimal behavior would be to do the following:
56 // (a) Look for any child browsing context with name="dolske".
57 // (b) If the result is cross-origin, null it out.
58 // (c) If we have null, look for a frame element whose 'name' attribute is
61 // Unfortunately, (c) would require some engineering effort to be performant
62 // in Gecko, and probably in other UAs as well. So we go with a simpler
63 // approximation of the above. This approximation will only break sites that
64 // rely on their cross-origin subframes setting window.name to a known value,
65 // which is unlikely to be very common. And while it does introduce a
66 // dependency on cross-origin state when doing global lookups, it doesn't
67 // allow the child to arbitrarily pollute the parent namespace, and requires
68 // cross-origin communication only in a limited set of cases that can be
69 // computed independently by the parent.
70 return e
&& e
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::name
,
71 aNameBeingResolved
, eCaseMatters
);
74 bool WindowNamedPropertiesHandler::getOwnPropDescriptor(
75 JSContext
* aCx
, JS::Handle
<JSObject
*> aProxy
, JS::Handle
<jsid
> aId
,
77 JS::MutableHandle
<Maybe
<JS::PropertyDescriptor
>> aDesc
) const {
81 if (aId
.isWellKnownSymbol(JS::SymbolCode::toStringTag
)) {
82 JS::Rooted
<JSString
*> toStringTagStr(
83 aCx
, JS_NewStringCopyZ(aCx
, "WindowProperties"));
84 if (!toStringTagStr
) {
89 JS::PropertyDescriptor::Data(JS::StringValue(toStringTagStr
),
90 {JS::PropertyAttribute::Configurable
})));
94 // Nothing to do if we're resolving another symbol property.
99 if (!HasPropertyOnPrototype(aCx
, aProxy
, aId
, &hasOnPrototype
)) {
102 if (hasOnPrototype
) {
107 if (!str
.init(aCx
, aId
)) {
115 // Grab the DOM window.
116 nsGlobalWindowInner
* win
= xpc::WindowGlobalOrNull(aProxy
);
117 if (win
->Length() > 0) {
118 RefPtr
<BrowsingContext
> child
= win
->GetChildWindow(str
);
119 if (child
&& ShouldExposeChildWindow(str
, child
)) {
120 // We found a subframe of the right name. Shadowing via |var foo| in
121 // global scope is still allowed, since |var| only looks up |own|
122 // properties. But unqualified shadowing will fail, per-spec.
123 JS::Rooted
<JS::Value
> v(aCx
);
124 if (!ToJSValue(aCx
, WindowProxyHolder(std::move(child
)), &v
)) {
127 aDesc
.set(mozilla::Some(
128 JS::PropertyDescriptor::Data(v
, {JS::PropertyAttribute::Configurable
,
129 JS::PropertyAttribute::Writable
})));
134 // The rest of this function is for HTML documents only.
135 Document
* doc
= win
->GetExtantDoc();
136 if (!doc
|| !doc
->IsHTMLOrXHTML()) {
139 nsHTMLDocument
* document
= doc
->AsHTMLDocument();
141 JS::Rooted
<JS::Value
> v(aCx
);
142 Element
* element
= document
->GetElementById(str
);
144 if (!ToJSValue(aCx
, element
, &v
)) {
147 aDesc
.set(mozilla::Some(
148 JS::PropertyDescriptor::Data(v
, {JS::PropertyAttribute::Configurable
,
149 JS::PropertyAttribute::Writable
})));
154 bool found
= document
->ResolveName(aCx
, str
, &v
, rv
);
155 if (rv
.MaybeSetPendingException(aCx
)) {
160 aDesc
.set(mozilla::Some(
161 JS::PropertyDescriptor::Data(v
, {JS::PropertyAttribute::Configurable
,
162 JS::PropertyAttribute::Writable
})));
167 bool WindowNamedPropertiesHandler::defineProperty(
168 JSContext
* aCx
, JS::Handle
<JSObject
*> aProxy
, JS::Handle
<jsid
> aId
,
169 JS::Handle
<JS::PropertyDescriptor
> aDesc
,
170 JS::ObjectOpResult
& result
) const {
171 return result
.failCantDefineWindowNamedProperty();
174 bool WindowNamedPropertiesHandler::ownPropNames(
175 JSContext
* aCx
, JS::Handle
<JSObject
*> aProxy
, unsigned flags
,
176 JS::MutableHandleVector
<jsid
> aProps
) const {
177 if (!(flags
& JSITER_HIDDEN
)) {
178 // None of our named properties are enumerable.
182 // Grab the DOM window.
183 nsGlobalWindowInner
* win
= xpc::WindowGlobalOrNull(aProxy
);
184 nsTArray
<nsString
> names
;
185 // The names live on the outer window, which might be null
186 nsGlobalWindowOuter
* outer
= win
->GetOuterWindowInternal();
188 if (BrowsingContext
* bc
= outer
->GetBrowsingContext()) {
189 for (const auto& child
: bc
->Children()) {
190 const nsString
& name
= child
->Name();
191 if (!name
.IsEmpty() && !names
.Contains(name
)) {
192 // Make sure we really would expose it from getOwnPropDescriptor.
193 if (ShouldExposeChildWindow(name
, child
)) {
194 names
.AppendElement(name
);
200 if (!AppendNamedPropertyIds(aCx
, aProxy
, names
, false, aProps
)) {
205 Document
* doc
= win
->GetExtantDoc();
206 if (!doc
|| !doc
->IsHTMLOrXHTML()) {
207 // Define to @@toStringTag on this object to keep Object.prototype.toString
208 // backwards compatible.
209 JS::Rooted
<jsid
> toStringTagId(aCx
, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(
210 aCx
, JS::SymbolCode::toStringTag
)));
211 return aProps
.append(toStringTagId
);
214 nsHTMLDocument
* document
= doc
->AsHTMLDocument();
215 // Document names are enumerable, so we want to get them no matter what flags
217 document
->GetSupportedNames(names
);
219 JS::RootedVector
<jsid
> docProps(aCx
);
220 if (!AppendNamedPropertyIds(aCx
, aProxy
, names
, false, &docProps
)) {
224 JS::Rooted
<jsid
> toStringTagId(aCx
, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(
225 aCx
, JS::SymbolCode::toStringTag
)));
226 if (!docProps
.append(toStringTagId
)) {
230 return js::AppendUnique(aCx
, aProps
, docProps
);
233 bool WindowNamedPropertiesHandler::delete_(JSContext
* aCx
,
234 JS::Handle
<JSObject
*> aProxy
,
235 JS::Handle
<jsid
> aId
,
236 JS::ObjectOpResult
& aResult
) const {
237 return aResult
.failCantDeleteWindowNamedProperty();
240 // Note that this class doesn't need any reserved slots, but SpiderMonkey
241 // asserts all proxy classes have at least one reserved slot.
242 static const DOMIfaceAndProtoJSClass WindowNamedPropertiesClass
= {
243 PROXY_CLASS_DEF("WindowProperties", JSCLASS_IS_DOMIFACEANDPROTOJSCLASS
|
244 JSCLASS_HAS_RESERVED_SLOTS(1)),
245 eNamedPropertiesObject
,
247 prototypes::id::_ID_Count
,
249 &sEmptyNativePropertyHooks
,
250 "[object WindowProperties]",
251 EventTarget_Binding::GetProtoObject
};
254 JSObject
* WindowNamedPropertiesHandler::Create(JSContext
* aCx
,
255 JS::Handle
<JSObject
*> aProto
) {
256 js::ProxyOptions options
;
257 options
.setClass(&WindowNamedPropertiesClass
.mBase
);
259 JS::Rooted
<JSObject
*> gsp(
260 aCx
, js::NewProxyObject(aCx
, WindowNamedPropertiesHandler::getInstance(),
261 JS::NullHandleValue
, aProto
, options
));
267 if (!JS_SetImmutablePrototype(aCx
, gsp
, &succeeded
)) {
270 MOZ_ASSERT(succeeded
,
271 "errors making the [[Prototype]] of the named properties object "
272 "immutable should have been JSAPI failures, not !succeeded");
277 } // namespace mozilla::dom