Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 sw=2 et 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 :
7 : /*
8 :
9 : This file provides the implementation for the XUL Command Dispatcher.
10 :
11 : */
12 :
13 : #include "nsIContent.h"
14 : #include "nsFocusManager.h"
15 : #include "nsIControllers.h"
16 : #include "nsIDOMDocument.h"
17 : #include "nsIDOMElement.h"
18 : #include "nsIDOMWindow.h"
19 : #include "nsIDOMXULElement.h"
20 : #include "nsIDocument.h"
21 : #include "nsPresContext.h"
22 : #include "nsIPresShell.h"
23 : #include "nsIScriptGlobalObject.h"
24 : #include "nsPIDOMWindow.h"
25 : #include "nsPIWindowRoot.h"
26 : #include "nsRDFCID.h"
27 : #include "nsXULCommandDispatcher.h"
28 : #include "mozilla/Logging.h"
29 : #include "nsContentUtils.h"
30 : #include "nsReadableUtils.h"
31 : #include "nsCRT.h"
32 : #include "nsError.h"
33 : #include "mozilla/BasicEvents.h"
34 : #include "mozilla/EventDispatcher.h"
35 : #include "mozilla/dom/Element.h"
36 :
37 : using namespace mozilla;
38 :
39 : static LazyLogModule gCommandLog("nsXULCommandDispatcher");
40 :
41 : ////////////////////////////////////////////////////////////////////////
42 :
43 1 : nsXULCommandDispatcher::nsXULCommandDispatcher(nsIDocument* aDocument)
44 1 : : mDocument(aDocument), mUpdaters(nullptr), mLocked(false)
45 : {
46 1 : }
47 :
48 0 : nsXULCommandDispatcher::~nsXULCommandDispatcher()
49 : {
50 0 : Disconnect();
51 0 : }
52 :
53 : // QueryInterface implementation for nsXULCommandDispatcher
54 :
55 56 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULCommandDispatcher)
56 55 : NS_INTERFACE_MAP_ENTRY(nsIDOMXULCommandDispatcher)
57 43 : NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
58 43 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMXULCommandDispatcher)
59 30 : NS_INTERFACE_MAP_END
60 :
61 37 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULCommandDispatcher)
62 34 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULCommandDispatcher)
63 :
64 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULCommandDispatcher)
65 :
66 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULCommandDispatcher)
67 0 : tmp->Disconnect();
68 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
69 :
70 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULCommandDispatcher)
71 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
72 0 : Updater* updater = tmp->mUpdaters;
73 0 : while (updater) {
74 0 : cb.NoteXPCOMChild(updater->mElement);
75 0 : updater = updater->mNext;
76 : }
77 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
78 :
79 : void
80 0 : nsXULCommandDispatcher::Disconnect()
81 : {
82 0 : while (mUpdaters) {
83 0 : Updater* doomed = mUpdaters;
84 0 : mUpdaters = mUpdaters->mNext;
85 0 : delete doomed;
86 : }
87 0 : mDocument = nullptr;
88 0 : }
89 :
90 : already_AddRefed<nsPIWindowRoot>
91 11 : nsXULCommandDispatcher::GetWindowRoot()
92 : {
93 11 : if (mDocument) {
94 11 : if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
95 11 : return window->GetTopWindowRoot();
96 : }
97 : }
98 :
99 0 : return nullptr;
100 : }
101 :
102 : nsIContent*
103 5 : nsXULCommandDispatcher::GetRootFocusedContentAndWindow(nsPIDOMWindowOuter** aWindow)
104 : {
105 5 : *aWindow = nullptr;
106 :
107 5 : if (!mDocument) {
108 0 : return nullptr;
109 : }
110 :
111 5 : if (nsCOMPtr<nsPIDOMWindowOuter> win = mDocument->GetWindow()) {
112 5 : if (nsCOMPtr<nsPIDOMWindowOuter> rootWindow = win->GetPrivateRoot()) {
113 5 : return nsFocusManager::GetFocusedDescendant(rootWindow, true, aWindow);
114 : }
115 : }
116 :
117 0 : return nullptr;
118 : }
119 :
120 : NS_IMETHODIMP
121 5 : nsXULCommandDispatcher::GetFocusedElement(nsIDOMElement** aElement)
122 : {
123 5 : *aElement = nullptr;
124 :
125 10 : nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
126 : nsIContent* focusedContent =
127 5 : GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
128 5 : if (focusedContent) {
129 5 : CallQueryInterface(focusedContent, aElement);
130 :
131 : // Make sure the caller can access the focused element.
132 10 : nsCOMPtr<nsINode> node = do_QueryInterface(*aElement);
133 5 : if (!node || !nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->Subsumes(node->NodePrincipal())) {
134 : // XXX This might want to return null, but we use that return value
135 : // to mean "there is no focused element," so to be clear, throw an
136 : // exception.
137 0 : NS_RELEASE(*aElement);
138 0 : return NS_ERROR_DOM_SECURITY_ERR;
139 : }
140 : }
141 :
142 5 : return NS_OK;
143 : }
144 :
145 : NS_IMETHODIMP
146 0 : nsXULCommandDispatcher::GetFocusedWindow(mozIDOMWindowProxy** aWindow)
147 : {
148 0 : *aWindow = nullptr;
149 :
150 0 : nsCOMPtr<nsPIDOMWindowOuter> window;
151 0 : GetRootFocusedContentAndWindow(getter_AddRefs(window));
152 0 : if (!window)
153 0 : return NS_OK;
154 :
155 : // Make sure the caller can access this window. The caller can access this
156 : // window iff it can access the document.
157 0 : nsCOMPtr<nsIDocument> doc = window->GetDoc();
158 :
159 : // Note: If there is no document, then this window has been cleared and
160 : // there's nothing left to protect, so let the window pass through.
161 0 : if (doc && !nsContentUtils::CanCallerAccess(doc))
162 0 : return NS_ERROR_DOM_SECURITY_ERR;
163 :
164 0 : window.forget(aWindow);
165 0 : return NS_OK;
166 : }
167 :
168 : NS_IMETHODIMP
169 0 : nsXULCommandDispatcher::SetFocusedElement(nsIDOMElement* aElement)
170 : {
171 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
172 0 : NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
173 :
174 0 : if (aElement)
175 0 : return fm->SetFocus(aElement, 0);
176 :
177 : // if aElement is null, clear the focus in the currently focused child window
178 0 : nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
179 0 : GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
180 0 : return fm->ClearFocus(focusedWindow);
181 : }
182 :
183 : NS_IMETHODIMP
184 0 : nsXULCommandDispatcher::SetFocusedWindow(mozIDOMWindowProxy* aWindow)
185 : {
186 0 : NS_ENSURE_TRUE(aWindow, NS_OK); // do nothing if set to null
187 :
188 0 : nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
189 0 : NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
190 :
191 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
192 0 : NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
193 :
194 : // get the containing frame for the window, and set it as focused. This will
195 : // end up focusing whatever is currently focused inside the frame. Since
196 : // setting the command dispatcher's focused window doesn't raise the window,
197 : // setting it to a top-level window doesn't need to do anything.
198 : nsCOMPtr<nsIDOMElement> frameElement =
199 0 : do_QueryInterface(window->GetFrameElementInternal());
200 0 : if (frameElement)
201 0 : return fm->SetFocus(frameElement, 0);
202 :
203 0 : return NS_OK;
204 : }
205 :
206 : NS_IMETHODIMP
207 0 : nsXULCommandDispatcher::AdvanceFocus()
208 : {
209 0 : return AdvanceFocusIntoSubtree(nullptr);
210 : }
211 :
212 : NS_IMETHODIMP
213 0 : nsXULCommandDispatcher::RewindFocus()
214 : {
215 0 : nsCOMPtr<nsPIDOMWindowOuter> win;
216 0 : GetRootFocusedContentAndWindow(getter_AddRefs(win));
217 :
218 0 : nsCOMPtr<nsIDOMElement> result;
219 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
220 0 : if (fm)
221 0 : return fm->MoveFocus(win, nullptr, nsIFocusManager::MOVEFOCUS_BACKWARD,
222 0 : 0, getter_AddRefs(result));
223 0 : return NS_OK;
224 : }
225 :
226 : NS_IMETHODIMP
227 0 : nsXULCommandDispatcher::AdvanceFocusIntoSubtree(nsIDOMElement* aElt)
228 : {
229 0 : nsCOMPtr<nsPIDOMWindowOuter> win;
230 0 : GetRootFocusedContentAndWindow(getter_AddRefs(win));
231 :
232 0 : nsCOMPtr<nsIDOMElement> result;
233 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
234 0 : if (fm)
235 0 : return fm->MoveFocus(win, aElt, nsIFocusManager::MOVEFOCUS_FORWARD,
236 0 : 0, getter_AddRefs(result));
237 0 : return NS_OK;
238 : }
239 :
240 : NS_IMETHODIMP
241 4 : nsXULCommandDispatcher::AddCommandUpdater(nsIDOMElement* aElement,
242 : const nsAString& aEvents,
243 : const nsAString& aTargets)
244 : {
245 4 : NS_PRECONDITION(aElement != nullptr, "null ptr");
246 4 : if (! aElement)
247 0 : return NS_ERROR_NULL_POINTER;
248 :
249 4 : NS_ENSURE_TRUE(mDocument, NS_ERROR_UNEXPECTED);
250 :
251 4 : nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, aElement);
252 :
253 4 : if (NS_FAILED(rv)) {
254 0 : return rv;
255 : }
256 :
257 4 : Updater* updater = mUpdaters;
258 4 : Updater** link = &mUpdaters;
259 :
260 16 : while (updater) {
261 6 : if (updater->mElement == aElement) {
262 :
263 : #ifdef DEBUG
264 0 : if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
265 0 : nsAutoCString eventsC, targetsC, aeventsC, atargetsC;
266 0 : eventsC.AssignWithConversion(updater->mEvents);
267 0 : targetsC.AssignWithConversion(updater->mTargets);
268 0 : CopyUTF16toUTF8(aEvents, aeventsC);
269 0 : CopyUTF16toUTF8(aTargets, atargetsC);
270 0 : MOZ_LOG(gCommandLog, LogLevel::Debug,
271 : ("xulcmd[%p] replace %p(events=%s targets=%s) with (events=%s targets=%s)",
272 : this, aElement,
273 : eventsC.get(),
274 : targetsC.get(),
275 : aeventsC.get(),
276 : atargetsC.get()));
277 : }
278 : #endif
279 :
280 : // If the updater was already in the list, then replace
281 : // (?) the 'events' and 'targets' filters with the new
282 : // specification.
283 0 : updater->mEvents = aEvents;
284 0 : updater->mTargets = aTargets;
285 0 : return NS_OK;
286 : }
287 :
288 6 : link = &(updater->mNext);
289 6 : updater = updater->mNext;
290 : }
291 : #ifdef DEBUG
292 4 : if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
293 0 : nsAutoCString aeventsC, atargetsC;
294 0 : CopyUTF16toUTF8(aEvents, aeventsC);
295 0 : CopyUTF16toUTF8(aTargets, atargetsC);
296 :
297 0 : MOZ_LOG(gCommandLog, LogLevel::Debug,
298 : ("xulcmd[%p] add %p(events=%s targets=%s)",
299 : this, aElement,
300 : aeventsC.get(),
301 : atargetsC.get()));
302 : }
303 : #endif
304 :
305 : // If we get here, this is a new updater. Append it to the list.
306 4 : *link = new Updater(aElement, aEvents, aTargets);
307 4 : return NS_OK;
308 : }
309 :
310 : NS_IMETHODIMP
311 0 : nsXULCommandDispatcher::RemoveCommandUpdater(nsIDOMElement* aElement)
312 : {
313 0 : NS_PRECONDITION(aElement != nullptr, "null ptr");
314 0 : if (! aElement)
315 0 : return NS_ERROR_NULL_POINTER;
316 :
317 0 : Updater* updater = mUpdaters;
318 0 : Updater** link = &mUpdaters;
319 :
320 0 : while (updater) {
321 0 : if (updater->mElement == aElement) {
322 : #ifdef DEBUG
323 0 : if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
324 0 : nsAutoCString eventsC, targetsC;
325 0 : eventsC.AssignWithConversion(updater->mEvents);
326 0 : targetsC.AssignWithConversion(updater->mTargets);
327 0 : MOZ_LOG(gCommandLog, LogLevel::Debug,
328 : ("xulcmd[%p] remove %p(events=%s targets=%s)",
329 : this, aElement,
330 : eventsC.get(),
331 : targetsC.get()));
332 : }
333 : #endif
334 :
335 0 : *link = updater->mNext;
336 0 : delete updater;
337 0 : return NS_OK;
338 : }
339 :
340 0 : link = &(updater->mNext);
341 0 : updater = updater->mNext;
342 : }
343 :
344 : // Hmm. Not found. Oh well.
345 0 : return NS_OK;
346 : }
347 :
348 : NS_IMETHODIMP
349 3 : nsXULCommandDispatcher::UpdateCommands(const nsAString& aEventName)
350 : {
351 3 : if (mLocked) {
352 0 : if (!mPendingUpdates.Contains(aEventName)) {
353 0 : mPendingUpdates.AppendElement(aEventName);
354 : }
355 :
356 0 : return NS_OK;
357 : }
358 :
359 6 : nsAutoString id;
360 6 : nsCOMPtr<nsIDOMElement> element;
361 3 : GetFocusedElement(getter_AddRefs(element));
362 3 : if (element) {
363 3 : nsresult rv = element->GetAttribute(NS_LITERAL_STRING("id"), id);
364 3 : NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get element's id");
365 3 : if (NS_FAILED(rv)) return rv;
366 : }
367 :
368 6 : nsCOMArray<nsIContent> updaters;
369 :
370 15 : for (Updater* updater = mUpdaters; updater != nullptr; updater = updater->mNext) {
371 : // Skip any nodes that don't match our 'events' or 'targets'
372 : // filters.
373 12 : if (! Matches(updater->mEvents, aEventName))
374 12 : continue;
375 :
376 6 : if (! Matches(updater->mTargets, id))
377 0 : continue;
378 :
379 12 : nsCOMPtr<nsIContent> content = do_QueryInterface(updater->mElement);
380 6 : NS_ASSERTION(content != nullptr, "not an nsIContent");
381 6 : if (! content)
382 0 : return NS_ERROR_UNEXPECTED;
383 :
384 6 : updaters.AppendObject(content);
385 : }
386 :
387 9 : for (int32_t u = 0; u < updaters.Count(); u++) {
388 6 : nsIContent* content = updaters[u];
389 :
390 : #ifdef DEBUG
391 6 : if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
392 0 : nsAutoCString aeventnameC;
393 0 : CopyUTF16toUTF8(aEventName, aeventnameC);
394 0 : MOZ_LOG(gCommandLog, LogLevel::Debug,
395 : ("xulcmd[%p] update %p event=%s",
396 : this, content,
397 : aeventnameC.get()));
398 : }
399 : #endif
400 :
401 12 : WidgetEvent event(true, eXULCommandUpdate);
402 6 : EventDispatcher::Dispatch(content, nullptr, &event);
403 : }
404 3 : return NS_OK;
405 : }
406 :
407 : bool
408 18 : nsXULCommandDispatcher::Matches(const nsString& aList,
409 : const nsAString& aElement)
410 : {
411 18 : if (aList.EqualsLiteral("*"))
412 6 : return true; // match _everything_!
413 :
414 12 : int32_t indx = aList.Find(PromiseFlatString(aElement));
415 12 : if (indx == -1)
416 6 : return false; // not in the list at all
417 :
418 : // okay, now make sure it's not a substring snafu; e.g., 'ur'
419 : // found inside of 'blur'.
420 6 : if (indx > 0) {
421 0 : char16_t ch = aList[indx - 1];
422 0 : if (! nsCRT::IsAsciiSpace(ch) && ch != char16_t(','))
423 0 : return false;
424 : }
425 :
426 6 : if (indx + aElement.Length() < aList.Length()) {
427 6 : char16_t ch = aList[indx + aElement.Length()];
428 6 : if (! nsCRT::IsAsciiSpace(ch) && ch != char16_t(','))
429 0 : return false;
430 : }
431 :
432 6 : return true;
433 : }
434 :
435 : NS_IMETHODIMP
436 0 : nsXULCommandDispatcher::GetControllers(nsIControllers** aResult)
437 : {
438 0 : nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
439 0 : NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
440 :
441 0 : return root->GetControllers(aResult);
442 : }
443 :
444 : NS_IMETHODIMP
445 11 : nsXULCommandDispatcher::GetControllerForCommand(const char *aCommand, nsIController** _retval)
446 : {
447 22 : nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
448 11 : NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
449 :
450 11 : return root->GetControllerForCommand(aCommand, _retval);
451 : }
452 :
453 : NS_IMETHODIMP
454 0 : nsXULCommandDispatcher::GetSuppressFocusScroll(bool* aSuppressFocusScroll)
455 : {
456 0 : *aSuppressFocusScroll = false;
457 0 : return NS_OK;
458 : }
459 :
460 : NS_IMETHODIMP
461 0 : nsXULCommandDispatcher::SetSuppressFocusScroll(bool aSuppressFocusScroll)
462 : {
463 0 : return NS_OK;
464 : }
465 :
466 : NS_IMETHODIMP
467 0 : nsXULCommandDispatcher::Lock()
468 : {
469 : // Since locking is used only as a performance optimization, we don't worry
470 : // about nested lock calls. If that does happen, it just means we will unlock
471 : // and process updates earlier.
472 0 : mLocked = true;
473 0 : return NS_OK;
474 : }
475 :
476 : NS_IMETHODIMP
477 0 : nsXULCommandDispatcher::Unlock()
478 : {
479 0 : if (mLocked) {
480 0 : mLocked = false;
481 :
482 : // Handle any pending updates one at a time. In the unlikely case where a
483 : // lock is added during the update, break out.
484 0 : while (!mLocked && mPendingUpdates.Length() > 0) {
485 0 : nsString name = mPendingUpdates.ElementAt(0);
486 0 : mPendingUpdates.RemoveElementAt(0);
487 0 : UpdateCommands(name);
488 : }
489 : }
490 :
491 0 : return NS_OK;
492 : }
|