Line data Source code
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 :
32 557 : JSEventHandler::JSEventHandler(nsISupports* aTarget,
33 : nsIAtom* aType,
34 557 : const TypedEventHandler& aTypedHandler)
35 : : mEventName(aType)
36 557 : , mTypedHandler(aTypedHandler)
37 : {
38 1114 : nsCOMPtr<nsISupports> base = do_QueryInterface(aTarget);
39 557 : mTarget = base.get();
40 : // Note, we call HoldJSObjects to get CanSkip called before CC.
41 557 : HoldJSObjects(this);
42 557 : }
43 :
44 30 : JSEventHandler::~JSEventHandler()
45 : {
46 10 : NS_ASSERTION(!mTarget, "Should have called Disconnect()!");
47 10 : DropJSObjects(this);
48 30 : }
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 : } else {
63 0 : NS_IMPL_CYCLE_COLLECTION_DESCRIBE(JSEventHandler, tmp->mRefCnt.get())
64 : }
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 : }
72 : // 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 : // Usually CanSkip ends up unmarking the event listeners of mTarget,
80 : // so tmp may become black.
81 0 : if (cp && canonical && cp->CanSkip(canonical, true)) {
82 0 : return tmp->IsBlackForCC();
83 : }
84 : }
85 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
86 :
87 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(JSEventHandler)
88 0 : return tmp->IsBlackForCC();
89 : 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 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
94 :
95 2288 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSEventHandler)
96 557 : NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
97 557 : NS_INTERFACE_MAP_ENTRY(nsISupports)
98 557 : NS_INTERFACE_MAP_ENTRY(JSEventHandler)
99 0 : NS_INTERFACE_MAP_END
100 :
101 1674 : NS_IMPL_CYCLE_COLLECTING_ADDREF(JSEventHandler)
102 1137 : NS_IMPL_CYCLE_COLLECTING_RELEASE(JSEventHandler)
103 :
104 : bool
105 0 : JSEventHandler::IsBlackForCC()
106 : {
107 : // We can claim to be black if all the things we reference are
108 : // effectively black already.
109 0 : return !mTypedHandler.HasEventHandler() ||
110 0 : !mTypedHandler.Ptr()->HasGrayCallable();
111 : }
112 :
113 : nsresult
114 15 : JSEventHandler::HandleEvent(nsIDOMEvent* aEvent)
115 : {
116 30 : nsCOMPtr<EventTarget> target = do_QueryInterface(mTarget);
117 60 : if (!target || !mTypedHandler.HasEventHandler() ||
118 60 : !GetTypedEventHandler().Ptr()->CallbackPreserveColor()) {
119 0 : return NS_ERROR_FAILURE;
120 : }
121 :
122 15 : Event* event = aEvent->InternalDOMEvent();
123 15 : bool isMainThread = event->IsMainThreadEvent();
124 : bool isChromeHandler =
125 45 : isMainThread ?
126 15 : nsContentUtils::ObjectPrincipal(
127 60 : GetTypedEventHandler().Ptr()->CallbackPreserveColor()) ==
128 15 : nsContentUtils::GetSystemPrincipal() :
129 15 : mozilla::dom::workers::IsCurrentThreadRunningChromeWorker();
130 :
131 15 : if (mTypedHandler.Type() == TypedEventHandler::eOnError) {
132 0 : MOZ_ASSERT_IF(mEventName, mEventName == nsGkAtoms::onerror);
133 :
134 0 : nsString errorMsg, file;
135 0 : EventOrString msgOrEvent;
136 0 : Optional<nsAString> fileName;
137 0 : Optional<uint32_t> lineNumber;
138 0 : Optional<uint32_t> columnNumber;
139 0 : Optional<JS::Handle<JS::Value>> error;
140 :
141 0 : NS_ENSURE_TRUE(aEvent, NS_ERROR_UNEXPECTED);
142 0 : ErrorEvent* scriptEvent = aEvent->InternalDOMEvent()->AsErrorEvent();
143 0 : if (scriptEvent) {
144 0 : scriptEvent->GetMessage(errorMsg);
145 0 : msgOrEvent.SetAsString().ShareOrDependUpon(errorMsg);
146 :
147 0 : scriptEvent->GetFilename(file);
148 0 : fileName = &file;
149 :
150 0 : lineNumber.Construct();
151 0 : lineNumber.Value() = scriptEvent->Lineno();
152 :
153 0 : columnNumber.Construct();
154 0 : columnNumber.Value() = scriptEvent->Colno();
155 :
156 0 : error.Construct(RootingCx());
157 0 : scriptEvent->GetError(&error.Value());
158 : } else {
159 0 : msgOrEvent.SetAsEvent() = aEvent->InternalDOMEvent();
160 : }
161 :
162 : RefPtr<OnErrorEventHandlerNonNull> handler =
163 0 : mTypedHandler.OnErrorEventHandler();
164 0 : ErrorResult rv;
165 0 : JS::Rooted<JS::Value> retval(RootingCx());
166 0 : handler->Call(mTarget, msgOrEvent, fileName, lineNumber,
167 0 : columnNumber, error, &retval, rv);
168 0 : if (rv.Failed()) {
169 0 : return rv.StealNSResult();
170 : }
171 :
172 0 : if (retval.isBoolean() &&
173 0 : retval.toBoolean() == bool(scriptEvent)) {
174 0 : event->PreventDefaultInternal(isChromeHandler);
175 : }
176 0 : return NS_OK;
177 : }
178 :
179 15 : if (mTypedHandler.Type() == TypedEventHandler::eOnBeforeUnload) {
180 0 : MOZ_ASSERT(mEventName == nsGkAtoms::onbeforeunload);
181 :
182 : RefPtr<OnBeforeUnloadEventHandlerNonNull> handler =
183 0 : mTypedHandler.OnBeforeUnloadEventHandler();
184 0 : ErrorResult rv;
185 0 : nsString retval;
186 0 : handler->Call(mTarget, *(aEvent->InternalDOMEvent()), retval, rv);
187 0 : if (rv.Failed()) {
188 0 : return rv.StealNSResult();
189 : }
190 :
191 0 : nsCOMPtr<nsIDOMBeforeUnloadEvent> beforeUnload = do_QueryInterface(aEvent);
192 0 : NS_ENSURE_STATE(beforeUnload);
193 :
194 0 : if (!DOMStringIsNull(retval)) {
195 0 : event->PreventDefaultInternal(isChromeHandler);
196 :
197 0 : nsAutoString text;
198 0 : beforeUnload->GetReturnValue(text);
199 :
200 : // Set the text in the beforeUnload event as long as it wasn't
201 : // already set (through event.returnValue, which takes
202 : // precedence over a value returned from a JS function in IE)
203 0 : if (text.IsEmpty()) {
204 0 : beforeUnload->SetReturnValue(retval);
205 : }
206 : }
207 :
208 0 : return NS_OK;
209 : }
210 :
211 15 : MOZ_ASSERT(mTypedHandler.Type() == TypedEventHandler::eNormal);
212 30 : ErrorResult rv;
213 30 : RefPtr<EventHandlerNonNull> handler = mTypedHandler.NormalEventHandler();
214 30 : JS::Rooted<JS::Value> retval(RootingCx());
215 15 : handler->Call(mTarget, *(aEvent->InternalDOMEvent()), &retval, rv);
216 15 : if (rv.Failed()) {
217 0 : return rv.StealNSResult();
218 : }
219 :
220 : // If the handler returned false, then prevent default.
221 15 : if (retval.isBoolean() && !retval.toBoolean()) {
222 0 : event->PreventDefaultInternal(isChromeHandler);
223 : }
224 :
225 15 : return NS_OK;
226 : }
227 :
228 : } // namespace mozilla
229 :
230 : using namespace mozilla;
231 :
232 : /*
233 : * Factory functions
234 : */
235 :
236 : nsresult
237 557 : NS_NewJSEventHandler(nsISupports* aTarget,
238 : nsIAtom* aEventType,
239 : const TypedEventHandler& aTypedHandler,
240 : JSEventHandler** aReturn)
241 : {
242 557 : NS_ENSURE_ARG(aEventType || !NS_IsMainThread());
243 : JSEventHandler* it =
244 557 : new JSEventHandler(aTarget, aEventType, aTypedHandler);
245 557 : NS_ADDREF(*aReturn = it);
246 :
247 557 : return NS_OK;
248 : }
|