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