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