Bumping manifests a=b2g-bump
[gecko.git] / dom / base / WindowNamedPropertiesHandler.cpp
blobdc8da122974c8161dfe9df660a42d404e6526476
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"
15 namespace mozilla {
16 namespace dom {
18 static bool
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())) {
25 return true;
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
41 // threads.
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
48 // the global.
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
54 // "dolske".
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)
75 nsGlobalWindow* win;
76 if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, aGlobal, win))) {
77 return win;
79 XPCWrappedNative* wrapper = XPCWrappedNative::Get(aGlobal);
80 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryWrappedNative(wrapper);
81 MOZ_ASSERT(piWin);
82 return static_cast<nsGlobalWindow*>(piWin.get());
85 bool
86 WindowNamedPropertiesHandler::getOwnPropDescriptor(JSContext* aCx,
87 JS::Handle<JSObject*> aProxy,
88 JS::Handle<jsid> aId,
89 bool /* unused */,
90 JS::MutableHandle<JSPropertyDescriptor> aDesc)
91 const
93 if (!JSID_IS_STRING(aId)) {
94 // Nothing to do if we're resolving a non-string property.
95 return true;
98 JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy));
99 if (HasPropertyOnPrototype(aCx, aProxy, aId)) {
100 return true;
103 nsAutoJSString str;
104 if (!str.init(aCx, JSID_TO_STRING(aId))) {
105 return false;
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)) {
118 return false;
120 aDesc.object().set(aProxy);
121 aDesc.value().set(v);
122 aDesc.setAttributes(JSPROP_ENUMERATE);
123 return true;
127 // The rest of this function is for HTML documents only.
128 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
129 if (!htmlDoc) {
130 return true;
132 nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get());
134 Element* element = document->GetElementById(str);
135 if (element) {
136 JS::Rooted<JS::Value> v(aCx);
137 if (!WrapObject(aCx, element, &v)) {
138 return false;
140 aDesc.object().set(aProxy);
141 aDesc.value().set(v);
142 aDesc.setAttributes(JSPROP_ENUMERATE);
143 return true;
146 nsWrapperCache* cache;
147 nsISupports* result = document->ResolveName(str, &cache);
148 if (!result) {
149 return true;
152 JS::Rooted<JS::Value> v(aCx);
153 if (!WrapObject(aCx, result, cache, nullptr, &v)) {
154 return false;
156 aDesc.object().set(aProxy);
157 aDesc.value().set(v);
158 aDesc.setAttributes(JSPROP_ENUMERATE);
159 return true;
162 bool
163 WindowNamedPropertiesHandler::defineProperty(JSContext* aCx,
164 JS::Handle<JSObject*> aProxy,
165 JS::Handle<jsid> aId,
166 JS::MutableHandle<JSPropertyDescriptor> aDesc) const
168 ErrorResult rv;
169 rv.ThrowTypeError(MSG_DEFINEPROPERTY_ON_GSP);
170 rv.ReportTypeError(aCx);
171 return false;
174 bool
175 WindowNamedPropertiesHandler::ownPropNames(JSContext* aCx,
176 JS::Handle<JSObject*> aProxy,
177 unsigned flags,
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)) {
194 return false;
197 names.Clear();
198 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
199 if (!htmlDoc) {
200 return true;
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)) {
207 return false;
210 return js::AppendUnique(aCx, aProps, docProps);
213 bool
214 WindowNamedPropertiesHandler::delete_(JSContext* aCx,
215 JS::Handle<JSObject*> aProxy,
216 JS::Handle<jsid> aId, bool* aBp) const
218 *aBp = false;
219 return true;
222 // static
223 void
224 WindowNamedPropertiesHandler::Install(JSContext* aCx,
225 JS::Handle<JSObject*> aProto)
227 JS::Rooted<JSObject*> protoProto(aCx);
228 if (!::JS_GetPrototype(aCx, aProto, &protoProto)) {
229 return;
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),
241 options);
242 if (!gsp) {
243 return;
246 // And then set the prototype of the interface prototype object to be the
247 // global scope polluter.
248 ::JS_SplicePrototype(aCx, aProto, gsp);
251 } // namespace dom
252 } // namespace mozilla