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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Original Author: David W. Hyatt (hyatt@netscape.com)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : /*
41 :
42 : This file provides the implementation for the XUL Command Dispatcher.
43 :
44 : */
45 :
46 : #include "nsIContent.h"
47 : #include "nsFocusManager.h"
48 : #include "nsIControllers.h"
49 : #include "nsIDOMDocument.h"
50 : #include "nsIDOMXULDocument.h"
51 : #include "nsIDOMHTMLDocument.h"
52 : #include "nsIDOMElement.h"
53 : #include "nsIDOMWindow.h"
54 : #include "nsIDOMXULElement.h"
55 : #include "nsIDocument.h"
56 : #include "nsPresContext.h"
57 : #include "nsIPresShell.h"
58 : #include "nsIScriptGlobalObject.h"
59 : #include "nsPIDOMWindow.h"
60 : #include "nsPIWindowRoot.h"
61 : #include "nsRDFCID.h"
62 : #include "nsXULCommandDispatcher.h"
63 : #include "prlog.h"
64 : #include "nsIDOMEventTarget.h"
65 : #include "nsGUIEvent.h"
66 : #include "nsContentUtils.h"
67 : #include "nsReadableUtils.h"
68 : #include "nsCRT.h"
69 : #include "nsDOMError.h"
70 : #include "nsEventDispatcher.h"
71 :
72 : #ifdef PR_LOGGING
73 : static PRLogModuleInfo* gLog;
74 : #endif
75 :
76 : ////////////////////////////////////////////////////////////////////////
77 :
78 0 : nsXULCommandDispatcher::nsXULCommandDispatcher(nsIDocument* aDocument)
79 0 : : mDocument(aDocument), mUpdaters(nsnull)
80 : {
81 :
82 : #ifdef PR_LOGGING
83 0 : if (! gLog)
84 0 : gLog = PR_NewLogModule("nsXULCommandDispatcher");
85 : #endif
86 0 : }
87 :
88 0 : nsXULCommandDispatcher::~nsXULCommandDispatcher()
89 : {
90 0 : Disconnect();
91 0 : }
92 :
93 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULCommandDispatcher)
94 :
95 : // QueryInterface implementation for nsXULCommandDispatcher
96 :
97 : DOMCI_DATA(XULCommandDispatcher, nsXULCommandDispatcher)
98 :
99 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULCommandDispatcher)
100 0 : NS_INTERFACE_MAP_ENTRY(nsIDOMXULCommandDispatcher)
101 0 : NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
102 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMXULCommandDispatcher)
103 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULCommandDispatcher)
104 0 : NS_INTERFACE_MAP_END
105 :
106 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULCommandDispatcher)
107 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULCommandDispatcher)
108 :
109 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULCommandDispatcher)
110 0 : tmp->Disconnect();
111 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
112 :
113 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULCommandDispatcher)
114 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
115 0 : Updater* updater = tmp->mUpdaters;
116 0 : while (updater) {
117 0 : cb.NoteXPCOMChild(updater->mElement);
118 0 : updater = updater->mNext;
119 : }
120 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
121 :
122 : void
123 0 : nsXULCommandDispatcher::Disconnect()
124 : {
125 0 : while (mUpdaters) {
126 0 : Updater* doomed = mUpdaters;
127 0 : mUpdaters = mUpdaters->mNext;
128 0 : delete doomed;
129 : }
130 0 : mDocument = nsnull;
131 0 : }
132 :
133 : already_AddRefed<nsPIWindowRoot>
134 0 : nsXULCommandDispatcher::GetWindowRoot()
135 : {
136 0 : if (mDocument) {
137 0 : nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->GetScriptGlobalObject()));
138 0 : if (window) {
139 0 : return window->GetTopWindowRoot();
140 : }
141 : }
142 :
143 0 : return nsnull;
144 : }
145 :
146 : nsIContent*
147 0 : nsXULCommandDispatcher::GetRootFocusedContentAndWindow(nsPIDOMWindow** aWindow)
148 : {
149 0 : *aWindow = nsnull;
150 :
151 0 : if (mDocument) {
152 0 : nsCOMPtr<nsPIDOMWindow> win = mDocument->GetWindow();
153 0 : if (win) {
154 0 : nsCOMPtr<nsPIDOMWindow> rootWindow = win->GetPrivateRoot();
155 0 : if (rootWindow) {
156 0 : return nsFocusManager::GetFocusedDescendant(rootWindow, true, aWindow);
157 : }
158 : }
159 : }
160 :
161 0 : return nsnull;
162 : }
163 :
164 : NS_IMETHODIMP
165 0 : nsXULCommandDispatcher::GetFocusedElement(nsIDOMElement** aElement)
166 : {
167 0 : *aElement = nsnull;
168 :
169 0 : nsCOMPtr<nsPIDOMWindow> focusedWindow;
170 : nsIContent* focusedContent =
171 0 : GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
172 0 : if (focusedContent) {
173 0 : CallQueryInterface(focusedContent, aElement);
174 :
175 : // Make sure the caller can access the focused element.
176 0 : if (!nsContentUtils::CanCallerAccess(*aElement)) {
177 : // XXX This might want to return null, but we use that return value
178 : // to mean "there is no focused element," so to be clear, throw an
179 : // exception.
180 0 : NS_RELEASE(*aElement);
181 0 : return NS_ERROR_DOM_SECURITY_ERR;
182 : }
183 : }
184 :
185 0 : return NS_OK;
186 : }
187 :
188 : NS_IMETHODIMP
189 0 : nsXULCommandDispatcher::GetFocusedWindow(nsIDOMWindow** aWindow)
190 : {
191 0 : *aWindow = nsnull;
192 :
193 0 : nsCOMPtr<nsPIDOMWindow> window;
194 0 : GetRootFocusedContentAndWindow(getter_AddRefs(window));
195 0 : if (!window)
196 0 : return NS_OK;
197 :
198 : // Make sure the caller can access this window. The caller can access this
199 : // window iff it can access the document.
200 0 : nsCOMPtr<nsIDOMDocument> domdoc;
201 0 : nsresult rv = window->GetDocument(getter_AddRefs(domdoc));
202 0 : NS_ENSURE_SUCCESS(rv, rv);
203 :
204 : // Note: If there is no document, then this window has been cleared and
205 : // there's nothing left to protect, so let the window pass through.
206 0 : if (domdoc && !nsContentUtils::CanCallerAccess(domdoc))
207 0 : return NS_ERROR_DOM_SECURITY_ERR;
208 :
209 0 : CallQueryInterface(window, aWindow);
210 0 : return NS_OK;
211 : }
212 :
213 : NS_IMETHODIMP
214 0 : nsXULCommandDispatcher::SetFocusedElement(nsIDOMElement* aElement)
215 : {
216 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
217 0 : NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
218 :
219 0 : if (aElement)
220 0 : return fm->SetFocus(aElement, 0);
221 :
222 : // if aElement is null, clear the focus in the currently focused child window
223 0 : nsCOMPtr<nsPIDOMWindow> focusedWindow;
224 0 : GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
225 0 : return fm->ClearFocus(focusedWindow);
226 : }
227 :
228 : NS_IMETHODIMP
229 0 : nsXULCommandDispatcher::SetFocusedWindow(nsIDOMWindow* aWindow)
230 : {
231 0 : NS_ENSURE_TRUE(aWindow, NS_OK); // do nothing if set to null
232 :
233 0 : nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
234 0 : NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
235 :
236 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
237 0 : NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
238 :
239 : // get the containing frame for the window, and set it as focused. This will
240 : // end up focusing whatever is currently focused inside the frame. Since
241 : // setting the command dispatcher's focused window doesn't raise the window,
242 : // setting it to a top-level window doesn't need to do anything.
243 0 : nsCOMPtr<nsIDOMElement> frameElement = window->GetFrameElementInternal();
244 0 : if (frameElement)
245 0 : return fm->SetFocus(frameElement, 0);
246 :
247 0 : return NS_OK;
248 : }
249 :
250 : NS_IMETHODIMP
251 0 : nsXULCommandDispatcher::AdvanceFocus()
252 : {
253 0 : return AdvanceFocusIntoSubtree(nsnull);
254 : }
255 :
256 : NS_IMETHODIMP
257 0 : nsXULCommandDispatcher::RewindFocus()
258 : {
259 0 : nsCOMPtr<nsPIDOMWindow> win;
260 0 : GetRootFocusedContentAndWindow(getter_AddRefs(win));
261 :
262 0 : nsCOMPtr<nsIDOMElement> result;
263 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
264 0 : if (fm)
265 : return fm->MoveFocus(win, nsnull, nsIFocusManager::MOVEFOCUS_BACKWARD,
266 0 : 0, getter_AddRefs(result));
267 0 : return NS_OK;
268 : }
269 :
270 : NS_IMETHODIMP
271 0 : nsXULCommandDispatcher::AdvanceFocusIntoSubtree(nsIDOMElement* aElt)
272 : {
273 0 : nsCOMPtr<nsPIDOMWindow> win;
274 0 : GetRootFocusedContentAndWindow(getter_AddRefs(win));
275 :
276 0 : nsCOMPtr<nsIDOMElement> result;
277 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
278 0 : if (fm)
279 : return fm->MoveFocus(win, aElt, nsIFocusManager::MOVEFOCUS_FORWARD,
280 0 : 0, getter_AddRefs(result));
281 0 : return NS_OK;
282 : }
283 :
284 : NS_IMETHODIMP
285 0 : nsXULCommandDispatcher::AddCommandUpdater(nsIDOMElement* aElement,
286 : const nsAString& aEvents,
287 : const nsAString& aTargets)
288 : {
289 0 : NS_PRECONDITION(aElement != nsnull, "null ptr");
290 0 : if (! aElement)
291 0 : return NS_ERROR_NULL_POINTER;
292 :
293 0 : nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, aElement);
294 :
295 0 : if (NS_FAILED(rv)) {
296 0 : return rv;
297 : }
298 :
299 0 : Updater* updater = mUpdaters;
300 0 : Updater** link = &mUpdaters;
301 :
302 0 : while (updater) {
303 0 : if (updater->mElement == aElement) {
304 :
305 : #ifdef NS_DEBUG
306 0 : if (PR_LOG_TEST(gLog, PR_LOG_NOTICE)) {
307 0 : nsCAutoString eventsC, targetsC, aeventsC, atargetsC;
308 0 : eventsC.AssignWithConversion(updater->mEvents);
309 0 : targetsC.AssignWithConversion(updater->mTargets);
310 0 : CopyUTF16toUTF8(aEvents, aeventsC);
311 0 : CopyUTF16toUTF8(aTargets, atargetsC);
312 0 : PR_LOG(gLog, PR_LOG_NOTICE,
313 : ("xulcmd[%p] replace %p(events=%s targets=%s) with (events=%s targets=%s)",
314 : this, aElement,
315 : eventsC.get(),
316 : targetsC.get(),
317 : aeventsC.get(),
318 : atargetsC.get()));
319 : }
320 : #endif
321 :
322 : // If the updater was already in the list, then replace
323 : // (?) the 'events' and 'targets' filters with the new
324 : // specification.
325 0 : updater->mEvents = aEvents;
326 0 : updater->mTargets = aTargets;
327 0 : return NS_OK;
328 : }
329 :
330 0 : link = &(updater->mNext);
331 0 : updater = updater->mNext;
332 : }
333 : #ifdef NS_DEBUG
334 0 : if (PR_LOG_TEST(gLog, PR_LOG_NOTICE)) {
335 0 : nsCAutoString aeventsC, atargetsC;
336 0 : CopyUTF16toUTF8(aEvents, aeventsC);
337 0 : CopyUTF16toUTF8(aTargets, atargetsC);
338 :
339 0 : PR_LOG(gLog, PR_LOG_NOTICE,
340 : ("xulcmd[%p] add %p(events=%s targets=%s)",
341 : this, aElement,
342 : aeventsC.get(),
343 : atargetsC.get()));
344 : }
345 : #endif
346 :
347 : // If we get here, this is a new updater. Append it to the list.
348 0 : updater = new Updater(aElement, aEvents, aTargets);
349 0 : if (! updater)
350 0 : return NS_ERROR_OUT_OF_MEMORY;
351 :
352 0 : *link = updater;
353 0 : return NS_OK;
354 : }
355 :
356 : NS_IMETHODIMP
357 0 : nsXULCommandDispatcher::RemoveCommandUpdater(nsIDOMElement* aElement)
358 : {
359 0 : NS_PRECONDITION(aElement != nsnull, "null ptr");
360 0 : if (! aElement)
361 0 : return NS_ERROR_NULL_POINTER;
362 :
363 0 : Updater* updater = mUpdaters;
364 0 : Updater** link = &mUpdaters;
365 :
366 0 : while (updater) {
367 0 : if (updater->mElement == aElement) {
368 : #ifdef NS_DEBUG
369 0 : if (PR_LOG_TEST(gLog, PR_LOG_NOTICE)) {
370 0 : nsCAutoString eventsC, targetsC;
371 0 : eventsC.AssignWithConversion(updater->mEvents);
372 0 : targetsC.AssignWithConversion(updater->mTargets);
373 0 : PR_LOG(gLog, PR_LOG_NOTICE,
374 : ("xulcmd[%p] remove %p(events=%s targets=%s)",
375 : this, aElement,
376 : eventsC.get(),
377 : targetsC.get()));
378 : }
379 : #endif
380 :
381 0 : *link = updater->mNext;
382 0 : delete updater;
383 0 : return NS_OK;
384 : }
385 :
386 0 : link = &(updater->mNext);
387 0 : updater = updater->mNext;
388 : }
389 :
390 : // Hmm. Not found. Oh well.
391 0 : return NS_OK;
392 : }
393 :
394 : NS_IMETHODIMP
395 0 : nsXULCommandDispatcher::UpdateCommands(const nsAString& aEventName)
396 : {
397 0 : nsAutoString id;
398 0 : nsCOMPtr<nsIDOMElement> element;
399 0 : GetFocusedElement(getter_AddRefs(element));
400 0 : if (element) {
401 0 : nsresult rv = element->GetAttribute(NS_LITERAL_STRING("id"), id);
402 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get element's id");
403 0 : if (NS_FAILED(rv)) return rv;
404 : }
405 :
406 0 : nsCOMArray<nsIContent> updaters;
407 :
408 0 : for (Updater* updater = mUpdaters; updater != nsnull; updater = updater->mNext) {
409 : // Skip any nodes that don't match our 'events' or 'targets'
410 : // filters.
411 0 : if (! Matches(updater->mEvents, aEventName))
412 0 : continue;
413 :
414 0 : if (! Matches(updater->mTargets, id))
415 0 : continue;
416 :
417 0 : nsCOMPtr<nsIContent> content = do_QueryInterface(updater->mElement);
418 0 : NS_ASSERTION(content != nsnull, "not an nsIContent");
419 0 : if (! content)
420 0 : return NS_ERROR_UNEXPECTED;
421 :
422 0 : updaters.AppendObject(content);
423 : }
424 :
425 0 : for (PRInt32 u = 0; u < updaters.Count(); u++) {
426 0 : nsIContent* content = updaters[u];
427 :
428 0 : nsCOMPtr<nsIDocument> document = content->GetDocument();
429 :
430 0 : NS_ASSERTION(document != nsnull, "element has no document");
431 0 : if (! document)
432 0 : continue;
433 :
434 : #ifdef NS_DEBUG
435 0 : if (PR_LOG_TEST(gLog, PR_LOG_NOTICE)) {
436 0 : nsCAutoString aeventnameC;
437 0 : CopyUTF16toUTF8(aEventName, aeventnameC);
438 0 : PR_LOG(gLog, PR_LOG_NOTICE,
439 : ("xulcmd[%p] update %p event=%s",
440 : this, content,
441 : aeventnameC.get()));
442 : }
443 : #endif
444 :
445 0 : nsCOMPtr<nsIPresShell> shell = document->GetShell();
446 0 : if (shell) {
447 : // Retrieve the context in which our DOM event will fire.
448 0 : nsRefPtr<nsPresContext> context = shell->GetPresContext();
449 :
450 : // Handle the DOM event
451 0 : nsEventStatus status = nsEventStatus_eIgnore;
452 :
453 0 : nsEvent event(true, NS_XUL_COMMAND_UPDATE);
454 :
455 0 : nsEventDispatcher::Dispatch(content, context, &event, nsnull, &status);
456 : }
457 : }
458 0 : return NS_OK;
459 : }
460 :
461 : bool
462 0 : nsXULCommandDispatcher::Matches(const nsString& aList,
463 : const nsAString& aElement)
464 : {
465 0 : if (aList.EqualsLiteral("*"))
466 0 : return true; // match _everything_!
467 :
468 0 : PRInt32 indx = aList.Find(PromiseFlatString(aElement));
469 0 : if (indx == -1)
470 0 : return false; // not in the list at all
471 :
472 : // okay, now make sure it's not a substring snafu; e.g., 'ur'
473 : // found inside of 'blur'.
474 0 : if (indx > 0) {
475 0 : PRUnichar ch = aList[indx - 1];
476 0 : if (! nsCRT::IsAsciiSpace(ch) && ch != PRUnichar(','))
477 0 : return false;
478 : }
479 :
480 0 : if (indx + aElement.Length() < aList.Length()) {
481 0 : PRUnichar ch = aList[indx + aElement.Length()];
482 0 : if (! nsCRT::IsAsciiSpace(ch) && ch != PRUnichar(','))
483 0 : return false;
484 : }
485 :
486 0 : return true;
487 : }
488 :
489 : NS_IMETHODIMP
490 0 : nsXULCommandDispatcher::GetControllers(nsIControllers** aResult)
491 : {
492 0 : nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
493 0 : NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
494 :
495 0 : return root->GetControllers(aResult);
496 : }
497 :
498 : NS_IMETHODIMP
499 0 : nsXULCommandDispatcher::GetControllerForCommand(const char *aCommand, nsIController** _retval)
500 : {
501 0 : nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
502 0 : NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
503 :
504 0 : return root->GetControllerForCommand(aCommand, _retval);
505 : }
506 :
507 : NS_IMETHODIMP
508 0 : nsXULCommandDispatcher::GetSuppressFocusScroll(bool* aSuppressFocusScroll)
509 : {
510 0 : *aSuppressFocusScroll = false;
511 0 : return NS_OK;
512 : }
513 :
514 : NS_IMETHODIMP
515 0 : nsXULCommandDispatcher::SetSuppressFocusScroll(bool aSuppressFocusScroll)
516 : {
517 0 : return NS_OK;
518 4392 : }
519 :
|