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