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 #ifndef mozilla_AsyncEventDispatcher_h_
8 #define mozilla_AsyncEventDispatcher_h_
9 
10 #include "mozilla/Attributes.h"
11 #include "mozilla/EventForwards.h"
12 #include "mozilla/RefPtr.h"
13 #include "mozilla/dom/Event.h"
14 #include "nsCOMPtr.h"
15 #include "mozilla/dom/Document.h"
16 #include "nsString.h"
17 #include "nsThreadUtils.h"
18 
19 class nsINode;
20 
21 namespace mozilla {
22 
23 /**
24  * Use AsyncEventDispatcher to fire a DOM event that requires safe a stable DOM.
25  * For example, you may need to fire an event from within layout, but
26  * want to ensure that the event handler doesn't mutate the DOM at
27  * the wrong time, in order to avoid resulting instability.
28  */
29 
30 class AsyncEventDispatcher : public CancelableRunnable {
31  public:
32   /**
33    * If aOnlyChromeDispatch is true, the event is dispatched to only
34    * chrome node. In that case, if aTarget is already a chrome node,
35    * the event is dispatched to it, otherwise the dispatch path starts
36    * at the first chrome ancestor of that target.
37    */
38   AsyncEventDispatcher(nsINode* aTarget, const nsAString& aEventType,
39                        CanBubble aCanBubble,
40                        ChromeOnlyDispatch aOnlyChromeDispatch,
41                        Composed aComposed = Composed::eDefault)
42       : CancelableRunnable("AsyncEventDispatcher"),
43         mTarget(aTarget),
44         mEventType(aEventType),
45         mEventMessage(eUnidentifiedEvent),
46         mCanBubble(aCanBubble),
47         mOnlyChromeDispatch(aOnlyChromeDispatch),
48         mComposed(aComposed) {}
49 
50   /**
51    * If aOnlyChromeDispatch is true, the event is dispatched to only
52    * chrome node. In that case, if aTarget is already a chrome node,
53    * the event is dispatched to it, otherwise the dispatch path starts
54    * at the first chrome ancestor of that target.
55    */
AsyncEventDispatcher(nsINode * aTarget,mozilla::EventMessage aEventMessage,CanBubble aCanBubble,ChromeOnlyDispatch aOnlyChromeDispatch)56   AsyncEventDispatcher(nsINode* aTarget, mozilla::EventMessage aEventMessage,
57                        CanBubble aCanBubble,
58                        ChromeOnlyDispatch aOnlyChromeDispatch)
59       : CancelableRunnable("AsyncEventDispatcher"),
60         mTarget(aTarget),
61         mEventMessage(aEventMessage),
62         mCanBubble(aCanBubble),
63         mOnlyChromeDispatch(aOnlyChromeDispatch) {
64     mEventType.SetIsVoid(true);
65     MOZ_ASSERT(mEventMessage != eUnidentifiedEvent);
66   }
67 
AsyncEventDispatcher(dom::EventTarget * aTarget,const nsAString & aEventType,CanBubble aCanBubble)68   AsyncEventDispatcher(dom::EventTarget* aTarget, const nsAString& aEventType,
69                        CanBubble aCanBubble)
70       : CancelableRunnable("AsyncEventDispatcher"),
71         mTarget(aTarget),
72         mEventType(aEventType),
73         mEventMessage(eUnidentifiedEvent),
74         mCanBubble(aCanBubble) {}
75 
AsyncEventDispatcher(dom::EventTarget * aTarget,mozilla::EventMessage aEventMessage,CanBubble aCanBubble)76   AsyncEventDispatcher(dom::EventTarget* aTarget,
77                        mozilla::EventMessage aEventMessage,
78                        CanBubble aCanBubble)
79       : CancelableRunnable("AsyncEventDispatcher"),
80         mTarget(aTarget),
81         mEventMessage(aEventMessage),
82         mCanBubble(aCanBubble) {
83     mEventType.SetIsVoid(true);
84     MOZ_ASSERT(mEventMessage != eUnidentifiedEvent);
85   }
86 
87   /**
88    * aEvent must have been created without Widget*Event and Internal*Event
89    * because this constructor assumes that it's safe to use aEvent
90    * asynchronously (i.e., after all objects allocated in the stack are
91    * destroyed).
92    */
AsyncEventDispatcher(dom::EventTarget * aTarget,dom::Event * aEvent)93   AsyncEventDispatcher(dom::EventTarget* aTarget, dom::Event* aEvent)
94       : CancelableRunnable("AsyncEventDispatcher"),
95         mTarget(aTarget),
96         mEvent(aEvent),
97         mEventMessage(eUnidentifiedEvent) {
98     MOZ_ASSERT(
99         aEvent->IsSafeToBeDispatchedAsynchronously(),
100         "The DOM event should be created without Widget*Event and "
101         "Internal*Event "
102         "because if it needs to be safe to be dispatched asynchronously");
103   }
104 
105   AsyncEventDispatcher(dom::EventTarget* aTarget, WidgetEvent& aEvent);
106 
107   NS_IMETHOD Run() override;
108   nsresult Cancel() override;
109   nsresult PostDOMEvent();
110   void RunDOMEventWhenSafe();
111 
112   // Calling this causes the Run() method to check that
113   // mTarget->IsInComposedDoc(). mTarget must be an nsINode or else we'll
114   // assert.
115   void RequireNodeInDocument();
116 
117   nsCOMPtr<dom::EventTarget> mTarget;
118   RefPtr<dom::Event> mEvent;
119   // If mEventType is set, mEventMessage will be eUnidentifiedEvent.
120   // If mEventMessage is set, mEventType will be void.
121   // They can never both be set at the same time.
122   nsString mEventType;
123   EventMessage mEventMessage;
124   CanBubble mCanBubble = CanBubble::eNo;
125   ChromeOnlyDispatch mOnlyChromeDispatch = ChromeOnlyDispatch::eNo;
126   Composed mComposed = Composed::eDefault;
127   bool mCanceled = false;
128   bool mCheckStillInDoc = false;
129 };
130 
131 class LoadBlockingAsyncEventDispatcher final : public AsyncEventDispatcher {
132  public:
LoadBlockingAsyncEventDispatcher(nsINode * aEventNode,const nsAString & aEventType,CanBubble aBubbles,ChromeOnlyDispatch aDispatchChromeOnly)133   LoadBlockingAsyncEventDispatcher(nsINode* aEventNode,
134                                    const nsAString& aEventType,
135                                    CanBubble aBubbles,
136                                    ChromeOnlyDispatch aDispatchChromeOnly)
137       : AsyncEventDispatcher(aEventNode, aEventType, aBubbles,
138                              aDispatchChromeOnly),
139         mBlockedDoc(aEventNode->OwnerDoc()) {
140     if (mBlockedDoc) {
141       mBlockedDoc->BlockOnload();
142     }
143   }
144 
LoadBlockingAsyncEventDispatcher(nsINode * aEventNode,dom::Event * aEvent)145   LoadBlockingAsyncEventDispatcher(nsINode* aEventNode, dom::Event* aEvent)
146       : AsyncEventDispatcher(aEventNode, aEvent),
147         mBlockedDoc(aEventNode->OwnerDoc()) {
148     if (mBlockedDoc) {
149       mBlockedDoc->BlockOnload();
150     }
151   }
152 
153   ~LoadBlockingAsyncEventDispatcher();
154 
155  private:
156   RefPtr<dom::Document> mBlockedDoc;
157 };
158 
159 }  // namespace mozilla
160 
161 #endif  // mozilla_AsyncEventDispatcher_h_
162