Source code

Revision control

Other Tools

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