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 #include "nsJSUtils.h"
7 #include "nsString.h"
8 #include "nsIServiceManager.h"
9 #include "nsIScriptSecurityManager.h"
10 #include "nsIScriptContext.h"
11 #include "nsIScriptGlobalObject.h"
12 #include "nsIXPConnect.h"
13 #include "nsIMutableArray.h"
14 #include "nsVariant.h"
15 #include "nsIDOMBeforeUnloadEvent.h"
16 #include "nsGkAtoms.h"
17 #include "xpcpublic.h"
18 #include "nsJSEnvironment.h"
19 #include "nsDOMJSUtils.h"
20 #include "WorkerPrivate.h"
21 #include "mozilla/ContentEvents.h"
22 #include "mozilla/CycleCollectedJSContext.h"
23 #include "mozilla/HoldDropJSObjects.h"
24 #include "mozilla/JSEventHandler.h"
25 #include "mozilla/Likely.h"
26 #include "mozilla/dom/ErrorEvent.h"
27
28 namespace mozilla {
29
30 using namespace dom;
31
JSEventHandler(nsISupports * aTarget,nsIAtom * aType,const TypedEventHandler & aTypedHandler)32 JSEventHandler::JSEventHandler(nsISupports* aTarget,
33 nsIAtom* aType,
34 const TypedEventHandler& aTypedHandler)
35 : mEventName(aType)
36 , mTypedHandler(aTypedHandler)
37 {
38 nsCOMPtr<nsISupports> base = do_QueryInterface(aTarget);
39 mTarget = base.get();
40 // Note, we call HoldJSObjects to get CanSkip called before CC.
41 HoldJSObjects(this);
42 }
43
~JSEventHandler()44 JSEventHandler::~JSEventHandler()
45 {
46 NS_ASSERTION(!mTarget, "Should have called Disconnect()!");
47 DropJSObjects(this);
48 }
49
50 NS_IMPL_CYCLE_COLLECTION_CLASS(JSEventHandler)
51
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSEventHandler)
53 tmp->mTypedHandler.ForgetHandler();
54 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(JSEventHandler)
56 if (MOZ_UNLIKELY(cb.WantDebugInfo()) && tmp->mEventName) {
57 nsAutoCString name;
58 name.AppendLiteral("JSEventHandler handlerName=");
59 name.Append(
60 NS_ConvertUTF16toUTF8(nsDependentAtomString(tmp->mEventName)).get());
61 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
62 } else {
63 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(JSEventHandler, tmp->mRefCnt.get())
64 }
65 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mTypedHandler.Ptr())
66 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
68
69 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(JSEventHandler)
70 if (tmp->IsBlackForCC()) {
71 return true;
72 }
73 // If we have a target, it is the one which has tmp as onfoo handler.
74 if (tmp->mTarget) {
75 nsXPCOMCycleCollectionParticipant* cp = nullptr;
76 CallQueryInterface(tmp->mTarget, &cp);
77 nsISupports* canonical = nullptr;
78 tmp->mTarget->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
79 reinterpret_cast<void**>(&canonical));
80 // Usually CanSkip ends up unmarking the event listeners of mTarget,
81 // so tmp may become black.
82 if (cp && canonical && cp->CanSkip(canonical, true)) {
83 return tmp->IsBlackForCC();
84 }
85 }
86 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
87
88 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(JSEventHandler)
89 return tmp->IsBlackForCC();
90 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
91
92 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(JSEventHandler)
93 return tmp->IsBlackForCC();
94 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
95
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSEventHandler)96 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSEventHandler)
97 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
98 NS_INTERFACE_MAP_ENTRY(nsISupports)
99 NS_INTERFACE_MAP_ENTRY(JSEventHandler)
100 NS_INTERFACE_MAP_END
101
102 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSEventHandler)
103 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSEventHandler)
104
105 bool
106 JSEventHandler::IsBlackForCC()
107 {
108 // We can claim to be black if all the things we reference are
109 // effectively black already.
110 return !mTypedHandler.HasEventHandler() ||
111 !mTypedHandler.Ptr()->HasGrayCallable();
112 }
113
114 nsresult
HandleEvent(nsIDOMEvent * aEvent)115 JSEventHandler::HandleEvent(nsIDOMEvent* aEvent)
116 {
117 nsCOMPtr<EventTarget> target = do_QueryInterface(mTarget);
118 if (!target || !mTypedHandler.HasEventHandler() ||
119 !GetTypedEventHandler().Ptr()->CallbackPreserveColor()) {
120 return NS_ERROR_FAILURE;
121 }
122
123 Event* event = aEvent->InternalDOMEvent();
124 bool isMainThread = event->IsMainThreadEvent();
125 bool isChromeHandler =
126 isMainThread ?
127 nsContentUtils::ObjectPrincipal(
128 GetTypedEventHandler().Ptr()->CallbackPreserveColor()) ==
129 nsContentUtils::GetSystemPrincipal() :
130 mozilla::dom::workers::IsCurrentThreadRunningChromeWorker();
131
132 if (mTypedHandler.Type() == TypedEventHandler::eOnError) {
133 MOZ_ASSERT_IF(mEventName, mEventName == nsGkAtoms::onerror);
134
135 nsString errorMsg, file;
136 EventOrString msgOrEvent;
137 Optional<nsAString> fileName;
138 Optional<uint32_t> lineNumber;
139 Optional<uint32_t> columnNumber;
140 Optional<JS::Handle<JS::Value>> error;
141
142 NS_ENSURE_TRUE(aEvent, NS_ERROR_UNEXPECTED);
143 ErrorEvent* scriptEvent = aEvent->InternalDOMEvent()->AsErrorEvent();
144 if (scriptEvent) {
145 scriptEvent->GetMessage(errorMsg);
146 msgOrEvent.SetAsString().ShareOrDependUpon(errorMsg);
147
148 scriptEvent->GetFilename(file);
149 fileName = &file;
150
151 lineNumber.Construct();
152 lineNumber.Value() = scriptEvent->Lineno();
153
154 columnNumber.Construct();
155 columnNumber.Value() = scriptEvent->Colno();
156
157 error.Construct(RootingCx());
158 scriptEvent->GetError(&error.Value());
159 } else {
160 msgOrEvent.SetAsEvent() = aEvent->InternalDOMEvent();
161 }
162
163 RefPtr<OnErrorEventHandlerNonNull> handler =
164 mTypedHandler.OnErrorEventHandler();
165 ErrorResult rv;
166 bool handled = handler->Call(mTarget, msgOrEvent, fileName, lineNumber,
167 columnNumber, error, rv);
168 if (rv.Failed()) {
169 return rv.StealNSResult();
170 }
171
172 if (handled) {
173 event->PreventDefaultInternal(isChromeHandler);
174 }
175 return NS_OK;
176 }
177
178 if (mTypedHandler.Type() == TypedEventHandler::eOnBeforeUnload) {
179 MOZ_ASSERT(mEventName == nsGkAtoms::onbeforeunload);
180
181 RefPtr<OnBeforeUnloadEventHandlerNonNull> handler =
182 mTypedHandler.OnBeforeUnloadEventHandler();
183 ErrorResult rv;
184 nsString retval;
185 handler->Call(mTarget, *(aEvent->InternalDOMEvent()), retval, rv);
186 if (rv.Failed()) {
187 return rv.StealNSResult();
188 }
189
190 nsCOMPtr<nsIDOMBeforeUnloadEvent> beforeUnload = do_QueryInterface(aEvent);
191 NS_ENSURE_STATE(beforeUnload);
192
193 if (!DOMStringIsNull(retval)) {
194 event->PreventDefaultInternal(isChromeHandler);
195
196 nsAutoString text;
197 beforeUnload->GetReturnValue(text);
198
199 // Set the text in the beforeUnload event as long as it wasn't
200 // already set (through event.returnValue, which takes
201 // precedence over a value returned from a JS function in IE)
202 if (text.IsEmpty()) {
203 beforeUnload->SetReturnValue(retval);
204 }
205 }
206
207 return NS_OK;
208 }
209
210 MOZ_ASSERT(mTypedHandler.Type() == TypedEventHandler::eNormal);
211 ErrorResult rv;
212 RefPtr<EventHandlerNonNull> handler = mTypedHandler.NormalEventHandler();
213 JS::Rooted<JS::Value> retval(RootingCx());
214 handler->Call(mTarget, *(aEvent->InternalDOMEvent()), &retval, rv);
215 if (rv.Failed()) {
216 return rv.StealNSResult();
217 }
218
219 // If the handler returned false and its sense is not reversed,
220 // or the handler returned true and its sense is reversed from
221 // the usual (false means cancel), then prevent default.
222 if (retval.isBoolean() &&
223 retval.toBoolean() == (mEventName == nsGkAtoms::onerror ||
224 mEventName == nsGkAtoms::onmouseover)) {
225 event->PreventDefaultInternal(isChromeHandler);
226 }
227
228 return NS_OK;
229 }
230
231 } // namespace mozilla
232
233 using namespace mozilla;
234
235 /*
236 * Factory functions
237 */
238
239 nsresult
NS_NewJSEventHandler(nsISupports * aTarget,nsIAtom * aEventType,const TypedEventHandler & aTypedHandler,JSEventHandler ** aReturn)240 NS_NewJSEventHandler(nsISupports* aTarget,
241 nsIAtom* aEventType,
242 const TypedEventHandler& aTypedHandler,
243 JSEventHandler** aReturn)
244 {
245 NS_ENSURE_ARG(aEventType || !NS_IsMainThread());
246 JSEventHandler* it =
247 new JSEventHandler(aTarget, aEventType, aTypedHandler);
248 NS_ADDREF(*aReturn = it);
249
250 return NS_OK;
251 }
252