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 }