/* -*- 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/. */ #ifndef mozilla_dom_BrowsingContext_h #define mozilla_dom_BrowsingContext_h #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "mozilla/Tuple.h" #include "mozilla/WeakPtr.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/LocationBase.h" #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" #include "nsIDocShell.h" #include "nsString.h" #include "nsTArray.h" #include "nsWrapperCache.h" #include "nsILoadInfo.h" class nsDocShellLoadState; class nsGlobalWindowOuter; class nsIPrincipal; class nsOuterWindowProxy; class PickleIterator; namespace IPC { class Message; } // namespace IPC namespace mozilla { class ErrorResult; class LogModule; namespace ipc { class IProtocol; template struct IPDLParamTraits; } // namespace ipc namespace dom { class BrowsingContent; class BrowsingContextGroup; class CanonicalBrowsingContext; class ContentParent; class Element; template struct Nullable; template class Sequence; class StructuredCloneHolder; struct WindowPostMessageOptions; class WindowProxyHolder; class BrowsingContextBase { protected: BrowsingContextBase() { // default-construct each field. #define MOZ_BC_FIELD(name, type) m##name = type(); #include "mozilla/dom/BrowsingContextFieldList.h" } ~BrowsingContextBase() = default; #define MOZ_BC_FIELD(name, type) \ type m##name; \ \ /* shadow to validate fields. aSource is setter process or null*/ \ bool MaySet##name(type const& aValue, ContentParent* aSource) { \ return true; \ } \ void DidSet##name() {} #include "mozilla/dom/BrowsingContextFieldList.h" }; // BrowsingContext, in this context, is the cross process replicated // environment in which information about documents is stored. In // particular the tree structure of nested browsing contexts is // represented by the tree of BrowsingContexts. // // The tree of BrowsingContexts is created in step with its // corresponding nsDocShell, and when nsDocShells are connected // through a parent/child relationship, so are BrowsingContexts. The // major difference is that BrowsingContexts are replicated (synced) // to the parent process, making it possible to traverse the // BrowsingContext tree for a tab, in both the parent and the child // process. // // Trees of BrowsingContexts should only ever contain nodes of the // same BrowsingContext::Type. This is enforced by asserts in the // BrowsingContext::Create* methods. class BrowsingContext : public nsWrapperCache, public BrowsingContextBase { public: enum class Type { Chrome, Content }; using Children = nsTArray>; static void Init(); static LogModule* GetLog(); static void CleanupContexts(uint64_t aProcessId); // Look up a BrowsingContext in the current process by ID. static already_AddRefed Get(uint64_t aId); static already_AddRefed Get(GlobalObject&, uint64_t aId) { return Get(aId); } static already_AddRefed GetFromWindow( WindowProxyHolder& aProxy); static already_AddRefed GetFromWindow( GlobalObject&, WindowProxyHolder& aProxy) { return GetFromWindow(aProxy); } // Create a brand-new BrowsingContext object. static already_AddRefed Create(BrowsingContext* aParent, BrowsingContext* aOpener, const nsAString& aName, Type aType); // Cast this object to a canonical browsing context, and return it. CanonicalBrowsingContext* Canonical(); // Is the most recent Document in this BrowsingContext loaded within this // process? This may be true with a null mDocShell after the Window has been // closed. bool IsInProcess() const { return mIsInProcess; } // Has this BrowsingContext been discarded. A discarded browsing context has // been destroyed, and may not be available on the other side of an IPC // message. bool IsDiscarded() const { return mIsDiscarded; } // Get the DocShell for this BrowsingContext if it is in-process, or // null if it's not. nsIDocShell* GetDocShell() { return mDocShell; } void SetDocShell(nsIDocShell* aDocShell); void ClearDocShell() { mDocShell = nullptr; } // This cleans up remote outer window proxies that might have been left behind // when the browsing context went from being remote to local. It does this by // turning them into cross-compartment wrappers to aOuter. If there is already // a remote proxy in the compartment of aOuter, then aOuter will get swapped // to it and the value of aOuter will be set to the object that used to be the // remote proxy and is now an OuterWindowProxy. void CleanUpDanglingRemoteOuterWindowProxies( JSContext* aCx, JS::MutableHandle aOuter); // Get the embedder element for this BrowsingContext if the embedder is // in-process, or null if it's not. Element* GetEmbedderElement() const { return mEmbedderElement; } void SetEmbedderElement(Element* aEmbedder); // Get the outer window object for this BrowsingContext if it is in-process // and still has a docshell, or null otherwise. nsPIDOMWindowOuter* GetDOMWindow() const { return mDocShell ? mDocShell->GetWindow() : nullptr; } // Attach the current BrowsingContext to its parent, in both the child and the // parent process. BrowsingContext objects are created attached by default, so // this method need only be called when restoring cached BrowsingContext // objects. void Attach(bool aFromIPC = false); // Detach the current BrowsingContext from its parent, in both the // child and the parent process. void Detach(bool aFromIPC = false); // Prepare this BrowsingContext to leave the current process. void PrepareForProcessChange(); // Remove all children from the current BrowsingContext and cache // them to allow them to be attached again. void CacheChildren(bool aFromIPC = false); // Restore cached browsing contexts. void RestoreChildren(Children&& aChildren, bool aFromIPC = false); // Triggers a load in the process which currently owns this BrowsingContext. // aAccessor is the context which initiated the load, and may be null only for // in-process BrowsingContexts. nsresult LoadURI(BrowsingContext* aAccessor, nsDocShellLoadState* aLoadState); // Determine if the current BrowsingContext was 'cached' by the logic in // CacheChildren. bool IsCached(); // Check that this browsing context is targetable for navigations (i.e. that // it is neither closed, cached, nor discarded). bool IsTargetable(); const nsString& Name() const { return mName; } void GetName(nsAString& aName) { aName = mName; } bool NameEquals(const nsAString& aName) { return mName.Equals(aName); } bool IsContent() const { return mType == Type::Content; } bool IsChrome() const { return !IsContent(); } bool IsTopContent() const { return IsContent() && !GetParent(); } uint64_t Id() const { return mBrowsingContextId; } BrowsingContext* GetParent() const { return mParent; } BrowsingContext* Top(); already_AddRefed GetOpener() const { RefPtr opener(Get(mOpenerId)); if (!mIsDiscarded && opener && !opener->mIsDiscarded) { return opener.forget(); } return nullptr; } void SetOpener(BrowsingContext* aOpener) { MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->Group() == Group()); SetOpenerId(aOpener ? aOpener->Id() : 0); } bool HasOpener() const; bool HadOriginalOpener() const { return mHadOriginalOpener; } /** * When a new browsing context is opened by a sandboxed document, it needs to * keep track of the browsing context that opened it, so that it can be * navigated by it. This is the "one permitted sandboxed navigator". */ already_AddRefed GetOnePermittedSandboxedNavigator() const { return Get(mOnePermittedSandboxedNavigatorId); } void SetOnePermittedSandboxedNavigator(BrowsingContext* aNavigator) { if (mOnePermittedSandboxedNavigatorId) { MOZ_ASSERT(false, "One Permitted Sandboxed Navigator should only be set once."); } else { SetOnePermittedSandboxedNavigatorId(aNavigator ? aNavigator->Id() : 0); } } void GetChildren(Children& aChildren); BrowsingContextGroup* Group() { return mGroup; } // Using the rules for choosing a browsing context we try to find // the browsing context with the given name in the set of // transitively reachable browsing contexts. Performs access control // with regards to this. // See // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name. // // BrowsingContext::FindWithName(const nsAString&) is equivalent to // calling nsIDocShellTreeItem::FindItemWithName(aName, nullptr, // nullptr, false, ). BrowsingContext* FindWithName(const nsAString& aName, BrowsingContext& aRequestingContext); // Find a browsing context in this context's list of // children. Doesn't consider the special names, '_self', '_parent', // '_top', or '_blank'. Performs access control with regard to // 'this'. BrowsingContext* FindChildWithName(const nsAString& aName, BrowsingContext& aRequestingContext); nsISupports* GetParentObject() const; JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; // This function would be called when its corresponding document is activated // by user gesture, and we would set the flag in the top level browsing // context. void NotifyUserGestureActivation(); // This function would be called when we want to reset the user gesture // activation flag of the top level browsing context. void NotifyResetUserGestureActivation(); // Return true if its corresponding document has transient user gesture // activation and the transient user gesture activation haven't yet timed // out. bool HasValidTransientUserGestureActivation(); // Return the window proxy object that corresponds to this browsing context. inline JSObject* GetWindowProxy() const { return mWindowProxy; } // Set the window proxy object that corresponds to this browsing context. void SetWindowProxy(JS::Handle aWindowProxy) { mWindowProxy = aWindowProxy; } MOZ_DECLARE_WEAKREFERENCE_TYPENAME(BrowsingContext) NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContext) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(BrowsingContext) const Children& GetChildren() { return mChildren; } // Perform a pre-order walk of this BrowsingContext subtree. void PreOrderWalk(const std::function& aCallback) { aCallback(this); for (auto& child : GetChildren()) { child->PreOrderWalk(aCallback); } } // Perform an post-order walk of this BrowsingContext subtree. void PostOrderWalk(const std::function& aCallback) { for (auto& child : GetChildren()) { child->PostOrderWalk(aCallback); } aCallback(this); } // Window APIs that are cross-origin-accessible (from the HTML spec). BrowsingContext* Window() { return Self(); } BrowsingContext* Self() { return this; } void Location(JSContext* aCx, JS::MutableHandle aLocation, ErrorResult& aError); void Close(CallerType aCallerType, ErrorResult& aError); bool GetClosed(ErrorResult&) { return mClosed; } void Focus(ErrorResult& aError); void Blur(ErrorResult& aError); BrowsingContext* GetFrames(ErrorResult& aError) { return Self(); } int32_t Length() const { return mChildren.Length(); } Nullable GetTop(ErrorResult& aError); void GetOpener(JSContext* aCx, JS::MutableHandle aOpener, ErrorResult& aError) const; Nullable GetParent(ErrorResult& aError); void PostMessageMoz(JSContext* aCx, JS::Handle aMessage, const nsAString& aTargetOrigin, const Sequence& aTransfer, nsIPrincipal& aSubjectPrincipal, ErrorResult& aError); void PostMessageMoz(JSContext* aCx, JS::Handle aMessage, const WindowPostMessageOptions& aOptions, nsIPrincipal& aSubjectPrincipal, ErrorResult& aError); JSObject* WrapObject(JSContext* aCx); static JSObject* ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader, StructuredCloneHolder* aHolder); bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder); void StartDelayedAutoplayMediaComponents(); /** * Transaction object. This object is used to specify and then commit * modifications to synchronized fields in BrowsingContexts. */ class Transaction { public: // Apply the changes from this transaction to the specified BrowsingContext // in all processes. This method will call the correct `MaySet` and // `DidSet` methods, as well as move the value. // // NOTE: This method mutates `this`, resetting all members to `Nothing()` void Commit(BrowsingContext* aOwner); // This method should be called before invoking `Apply` on this transaction // object. // // |aSource| is the ContentParent which is performing the mutation in the // parent process. MOZ_MUST_USE bool Validate(BrowsingContext* aOwner, ContentParent* aSource, uint64_t aEpoch); MOZ_MUST_USE bool Validate(BrowsingContext* aOwner, ContentParent* aSource); // You probably don't want to directly call this method - instead call // `Commit`, which will perform the necessary synchronization. // // `Validate` must be called before calling this method. void Apply(BrowsingContext* aOwner); #define MOZ_BC_FIELD(name, type) mozilla::Maybe m##name; #include "mozilla/dom/BrowsingContextFieldList.h" private: friend struct mozilla::ipc::IPDLParamTraits; // Has `Validate` been called on this method yet? // NOTE: This field is not synced, and must be called in every process. bool mValidated = false; }; #define MOZ_BC_FIELD(name, type) \ template \ void Set##name(Args&&... aValue) { \ Transaction txn; \ txn.m##name.emplace(std::forward(aValue)...); \ txn.Commit(this); \ } \ \ type const& Get##name() const { return m##name; } #include "mozilla/dom/BrowsingContextFieldList.h" /** * Information required to initialize a BrowsingContext in another process. * This object may be serialized over IPC. */ struct IPCInitializer { uint64_t mId; // IDs are used for Parent and Opener to allow for this object to be // deserialized before other BrowsingContext in the BrowsingContextGroup // have been initialized. uint64_t mParentId; already_AddRefed GetParent(); already_AddRefed GetOpener(); bool mCached; // Include each field, skipping mOpener, as we want to handle it // separately. #define MOZ_BC_FIELD(name, type) type m##name; #include "mozilla/dom/BrowsingContextFieldList.h" }; // Create an IPCInitializer object for this BrowsingContext. IPCInitializer GetIPCInitializer(); // Create a BrowsingContext object from over IPC. static already_AddRefed CreateFromIPC( IPCInitializer&& aInitializer, BrowsingContextGroup* aGroup, ContentParent* aOriginProcess); // Performs access control to check that 'this' can access 'aTarget'. bool CanAccess(BrowsingContext* aTarget, bool aConsiderOpener = true); protected: virtual ~BrowsingContext(); BrowsingContext(BrowsingContext* aParent, BrowsingContextGroup* aGroup, uint64_t aBrowsingContextId, Type aType); private: // Returns true if the given name is one of the "special" names, currently: // "_self", "_parent", "_top", or "_blank". static bool IsSpecialName(const nsAString& aName); // Find the special browsing context if aName is '_self', '_parent', // '_top', but not '_blank'. The latter is handled in FindWithName BrowsingContext* FindWithSpecialName(const nsAString& aName, BrowsingContext& aRequestingContext); // Find a browsing context in the subtree rooted at 'this' Doesn't // consider the special names, '_self', '_parent', '_top', or // '_blank'. Performs access control with regard to // 'aRequestingContext'. BrowsingContext* FindWithNameInSubtree(const nsAString& aName, BrowsingContext& aRequestingContext); // Removes the context from its group and sets mIsDetached to true. void Unregister(); friend class ::nsOuterWindowProxy; friend class ::nsGlobalWindowOuter; // Update the window proxy object that corresponds to this browsing context. // This should be called from the window proxy object's objectMoved hook, if // the object mWindowProxy points to was moved by the JS GC. void UpdateWindowProxy(JSObject* obj, JSObject* old) { if (mWindowProxy) { MOZ_ASSERT(mWindowProxy == old); mWindowProxy = obj; } } // Clear the window proxy object that corresponds to this browsing context. // This should be called if the window proxy object is finalized, or it can't // reach its browsing context anymore. void ClearWindowProxy() { mWindowProxy = nullptr; } friend class Location; friend class RemoteLocationProxy; /** * LocationProxy is the class for the native object stored as a private in a * RemoteLocationProxy proxy representing a Location object in a different * process. It forwards all operations to its BrowsingContext and aggregates * its refcount to that BrowsingContext. */ class LocationProxy final : public LocationBase { public: MozExternalRefCountType AddRef() { return GetBrowsingContext()->AddRef(); } MozExternalRefCountType Release() { return GetBrowsingContext()->Release(); } protected: friend class RemoteLocationProxy; BrowsingContext* GetBrowsingContext() override { return reinterpret_cast( uintptr_t(this) - offsetof(BrowsingContext, mLocation)); } already_AddRefed GetDocShell() override { return nullptr; } }; // Ensure that opener is in the same BrowsingContextGroup. bool MaySetOpener(const uint64_t& aValue, ContentParent* aSource) { if (aValue != 0) { RefPtr opener = Get(aValue); return opener && opener->Group() == Group(); } return true; } // Ensure that we only set the flag on the top level browsing context. void DidSetIsActivatedByUserGesture(); // Ensure that we only set the flag on the top level browsingContext. // And then, we do a pre-order walk in the tree to refresh the // volume of all media elements. void DidSetMuted(); // Type of BrowsingContent const Type mType; // Unique id identifying BrowsingContext const uint64_t mBrowsingContextId; RefPtr mGroup; RefPtr mParent; Children mChildren; nsCOMPtr mDocShell; RefPtr mEmbedderElement; // This is not a strong reference, but using a JS::Heap for that should be // fine. The JSObject stored in here should be a proxy with a // nsOuterWindowProxy handler, which will update the pointer from its // objectMoved hook and clear it from its finalize hook. JS::Heap mWindowProxy; LocationProxy mLocation; // Whenever a `Transaction` is committed, it is associated with a new // "Browsing Context Epoch". The epoch is associated with a specific content // process. This `mEpochs` field tracks the epoch of the most recent comitted // transaction in this process, and is used to resolve races between processes // and ensure browsing context field consistency. // // This field is only used by content processes. struct { #define MOZ_BC_FIELD(name, ...) uint64_t name = 0; #include "mozilla/dom/BrowsingContextFieldList.h" } mEpochs; // Is the most recent Document in this BrowsingContext loaded within this // process? This may be true with a null mDocShell after the Window has been // closed. bool mIsInProcess : 1; // Has this browsing context been discarded? BrowsingContexts should // only be discarded once. bool mIsDiscarded : 1; // This is true if the BrowsingContext was out of process, but is now in // process, and might have remote window proxies that need to be cleaned up. bool mDanglingRemoteOuterProxies : 1; // The start time of user gesture, this is only available if the browsing // context is in process. TimeStamp mUserGestureStart; }; /** * Gets a WindowProxy object for a BrowsingContext that lives in a different * process (creating the object if it doesn't already exist). The WindowProxy * object will be in the compartment that aCx is currently in. This should only * be called if aContext doesn't hold a docshell, otherwise the BrowsingContext * lives in this process, and a same-process WindowProxy should be used (see * nsGlobalWindowOuter). This should only be called by bindings code, ToJSValue * is the right API to get a WindowProxy for a BrowsingContext. * * If aTransplantTo is non-null, then the WindowProxy object will eventually be * transplanted onto it. Therefore it should be used as the value in the remote * proxy map. */ extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext, JS::Handle aTransplantTo, JS::MutableHandle aRetVal); typedef BrowsingContext::Transaction BrowsingContextTransaction; typedef BrowsingContext::IPCInitializer BrowsingContextInitializer; typedef BrowsingContext::Children BrowsingContextChildren; } // namespace dom // Allow sending BrowsingContext objects over IPC. namespace ipc { template <> struct IPDLParamTraits { static void Write(IPC::Message* aMsg, IProtocol* aActor, dom::BrowsingContext* aParam); static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, RefPtr* aResult); }; template <> struct IPDLParamTraits { static void Write(IPC::Message* aMessage, IProtocol* aActor, const dom::BrowsingContext::Transaction& aTransaction); static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor, dom::BrowsingContext::Transaction* aTransaction); }; template <> struct IPDLParamTraits { static void Write(IPC::Message* aMessage, IProtocol* aActor, const dom::BrowsingContext::IPCInitializer& aInitializer); static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor, dom::BrowsingContext::IPCInitializer* aInitializer); }; } // namespace ipc } // namespace mozilla #endif // !defined(mozilla_dom_BrowsingContext_h)