tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsXULCommandDispatcher.cpp (13461B)


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