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/. */
8 #include "nsIScriptContext.h"
9 #include "nsIScriptGlobalObject.h"
10 #include "nsVariant.h"
11 #include "nsGkAtoms.h"
12 #include "xpcpublic.h"
13 #include "nsJSEnvironment.h"
14 #include "nsDOMJSUtils.h"
15 #include "mozilla/ContentEvents.h"
16 #include "mozilla/CycleCollectedJSContext.h"
17 #include "mozilla/HoldDropJSObjects.h"
18 #include "mozilla/JSEventHandler.h"
19 #include "mozilla/Likely.h"
20 #include "mozilla/dom/BeforeUnloadEvent.h"
21 #include "mozilla/dom/ErrorEvent.h"
22 #include "mozilla/dom/WorkerPrivate.h"
28 JSEventHandler::JSEventHandler(EventTarget
* aTarget
, nsAtom
* aType
,
29 const TypedEventHandler
& aTypedHandler
)
30 : mTarget(aTarget
), mEventName(aType
), mTypedHandler(aTypedHandler
) {
31 // Note, we call HoldJSObjects to get CanSkip called before CC.
35 JSEventHandler::~JSEventHandler() {
36 NS_ASSERTION(!mTarget
, "Should have called Disconnect()!");
40 NS_IMPL_CYCLE_COLLECTION_CLASS(JSEventHandler
)
42 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSEventHandler
)
43 tmp
->mTypedHandler
.ForgetHandler();
44 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(JSEventHandler
)
46 if (MOZ_UNLIKELY(cb
.WantDebugInfo()) && tmp
->mEventName
) {
48 name
.AppendLiteral("JSEventHandler handlerName=");
50 NS_ConvertUTF16toUTF8(nsDependentAtomString(tmp
->mEventName
)).get());
51 cb
.DescribeRefCountedNode(tmp
->mRefCnt
.get(), name
.get());
53 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(JSEventHandler
, tmp
->mRefCnt
.get())
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mTypedHandler
.Ptr())
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
58 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(JSEventHandler
)
59 if (tmp
->IsBlackForCC()) {
62 // If we have a target, it is the one which has tmp as onfoo handler.
64 nsXPCOMCycleCollectionParticipant
* cp
= nullptr;
65 CallQueryInterface(tmp
->mTarget
, &cp
);
66 nsISupports
* canonical
= nullptr;
67 tmp
->mTarget
->QueryInterface(NS_GET_IID(nsCycleCollectionISupports
),
68 reinterpret_cast<void**>(&canonical
));
69 // Usually CanSkip ends up unmarking the event listeners of mTarget,
70 // so tmp may become black.
71 if (cp
&& canonical
&& cp
->CanSkip(canonical
, true)) {
72 return tmp
->IsBlackForCC();
75 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
77 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(JSEventHandler
)
78 return tmp
->IsBlackForCC();
79 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
81 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(JSEventHandler
)
82 return tmp
->IsBlackForCC();
83 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
85 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSEventHandler
)
86 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
87 NS_INTERFACE_MAP_ENTRY(nsISupports
)
88 NS_INTERFACE_MAP_ENTRY(JSEventHandler
)
91 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSEventHandler
)
92 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSEventHandler
)
94 bool JSEventHandler::IsBlackForCC() {
95 // We can claim to be black if all the things we reference are
96 // effectively black already.
97 return !mTypedHandler
.HasEventHandler() ||
98 mTypedHandler
.Ptr()->IsBlackForCC();
101 nsresult
JSEventHandler::HandleEvent(Event
* aEvent
) {
102 nsCOMPtr
<EventTarget
> target
= mTarget
;
103 if (!target
|| !mTypedHandler
.HasEventHandler() ||
104 !GetTypedEventHandler().Ptr()->CallbackPreserveColor()) {
105 return NS_ERROR_FAILURE
;
108 bool isMainThread
= aEvent
->IsMainThreadEvent();
109 bool isChromeHandler
=
111 ? nsContentUtils::ObjectPrincipal(
112 GetTypedEventHandler().Ptr()->CallbackGlobalOrNull()) ==
113 nsContentUtils::GetSystemPrincipal()
114 : mozilla::dom::IsCurrentThreadRunningChromeWorker();
116 if (mTypedHandler
.Type() == TypedEventHandler::eOnError
) {
117 MOZ_ASSERT_IF(mEventName
, mEventName
== nsGkAtoms::onerror
);
119 nsString errorMsg
, file
;
120 EventOrString msgOrEvent
;
121 Optional
<nsAString
> fileName
;
122 Optional
<uint32_t> lineNumber
;
123 Optional
<uint32_t> columnNumber
;
124 Optional
<JS::Handle
<JS::Value
>> error
;
126 NS_ENSURE_TRUE(aEvent
, NS_ERROR_UNEXPECTED
);
127 ErrorEvent
* scriptEvent
= aEvent
->AsErrorEvent();
129 scriptEvent
->GetMessage(errorMsg
);
130 msgOrEvent
.SetAsString().ShareOrDependUpon(errorMsg
);
132 scriptEvent
->GetFilename(file
);
135 lineNumber
.Construct();
136 lineNumber
.Value() = scriptEvent
->Lineno();
138 columnNumber
.Construct();
139 columnNumber
.Value() = scriptEvent
->Colno();
141 error
.Construct(RootingCx());
142 scriptEvent
->GetError(&error
.Value());
144 msgOrEvent
.SetAsEvent() = aEvent
;
147 RefPtr
<OnErrorEventHandlerNonNull
> handler
=
148 mTypedHandler
.OnErrorEventHandler();
150 JS::Rooted
<JS::Value
> retval(RootingCx());
151 handler
->Call(target
, msgOrEvent
, fileName
, lineNumber
, columnNumber
, error
,
154 return rv
.StealNSResult();
157 if (retval
.isBoolean() && retval
.toBoolean() == bool(scriptEvent
)) {
158 aEvent
->PreventDefaultInternal(isChromeHandler
);
163 if (mTypedHandler
.Type() == TypedEventHandler::eOnBeforeUnload
) {
164 MOZ_ASSERT(mEventName
== nsGkAtoms::onbeforeunload
);
166 RefPtr
<OnBeforeUnloadEventHandlerNonNull
> handler
=
167 mTypedHandler
.OnBeforeUnloadEventHandler();
170 handler
->Call(target
, *aEvent
, retval
, rv
);
172 return rv
.StealNSResult();
175 BeforeUnloadEvent
* beforeUnload
= aEvent
->AsBeforeUnloadEvent();
176 NS_ENSURE_STATE(beforeUnload
);
178 if (!DOMStringIsNull(retval
)) {
179 aEvent
->PreventDefaultInternal(isChromeHandler
);
182 beforeUnload
->GetReturnValue(text
);
184 // Set the text in the beforeUnload event as long as it wasn't
185 // already set (through event.returnValue, which takes
186 // precedence over a value returned from a JS function in IE)
187 if (text
.IsEmpty()) {
188 beforeUnload
->SetReturnValue(retval
);
195 MOZ_ASSERT(mTypedHandler
.Type() == TypedEventHandler::eNormal
);
197 RefPtr
<EventHandlerNonNull
> handler
= mTypedHandler
.NormalEventHandler();
198 JS::Rooted
<JS::Value
> retval(RootingCx());
199 handler
->Call(target
, *aEvent
, &retval
, rv
);
201 return rv
.StealNSResult();
204 // If the handler returned false, then prevent default.
205 if (retval
.isBoolean() && !retval
.toBoolean()) {
206 aEvent
->PreventDefaultInternal(isChromeHandler
);
212 } // namespace mozilla
214 using namespace mozilla
;
220 nsresult
NS_NewJSEventHandler(mozilla::dom::EventTarget
* aTarget
,
222 const TypedEventHandler
& aTypedHandler
,
223 JSEventHandler
** aReturn
) {
224 NS_ENSURE_ARG(aEventType
|| !NS_IsMainThread());
225 JSEventHandler
* it
= new JSEventHandler(aTarget
, aEventType
, aTypedHandler
);
226 NS_ADDREF(*aReturn
= it
);