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