nsGlobalWindowOuter.cpp 257 KB
Newer Older
1
2
3
4
5
6
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

7
8
9
10
11
12
13
14
15
16
17
18
19
#include "nsGlobalWindow.h"

#include <algorithm>

#include "mozilla/MemoryReporting.h"

// Local Includes
#include "Navigator.h"
#include "nsContentSecurityManager.h"
#include "nsScreen.h"
#include "nsHistory.h"
#include "nsDOMNavigationTiming.h"
#include "nsIDOMStorageManager.h"
20
21
#include "nsISecureBrowserUI.h"
#include "nsIWebProgressListener.h"
22
#include "mozilla/AntiTrackingUtils.h"
23
#include "mozilla/ContentBlocking.h"
24
#include "mozilla/dom/BindingUtils.h"
25
#include "mozilla/dom/BrowserChild.h"
26
#include "mozilla/dom/BrowsingContextBinding.h"
27
#include "mozilla/dom/ContentFrameMessageManager.h"
28
#include "mozilla/dom/EventTarget.h"
29
#include "mozilla/dom/LocalStorage.h"
30
#include "mozilla/dom/LSObject.h"
31
#include "mozilla/dom/Storage.h"
32
#include "mozilla/dom/MaybeCrossOriginObject.h"
33
34
35
36
37
38
39
40
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/dom/StorageNotifierService.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/dom/Timeout.h"
#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/dom/TimeoutManager.h"
41
#include "mozilla/dom/WindowContext.h"
42
#include "mozilla/dom/WindowFeatures.h"  // WindowFeatures
43
#include "mozilla/dom/WindowProxyHolder.h"
44
45
#include "mozilla/IntegerPrintfMacros.h"
#if defined(MOZ_WIDGET_ANDROID)
46
#  include "mozilla/dom/WindowOrientationObserver.h"
47
#endif
48
#include "nsBaseCommandController.h"
49
#include "nsError.h"
50
#include "nsICookieService.h"
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include "nsISizeOfEventTarget.h"
#include "nsDOMJSUtils.h"
#include "nsArrayUtils.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPermissionManager.h"
#include "nsIScriptContext.h"
#include "nsWindowMemoryReporter.h"
#include "nsWindowSizes.h"
#include "WindowNamedPropertiesHandler.h"
#include "nsFrameSelection.h"
#include "nsNetUtil.h"
#include "nsVariant.h"
#include "nsPrintfCString.h"
#include "mozilla/intl/LocaleService.h"
#include "WindowDestroyedEvent.h"
69
#include "nsDocShellLoadState.h"
70
#include "mozilla/dom/WindowGlobalChild.h"
71
72
73

// Helper Classes
#include "nsJSUtils.h"
74
#include "jsapi.h"
75
#include "jsfriendapi.h"
76
#include "js/PropertySpec.h"
77
#include "js/Wrapper.h"
78
79
80
81
82
83
84
85
86
87
88
89
90
#include "nsReadableUtils.h"
#include "nsJSEnvironment.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/Preferences.h"
#include "mozilla/Likely.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Unused.h"

// Other Classes
#include "mozilla/dom/BarProps.h"
#include "nsContentCID.h"
#include "nsLayoutStatics.h"
#include "nsCCUncollectableMarker.h"
91
#include "mozilla/dom/WorkerCommon.h"
92
93
94
#include "mozilla/dom/ToJSValue.h"
#include "nsJSPrincipals.h"
#include "mozilla/Attributes.h"
95
#include "mozilla/Components.h"
96
97
98
99
#include "mozilla/Debug.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/MouseEvents.h"
100
#include "mozilla/PresShell.h"
101
#include "mozilla/ProcessHangMonitor.h"
102
#include "mozilla/StaticPrefs_dom.h"
103
#include "mozilla/StaticPrefs_fission.h"
104
105
106
#include "mozilla/ThrottledEventQueue.h"
#include "AudioChannelService.h"
#include "nsAboutProtocolUtils.h"
107
#include "nsCharTraits.h"  // NS_IS_HIGH/LOW_SURROGATE
108
109
#include "PostMessageEvent.h"
#include "mozilla/dom/DocGroup.h"
110
#include "mozilla/net/CookieJarSettings.h"
111
112
113
114
115
116
117
118
119
120

// Interfaces Needed
#include "nsIFrame.h"
#include "nsCanvasFrame.h"
#include "nsIWidget.h"
#include "nsIWidgetListener.h"
#include "nsIBaseWindow.h"
#include "nsIDeviceSensors.h"
#include "nsIContent.h"
#include "nsIDocShell.h"
121
#include "mozilla/dom/Document.h"
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "Crypto.h"
#include "nsDOMString.h"
#include "nsIEmbeddingSiteWindow.h"
#include "nsThreadUtils.h"
#include "nsILoadContext.h"
#include "nsIScrollableFrame.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsIPrompt.h"
#include "nsIPromptService.h"
#include "nsIPromptFactory.h"
#include "nsIWritablePropertyBag2.h"
#include "nsIWebNavigation.h"
#include "nsIWebBrowserChrome.h"
#include "nsIWebBrowserFind.h"  // For window.find()
#include "nsComputedDOMStyle.h"
#include "nsDOMCID.h"
#include "nsDOMWindowUtils.h"
#include "nsIWindowWatcher.h"
#include "nsPIWindowWatcher.h"
#include "nsIContentViewer.h"
#include "nsIScriptError.h"
#include "nsIControllers.h"
#include "nsGlobalWindowCommands.h"
#include "nsQueryObject.h"
#include "nsContentUtils.h"
#include "nsCSSProps.h"
#include "nsIURIFixup.h"
150
#include "nsIURIMutator.h"
151
152
153
154
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "nsIObserverService.h"
#include "nsFocusManager.h"
155
#include "nsIAppWindow.h"
156
157
158
#include "nsServiceManagerUtils.h"
#include "mozilla/dom/CustomEvent.h"
#include "nsIScreenManager.h"
159
#include "nsIClassifiedChannel.h"
160
161
162
163

#include "xpcprivate.h"

#ifdef NS_PRINTING
164
165
166
#  include "nsIPrintSettings.h"
#  include "nsIPrintSettingsService.h"
#  include "nsIWebBrowserPrint.h"
167
168
169
170
171
172
173
174
#endif

#include "nsWindowRoot.h"
#include "nsNetCID.h"
#include "nsIArray.h"

#include "nsIDOMXULCommandDispatcher.h"

175
#include "mozilla/GlobalKeyListener.h"
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192

#include "nsIDragService.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "nsFrameLoader.h"
#include "nsXPCOMCID.h"
#include "mozilla/Logging.h"
#include "prenv.h"

#include "mozilla/dom/IDBFactory.h"
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/Promise.h"

#include "mozilla/dom/Gamepad.h"
#include "mozilla/dom/GamepadManager.h"

#include "gfxVR.h"
193
194
#include "VRShMem.h"
#include "FxRWindowManager.h"
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#include "mozilla/dom/VRDisplay.h"
#include "mozilla/dom/VRDisplayEvent.h"
#include "mozilla/dom/VRDisplayEventBinding.h"
#include "mozilla/dom/VREventObserver.h"

#include "nsRefreshDriver.h"
#include "Layers.h"

#include "mozilla/BasePrincipal.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/Location.h"
#include "nsHTMLDocument.h"
#include "nsWrapperCacheInlines.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "prrng.h"
#include "nsSandboxFlags.h"
212
#include "nsBaseCommandController.h"
213
#include "nsXULControllers.h"
214
215
#include "mozilla/dom/AudioContext.h"
#include "mozilla/dom/BrowserElementDictionariesBinding.h"
216
#include "mozilla/dom/BrowsingContextGroup.h"
217
218
219
220
221
222
223
224
225
226
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/Console.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/HashChangeEvent.h"
#include "mozilla/dom/IntlUtils.h"
#include "mozilla/dom/PopStateEvent.h"
#include "mozilla/dom/PopupBlockedEvent.h"
#include "mozilla/dom/PrimitiveConversions.h"
#include "mozilla/dom/WindowBinding.h"
227
#include "nsIBrowserChild.h"
228
229
230
231
232
233
234
235
236
#include "mozilla/dom/MediaQueryList.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/NavigatorBinding.h"
#include "mozilla/dom/ImageBitmap.h"
#include "mozilla/dom/ImageBitmapBinding.h"
#include "mozilla/dom/ServiceWorkerRegistration.h"
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#include "mozilla/dom/Worklet.h"
237

238
#ifdef HAVE_SIDEBAR
239
#  include "mozilla/dom/ExternalBinding.h"
240
241
242
#endif

#ifdef MOZ_WEBSPEECH
243
#  include "mozilla/dom/SpeechSynthesis.h"
244
245
246
247
248
#endif

// Apple system headers seem to have a check() macro.  <sigh>
#ifdef check
class nsIScriptTimeoutHandler;
249
#  undef check
250
#endif  // check
251
252
253
#include "AccessCheck.h"

#ifdef ANDROID
254
#  include <android/log.h>
255
256
257
#endif

#ifdef XP_WIN
258
259
#  include <process.h>
#  define getpid _getpid
260
#else
261
#  include <unistd.h>  // for getpid()
262
263
#endif

264
265
266
267
268
269
270
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::ipc;
using mozilla::BasePrincipal;
using mozilla::OriginAttributes;
using mozilla::TimeStamp;

271
272
273
274
275
276
277
#define FORWARD_TO_INNER(method, args, err_rval)       \
  PR_BEGIN_MACRO                                       \
  if (!mInnerWindow) {                                 \
    NS_WARNING("No inner window available!");          \
    return err_rval;                                   \
  }                                                    \
  return GetCurrentInnerWindowInternal()->method args; \
278
279
  PR_END_MACRO

280
281
282
283
284
285
286
287
#define FORWARD_TO_INNER_VOID(method, args)     \
  PR_BEGIN_MACRO                                \
  if (!mInnerWindow) {                          \
    NS_WARNING("No inner window available!");   \
    return;                                     \
  }                                             \
  GetCurrentInnerWindowInternal()->method args; \
  return;                                       \
288
289
290
291
  PR_END_MACRO

// Same as FORWARD_TO_INNER, but this will create a fresh inner if an
// inner doesn't already exists.
292
293
294
295
296
297
#define FORWARD_TO_INNER_CREATE(method, args, err_rval) \
  PR_BEGIN_MACRO                                        \
  if (!mInnerWindow) {                                  \
    if (mIsClosed) {                                    \
      return err_rval;                                  \
    }                                                   \
298
    nsCOMPtr<Document> kungFuDeathGrip = GetDoc();      \
299
300
301
302
303
304
    ::mozilla::Unused << kungFuDeathGrip;               \
    if (!mInnerWindow) {                                \
      return err_rval;                                  \
    }                                                   \
  }                                                     \
  return GetCurrentInnerWindowInternal()->method args;  \
305
306
  PR_END_MACRO

307
static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");
308
extern LazyLogModule gPageCacheLog;
309

310
311
312
313
314
#ifdef DEBUG
static LazyLogModule gDocShellAndDOMWindowLeakLogging(
    "DocShellAndDOMWindowLeak");
#endif

315
316
nsGlobalWindowOuter::OuterWindowByIdTable*
    nsGlobalWindowOuter::sOuterWindowsById = nullptr;
317
318

/* static */
319
320
nsPIDOMWindowOuter* nsPIDOMWindowOuter::GetFromCurrentInner(
    nsPIDOMWindowInner* aInner) {
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
  if (!aInner) {
    return nullptr;
  }

  nsPIDOMWindowOuter* outer = aInner->GetOuterWindow();
  if (!outer || outer->GetCurrentInnerWindow() != aInner) {
    return nullptr;
  }

  return outer;
}

//*****************************************************************************
// nsOuterWindowProxy: Outer Window Proxy
//*****************************************************************************

337
338
339
340
341
342
343
// Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so
// JSObject::swap can swap it with CrossCompartmentWrappers without requiring
// malloc.
//
// We store the nsGlobalWindowOuter* in our first slot.
//
// We store our holder weakmap in the second slot.
344
const JSClass OuterWindowProxyClass = PROXY_CLASS_DEF(
345
346
347
348
349
350
351
352
    "Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */

static const size_t OUTER_WINDOW_SLOT = 0;
static const size_t HOLDER_WEAKMAP_SLOT = 1;

class nsOuterWindowProxy : public MaybeCrossOriginObject<js::Wrapper> {
  typedef MaybeCrossOriginObject<js::Wrapper> Base;

353
 public:
354
  constexpr nsOuterWindowProxy() : Base(0) {}
355
356
357
358
359
360

  bool finalizeInBackground(const JS::Value& priv) const override {
    return false;
  }

  // Standard internal methods
361
362
363
364
365
366
367
  /**
   * Implementation of [[GetOwnProperty]] as defined at
   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
368
369
370
  bool getOwnPropertyDescriptor(
      JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
      JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388

  /*
   * Implementation of the same-origin case of
   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty>.
   */
  bool definePropertySameOrigin(JSContext* cx, JS::Handle<JSObject*> proxy,
                                JS::Handle<jsid> id,
                                JS::Handle<JS::PropertyDescriptor> desc,
                                JS::ObjectOpResult& result) const override;

  /**
   * Implementation of [[OwnPropertyKeys]] as defined at
   *
   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
389
  bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
390
                       JS::MutableHandleVector<jsid> props) const override;
391
392
393
394
395
396
397
  /**
   * Implementation of [[Delete]] as defined at
   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
398
399
400
  bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
               JS::ObjectOpResult& result) const override;

401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
  /**
   * Implementaton of hook for superclass getPrototype() method.
   */
  JSObject* getSameOriginPrototype(JSContext* cx) const override;

  /**
   * Implementation of [[HasProperty]] internal method as defined at
   * https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   *
   * Note that the HTML spec does not define an override for this internal
   * method, so we just want the "normal object" behavior.  We have to override
   * it, because js::Wrapper also overrides, with "not normal" behavior.
   */
417
418
  bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
           bool* bp) const override;
419
420
421
422
423
424
425
426
427
428
429
430
431

  /**
   * Implementation of [[Get]] internal method as defined at
   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-get>.
   *
   * "proxy" is the WindowProxy object involved.  It may or may not be
   * same-compartment with "cx".
   *
   * "receiver" is the receiver ("this") for the get.  It will be
   * same-compartment with "cx".
   *
   * "vp" is the return value.  It will be same-compartment with "cx".
   */
432
433
  bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
           JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
434
           JS::MutableHandle<JS::Value> vp) const override;
435
436
437
438
439
440
441
442
443
444
445
446
447

  /**
   * Implementation of [[Set]] internal method as defined at
   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set>.
   *
   * "proxy" is the WindowProxy object involved.  It may or may not be
   * same-compartment with "cx".
   *
   * "v" is the value being set.  It will be same-compartment with "cx".
   *
   * "receiver" is the receiver ("this") for the set.  It will be
   * same-compartment with "cx".
   */
448
449
450
  bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
           JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
           JS::ObjectOpResult& result) const override;
451
452

  // SpiderMonkey extensions
453
454
455
456
457
458
459
460
461
462
463
  /**
   * Implementation of SpiderMonkey extension which just checks whether this
   * object has the property.  Basically Object.getOwnPropertyDescriptor(obj,
   * prop) !== undefined. but does not require reifying the descriptor.
   *
   * We have to override this because js::Wrapper overrides it, but we want
   * different behavior from js::Wrapper.
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
464
465
  bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
              bool* bp) const override;
466
467
468
469
470
471
472
473
474
475
476

  /**
   * Implementation of SpiderMonkey extension which is used as a fast path for
   * enumerating.
   *
   * We have to override this because js::Wrapper overrides it, but we want
   * different behavior from js::Wrapper.
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
477
478
479
  bool getOwnEnumerablePropertyKeys(
      JSContext* cx, JS::Handle<JSObject*> proxy,
      JS::MutableHandleVector<jsid> props) const override;
480
481
482
483

  /**
   * Hook used by SpiderMonkey to implement Object.prototype.toString.
   */
484
  const char* className(JSContext* cx,
485
486
                        JS::Handle<JSObject*> wrapper) const override;

487
  void finalize(JSFreeOp* fop, JSObject* proxy) const override;
488
489
  size_t objectMoved(JSObject* proxy, JSObject* old) const override;

490
491
  bool isCallable(JSObject* obj) const override { return false; }
  bool isConstructor(JSObject* obj) const override { return false; }
492
493
494

  static const nsOuterWindowProxy singleton;

495
496
497
  static nsGlobalWindowOuter* GetOuterWindow(JSObject* proxy) {
    nsGlobalWindowOuter* outerWindow =
        nsGlobalWindowOuter::FromSupports(static_cast<nsISupports*>(
498
            js::GetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT).toPrivate()));
499
500
501
    return outerWindow;
  }

502
 protected:
503
504
  // False return value means we threw an exception.  True return value
  // but false "found" means we didn't have a subframe at that index.
505
506
507
  bool GetSubframeWindow(JSContext* cx, JS::Handle<JSObject*> proxy,
                         JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
                         bool& found) const;
508
509
510

  // Returns a non-null window only if id is an index and we have a
  // window at that index.
511
512
513
  Nullable<WindowProxyHolder> GetSubframeWindow(JSContext* cx,
                                                JS::Handle<JSObject*> proxy,
                                                JS::Handle<jsid> id) const;
514

515
  bool AppendIndexedPropertyNames(JSObject* proxy,
516
                                  JS::MutableHandleVector<jsid> props) const;
517

518
519
520
  using MaybeCrossOriginObjectMixins::EnsureHolder;
  bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
                    JS::MutableHandle<JSObject*> holder) const override;
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541

  // Helper method for creating a special "print" method that allows printing
  // our PDF-viewer documents even if you're not same-origin with them.
  //
  // aProxy must be our nsOuterWindowProxy.  It will not be same-compartment
  // with aCx, since we only use this on the different-origin codepath!
  //
  // Can return true without filling in aDesc, which corresponds to not exposing
  // a "print" method.
  static bool MaybeGetPDFJSPrintMethod(
      JSContext* cx, JS::Handle<JSObject*> proxy,
      JS::MutableHandle<JS::PropertyDescriptor> desc);

  // The actual "print" method we use for the PDFJS case.
  static bool PDFJSPrintMethod(JSContext* cx, unsigned argc, JS::Value* vp);

  // Helper method to get the pre-PDF-viewer-messing-with-it principal from an
  // inner window.  Will return null if this is not a PDF-viewer inner or if the
  // principal could not be found for some reason.
  static already_AddRefed<nsIPrincipal> GetNoPDFJSPrincipal(
      nsGlobalWindowInner* inner);
542
};
543

544
545
546
const char* nsOuterWindowProxy::className(JSContext* cx,
                                          JS::Handle<JSObject*> proxy) const {
  MOZ_ASSERT(js::IsProxy(proxy));
547

548
549
550
551
  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return "Object";
  }

552
  return "Window";
553
554
}

555
void nsOuterWindowProxy::finalize(JSFreeOp* fop, JSObject* proxy) const {
556
557
558
  nsGlobalWindowOuter* outerWindow = GetOuterWindow(proxy);
  if (outerWindow) {
    outerWindow->ClearWrapper(proxy);
559
560
561
562
    BrowsingContext* bc = outerWindow->GetBrowsingContext();
    if (bc) {
      bc->ClearWindowProxy();
    }
563
564
565
566
567
568
569
570
571

    // Ideally we would use OnFinalize here, but it's possible that
    // EnsureScriptEnvironment will later be called on the window, and we don't
    // want to create a new script object in that case. Therefore, we need to
    // write a non-null value that will reliably crash when dereferenced.
    outerWindow->PoisonOuterWindowProxy(proxy);
  }
}

572
573
574
575
576
577
578
579
580
581
582
583
584
/**
 * IsNonConfigurableReadonlyPrimitiveGlobalProp returns true for
 * property names that fit the following criteria:
 *
 * 1) The ES spec defines a property with that name on globals.
 * 2) The property is non-configurable.
 * 3) The property is non-writable (readonly).
 * 4) The value of the property is a primitive (so doesn't change
 *    observably on when navigation happens).
 *
 * Such properties can act as actual non-configurable properties on a
 * WindowProxy, because they are not affected by navigation.
 */
585
#ifndef RELEASE_OR_BETA
586
587
static bool IsNonConfigurableReadonlyPrimitiveGlobalProp(JSContext* cx,
                                                         JS::Handle<jsid> id) {
588
589
590
591
  return id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAN) ||
         id == GetJSIDByIndex(cx, XPCJSContext::IDX_UNDEFINED) ||
         id == GetJSIDByIndex(cx, XPCJSContext::IDX_INFINITY);
}
592
#endif
593

594
595
596
bool nsOuterWindowProxy::getOwnPropertyDescriptor(
    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
    JS::MutableHandle<JS::PropertyDescriptor> desc) const {
597
598
599
  // First check for indexed access.  This is
  // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
  // step 2, mostly.
600
601
602
603
604
  bool found;
  if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) {
    return false;
  }
  if (found) {
605
    // Step 2.4.
606
607
608
609
    FillPropertyDescriptor(desc, proxy, true);
    return true;
  }

610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
  bool isSameOrigin = IsPlatformObjectSameOrigin(cx, proxy);

  // If we did not find a subframe, we could still have an indexed property
  // access.  In that case we should throw a SecurityError in the cross-origin
  // case.
  if (!isSameOrigin && IsArrayIndex(GetArrayIndexFromId(id))) {
    // Step 2.5.2.
    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("access"));
  }

  // Step 2.5.1 is handled via the forwarding to js::Wrapper; it saves us an
  // IsArrayIndex(GetArrayIndexFromId(id)) here.  We'll never have a property on
  // the Window whose name is an index, because our defineProperty doesn't pass
  // those on to the Window.

  // Step 3.
  if (isSameOrigin) {
627
628
629
630
    if (StaticPrefs::dom_missing_prop_counters_enabled() && JSID_IS_ATOM(id)) {
      Window_Binding::CountMaybeMissingProperty(proxy, id);
    }

631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
    // Fall through to js::Wrapper.
    {  // Scope for JSAutoRealm while we are dealing with js::Wrapper.
      // When forwarding to js::Wrapper, we should just enter the Realm of proxy
      // for now.  That's what js::Wrapper expects, and since we're same-origin
      // anyway this is not changing any security behavior.
      JSAutoRealm ar(cx, proxy);
      JS_MarkCrossZoneId(cx, id);
      bool ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
      if (!ok) {
        return false;
      }

#ifndef RELEASE_OR_BETA  // To be turned on in bug 1496510.
      if (!IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
        desc.setConfigurable(true);
      }
#endif
    }

    // Now wrap our descriptor back into the Realm that asked for it.
    return JS_WrapPropertyDescriptor(cx, desc);
  }

  // Step 4.
  if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) {
656
657
658
    return false;
  }

659
660
661
  // Step 5
  if (desc.object()) {
    return true;
662
  }
663

664
665
666
667
668
669
670
671
672
673
674
675
676
  // Non-spec step for the PDF viewer's window.print().  This comes before we
  // check for named subframes, because in the same-origin case print() would
  // shadow those.
  if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_PRINT)) {
    if (!MaybeGetPDFJSPrintMethod(cx, proxy, desc)) {
      return false;
    }

    if (desc.object()) {
      return true;
    }
  }

677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
  // Step 6 -- check for named subframes.
  if (JSID_IS_STRING(id)) {
    nsAutoJSString name;
    if (!name.init(cx, JSID_TO_STRING(id))) {
      return false;
    }
    nsGlobalWindowOuter* win = GetOuterWindow(proxy);
    if (RefPtr<BrowsingContext> childDOMWin = win->GetChildWindow(name)) {
      JS::Rooted<JS::Value> childValue(cx);
      if (!ToJSValue(cx, WindowProxyHolder(childDOMWin), &childValue)) {
        return false;
      }
      FillPropertyDescriptor(desc, proxy, childValue,
                             /* readonly = */ true,
                             /* enumerable = */ false);
      return true;
    }
  }

  // And step 7.
  return CrossOriginPropertyFallback(cx, proxy, id, desc);
698
699
}

700
701
702
bool nsOuterWindowProxy::definePropertySameOrigin(
    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
    JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
703
  if (IsArrayIndex(GetArrayIndexFromId(id))) {
704
705
706
707
708
709
    // Spec says to Reject whether this is a supported index or not,
    // since we have no indexed setter or indexed creator.  It is up
    // to the caller to decide whether to throw a TypeError.
    return result.failCantDefineWindowElement();
  }

710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
  JS::ObjectOpResult ourResult;
  bool ok = js::Wrapper::defineProperty(cx, proxy, id, desc, ourResult);
  if (!ok) {
    return false;
  }

  if (!ourResult.ok()) {
    // It's possible that this failed because the page got the existing
    // descriptor (which we force to claim to be configurable) and then tried to
    // redefine the property with the descriptor it got but a different value.
    // We want to allow this case to succeed, so check for it and if we're in
    // that case try again but now with an attempt to define a non-configurable
    // property.
    if (!desc.hasConfigurable() || !desc.configurable()) {
      // The incoming descriptor was not explicitly marked "configurable: true",
      // so it failed for some other reason.  Just propagate that reason out.
      result = ourResult;
      return true;
    }

    JS::Rooted<JS::PropertyDescriptor> existingDesc(cx);
    ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, &existingDesc);
    if (!ok) {
      return false;
    }
    if (!existingDesc.object() || existingDesc.configurable()) {
      // We have no existing property, or its descriptor is already configurable
      // (on the Window itself, where things really can be non-configurable).
      // So we failed for some other reason, which we should propagate out.
      result = ourResult;
      return true;
    }

    JS::Rooted<JS::PropertyDescriptor> updatedDesc(cx, desc);
    updatedDesc.setConfigurable(false);

    JS::ObjectOpResult ourNewResult;
    ok = js::Wrapper::defineProperty(cx, proxy, id, updatedDesc, ourNewResult);
    if (!ok) {
      return false;
    }

    if (!ourNewResult.ok()) {
      // Twiddling the configurable flag didn't help.  Just return this failure
      // out to the caller.
      result = ourNewResult;
      return true;
    }
  }

760
#ifndef RELEASE_OR_BETA  // To be turned on in bug 1496510.
761
762
763
764
765
766
767
768
769
770
771
  if (desc.hasConfigurable() && !desc.configurable() &&
      !IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
    // Give callers a way to detect that they failed to "really" define a
    // non-configurable property.
    result.failCantDefineWindowNonConfigurable();
    return true;
  }
#endif

  result.succeed();
  return true;
772
773
}

774
775
776
bool nsOuterWindowProxy::ownPropertyKeys(
    JSContext* cx, JS::Handle<JSObject*> proxy,
    JS::MutableHandleVector<jsid> props) const {
777
  // Just our indexed stuff followed by our "normal" own property names.
778
  if (!AppendIndexedPropertyNames(proxy, props)) {
779
780
781
    return false;
  }

782
783
784
785
  if (IsPlatformObjectSameOrigin(cx, proxy)) {
    // When forwarding to js::Wrapper, we should just enter the Realm of proxy
    // for now.  That's what js::Wrapper expects, and since we're same-origin
    // anyway this is not changing any security behavior.
786
    JS::RootedVector<jsid> innerProps(cx);
787
788
    {  // Scope for JSAutoRealm so we can mark the ids once we exit it
      JSAutoRealm ar(cx, proxy);
789
      if (!js::Wrapper::ownPropertyKeys(cx, proxy, &innerProps)) {
790
791
792
793
794
795
796
797
798
799
800
801
802
        return false;
      }
    }
    for (auto& id : innerProps) {
      JS_MarkCrossZoneId(cx, id);
    }
    return js::AppendUnique(cx, props, innerProps);
  }

  // In the cross-origin case we purposefully exclude subframe names from the
  // list of property names we report here.
  JS::Rooted<JSObject*> holder(cx);
  if (!EnsureHolder(cx, proxy, &holder)) {
803
804
    return false;
  }
805

806
  JS::RootedVector<jsid> crossOriginProps(cx);
807
808
809
810
811
812
813
  if (!js::GetPropertyKeys(cx, holder,
                           JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
                           &crossOriginProps) ||
      !js::AppendUnique(cx, props, crossOriginProps)) {
    return false;
  }

814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
  // Add the "print" property if needed.
  nsGlobalWindowOuter* outer = GetOuterWindow(proxy);
  nsGlobalWindowInner* inner =
      nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow());
  if (inner) {
    nsCOMPtr<nsIPrincipal> targetPrincipal = GetNoPDFJSPrincipal(inner);
    if (targetPrincipal &&
        nsContentUtils::SubjectPrincipal(cx)->Equals(targetPrincipal)) {
      JS::RootedVector<jsid> printProp(cx);
      if (!printProp.append(GetJSIDByIndex(cx, XPCJSContext::IDX_PRINT)) ||
          !js::AppendUnique(cx, props, printProp)) {
        return false;
      }
    }
  }

830
  return xpc::AppendCrossOriginWhitelistedPropNames(cx, props);
831
832
}

833
834
835
bool nsOuterWindowProxy::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                                 JS::Handle<jsid> id,
                                 JS::ObjectOpResult& result) const {
836
837
838
839
  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("delete"));
  }

840
  if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
841
842
843
844
    // Fail (which means throw if strict, else return false).
    return result.failCantDeleteWindowElement();
  }

845
  if (IsArrayIndex(GetArrayIndexFromId(id))) {
846
847
848
849
    // Indexed, but not supported.  Spec says return true.
    return result.succeed();
  }

850
851
852
853
854
  // We're same-origin, so it should be safe to enter the Realm of "proxy".
  // Let's do that, just in case, to avoid cross-compartment issues in our
  // js::Wrapper caller..
  JSAutoRealm ar(cx, proxy);
  JS_MarkCrossZoneId(cx, id);
855
856
857
  return js::Wrapper::delete_(cx, proxy, id, result);
}

858
859
JSObject* nsOuterWindowProxy::getSameOriginPrototype(JSContext* cx) const {
  return Window_Binding::GetProtoObjectHandle(cx);
860
861
}

862
863
bool nsOuterWindowProxy::has(JSContext* cx, JS::Handle<JSObject*> proxy,
                             JS::Handle<jsid> id, bool* bp) const {
864
865
866
867
868
869
870
871
872
873
  // We could just directly forward this method to js::BaseProxyHandler, but
  // that involves reifying the actual property descriptor, which might be more
  // work than we have to do for has() on the Window.

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    // In the cross-origin case we only have own properties.  Just call hasOwn
    // directly.
    return hasOwn(cx, proxy, id, bp);
  }

874
  if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
875
876
877
878
    *bp = true;
    return true;
  }

879
880
881
882
  // Just to be safe in terms of compartment asserts, enter the Realm of
  // "proxy".  We're same-origin with it, so this should be safe.
  JSAutoRealm ar(cx, proxy);
  JS_MarkCrossZoneId(cx, id);
883
884
885
  return js::Wrapper::has(cx, proxy, id, bp);
}

886
887
bool nsOuterWindowProxy::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
                                JS::Handle<jsid> id, bool* bp) const {
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
  // We could just directly forward this method to js::BaseProxyHandler, but
  // that involves reifying the actual property descriptor, which might be more
  // work than we have to do for hasOwn() on the Window.

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    // Avoiding reifying the property descriptor here would require duplicating
    // a bunch of "is this property exposed cross-origin" logic, which is
    // probably not worth it.  Just forward this along to the base
    // implementation.
    //
    // It's very important to not forward this to js::Wrapper, because that will
    // not do the right security and cross-origin checks and will pass through
    // the call to the Window.
    //
    // The BaseProxyHandler code is OK with this happening without entering the
    // compartment of "proxy".
    return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
  }

907
  if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
908
909
910
911
    *bp = true;
    return true;
  }

912
913
914
915
  // Just to be safe in terms of compartment asserts, enter the Realm of
  // "proxy".  We're same-origin with it, so this should be safe.
  JSAutoRealm ar(cx, proxy);
  JS_MarkCrossZoneId(cx, id);
916
917
918
  return js::Wrapper::hasOwn(cx, proxy, id, bp);
}

919
920
921
922
bool nsOuterWindowProxy::get(JSContext* cx, JS::Handle<JSObject*> proxy,
                             JS::Handle<JS::Value> receiver,
                             JS::Handle<jsid> id,
                             JS::MutableHandle<JS::Value> vp) const {
923
  if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_WRAPPED_JSOBJECT) &&
924
925
      xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
    vp.set(JS::ObjectValue(*proxy));
926
927
928
929
930
    return MaybeWrapValue(cx, vp);
  }

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return CrossOriginGet(cx, proxy, receiver, id, vp);
931
932
933
934
935
936
  }

  bool found;
  if (!GetSubframeWindow(cx, proxy, id, vp, found)) {
    return false;
  }
937

938
939
940
941
  if (found) {
    return true;
  }

942
943
944
945
  if (StaticPrefs::dom_missing_prop_counters_enabled() && JSID_IS_ATOM(id)) {
    Window_Binding::CountMaybeMissingProperty(proxy, id);
  }

946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
  {  // Scope for JSAutoRealm
    // Enter "proxy"'s Realm.  We're in the same-origin case, so this should be
    // safe.
    JSAutoRealm ar(cx, proxy);

    JS_MarkCrossZoneId(cx, id);

    JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
    if (!MaybeWrapValue(cx, &wrappedReceiver)) {
      return false;
    }

    // Fall through to js::Wrapper.
    if (!js::Wrapper::get(cx, proxy, wrappedReceiver, id, vp)) {
      return false;
    }
  }

  // Make sure our return value is in the caller compartment.
  return MaybeWrapValue(cx, vp);
966
967
}

968
969
970
971
bool nsOuterWindowProxy::set(JSContext* cx, JS::Handle<JSObject*> proxy,
                             JS::Handle<jsid> id, JS::Handle<JS::Value> v,
                             JS::Handle<JS::Value> receiver,
                             JS::ObjectOpResult& result) const {
972
973
974
975
  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return CrossOriginSet(cx, proxy, id, v, receiver, result);
  }

976
  if (IsArrayIndex(GetArrayIndexFromId(id))) {
977
978
979
980
981
    // Reject the set.  It's up to the caller to decide whether to throw a
    // TypeError.  If the caller is strict mode JS code, it'll throw.
    return result.failReadOnly();
  }

982
983
984
985
986
987
988
989
990
991
992
993
994
995
  // Do the rest in the Realm of "proxy", since we're in the same-origin case.
  JSAutoRealm ar(cx, proxy);
  JS::Rooted<JS::Value> wrappedArg(cx, v);
  if (!MaybeWrapValue(cx, &wrappedArg)) {
    return false;
  }
  JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
  if (!MaybeWrapValue(cx, &wrappedReceiver)) {
    return false;
  }

  JS_MarkCrossZoneId(cx, id);

  return js::Wrapper::set(cx, proxy, id, wrappedArg, wrappedReceiver, result);
996
997
}

998
bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys(
999
1000
    JSContext* cx, JS::Handle<JSObject*> proxy,
    JS::MutableHandleVector<jsid> props) const {