Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/script/ScriptSettings.h
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
/* Utilities for managing the script settings object stack defined in webapps */
8
9
#ifndef mozilla_dom_ScriptSettings_h
10
#define mozilla_dom_ScriptSettings_h
11
12
#include "MainThreadUtils.h"
13
#include "nsIGlobalObject.h"
14
#include "nsIPrincipal.h"
15
#include "xpcpublic.h"
16
17
#include "mozilla/Maybe.h"
18
19
#include "jsapi.h"
20
#include "js/Debug.h"
21
22
class nsPIDOMWindowInner;
23
class nsGlobalWindowInner;
24
class nsIScriptContext;
25
class nsIDocument;
26
class nsIDocShell;
27
28
namespace mozilla {
29
namespace dom {
30
31
/*
32
 * System-wide setup/teardown routines. Init and Destroy should be invoked
33
 * once each, at startup and shutdown (respectively).
34
 */
35
void InitScriptSettings();
36
void DestroyScriptSettings();
37
bool ScriptSettingsInitialized();
38
39
/*
40
 * Static helpers in ScriptSettings which track the number of listeners
41
 * of Javascript RunToCompletion events.  These should be used by the code in
42
 * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script
43
 * settings that script run-to-completion needs to be monitored.
44
 * SHOULD BE CALLED ONLY BY MAIN THREAD.
45
 */
46
void UseEntryScriptProfiling();
47
void UnuseEntryScriptProfiling();
48
49
// To implement a web-compatible browser, it is often necessary to obtain the
50
// global object that is "associated" with the currently-running code. This
51
// process is made more complicated by the fact that, historically, different
52
// algorithms have operated with different definitions of the "associated"
53
// global.
54
//
55
// HTML5 formalizes this into two concepts: the "incumbent global" and the
56
// "entry global". The incumbent global corresponds to the global of the
57
// current script being executed, whereas the entry global corresponds to the
58
// global of the script where the current JS execution began.
59
//
60
// There is also a potentially-distinct third global that is determined by the
61
// current compartment. This roughly corresponds with the notion of Realms in
62
// ECMAScript.
63
//
64
// Suppose some event triggers an event listener in window |A|, which invokes a
65
// scripted function in window |B|, which invokes the |window.location.href|
66
// setter in window |C|. The entry global would be |A|, the incumbent global
67
// would be |B|, and the current compartment would be that of |C|.
68
//
69
// In general, it's best to use to use the most-closely-associated global
70
// unless the spec says to do otherwise. In 95% of the cases, the global of
71
// the current compartment (GetCurrentGlobal()) is the right thing. For
72
// example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with
73
// the global of the current compartment (i.e. |C|).
74
//
75
// The incumbent global is very similar, but differs in a few edge cases. For
76
// example, if window |B| does |C.location.href = "..."|, the incumbent global
77
// used for the navigation algorithm is B, because no script from |C| was ever run.
78
//
79
// The entry global is used for various things like computing base URIs, mostly
80
// for historical reasons.
81
//
82
// Note that all of these functions return bonafide global objects. This means
83
// that, for Windows, they always return the inner.
84
85
// Returns the global associated with the top-most Candidate Entry Point on
86
// the Script Settings Stack. See the HTML spec. This may be null.
87
nsIGlobalObject* GetEntryGlobal();
88
89
// If the entry global is a window, returns its extant document. Otherwise,
90
// returns null.
91
nsIDocument* GetEntryDocument();
92
93
// Returns the global associated with the top-most entry of the the Script
94
// Settings Stack. See the HTML spec. This may be null.
95
nsIGlobalObject* GetIncumbentGlobal();
96
97
// Returns the global associated with the current compartment. This may be null.
98
nsIGlobalObject* GetCurrentGlobal();
99
100
// JS-implemented WebIDL presents an interesting situation with respect to the
101
// subject principal. A regular C++-implemented API can simply examine the
102
// compartment of the most-recently-executed script, and use that to infer the
103
// responsible party. However, JS-implemented APIs are run with system
104
// principal, and thus clobber the subject principal of the script that
105
// invoked the API. So we have to do some extra work to keep track of this
106
// information.
107
//
108
// We therefore implement the following behavior:
109
// * Each Script Settings Object has an optional WebIDL Caller Principal field.
110
//   This defaults to null.
111
// * When we push an Entry Point in preparation to run a JS-implemented WebIDL
112
//   callback, we grab the subject principal at the time of invocation, and
113
//   store that as the WebIDL Caller Principal.
114
// * When non-null, callers can query this principal from script via an API on
115
//   Components.utils.
116
nsIPrincipal* GetWebIDLCallerPrincipal();
117
118
// This may be used by callers that know that their incumbent global is non-
119
// null (i.e. they know there have been no System Caller pushes since the
120
// inner-most script execution).
121
inline JSObject& IncumbentJSGlobal()
122
{
123
  return *GetIncumbentGlobal()->GetGlobalJSObject();
124
}
125
126
// Returns whether JSAPI is active right now.  If it is not, working with a
127
// JSContext you grab from somewhere random is not OK and you should be doing
128
// AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext.
129
bool IsJSAPIActive();
130
131
namespace danger {
132
133
// Get the JSContext for this thread.  This is in the "danger" namespace because
134
// we generally want people using AutoJSAPI instead, unless they really know
135
// what they're doing.
136
JSContext* GetJSContext();
137
138
} // namespace danger
139
140
JS::RootingContext* RootingCx();
141
142
class ScriptSettingsStack;
143
class ScriptSettingsStackEntry {
144
  friend class ScriptSettingsStack;
145
146
public:
147
  ~ScriptSettingsStackEntry();
148
149
60.0M
  bool NoJSAPI() const { return mType == eNoJSAPI; }
150
0
  bool IsEntryCandidate() const {
151
0
    return mType == eEntryScript || mType == eNoJSAPI;
152
0
  }
153
0
  bool IsIncumbentCandidate() { return mType != eJSAPI; }
154
  bool IsIncumbentScript() { return mType == eIncumbentScript; }
155
156
protected:
157
  enum Type {
158
    eEntryScript,
159
    eIncumbentScript,
160
    eJSAPI,
161
    eNoJSAPI
162
  };
163
164
  ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
165
                           Type aEntryType);
166
167
  nsCOMPtr<nsIGlobalObject> mGlobalObject;
168
  Type mType;
169
170
private:
171
  ScriptSettingsStackEntry *mOlder;
172
};
173
174
/*
175
 * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
176
 * must be on the stack.
177
 *
178
 * This base class should be instantiated as-is when the caller wants to use
179
 * JSAPI but doesn't expect to run script. The caller must then call one of its
180
 * Init functions before being able to access the JSContext through cx().
181
 * Its current duties are as-follows (see individual Init comments for details):
182
 *
183
 * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
184
 *   the JSContext stack.
185
 * * Entering an initial (possibly null) compartment, to ensure that the
186
 *   previously entered compartment for that JSContext is not used by mistake.
187
 * * Reporting any exceptions left on the JSRuntime, unless the caller steals
188
 *   or silences them.
189
 *
190
 * Additionally, the following duties are planned, but not yet implemented:
191
 *
192
 * * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires
193
 *   implementing the poisoning first.  For now, this de-poisoning
194
 *   effectively corresponds to having a non-null cx on the stack.
195
 *
196
 * In situations where the consumer expects to run script, AutoEntryScript
197
 * should be used, which does additional manipulation of the script settings
198
 * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
199
 * any attempt to run script without an AutoEntryScript on the stack will
200
 * fail. This prevents system code from accidentally triggering script
201
 * execution at inopportune moments via surreptitious getters and proxies.
202
 */
203
class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry {
204
public:
205
  // Trivial constructor. One of the Init functions must be called before
206
  // accessing the JSContext through cx().
207
  AutoJSAPI();
208
209
  ~AutoJSAPI();
210
211
  // This uses the SafeJSContext (or worker equivalent), and enters a null
212
  // compartment, so that the consumer is forced to select a compartment to
213
  // enter before manipulating objects.
214
  //
215
  // This variant will ensure that any errors reported by this AutoJSAPI as it
216
  // comes off the stack will not fire error events or be associated with any
217
  // particular web-visible global.
218
  void Init();
219
220
  // This uses the SafeJSContext (or worker equivalent), and enters the
221
  // compartment of aGlobalObject.
222
  // If aGlobalObject or its associated JS global are null then it returns
223
  // false and use of cx() will cause an assertion.
224
  //
225
  // If aGlobalObject represents a web-visible global, errors reported by this
226
  // AutoJSAPI as it comes off the stack will fire the relevant error events and
227
  // show up in the corresponding web console.
228
  MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject);
229
230
  // This is a helper that grabs the native global associated with aObject and
231
  // invokes the above Init() with that. aObject must not be a cross-compartment
232
  // wrapper: CCWs are not associated with a single global.
233
  MOZ_MUST_USE bool Init(JSObject* aObject);
234
235
  // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject.
236
  // If aGlobalObject or its associated JS global are null then it returns
237
  // false and use of cx() will cause an assertion.
238
  // If aCx is null it will cause an assertion.
239
  //
240
  // If aGlobalObject represents a web-visible global, errors reported by this
241
  // AutoJSAPI as it comes off the stack will fire the relevant error events and
242
  // show up in the corresponding web console.
243
  MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
244
245
  // Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*,
246
  // when it is more easily available than an nsIGlobalObject.
247
  MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow);
248
  MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
249
250
  MOZ_MUST_USE bool Init(nsGlobalWindowInner* aWindow);
251
  MOZ_MUST_USE bool Init(nsGlobalWindowInner* aWindow, JSContext* aCx);
252
253
  JSContext* cx() const {
254
    MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
255
    MOZ_ASSERT(IsStackTop());
256
    return mCx;
257
  }
258
259
#ifdef DEBUG
260
  bool IsStackTop() const;
261
#endif
262
263
  // If HasException, report it.  Otherwise, a no-op.
264
  void ReportException();
265
266
  bool HasException() const {
267
    MOZ_ASSERT(IsStackTop());
268
    return JS_IsExceptionPending(cx());
269
  };
270
271
  // Transfers ownership of the current exception from the JS engine to the
272
  // caller. Callers must ensure that HasException() is true, and that cx()
273
  // is in a non-null compartment.
274
  //
275
  // Note that this fails if and only if we OOM while wrapping the exception
276
  // into the current compartment.
277
  MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal);
278
279
  // Peek the current exception from the JS engine, without stealing it.
280
  // Callers must ensure that HasException() is true, and that cx() is in a
281
  // non-null compartment.
282
  //
283
  // Note that this fails if and only if we OOM while wrapping the exception
284
  // into the current compartment.
285
  MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal);
286
287
  void ClearException() {
288
    MOZ_ASSERT(IsStackTop());
289
    JS_ClearPendingException(cx());
290
  }
291
292
protected:
293
  // Protected constructor for subclasses.  This constructor initialises the
294
  // AutoJSAPI, so Init must NOT be called on subclasses that use this.
295
  AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
296
297
  mozilla::Maybe<JSAutoNullableRealm> mAutoNullableRealm;
298
  JSContext *mCx;
299
300
  // Whether we're mainthread or not; set when we're initialized.
301
  bool mIsMainThread;
302
  Maybe<JS::WarningReporter> mOldWarningReporter;
303
304
private:
305
  void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
306
                    JSContext* aCx, bool aIsMainThread);
307
308
  AutoJSAPI(const AutoJSAPI&) = delete;
309
  AutoJSAPI& operator= (const AutoJSAPI&) = delete;
310
};
311
312
/*
313
 * A class that represents a new script entry point.
314
 *
315
 * |aReason| should be a statically-allocated C string naming the reason we're
316
 * invoking JavaScript code: "setTimeout", "event", and so on. The devtools use
317
 * these strings to label JS execution in timeline and profiling displays.
318
 */
319
class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI {
320
public:
321
  AutoEntryScript(nsIGlobalObject* aGlobalObject,
322
                  const char *aReason,
323
                  bool aIsMainThread = NS_IsMainThread());
324
325
  // aObject can be any object from the relevant global. It must not be a
326
  // cross-compartment wrapper because CCWs are not associated with a single
327
  // global.
328
  AutoEntryScript(JSObject* aObject,
329
                  const char *aReason,
330
                  bool aIsMainThread = NS_IsMainThread());
331
332
  ~AutoEntryScript();
333
334
  void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) {
335
    mWebIDLCallerPrincipal = aPrincipal;
336
  }
337
338
private:
339
  // A subclass of AutoEntryMonitor that notifies the docshell.
340
  class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor
341
  {
342
  public:
343
    DocshellEntryMonitor(JSContext* aCx, const char* aReason);
344
345
    // Please note that |aAsyncCause| here is owned by the caller, and its
346
    // lifetime must outlive the lifetime of the DocshellEntryMonitor object.
347
    // In practice, |aAsyncCause| is identical to |aReason| passed into
348
    // the AutoEntryScript constructor, so the lifetime requirements are
349
    // trivially satisfied by |aReason| being a statically allocated string.
350
    void Entry(JSContext* aCx, JSFunction* aFunction,
351
               JS::Handle<JS::Value> aAsyncStack,
352
               const char* aAsyncCause) override
353
0
    {
354
0
      Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
355
0
    }
356
357
    void Entry(JSContext* aCx, JSScript* aScript,
358
               JS::Handle<JS::Value> aAsyncStack,
359
               const char* aAsyncCause) override
360
0
    {
361
0
      Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
362
0
    }
363
364
    void Exit(JSContext* aCx) override;
365
366
  private:
367
    void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
368
               JS::Handle<JS::Value> aAsyncStack,
369
               const char* aAsyncCause);
370
371
    const char* mReason;
372
  };
373
374
  // It's safe to make this a weak pointer, since it's the subject principal
375
  // when we go on the stack, so can't go away until after we're gone.  In
376
  // particular, this is only used from the CallSetup constructor, and only in
377
  // the aIsJSImplementedWebIDL case.  And in that case, the subject principal
378
  // is the principal of the callee function that is part of the CallArgs just a
379
  // bit up the stack, and which will outlive us.  So we know the principal
380
  // can't go away until then either.
381
  nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal;
382
  friend nsIPrincipal* GetWebIDLCallerPrincipal();
383
384
  Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
385
  Maybe<xpc::AutoScriptActivity> mScriptActivity;
386
  JS::AutoHideScriptedCaller mCallerOverride;
387
#ifdef MOZ_GECKO_PROFILER
388
  AutoProfilerLabel mAutoProfilerLabel;
389
#endif
390
};
391
392
/*
393
 * A class that can be used to force a particular incumbent script on the stack.
394
 */
395
class AutoIncumbentScript : protected ScriptSettingsStackEntry {
396
public:
397
  explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
398
  ~AutoIncumbentScript();
399
400
private:
401
  JS::AutoHideScriptedCaller mCallerOverride;
402
};
403
404
/*
405
 * A class to put the JS engine in an unusable state. The subject principal
406
 * will become System, the information on the script settings stack is
407
 * rendered inaccessible, and JSAPI may not be manipulated until the class is
408
 * either popped or an AutoJSAPI instance is subsequently pushed.
409
 *
410
 * This class may not be instantiated if an exception is pending.
411
 */
412
class AutoNoJSAPI : protected ScriptSettingsStackEntry {
413
public:
414
  explicit AutoNoJSAPI();
415
  ~AutoNoJSAPI();
416
};
417
418
} // namespace dom
419
420
/**
421
 * Use AutoJSContext when you need a JS context on the stack but don't have one
422
 * passed as a parameter. AutoJSContext will take care of finding the most
423
 * appropriate JS context and release it when leaving the stack.
424
 */
425
class MOZ_RAII AutoJSContext {
426
public:
427
  explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
428
  operator JSContext*() const;
429
430
protected:
431
  JSContext* mCx;
432
  dom::AutoJSAPI mJSAPI;
433
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
434
};
435
436
/**
437
 * AutoSafeJSContext is similar to AutoJSContext but will only return the safe
438
 * JS context. That means it will never call nsContentUtils::GetCurrentJSContext().
439
 *
440
 * Note - This is deprecated. Please use AutoJSAPI instead.
441
 */
442
class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
443
public:
444
  explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
445
  operator JSContext*() const
446
  {
447
    return cx();
448
  }
449
450
private:
451
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
452
};
453
454
/**
455
 * Use AutoSlowOperation when native side calls many JS callbacks in a row
456
 * and slow script dialog should be activated if too much time is spent going
457
 * through those callbacks.
458
 * AutoSlowOperation puts an AutoScriptActivity on the stack so that we don't
459
 * continue to reset the watchdog. CheckForInterrupt can then be used to check
460
 * whether JS execution should be interrupted.
461
 * This class (including CheckForInterrupt) is a no-op when used off the main
462
 * thread.
463
 */
464
class MOZ_RAII AutoSlowOperation
465
{
466
public:
467
  explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
468
  void CheckForInterrupt();
469
private:
470
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
471
  bool mIsMainThread;
472
  Maybe<xpc::AutoScriptActivity> mScriptActivity;
473
};
474
475
/**
476
 * A class to disable interrupt callback temporary.
477
 */
478
class MOZ_RAII AutoDisableJSInterruptCallback
479
{
480
public:
481
  explicit AutoDisableJSInterruptCallback(JSContext* aCx)
482
    : mCx(aCx)
483
    , mOld(JS_DisableInterruptCallback(aCx))
484
  { }
485
486
  ~AutoDisableJSInterruptCallback() {
487
    JS_ResetInterruptCallback(mCx, mOld);
488
  }
489
490
private:
491
  JSContext* mCx;
492
  bool mOld;
493
};
494
495
} // namespace mozilla
496
497
#endif // mozilla_dom_ScriptSettings_h