Bug 1908539 restrict MacOS platform audio processing to Nightly r=webrtc-reviewers...
[gecko.git] / dom / base / WindowNamedPropertiesHandler.cpp
blob8b5f8a29a8ca03cf1254af0b59b6631d2bc8ddf9
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/ProxyHandlerUtils.h"
10 #include "mozilla/dom/WindowBinding.h"
11 #include "mozilla/dom/WindowProxyHolder.h"
12 #include "nsContentUtils.h"
13 #include "nsGlobalWindowInner.h"
14 #include "nsGlobalWindowOuter.h"
15 #include "nsHTMLDocument.h"
16 #include "nsJSUtils.h"
17 #include "xpcprivate.h"
19 namespace mozilla::dom {
21 static bool ShouldExposeChildWindow(const nsString& aNameBeingResolved,
22 BrowsingContext* aChild) {
23 Element* e = aChild->GetEmbedderElement();
24 if (e && e->IsInShadowTree()) {
25 return false;
28 // If we're same-origin with the child, go ahead and expose it.
29 nsPIDOMWindowOuter* child = aChild->GetDOMWindow();
30 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(child);
31 if (sop && nsContentUtils::SubjectPrincipal()->Equals(sop->GetPrincipal())) {
32 return true;
35 // If we're not same-origin, expose it _only_ if the name of the browsing
36 // context matches the 'name' attribute of the frame element in the parent.
37 // The motivations behind this heuristic are worth explaining here.
39 // Historically, all UAs supported global named access to any child browsing
40 // context (that is to say, window.dolske returns a child frame where either
41 // the "name" attribute on the frame element was set to "dolske", or where
42 // the child explicitly set window.name = "dolske").
44 // This is problematic because it allows possibly-malicious and unrelated
45 // cross-origin subframes to pollute the global namespace of their parent in
46 // unpredictable ways (see bug 860494). This is also problematic for browser
47 // engines like Servo that want to run cross-origin script on different
48 // threads.
50 // The naive solution here would be to filter out any cross-origin subframes
51 // obtained when doing named lookup in global scope. But that is unlikely to
52 // be web-compatible, since it will break named access for consumers that do
53 // <iframe name="dolske" src="http://cross-origin.com/sadtrombone.html"> and
54 // expect to be able to access the cross-origin subframe via named lookup on
55 // the global.
57 // The optimal behavior would be to do the following:
58 // (a) Look for any child browsing context with name="dolske".
59 // (b) If the result is cross-origin, null it out.
60 // (c) If we have null, look for a frame element whose 'name' attribute is
61 // "dolske".
63 // Unfortunately, (c) would require some engineering effort to be performant
64 // in Gecko, and probably in other UAs as well. So we go with a simpler
65 // approximation of the above. This approximation will only break sites that
66 // rely on their cross-origin subframes setting window.name to a known value,
67 // which is unlikely to be very common. And while it does introduce a
68 // dependency on cross-origin state when doing global lookups, it doesn't
69 // allow the child to arbitrarily pollute the parent namespace, and requires
70 // cross-origin communication only in a limited set of cases that can be
71 // computed independently by the parent.
72 return e && e->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
73 aNameBeingResolved, eCaseMatters);
76 bool WindowNamedPropertiesHandler::getOwnPropDescriptor(
77 JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
78 bool /* unused */,
79 JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const {
80 aDesc.reset();
82 if (aId.isSymbol()) {
83 if (aId.isWellKnownSymbol(JS::SymbolCode::toStringTag)) {
84 JS::Rooted<JSString*> toStringTagStr(
85 aCx, JS_NewStringCopyZ(aCx, "WindowProperties"));
86 if (!toStringTagStr) {
87 return false;
90 aDesc.set(Some(
91 JS::PropertyDescriptor::Data(JS::StringValue(toStringTagStr),
92 {JS::PropertyAttribute::Configurable})));
93 return true;
96 // Nothing to do if we're resolving another symbol property.
97 return true;
100 bool hasOnPrototype;
101 if (!HasPropertyOnPrototype(aCx, aProxy, aId, &hasOnPrototype)) {
102 return false;
104 if (hasOnPrototype) {
105 return true;
108 nsAutoJSString str;
109 if (!str.init(aCx, aId)) {
110 return false;
113 if (str.IsEmpty()) {
114 return true;
117 // Grab the DOM window.
118 nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aProxy);
119 if (win->Length() > 0) {
120 RefPtr<BrowsingContext> child = win->GetChildWindow(str);
121 if (child && ShouldExposeChildWindow(str, child)) {
122 // We found a subframe of the right name. Shadowing via |var foo| in
123 // global scope is still allowed, since |var| only looks up |own|
124 // properties. But unqualified shadowing will fail, per-spec.
125 JS::Rooted<JS::Value> v(aCx);
126 if (!ToJSValue(aCx, WindowProxyHolder(std::move(child)), &v)) {
127 return false;
129 aDesc.set(mozilla::Some(
130 JS::PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
131 JS::PropertyAttribute::Writable})));
132 return true;
136 // The rest of this function is for HTML documents only.
137 Document* doc = win->GetExtantDoc();
138 if (!doc || !doc->IsHTMLOrXHTML()) {
139 return true;
141 nsHTMLDocument* document = doc->AsHTMLDocument();
143 JS::Rooted<JS::Value> v(aCx);
144 Element* element = document->GetElementById(str);
145 if (element) {
146 if (!ToJSValue(aCx, element, &v)) {
147 return false;
149 aDesc.set(mozilla::Some(
150 JS::PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
151 JS::PropertyAttribute::Writable})));
152 return true;
155 ErrorResult rv;
156 bool found = document->ResolveName(aCx, str, &v, rv);
157 if (rv.MaybeSetPendingException(aCx)) {
158 return false;
161 if (found) {
162 aDesc.set(mozilla::Some(
163 JS::PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
164 JS::PropertyAttribute::Writable})));
166 return true;
169 bool WindowNamedPropertiesHandler::defineProperty(
170 JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
171 JS::Handle<JS::PropertyDescriptor> aDesc,
172 JS::ObjectOpResult& result) const {
173 return result.failCantDefineWindowNamedProperty();
176 bool WindowNamedPropertiesHandler::ownPropNames(
177 JSContext* aCx, JS::Handle<JSObject*> aProxy, unsigned flags,
178 JS::MutableHandleVector<jsid> aProps) const {
179 if (!(flags & JSITER_HIDDEN)) {
180 // None of our named properties are enumerable.
181 return true;
184 // Grab the DOM window.
185 nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aProxy);
186 nsTArray<nsString> names;
187 // The names live on the outer window, which might be null
188 nsGlobalWindowOuter* outer = win->GetOuterWindowInternal();
189 if (outer) {
190 if (BrowsingContext* bc = outer->GetBrowsingContext()) {
191 for (const auto& child : bc->Children()) {
192 const nsString& name = child->Name();
193 if (!name.IsEmpty() && !names.Contains(name)) {
194 // Make sure we really would expose it from getOwnPropDescriptor.
195 if (ShouldExposeChildWindow(name, child)) {
196 names.AppendElement(name);
202 if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) {
203 return false;
206 names.Clear();
207 Document* doc = win->GetExtantDoc();
208 if (!doc || !doc->IsHTMLOrXHTML()) {
209 // Define to @@toStringTag on this object to keep Object.prototype.toString
210 // backwards compatible.
211 JS::Rooted<jsid> toStringTagId(
212 aCx, JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::toStringTag));
213 return aProps.append(toStringTagId);
216 nsHTMLDocument* document = doc->AsHTMLDocument();
217 // Document names are enumerable, so we want to get them no matter what flags
218 // is.
219 document->GetSupportedNames(names);
221 JS::RootedVector<jsid> docProps(aCx);
222 if (!AppendNamedPropertyIds(aCx, aProxy, names, false, &docProps)) {
223 return false;
226 JS::Rooted<jsid> toStringTagId(
227 aCx, JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::toStringTag));
228 if (!docProps.append(toStringTagId)) {
229 return false;
232 return js::AppendUnique(aCx, aProps, docProps);
235 bool WindowNamedPropertiesHandler::delete_(JSContext* aCx,
236 JS::Handle<JSObject*> aProxy,
237 JS::Handle<jsid> aId,
238 JS::ObjectOpResult& aResult) const {
239 return aResult.failCantDeleteWindowNamedProperty();
242 // Note that this class doesn't need any reserved slots, but SpiderMonkey
243 // asserts all proxy classes have at least one reserved slot.
244 static const DOMIfaceAndProtoJSClass WindowNamedPropertiesClass = {
245 PROXY_CLASS_DEF("WindowProperties", JSCLASS_IS_DOMIFACEANDPROTOJSCLASS |
246 JSCLASS_HAS_RESERVED_SLOTS(1)),
247 eNamedPropertiesObject,
248 prototypes::id::_ID_Count,
250 &sEmptyNativePropertyHooks,
251 EventTarget_Binding::GetProtoObject};
253 // static
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));
262 if (!gsp) {
263 return nullptr;
266 bool succeeded;
267 if (!JS_SetImmutablePrototype(aCx, gsp, &succeeded)) {
268 return nullptr;
270 MOZ_ASSERT(succeeded,
271 "errors making the [[Prototype]] of the named properties object "
272 "immutable should have been JSAPI failures, not !succeeded");
274 return gsp;
277 } // namespace mozilla::dom