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