diff options
Diffstat (limited to 'dom/base/ScriptSettings.h')
-rw-r--r-- | dom/base/ScriptSettings.h | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h new file mode 100644 index 000000000..05e62f55e --- /dev/null +++ b/dom/base/ScriptSettings.h @@ -0,0 +1,465 @@ +/* -*- 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/. */ + +/* Utilities for managing the script settings object stack defined in webapps */ + +#ifndef mozilla_dom_ScriptSettings_h +#define mozilla_dom_ScriptSettings_h + +#include "MainThreadUtils.h" +#include "nsIGlobalObject.h" +#include "nsIPrincipal.h" + +#include "mozilla/Maybe.h" + +#include "jsapi.h" +#include "js/Debug.h" + +class nsPIDOMWindowInner; +class nsGlobalWindow; +class nsIScriptContext; +class nsIDocument; +class nsIDocShell; + +namespace mozilla { +namespace dom { + +/* + * System-wide setup/teardown routines. Init and Destroy should be invoked + * once each, at startup and shutdown (respectively). + */ +void InitScriptSettings(); +void DestroyScriptSettings(); +bool ScriptSettingsInitialized(); + +/* + * Static helpers in ScriptSettings which track the number of listeners + * of Javascript RunToCompletion events. These should be used by the code in + * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script + * settings that script run-to-completion needs to be monitored. + * SHOULD BE CALLED ONLY BY MAIN THREAD. + */ +void UseEntryScriptProfiling(); +void UnuseEntryScriptProfiling(); + +// To implement a web-compatible browser, it is often necessary to obtain the +// global object that is "associated" with the currently-running code. This +// process is made more complicated by the fact that, historically, different +// algorithms have operated with different definitions of the "associated" +// global. +// +// HTML5 formalizes this into two concepts: the "incumbent global" and the +// "entry global". The incumbent global corresponds to the global of the +// current script being executed, whereas the entry global corresponds to the +// global of the script where the current JS execution began. +// +// There is also a potentially-distinct third global that is determined by the +// current compartment. This roughly corresponds with the notion of Realms in +// ECMAScript. +// +// Suppose some event triggers an event listener in window |A|, which invokes a +// scripted function in window |B|, which invokes the |window.location.href| +// setter in window |C|. The entry global would be |A|, the incumbent global +// would be |B|, and the current compartment would be that of |C|. +// +// In general, it's best to use to use the most-closely-associated global +// unless the spec says to do otherwise. In 95% of the cases, the global of +// the current compartment (GetCurrentGlobal()) is the right thing. For +// example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with +// the global of the current compartment (i.e. |C|). +// +// The incumbent global is very similar, but differs in a few edge cases. For +// example, if window |B| does |C.location.href = "..."|, the incumbent global +// used for the navigation algorithm is B, because no script from |C| was ever run. +// +// The entry global is used for various things like computing base URIs, mostly +// for historical reasons. +// +// Note that all of these functions return bonafide global objects. This means +// that, for Windows, they always return the inner. + +// Returns the global associated with the top-most Candidate Entry Point on +// the Script Settings Stack. See the HTML spec. This may be null. +nsIGlobalObject* GetEntryGlobal(); + +// If the entry global is a window, returns its extant document. Otherwise, +// returns null. +nsIDocument* GetEntryDocument(); + +// Returns the global associated with the top-most entry of the the Script +// Settings Stack. See the HTML spec. This may be null. +nsIGlobalObject* GetIncumbentGlobal(); + +// Returns the global associated with the current compartment. This may be null. +nsIGlobalObject* GetCurrentGlobal(); + +// JS-implemented WebIDL presents an interesting situation with respect to the +// subject principal. A regular C++-implemented API can simply examine the +// compartment of the most-recently-executed script, and use that to infer the +// responsible party. However, JS-implemented APIs are run with system +// principal, and thus clobber the subject principal of the script that +// invoked the API. So we have to do some extra work to keep track of this +// information. +// +// We therefore implement the following behavior: +// * Each Script Settings Object has an optional WebIDL Caller Principal field. +// This defaults to null. +// * When we push an Entry Point in preparation to run a JS-implemented WebIDL +// callback, we grab the subject principal at the time of invocation, and +// store that as the WebIDL Caller Principal. +// * When non-null, callers can query this principal from script via an API on +// Components.utils. +nsIPrincipal* GetWebIDLCallerPrincipal(); + +// This may be used by callers that know that their incumbent global is non- +// null (i.e. they know there have been no System Caller pushes since the +// inner-most script execution). +inline JSObject& IncumbentJSGlobal() +{ + return *GetIncumbentGlobal()->GetGlobalJSObject(); +} + +// Returns whether JSAPI is active right now. If it is not, working with a +// JSContext you grab from somewhere random is not OK and you should be doing +// AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext. +bool IsJSAPIActive(); + +namespace danger { + +// Get the JSContext for this thread. This is in the "danger" namespace because +// we generally want people using AutoJSAPI instead, unless they really know +// what they're doing. +JSContext* GetJSContext(); + +} // namespace danger + +JS::RootingContext* RootingCx(); + +class ScriptSettingsStack; +class ScriptSettingsStackEntry { + friend class ScriptSettingsStack; + +public: + ~ScriptSettingsStackEntry(); + + bool NoJSAPI() const { return mType == eNoJSAPI; } + bool IsEntryCandidate() const { + return mType == eEntryScript || mType == eNoJSAPI; + } + bool IsIncumbentCandidate() { return mType != eJSAPI; } + bool IsIncumbentScript() { return mType == eIncumbentScript; } + +protected: + enum Type { + eEntryScript, + eIncumbentScript, + eJSAPI, + eNoJSAPI + }; + + ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, + Type aEntryType); + + nsCOMPtr<nsIGlobalObject> mGlobalObject; + Type mType; + +private: + ScriptSettingsStackEntry *mOlder; +}; + +/* + * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses) + * must be on the stack. + * + * This base class should be instantiated as-is when the caller wants to use + * JSAPI but doesn't expect to run script. The caller must then call one of its + * Init functions before being able to access the JSContext through cx(). + * Its current duties are as-follows (see individual Init comments for details): + * + * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto + * the JSContext stack. + * * Entering an initial (possibly null) compartment, to ensure that the + * previously entered compartment for that JSContext is not used by mistake. + * * Reporting any exceptions left on the JSRuntime, unless the caller steals + * or silences them. + * * On main thread, entering a JSAutoRequest. + * + * Additionally, the following duties are planned, but not yet implemented: + * + * * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires + * implementing the poisoning first. For now, this de-poisoning + * effectively corresponds to having a non-null cx on the stack. + * + * In situations where the consumer expects to run script, AutoEntryScript + * should be used, which does additional manipulation of the script settings + * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that + * any attempt to run script without an AutoEntryScript on the stack will + * fail. This prevents system code from accidentally triggering script + * execution at inopportune moments via surreptitious getters and proxies. + */ +class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry { +public: + // Trivial constructor. One of the Init functions must be called before + // accessing the JSContext through cx(). + AutoJSAPI(); + + ~AutoJSAPI(); + + // This uses the SafeJSContext (or worker equivalent), and enters a null + // compartment, so that the consumer is forced to select a compartment to + // enter before manipulating objects. + // + // This variant will ensure that any errors reported by this AutoJSAPI as it + // comes off the stack will not fire error events or be associated with any + // particular web-visible global. + void Init(); + + // This uses the SafeJSContext (or worker equivalent), and enters the + // compartment of aGlobalObject. + // If aGlobalObject or its associated JS global are null then it returns + // false and use of cx() will cause an assertion. + // + // If aGlobalObject represents a web-visible global, errors reported by this + // AutoJSAPI as it comes off the stack will fire the relevant error events and + // show up in the corresponding web console. + MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject); + + // This is a helper that grabs the native global associated with aObject and + // invokes the above Init() with that. + MOZ_MUST_USE bool Init(JSObject* aObject); + + // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject. + // If aGlobalObject or its associated JS global are null then it returns + // false and use of cx() will cause an assertion. + // If aCx is null it will cause an assertion. + // + // If aGlobalObject represents a web-visible global, errors reported by this + // AutoJSAPI as it comes off the stack will fire the relevant error events and + // show up in the corresponding web console. + MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx); + + // Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*, + // when it is more easily available than an nsIGlobalObject. + MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow); + MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx); + + MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow); + MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow, JSContext* aCx); + + JSContext* cx() const { + MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI"); + MOZ_ASSERT(IsStackTop()); + return mCx; + } + +#ifdef DEBUG + bool IsStackTop() const; +#endif + + // If HasException, report it. Otherwise, a no-op. + void ReportException(); + + bool HasException() const { + MOZ_ASSERT(IsStackTop()); + return JS_IsExceptionPending(cx()); + }; + + // Transfers ownership of the current exception from the JS engine to the + // caller. Callers must ensure that HasException() is true, and that cx() + // is in a non-null compartment. + // + // Note that this fails if and only if we OOM while wrapping the exception + // into the current compartment. + MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal); + + // Peek the current exception from the JS engine, without stealing it. + // Callers must ensure that HasException() is true, and that cx() is in a + // non-null compartment. + // + // Note that this fails if and only if we OOM while wrapping the exception + // into the current compartment. + MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal); + + void ClearException() { + MOZ_ASSERT(IsStackTop()); + JS_ClearPendingException(cx()); + } + +protected: + // Protected constructor for subclasses. This constructor initialises the + // AutoJSAPI, so Init must NOT be called on subclasses that use this. + AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType); + +private: + mozilla::Maybe<JSAutoRequest> mAutoRequest; + mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment; + JSContext *mCx; + + // Whether we're mainthread or not; set when we're initialized. + bool mIsMainThread; + Maybe<JS::WarningReporter> mOldWarningReporter; + + void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal, + JSContext* aCx, bool aIsMainThread); + + AutoJSAPI(const AutoJSAPI&) = delete; + AutoJSAPI& operator= (const AutoJSAPI&) = delete; +}; + +/* + * A class that represents a new script entry point. + * + * |aReason| should be a statically-allocated C string naming the reason we're + * invoking JavaScript code: "setTimeout", "event", and so on. The devtools use + * these strings to label JS execution in timeline and profiling displays. + */ +class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI { +public: + AutoEntryScript(nsIGlobalObject* aGlobalObject, + const char *aReason, + bool aIsMainThread = NS_IsMainThread()); + + AutoEntryScript(JSObject* aObject, // Any object from the relevant global + const char *aReason, + bool aIsMainThread = NS_IsMainThread()); + + ~AutoEntryScript(); + + void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) { + mWebIDLCallerPrincipal = aPrincipal; + } + +private: + // A subclass of AutoEntryMonitor that notifies the docshell. + class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor + { + public: + DocshellEntryMonitor(JSContext* aCx, const char* aReason); + + // Please note that |aAsyncCause| here is owned by the caller, and its + // lifetime must outlive the lifetime of the DocshellEntryMonitor object. + // In practice, |aAsyncCause| is identical to |aReason| passed into + // the AutoEntryScript constructor, so the lifetime requirements are + // trivially satisfied by |aReason| being a statically allocated string. + void Entry(JSContext* aCx, JSFunction* aFunction, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) override + { + Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause); + } + + void Entry(JSContext* aCx, JSScript* aScript, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) override + { + Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause); + } + + void Exit(JSContext* aCx) override; + + private: + void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause); + + const char* mReason; + }; + + // It's safe to make this a weak pointer, since it's the subject principal + // when we go on the stack, so can't go away until after we're gone. In + // particular, this is only used from the CallSetup constructor, and only in + // the aIsJSImplementedWebIDL case. And in that case, the subject principal + // is the principal of the callee function that is part of the CallArgs just a + // bit up the stack, and which will outlive us. So we know the principal + // can't go away until then either. + nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal; + friend nsIPrincipal* GetWebIDLCallerPrincipal(); + + Maybe<DocshellEntryMonitor> mDocShellEntryMonitor; +}; + +/* + * A class that can be used to force a particular incumbent script on the stack. + */ +class AutoIncumbentScript : protected ScriptSettingsStackEntry { +public: + explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject); + ~AutoIncumbentScript(); + +private: + JS::AutoHideScriptedCaller mCallerOverride; +}; + +/* + * A class to put the JS engine in an unusable state. The subject principal + * will become System, the information on the script settings stack is + * rendered inaccessible, and JSAPI may not be manipulated until the class is + * either popped or an AutoJSAPI instance is subsequently pushed. + * + * This class may not be instantiated if an exception is pending. + */ +class AutoNoJSAPI : protected ScriptSettingsStackEntry { +public: + explicit AutoNoJSAPI(); + ~AutoNoJSAPI(); +}; + +} // namespace dom + +/** + * Use AutoJSContext when you need a JS context on the stack but don't have one + * passed as a parameter. AutoJSContext will take care of finding the most + * appropriate JS context and release it when leaving the stack. + */ +class MOZ_RAII AutoJSContext { +public: + explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + operator JSContext*() const; + +protected: + JSContext* mCx; + dom::AutoJSAPI mJSAPI; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +/** + * AutoSafeJSContext is similar to AutoJSContext but will only return the safe + * JS context. That means it will never call nsContentUtils::GetCurrentJSContext(). + * + * Note - This is deprecated. Please use AutoJSAPI instead. + */ +class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI { +public: + explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + operator JSContext*() const + { + return cx(); + } + +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +/** + * Use AutoSlowOperation when native side calls many JS callbacks in a row + * and slow script dialog should be activated if too much time is spent going + * through those callbacks. + * AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue + * to reset the watchdog and CheckForInterrupt can be then used to check whether + * JS execution should be interrupted. + */ +class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI +{ +public: + explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + void CheckForInterrupt(); +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +} // namespace mozilla + +#endif // mozilla_dom_ScriptSettings_h |