|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "mozilla/ArrayUtils.h" |
|
7 |
|
8 #include "nsCOMPtr.h" |
|
9 #include "nsXBLPrototypeHandler.h" |
|
10 #include "nsXBLPrototypeBinding.h" |
|
11 #include "nsContentUtils.h" |
|
12 #include "nsCxPusher.h" |
|
13 #include "nsIContent.h" |
|
14 #include "nsIAtom.h" |
|
15 #include "nsIDOMKeyEvent.h" |
|
16 #include "nsIDOMMouseEvent.h" |
|
17 #include "nsNameSpaceManager.h" |
|
18 #include "nsIScriptContext.h" |
|
19 #include "nsIDocument.h" |
|
20 #include "nsIController.h" |
|
21 #include "nsIControllers.h" |
|
22 #include "nsIDOMXULElement.h" |
|
23 #include "nsIURI.h" |
|
24 #include "nsIDOMHTMLTextAreaElement.h" |
|
25 #include "nsIDOMHTMLInputElement.h" |
|
26 #include "nsFocusManager.h" |
|
27 #include "nsIDOMEventListener.h" |
|
28 #include "nsPIDOMWindow.h" |
|
29 #include "nsPIWindowRoot.h" |
|
30 #include "nsIDOMWindow.h" |
|
31 #include "nsIServiceManager.h" |
|
32 #include "nsIScriptError.h" |
|
33 #include "nsXPIDLString.h" |
|
34 #include "nsReadableUtils.h" |
|
35 #include "nsGkAtoms.h" |
|
36 #include "nsIXPConnect.h" |
|
37 #include "nsIDOMScriptObjectFactory.h" |
|
38 #include "nsDOMCID.h" |
|
39 #include "nsUnicharUtils.h" |
|
40 #include "nsCRT.h" |
|
41 #include "nsXBLEventHandler.h" |
|
42 #include "nsXBLSerialize.h" |
|
43 #include "nsJSUtils.h" |
|
44 #include "mozilla/BasicEvents.h" |
|
45 #include "mozilla/JSEventHandler.h" |
|
46 #include "mozilla/Preferences.h" |
|
47 #include "mozilla/dom/EventHandlerBinding.h" |
|
48 |
|
49 using namespace mozilla; |
|
50 using namespace mozilla::dom; |
|
51 |
|
52 uint32_t nsXBLPrototypeHandler::gRefCnt = 0; |
|
53 |
|
54 int32_t nsXBLPrototypeHandler::kMenuAccessKey = -1; |
|
55 int32_t nsXBLPrototypeHandler::kAccelKey = -1; |
|
56 |
|
57 const int32_t nsXBLPrototypeHandler::cShift = (1<<0); |
|
58 const int32_t nsXBLPrototypeHandler::cAlt = (1<<1); |
|
59 const int32_t nsXBLPrototypeHandler::cControl = (1<<2); |
|
60 const int32_t nsXBLPrototypeHandler::cMeta = (1<<3); |
|
61 const int32_t nsXBLPrototypeHandler::cOS = (1<<4); |
|
62 |
|
63 const int32_t nsXBLPrototypeHandler::cShiftMask = (1<<5); |
|
64 const int32_t nsXBLPrototypeHandler::cAltMask = (1<<6); |
|
65 const int32_t nsXBLPrototypeHandler::cControlMask = (1<<7); |
|
66 const int32_t nsXBLPrototypeHandler::cMetaMask = (1<<8); |
|
67 const int32_t nsXBLPrototypeHandler::cOSMask = (1<<9); |
|
68 |
|
69 const int32_t nsXBLPrototypeHandler::cAllModifiers = |
|
70 cShiftMask | cAltMask | cControlMask | cMetaMask | cOSMask; |
|
71 |
|
72 nsXBLPrototypeHandler::nsXBLPrototypeHandler(const char16_t* aEvent, |
|
73 const char16_t* aPhase, |
|
74 const char16_t* aAction, |
|
75 const char16_t* aCommand, |
|
76 const char16_t* aKeyCode, |
|
77 const char16_t* aCharCode, |
|
78 const char16_t* aModifiers, |
|
79 const char16_t* aButton, |
|
80 const char16_t* aClickCount, |
|
81 const char16_t* aGroup, |
|
82 const char16_t* aPreventDefault, |
|
83 const char16_t* aAllowUntrusted, |
|
84 nsXBLPrototypeBinding* aBinding, |
|
85 uint32_t aLineNumber) |
|
86 : mHandlerText(nullptr), |
|
87 mLineNumber(aLineNumber), |
|
88 mNextHandler(nullptr), |
|
89 mPrototypeBinding(aBinding) |
|
90 { |
|
91 Init(); |
|
92 |
|
93 ConstructPrototype(nullptr, aEvent, aPhase, aAction, aCommand, aKeyCode, |
|
94 aCharCode, aModifiers, aButton, aClickCount, |
|
95 aGroup, aPreventDefault, aAllowUntrusted); |
|
96 } |
|
97 |
|
98 nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsIContent* aHandlerElement) |
|
99 : mHandlerElement(nullptr), |
|
100 mLineNumber(0), |
|
101 mNextHandler(nullptr), |
|
102 mPrototypeBinding(nullptr) |
|
103 { |
|
104 Init(); |
|
105 |
|
106 // Make sure our prototype is initialized. |
|
107 ConstructPrototype(aHandlerElement); |
|
108 } |
|
109 |
|
110 nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsXBLPrototypeBinding* aBinding) |
|
111 : mHandlerText(nullptr), |
|
112 mLineNumber(0), |
|
113 mNextHandler(nullptr), |
|
114 mPrototypeBinding(aBinding) |
|
115 { |
|
116 Init(); |
|
117 } |
|
118 |
|
119 nsXBLPrototypeHandler::~nsXBLPrototypeHandler() |
|
120 { |
|
121 --gRefCnt; |
|
122 if (mType & NS_HANDLER_TYPE_XUL) { |
|
123 NS_IF_RELEASE(mHandlerElement); |
|
124 } else if (mHandlerText) { |
|
125 nsMemory::Free(mHandlerText); |
|
126 } |
|
127 |
|
128 // We own the next handler in the chain, so delete it now. |
|
129 NS_CONTENT_DELETE_LIST_MEMBER(nsXBLPrototypeHandler, this, mNextHandler); |
|
130 } |
|
131 |
|
132 already_AddRefed<nsIContent> |
|
133 nsXBLPrototypeHandler::GetHandlerElement() |
|
134 { |
|
135 if (mType & NS_HANDLER_TYPE_XUL) { |
|
136 nsCOMPtr<nsIContent> element = do_QueryReferent(mHandlerElement); |
|
137 return element.forget(); |
|
138 } |
|
139 |
|
140 return nullptr; |
|
141 } |
|
142 |
|
143 void |
|
144 nsXBLPrototypeHandler::AppendHandlerText(const nsAString& aText) |
|
145 { |
|
146 if (mHandlerText) { |
|
147 // Append our text to the existing text. |
|
148 char16_t* temp = mHandlerText; |
|
149 mHandlerText = ToNewUnicode(nsDependentString(temp) + aText); |
|
150 nsMemory::Free(temp); |
|
151 } |
|
152 else { |
|
153 mHandlerText = ToNewUnicode(aText); |
|
154 } |
|
155 } |
|
156 |
|
157 ///////////////////////////////////////////////////////////////////////////// |
|
158 // Get the menu access key from prefs. |
|
159 // XXX Eventually pick up using CSS3 key-equivalent property or somesuch |
|
160 void |
|
161 nsXBLPrototypeHandler::InitAccessKeys() |
|
162 { |
|
163 if (kAccelKey >= 0 && kMenuAccessKey >= 0) |
|
164 return; |
|
165 |
|
166 // Compiled-in defaults, in case we can't get the pref -- |
|
167 // mac doesn't have menu shortcuts, other platforms use alt. |
|
168 #ifdef XP_MACOSX |
|
169 kMenuAccessKey = 0; |
|
170 kAccelKey = nsIDOMKeyEvent::DOM_VK_META; |
|
171 #else |
|
172 kMenuAccessKey = nsIDOMKeyEvent::DOM_VK_ALT; |
|
173 kAccelKey = nsIDOMKeyEvent::DOM_VK_CONTROL; |
|
174 #endif |
|
175 |
|
176 // Get the menu access key value from prefs, overriding the default: |
|
177 kMenuAccessKey = |
|
178 Preferences::GetInt("ui.key.menuAccessKey", kMenuAccessKey); |
|
179 kAccelKey = Preferences::GetInt("ui.key.accelKey", kAccelKey); |
|
180 } |
|
181 |
|
182 nsresult |
|
183 nsXBLPrototypeHandler::ExecuteHandler(EventTarget* aTarget, |
|
184 nsIDOMEvent* aEvent) |
|
185 { |
|
186 nsresult rv = NS_ERROR_FAILURE; |
|
187 |
|
188 // Prevent default action? |
|
189 if (mType & NS_HANDLER_TYPE_PREVENTDEFAULT) { |
|
190 aEvent->PreventDefault(); |
|
191 // If we prevent default, then it's okay for |
|
192 // mHandlerElement and mHandlerText to be null |
|
193 rv = NS_OK; |
|
194 } |
|
195 |
|
196 if (!mHandlerElement) // This works for both types of handlers. In both cases, the union's var should be defined. |
|
197 return rv; |
|
198 |
|
199 // See if our event receiver is a content node (and not us). |
|
200 bool isXULKey = !!(mType & NS_HANDLER_TYPE_XUL); |
|
201 bool isXBLCommand = !!(mType & NS_HANDLER_TYPE_XBL_COMMAND); |
|
202 NS_ASSERTION(!(isXULKey && isXBLCommand), |
|
203 "can't be both a key and xbl command handler"); |
|
204 |
|
205 // XUL handlers and commands shouldn't be triggered by non-trusted |
|
206 // events. |
|
207 if (isXULKey || isXBLCommand) { |
|
208 bool trustedEvent = false; |
|
209 aEvent->GetIsTrusted(&trustedEvent); |
|
210 |
|
211 if (!trustedEvent) |
|
212 return NS_OK; |
|
213 } |
|
214 |
|
215 if (isXBLCommand) { |
|
216 return DispatchXBLCommand(aTarget, aEvent); |
|
217 } |
|
218 |
|
219 // If we're executing on a XUL key element, just dispatch a command |
|
220 // event at the element. It will take care of retargeting it to its |
|
221 // command element, if applicable, and executing the event handler. |
|
222 if (isXULKey) { |
|
223 return DispatchXULKeyCommand(aEvent); |
|
224 } |
|
225 |
|
226 // Look for a compiled handler on the element. |
|
227 // Should be compiled and bound with "on" in front of the name. |
|
228 nsCOMPtr<nsIAtom> onEventAtom = do_GetAtom(NS_LITERAL_STRING("onxbl") + |
|
229 nsDependentAtomString(mEventName)); |
|
230 |
|
231 // Compile the handler and bind it to the element. |
|
232 nsCOMPtr<nsIScriptGlobalObject> boundGlobal; |
|
233 nsCOMPtr<nsPIWindowRoot> winRoot(do_QueryInterface(aTarget)); |
|
234 nsCOMPtr<nsPIDOMWindow> window; |
|
235 |
|
236 if (winRoot) { |
|
237 window = winRoot->GetWindow(); |
|
238 } |
|
239 |
|
240 if (window) { |
|
241 window = window->GetCurrentInnerWindow(); |
|
242 NS_ENSURE_TRUE(window, NS_ERROR_UNEXPECTED); |
|
243 |
|
244 boundGlobal = do_QueryInterface(window->GetPrivateRoot()); |
|
245 } |
|
246 else boundGlobal = do_QueryInterface(aTarget); |
|
247 |
|
248 if (!boundGlobal) { |
|
249 nsCOMPtr<nsIDocument> boundDocument(do_QueryInterface(aTarget)); |
|
250 if (!boundDocument) { |
|
251 // We must be an element. |
|
252 nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget)); |
|
253 if (!content) |
|
254 return NS_OK; |
|
255 boundDocument = content->OwnerDoc(); |
|
256 } |
|
257 |
|
258 boundGlobal = do_QueryInterface(boundDocument->GetScopeObject()); |
|
259 } |
|
260 |
|
261 if (!boundGlobal) |
|
262 return NS_OK; |
|
263 |
|
264 nsIScriptContext *boundContext = boundGlobal->GetScriptContext(); |
|
265 if (!boundContext) |
|
266 return NS_OK; |
|
267 |
|
268 nsISupports *scriptTarget; |
|
269 |
|
270 if (winRoot) { |
|
271 scriptTarget = boundGlobal; |
|
272 } else { |
|
273 scriptTarget = aTarget; |
|
274 } |
|
275 |
|
276 // We're about to create a new JSEventHandler, which means that we're |
|
277 // responsible for pushing the context of the event target. See the similar |
|
278 // comment in nsEventManagerListener.cpp. |
|
279 nsCxPusher pusher; |
|
280 NS_ENSURE_STATE(pusher.Push(aTarget)); |
|
281 |
|
282 AutoPushJSContext cx(boundContext->GetNativeContext()); |
|
283 JS::Rooted<JSObject*> handler(cx); |
|
284 |
|
285 rv = EnsureEventHandler(boundGlobal, boundContext, onEventAtom, &handler); |
|
286 NS_ENSURE_SUCCESS(rv, rv); |
|
287 |
|
288 JS::Rooted<JSObject*> globalObject(cx, boundGlobal->GetGlobalJSObject()); |
|
289 JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); |
|
290 NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); |
|
291 |
|
292 // Bind it to the bound element. Note that if we're using a separate XBL scope, |
|
293 // we'll actually be binding the event handler to a cross-compartment wrapper |
|
294 // to the bound element's reflector. |
|
295 |
|
296 // First, enter our XBL scope. This is where the generic handler should have |
|
297 // been compiled, above. |
|
298 JSAutoCompartment ac(cx, scopeObject); |
|
299 JS::Rooted<JSObject*> genericHandler(cx, handler.get()); |
|
300 bool ok = JS_WrapObject(cx, &genericHandler); |
|
301 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); |
|
302 MOZ_ASSERT(!js::IsCrossCompartmentWrapper(genericHandler)); |
|
303 |
|
304 // Wrap the native into the XBL scope. This creates a reflector in the document |
|
305 // scope if one doesn't already exist, and potentially wraps it cross- |
|
306 // compartment into our scope (via aAllowWrapping=true). |
|
307 JS::Rooted<JS::Value> targetV(cx, JS::UndefinedValue()); |
|
308 rv = nsContentUtils::WrapNative(cx, scriptTarget, &targetV); |
|
309 NS_ENSURE_SUCCESS(rv, rv); |
|
310 |
|
311 // Next, clone the generic handler to be parented to the target. |
|
312 JS::Rooted<JSObject*> target(cx, &targetV.toObject()); |
|
313 JS::Rooted<JSObject*> bound(cx, JS_CloneFunctionObject(cx, genericHandler, target)); |
|
314 NS_ENSURE_TRUE(bound, NS_ERROR_FAILURE); |
|
315 |
|
316 // Now, wrap the bound handler into the content compartment and use it. |
|
317 JSAutoCompartment ac2(cx, globalObject); |
|
318 if (!JS_WrapObject(cx, &bound)) { |
|
319 return NS_ERROR_FAILURE; |
|
320 } |
|
321 |
|
322 nsRefPtr<EventHandlerNonNull> handlerCallback = |
|
323 new EventHandlerNonNull(bound, /* aIncumbentGlobal = */ nullptr); |
|
324 |
|
325 TypedEventHandler typedHandler(handlerCallback); |
|
326 |
|
327 // Execute it. |
|
328 nsCOMPtr<JSEventHandler> jsEventHandler; |
|
329 rv = NS_NewJSEventHandler(scriptTarget, onEventAtom, |
|
330 typedHandler, |
|
331 getter_AddRefs(jsEventHandler)); |
|
332 NS_ENSURE_SUCCESS(rv, rv); |
|
333 |
|
334 // Handle the event. |
|
335 jsEventHandler->HandleEvent(aEvent); |
|
336 jsEventHandler->Disconnect(); |
|
337 return NS_OK; |
|
338 } |
|
339 |
|
340 nsresult |
|
341 nsXBLPrototypeHandler::EnsureEventHandler(nsIScriptGlobalObject* aGlobal, |
|
342 nsIScriptContext *aBoundContext, |
|
343 nsIAtom *aName, |
|
344 JS::MutableHandle<JSObject*> aHandler) |
|
345 { |
|
346 AutoPushJSContext cx(aBoundContext->GetNativeContext()); |
|
347 |
|
348 // Check to see if we've already compiled this |
|
349 nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(aGlobal); |
|
350 if (pWindow) { |
|
351 JS::Rooted<JSObject*> cachedHandler(cx, pWindow->GetCachedXBLPrototypeHandler(this)); |
|
352 if (cachedHandler) { |
|
353 JS::ExposeObjectToActiveJS(cachedHandler); |
|
354 aHandler.set(cachedHandler); |
|
355 NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE); |
|
356 return NS_OK; |
|
357 } |
|
358 } |
|
359 |
|
360 // Ensure that we have something to compile |
|
361 nsDependentString handlerText(mHandlerText); |
|
362 NS_ENSURE_TRUE(!handlerText.IsEmpty(), NS_ERROR_FAILURE); |
|
363 |
|
364 JS::Rooted<JSObject*> globalObject(cx, aGlobal->GetGlobalJSObject()); |
|
365 JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); |
|
366 NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); |
|
367 |
|
368 nsAutoCString bindingURI; |
|
369 mPrototypeBinding->DocURI()->GetSpec(bindingURI); |
|
370 |
|
371 uint32_t argCount; |
|
372 const char **argNames; |
|
373 nsContentUtils::GetEventArgNames(kNameSpaceID_XBL, aName, &argCount, |
|
374 &argNames); |
|
375 |
|
376 // Compile the event handler in the xbl scope. |
|
377 JSAutoCompartment ac(cx, scopeObject); |
|
378 JS::CompileOptions options(cx); |
|
379 options.setFileAndLine(bindingURI.get(), mLineNumber) |
|
380 .setVersion(JSVERSION_LATEST); |
|
381 |
|
382 JS::Rooted<JSObject*> handlerFun(cx); |
|
383 nsresult rv = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, |
|
384 nsAtomCString(aName), argCount, |
|
385 argNames, handlerText, |
|
386 handlerFun.address()); |
|
387 NS_ENSURE_SUCCESS(rv, rv); |
|
388 NS_ENSURE_TRUE(handlerFun, NS_ERROR_FAILURE); |
|
389 |
|
390 // Wrap the handler into the content scope, since we're about to stash it |
|
391 // on the DOM window and such. |
|
392 JSAutoCompartment ac2(cx, globalObject); |
|
393 bool ok = JS_WrapObject(cx, &handlerFun); |
|
394 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); |
|
395 aHandler.set(handlerFun); |
|
396 NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE); |
|
397 |
|
398 if (pWindow) { |
|
399 pWindow->CacheXBLPrototypeHandler(this, aHandler); |
|
400 } |
|
401 |
|
402 return NS_OK; |
|
403 } |
|
404 |
|
405 nsresult |
|
406 nsXBLPrototypeHandler::DispatchXBLCommand(EventTarget* aTarget, nsIDOMEvent* aEvent) |
|
407 { |
|
408 // This is a special-case optimization to make command handling fast. |
|
409 // It isn't really a part of XBL, but it helps speed things up. |
|
410 |
|
411 if (aEvent) { |
|
412 // See if preventDefault has been set. If so, don't execute. |
|
413 bool preventDefault = false; |
|
414 aEvent->GetDefaultPrevented(&preventDefault); |
|
415 if (preventDefault) { |
|
416 return NS_OK; |
|
417 } |
|
418 bool dispatchStopped = aEvent->IsDispatchStopped(); |
|
419 if (dispatchStopped) { |
|
420 return NS_OK; |
|
421 } |
|
422 } |
|
423 |
|
424 // Instead of executing JS, let's get the controller for the bound |
|
425 // element and call doCommand on it. |
|
426 nsCOMPtr<nsIController> controller; |
|
427 |
|
428 nsCOMPtr<nsPIDOMWindow> privateWindow; |
|
429 nsCOMPtr<nsPIWindowRoot> windowRoot(do_QueryInterface(aTarget)); |
|
430 if (windowRoot) { |
|
431 privateWindow = windowRoot->GetWindow(); |
|
432 } |
|
433 else { |
|
434 privateWindow = do_QueryInterface(aTarget); |
|
435 if (!privateWindow) { |
|
436 nsCOMPtr<nsIContent> elt(do_QueryInterface(aTarget)); |
|
437 nsCOMPtr<nsIDocument> doc; |
|
438 // XXXbz sXBL/XBL2 issue -- this should be the "scope doc" or |
|
439 // something... whatever we use when wrapping DOM nodes |
|
440 // normally. It's not clear that the owner doc is the right |
|
441 // thing. |
|
442 if (elt) |
|
443 doc = elt->OwnerDoc(); |
|
444 |
|
445 if (!doc) |
|
446 doc = do_QueryInterface(aTarget); |
|
447 |
|
448 if (!doc) |
|
449 return NS_ERROR_FAILURE; |
|
450 |
|
451 privateWindow = doc->GetWindow(); |
|
452 if (!privateWindow) |
|
453 return NS_ERROR_FAILURE; |
|
454 } |
|
455 |
|
456 windowRoot = privateWindow->GetTopWindowRoot(); |
|
457 } |
|
458 |
|
459 NS_LossyConvertUTF16toASCII command(mHandlerText); |
|
460 if (windowRoot) |
|
461 windowRoot->GetControllerForCommand(command.get(), getter_AddRefs(controller)); |
|
462 else |
|
463 controller = GetController(aTarget); // We're attached to the receiver possibly. |
|
464 |
|
465 if (mEventName == nsGkAtoms::keypress && |
|
466 mDetail == nsIDOMKeyEvent::DOM_VK_SPACE && |
|
467 mMisc == 1) { |
|
468 // get the focused element so that we can pageDown only at |
|
469 // certain times. |
|
470 |
|
471 nsCOMPtr<nsPIDOMWindow> windowToCheck; |
|
472 if (windowRoot) |
|
473 windowToCheck = windowRoot->GetWindow(); |
|
474 else |
|
475 windowToCheck = privateWindow->GetPrivateRoot(); |
|
476 |
|
477 nsCOMPtr<nsIContent> focusedContent; |
|
478 if (windowToCheck) { |
|
479 nsCOMPtr<nsPIDOMWindow> focusedWindow; |
|
480 focusedContent = |
|
481 nsFocusManager::GetFocusedDescendant(windowToCheck, true, getter_AddRefs(focusedWindow)); |
|
482 } |
|
483 |
|
484 bool isLink = false; |
|
485 nsIContent *content = focusedContent; |
|
486 |
|
487 // if the focused element is a link then we do want space to |
|
488 // scroll down. The focused element may be an element in a link, |
|
489 // we need to check the parent node too. Only do this check if an |
|
490 // element is focused and has a parent. |
|
491 if (focusedContent && focusedContent->GetParent()) { |
|
492 while (content) { |
|
493 if (content->Tag() == nsGkAtoms::a && content->IsHTML()) { |
|
494 isLink = true; |
|
495 break; |
|
496 } |
|
497 |
|
498 if (content->HasAttr(kNameSpaceID_XLink, nsGkAtoms::type)) { |
|
499 isLink = content->AttrValueIs(kNameSpaceID_XLink, nsGkAtoms::type, |
|
500 nsGkAtoms::simple, eCaseMatters); |
|
501 |
|
502 if (isLink) { |
|
503 break; |
|
504 } |
|
505 } |
|
506 |
|
507 content = content->GetParent(); |
|
508 } |
|
509 |
|
510 if (!isLink) |
|
511 return NS_OK; |
|
512 } |
|
513 } |
|
514 |
|
515 // We are the default action for this command. |
|
516 // Stop any other default action from executing. |
|
517 aEvent->PreventDefault(); |
|
518 |
|
519 if (controller) |
|
520 controller->DoCommand(command.get()); |
|
521 |
|
522 return NS_OK; |
|
523 } |
|
524 |
|
525 nsresult |
|
526 nsXBLPrototypeHandler::DispatchXULKeyCommand(nsIDOMEvent* aEvent) |
|
527 { |
|
528 nsCOMPtr<nsIContent> handlerElement = GetHandlerElement(); |
|
529 NS_ENSURE_STATE(handlerElement); |
|
530 if (handlerElement->AttrValueIs(kNameSpaceID_None, |
|
531 nsGkAtoms::disabled, |
|
532 nsGkAtoms::_true, |
|
533 eCaseMatters)) { |
|
534 // Don't dispatch command events for disabled keys. |
|
535 return NS_OK; |
|
536 } |
|
537 |
|
538 aEvent->PreventDefault(); |
|
539 |
|
540 // Copy the modifiers from the key event. |
|
541 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent); |
|
542 if (!keyEvent) { |
|
543 NS_ERROR("Trying to execute a key handler for a non-key event!"); |
|
544 return NS_ERROR_FAILURE; |
|
545 } |
|
546 |
|
547 // XXX We should use mozilla::Modifiers for supporting all modifiers. |
|
548 |
|
549 bool isAlt = false; |
|
550 bool isControl = false; |
|
551 bool isShift = false; |
|
552 bool isMeta = false; |
|
553 keyEvent->GetAltKey(&isAlt); |
|
554 keyEvent->GetCtrlKey(&isControl); |
|
555 keyEvent->GetShiftKey(&isShift); |
|
556 keyEvent->GetMetaKey(&isMeta); |
|
557 |
|
558 nsContentUtils::DispatchXULCommand(handlerElement, true, |
|
559 nullptr, nullptr, |
|
560 isControl, isAlt, isShift, isMeta); |
|
561 return NS_OK; |
|
562 } |
|
563 |
|
564 already_AddRefed<nsIAtom> |
|
565 nsXBLPrototypeHandler::GetEventName() |
|
566 { |
|
567 nsCOMPtr<nsIAtom> eventName = mEventName; |
|
568 return eventName.forget(); |
|
569 } |
|
570 |
|
571 already_AddRefed<nsIController> |
|
572 nsXBLPrototypeHandler::GetController(EventTarget* aTarget) |
|
573 { |
|
574 // XXX Fix this so there's a generic interface that describes controllers, |
|
575 // This code should have no special knowledge of what objects might have controllers. |
|
576 nsCOMPtr<nsIControllers> controllers; |
|
577 |
|
578 nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(aTarget)); |
|
579 if (xulElement) |
|
580 xulElement->GetControllers(getter_AddRefs(controllers)); |
|
581 |
|
582 if (!controllers) { |
|
583 nsCOMPtr<nsIDOMHTMLTextAreaElement> htmlTextArea(do_QueryInterface(aTarget)); |
|
584 if (htmlTextArea) |
|
585 htmlTextArea->GetControllers(getter_AddRefs(controllers)); |
|
586 } |
|
587 |
|
588 if (!controllers) { |
|
589 nsCOMPtr<nsIDOMHTMLInputElement> htmlInputElement(do_QueryInterface(aTarget)); |
|
590 if (htmlInputElement) |
|
591 htmlInputElement->GetControllers(getter_AddRefs(controllers)); |
|
592 } |
|
593 |
|
594 if (!controllers) { |
|
595 nsCOMPtr<nsIDOMWindow> domWindow(do_QueryInterface(aTarget)); |
|
596 if (domWindow) |
|
597 domWindow->GetControllers(getter_AddRefs(controllers)); |
|
598 } |
|
599 |
|
600 // Return the first controller. |
|
601 // XXX This code should be checking the command name and using supportscommand and |
|
602 // iscommandenabled. |
|
603 nsCOMPtr<nsIController> controller; |
|
604 if (controllers) { |
|
605 controllers->GetControllerAt(0, getter_AddRefs(controller)); |
|
606 } |
|
607 |
|
608 return controller.forget(); |
|
609 } |
|
610 |
|
611 bool |
|
612 nsXBLPrototypeHandler::KeyEventMatched(nsIDOMKeyEvent* aKeyEvent, |
|
613 uint32_t aCharCode, |
|
614 bool aIgnoreShiftKey) |
|
615 { |
|
616 if (mDetail != -1) { |
|
617 // Get the keycode or charcode of the key event. |
|
618 uint32_t code; |
|
619 |
|
620 if (mMisc) { |
|
621 if (aCharCode) |
|
622 code = aCharCode; |
|
623 else |
|
624 aKeyEvent->GetCharCode(&code); |
|
625 if (IS_IN_BMP(code)) |
|
626 code = ToLowerCase(char16_t(code)); |
|
627 } |
|
628 else |
|
629 aKeyEvent->GetKeyCode(&code); |
|
630 |
|
631 if (code != uint32_t(mDetail)) |
|
632 return false; |
|
633 } |
|
634 |
|
635 return ModifiersMatchMask(aKeyEvent, aIgnoreShiftKey); |
|
636 } |
|
637 |
|
638 bool |
|
639 nsXBLPrototypeHandler::MouseEventMatched(nsIDOMMouseEvent* aMouseEvent) |
|
640 { |
|
641 if (mDetail == -1 && mMisc == 0 && (mKeyMask & cAllModifiers) == 0) |
|
642 return true; // No filters set up. It's generic. |
|
643 |
|
644 int16_t button; |
|
645 aMouseEvent->GetButton(&button); |
|
646 if (mDetail != -1 && (button != mDetail)) |
|
647 return false; |
|
648 |
|
649 int32_t clickcount; |
|
650 aMouseEvent->GetDetail(&clickcount); |
|
651 if (mMisc != 0 && (clickcount != mMisc)) |
|
652 return false; |
|
653 |
|
654 return ModifiersMatchMask(aMouseEvent); |
|
655 } |
|
656 |
|
657 struct keyCodeData { |
|
658 const char* str; |
|
659 size_t strlength; |
|
660 uint32_t keycode; |
|
661 }; |
|
662 |
|
663 // All of these must be uppercase, since the function below does |
|
664 // case-insensitive comparison by converting to uppercase. |
|
665 // XXX: be sure to check this periodically for new symbol additions! |
|
666 static const keyCodeData gKeyCodes[] = { |
|
667 |
|
668 #define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \ |
|
669 { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode } |
|
670 #include "mozilla/VirtualKeyCodeList.h" |
|
671 #undef NS_DEFINE_VK |
|
672 }; |
|
673 |
|
674 int32_t nsXBLPrototypeHandler::GetMatchingKeyCode(const nsAString& aKeyName) |
|
675 { |
|
676 nsAutoCString keyName; |
|
677 keyName.AssignWithConversion(aKeyName); |
|
678 ToUpperCase(keyName); // We want case-insensitive comparison with data |
|
679 // stored as uppercase. |
|
680 |
|
681 uint32_t keyNameLength = keyName.Length(); |
|
682 const char* keyNameStr = keyName.get(); |
|
683 for (uint16_t i = 0; i < (sizeof(gKeyCodes) / sizeof(gKeyCodes[0])); ++i) |
|
684 if (keyNameLength == gKeyCodes[i].strlength && |
|
685 !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) |
|
686 return gKeyCodes[i].keycode; |
|
687 |
|
688 return 0; |
|
689 } |
|
690 |
|
691 int32_t nsXBLPrototypeHandler::KeyToMask(int32_t key) |
|
692 { |
|
693 switch (key) |
|
694 { |
|
695 case nsIDOMKeyEvent::DOM_VK_META: |
|
696 return cMeta | cMetaMask; |
|
697 |
|
698 case nsIDOMKeyEvent::DOM_VK_WIN: |
|
699 return cOS | cOSMask; |
|
700 |
|
701 case nsIDOMKeyEvent::DOM_VK_ALT: |
|
702 return cAlt | cAltMask; |
|
703 |
|
704 case nsIDOMKeyEvent::DOM_VK_CONTROL: |
|
705 default: |
|
706 return cControl | cControlMask; |
|
707 } |
|
708 return cControl | cControlMask; // for warning avoidance |
|
709 } |
|
710 |
|
711 void |
|
712 nsXBLPrototypeHandler::GetEventType(nsAString& aEvent) |
|
713 { |
|
714 nsCOMPtr<nsIContent> handlerElement = GetHandlerElement(); |
|
715 if (!handlerElement) { |
|
716 aEvent.Truncate(); |
|
717 return; |
|
718 } |
|
719 handlerElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, aEvent); |
|
720 |
|
721 if (aEvent.IsEmpty() && (mType & NS_HANDLER_TYPE_XUL)) |
|
722 // If no type is specified for a XUL <key> element, let's assume that we're "keypress". |
|
723 aEvent.AssignLiteral("keypress"); |
|
724 } |
|
725 |
|
726 void |
|
727 nsXBLPrototypeHandler::ConstructPrototype(nsIContent* aKeyElement, |
|
728 const char16_t* aEvent, |
|
729 const char16_t* aPhase, |
|
730 const char16_t* aAction, |
|
731 const char16_t* aCommand, |
|
732 const char16_t* aKeyCode, |
|
733 const char16_t* aCharCode, |
|
734 const char16_t* aModifiers, |
|
735 const char16_t* aButton, |
|
736 const char16_t* aClickCount, |
|
737 const char16_t* aGroup, |
|
738 const char16_t* aPreventDefault, |
|
739 const char16_t* aAllowUntrusted) |
|
740 { |
|
741 mType = 0; |
|
742 |
|
743 if (aKeyElement) { |
|
744 mType |= NS_HANDLER_TYPE_XUL; |
|
745 nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(aKeyElement); |
|
746 if (!weak) { |
|
747 return; |
|
748 } |
|
749 weak.swap(mHandlerElement); |
|
750 } |
|
751 else { |
|
752 mType |= aCommand ? NS_HANDLER_TYPE_XBL_COMMAND : NS_HANDLER_TYPE_XBL_JS; |
|
753 mHandlerText = nullptr; |
|
754 } |
|
755 |
|
756 mDetail = -1; |
|
757 mMisc = 0; |
|
758 mKeyMask = 0; |
|
759 mPhase = NS_PHASE_BUBBLING; |
|
760 |
|
761 if (aAction) |
|
762 mHandlerText = ToNewUnicode(nsDependentString(aAction)); |
|
763 else if (aCommand) |
|
764 mHandlerText = ToNewUnicode(nsDependentString(aCommand)); |
|
765 |
|
766 nsAutoString event(aEvent); |
|
767 if (event.IsEmpty()) { |
|
768 if (mType & NS_HANDLER_TYPE_XUL) |
|
769 GetEventType(event); |
|
770 if (event.IsEmpty()) |
|
771 return; |
|
772 } |
|
773 |
|
774 mEventName = do_GetAtom(event); |
|
775 |
|
776 if (aPhase) { |
|
777 const nsDependentString phase(aPhase); |
|
778 if (phase.EqualsLiteral("capturing")) |
|
779 mPhase = NS_PHASE_CAPTURING; |
|
780 else if (phase.EqualsLiteral("target")) |
|
781 mPhase = NS_PHASE_TARGET; |
|
782 } |
|
783 |
|
784 // Button and clickcount apply only to XBL handlers and don't apply to XUL key |
|
785 // handlers. |
|
786 if (aButton && *aButton) |
|
787 mDetail = *aButton - '0'; |
|
788 |
|
789 if (aClickCount && *aClickCount) |
|
790 mMisc = *aClickCount - '0'; |
|
791 |
|
792 // Modifiers are supported by both types of handlers (XUL and XBL). |
|
793 nsAutoString modifiers(aModifiers); |
|
794 if (mType & NS_HANDLER_TYPE_XUL) |
|
795 aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers); |
|
796 |
|
797 if (!modifiers.IsEmpty()) { |
|
798 mKeyMask = cAllModifiers; |
|
799 char* str = ToNewCString(modifiers); |
|
800 char* newStr; |
|
801 char* token = nsCRT::strtok( str, ", \t", &newStr ); |
|
802 while( token != nullptr ) { |
|
803 if (PL_strcmp(token, "shift") == 0) |
|
804 mKeyMask |= cShift | cShiftMask; |
|
805 else if (PL_strcmp(token, "alt") == 0) |
|
806 mKeyMask |= cAlt | cAltMask; |
|
807 else if (PL_strcmp(token, "meta") == 0) |
|
808 mKeyMask |= cMeta | cMetaMask; |
|
809 else if (PL_strcmp(token, "os") == 0) |
|
810 mKeyMask |= cOS | cOSMask; |
|
811 else if (PL_strcmp(token, "control") == 0) |
|
812 mKeyMask |= cControl | cControlMask; |
|
813 else if (PL_strcmp(token, "accel") == 0) |
|
814 mKeyMask |= KeyToMask(kAccelKey); |
|
815 else if (PL_strcmp(token, "access") == 0) |
|
816 mKeyMask |= KeyToMask(kMenuAccessKey); |
|
817 else if (PL_strcmp(token, "any") == 0) |
|
818 mKeyMask &= ~(mKeyMask << 5); |
|
819 |
|
820 token = nsCRT::strtok( newStr, ", \t", &newStr ); |
|
821 } |
|
822 |
|
823 nsMemory::Free(str); |
|
824 } |
|
825 |
|
826 nsAutoString key(aCharCode); |
|
827 if (key.IsEmpty()) { |
|
828 if (mType & NS_HANDLER_TYPE_XUL) { |
|
829 aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key); |
|
830 if (key.IsEmpty()) |
|
831 aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, key); |
|
832 } |
|
833 } |
|
834 |
|
835 if (!key.IsEmpty()) { |
|
836 if (mKeyMask == 0) |
|
837 mKeyMask = cAllModifiers; |
|
838 ToLowerCase(key); |
|
839 |
|
840 // We have a charcode. |
|
841 mMisc = 1; |
|
842 mDetail = key[0]; |
|
843 const uint8_t GTK2Modifiers = cShift | cControl | cShiftMask | cControlMask; |
|
844 if ((mKeyMask & GTK2Modifiers) == GTK2Modifiers && |
|
845 modifiers.First() != char16_t(',') && |
|
846 (mDetail == 'u' || mDetail == 'U')) |
|
847 ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "GTK2Conflict"); |
|
848 const uint8_t WinModifiers = cControl | cAlt | cControlMask | cAltMask; |
|
849 if ((mKeyMask & WinModifiers) == WinModifiers && |
|
850 modifiers.First() != char16_t(',') && |
|
851 (('A' <= mDetail && mDetail <= 'Z') || |
|
852 ('a' <= mDetail && mDetail <= 'z'))) |
|
853 ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "WinConflict"); |
|
854 } |
|
855 else { |
|
856 key.Assign(aKeyCode); |
|
857 if (mType & NS_HANDLER_TYPE_XUL) |
|
858 aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, key); |
|
859 |
|
860 if (!key.IsEmpty()) { |
|
861 if (mKeyMask == 0) |
|
862 mKeyMask = cAllModifiers; |
|
863 mDetail = GetMatchingKeyCode(key); |
|
864 } |
|
865 } |
|
866 |
|
867 if (aGroup && nsDependentString(aGroup).EqualsLiteral("system")) |
|
868 mType |= NS_HANDLER_TYPE_SYSTEM; |
|
869 |
|
870 if (aPreventDefault && |
|
871 nsDependentString(aPreventDefault).EqualsLiteral("true")) |
|
872 mType |= NS_HANDLER_TYPE_PREVENTDEFAULT; |
|
873 |
|
874 if (aAllowUntrusted) { |
|
875 mType |= NS_HANDLER_HAS_ALLOW_UNTRUSTED_ATTR; |
|
876 if (nsDependentString(aAllowUntrusted).EqualsLiteral("true")) { |
|
877 mType |= NS_HANDLER_ALLOW_UNTRUSTED; |
|
878 } else { |
|
879 mType &= ~NS_HANDLER_ALLOW_UNTRUSTED; |
|
880 } |
|
881 } |
|
882 } |
|
883 |
|
884 void |
|
885 nsXBLPrototypeHandler::ReportKeyConflict(const char16_t* aKey, const char16_t* aModifiers, nsIContent* aKeyElement, const char *aMessageName) |
|
886 { |
|
887 nsCOMPtr<nsIDocument> doc; |
|
888 if (mPrototypeBinding) { |
|
889 nsXBLDocumentInfo* docInfo = mPrototypeBinding->XBLDocumentInfo(); |
|
890 if (docInfo) { |
|
891 doc = docInfo->GetDocument(); |
|
892 } |
|
893 } else if (aKeyElement) { |
|
894 doc = aKeyElement->OwnerDoc(); |
|
895 } |
|
896 |
|
897 const char16_t* params[] = { aKey, aModifiers }; |
|
898 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, |
|
899 NS_LITERAL_CSTRING("XBL Prototype Handler"), doc, |
|
900 nsContentUtils::eXBL_PROPERTIES, |
|
901 aMessageName, |
|
902 params, ArrayLength(params), |
|
903 nullptr, EmptyString(), mLineNumber); |
|
904 } |
|
905 |
|
906 bool |
|
907 nsXBLPrototypeHandler::ModifiersMatchMask(nsIDOMUIEvent* aEvent, |
|
908 bool aIgnoreShiftKey) |
|
909 { |
|
910 WidgetInputEvent* inputEvent = aEvent->GetInternalNSEvent()->AsInputEvent(); |
|
911 NS_ENSURE_TRUE(inputEvent, false); |
|
912 |
|
913 if (mKeyMask & cMetaMask) { |
|
914 if (inputEvent->IsMeta() != ((mKeyMask & cMeta) != 0)) { |
|
915 return false; |
|
916 } |
|
917 } |
|
918 |
|
919 if (mKeyMask & cOSMask) { |
|
920 if (inputEvent->IsOS() != ((mKeyMask & cOS) != 0)) { |
|
921 return false; |
|
922 } |
|
923 } |
|
924 |
|
925 if (mKeyMask & cShiftMask && !aIgnoreShiftKey) { |
|
926 if (inputEvent->IsShift() != ((mKeyMask & cShift) != 0)) { |
|
927 return false; |
|
928 } |
|
929 } |
|
930 |
|
931 if (mKeyMask & cAltMask) { |
|
932 if (inputEvent->IsAlt() != ((mKeyMask & cAlt) != 0)) { |
|
933 return false; |
|
934 } |
|
935 } |
|
936 |
|
937 if (mKeyMask & cControlMask) { |
|
938 if (inputEvent->IsControl() != ((mKeyMask & cControl) != 0)) { |
|
939 return false; |
|
940 } |
|
941 } |
|
942 |
|
943 return true; |
|
944 } |
|
945 |
|
946 nsresult |
|
947 nsXBLPrototypeHandler::Read(nsIObjectInputStream* aStream) |
|
948 { |
|
949 AssertInCompilationScope(); |
|
950 nsresult rv = aStream->Read8(&mPhase); |
|
951 NS_ENSURE_SUCCESS(rv, rv); |
|
952 rv = aStream->Read8(&mType); |
|
953 NS_ENSURE_SUCCESS(rv, rv); |
|
954 rv = aStream->Read8(&mMisc); |
|
955 NS_ENSURE_SUCCESS(rv, rv); |
|
956 |
|
957 rv = aStream->Read32(reinterpret_cast<uint32_t*>(&mKeyMask)); |
|
958 NS_ENSURE_SUCCESS(rv, rv); |
|
959 uint32_t detail; |
|
960 rv = aStream->Read32(&detail); |
|
961 NS_ENSURE_SUCCESS(rv, rv); |
|
962 mDetail = detail; |
|
963 |
|
964 nsAutoString name; |
|
965 rv = aStream->ReadString(name); |
|
966 NS_ENSURE_SUCCESS(rv, rv); |
|
967 mEventName = do_GetAtom(name); |
|
968 |
|
969 rv = aStream->Read32(&mLineNumber); |
|
970 NS_ENSURE_SUCCESS(rv, rv); |
|
971 |
|
972 nsAutoString handlerText; |
|
973 rv = aStream->ReadString(handlerText); |
|
974 NS_ENSURE_SUCCESS(rv, rv); |
|
975 if (!handlerText.IsEmpty()) |
|
976 mHandlerText = ToNewUnicode(handlerText); |
|
977 |
|
978 return NS_OK; |
|
979 } |
|
980 |
|
981 nsresult |
|
982 nsXBLPrototypeHandler::Write(nsIObjectOutputStream* aStream) |
|
983 { |
|
984 AssertInCompilationScope(); |
|
985 // Make sure we don't write out NS_HANDLER_TYPE_XUL types, as they are used |
|
986 // for <keyset> elements. |
|
987 if ((mType & NS_HANDLER_TYPE_XUL) || !mEventName) |
|
988 return NS_OK; |
|
989 |
|
990 XBLBindingSerializeDetails type = XBLBinding_Serialize_Handler; |
|
991 |
|
992 nsresult rv = aStream->Write8(type); |
|
993 rv = aStream->Write8(mPhase); |
|
994 NS_ENSURE_SUCCESS(rv, rv); |
|
995 rv = aStream->Write8(mType); |
|
996 NS_ENSURE_SUCCESS(rv, rv); |
|
997 rv = aStream->Write8(mMisc); |
|
998 NS_ENSURE_SUCCESS(rv, rv); |
|
999 rv = aStream->Write32(static_cast<uint32_t>(mKeyMask)); |
|
1000 NS_ENSURE_SUCCESS(rv, rv); |
|
1001 rv = aStream->Write32(mDetail); |
|
1002 NS_ENSURE_SUCCESS(rv, rv); |
|
1003 |
|
1004 rv = aStream->WriteWStringZ(nsDependentAtomString(mEventName).get()); |
|
1005 NS_ENSURE_SUCCESS(rv, rv); |
|
1006 |
|
1007 rv = aStream->Write32(mLineNumber); |
|
1008 NS_ENSURE_SUCCESS(rv, rv); |
|
1009 return aStream->WriteWStringZ(mHandlerText ? mHandlerText : MOZ_UTF16("")); |
|
1010 } |