Coverage Report

Created: 2018-09-25 14:53

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