1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/xul/document/src/nsXULCommandDispatcher.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,490 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 sw=2 et tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + 1.12 + This file provides the implementation for the XUL Command Dispatcher. 1.13 + 1.14 + */ 1.15 + 1.16 +#include "nsIContent.h" 1.17 +#include "nsFocusManager.h" 1.18 +#include "nsIControllers.h" 1.19 +#include "nsIDOMDocument.h" 1.20 +#include "nsIDOMElement.h" 1.21 +#include "nsIDOMWindow.h" 1.22 +#include "nsIDOMXULElement.h" 1.23 +#include "nsIDocument.h" 1.24 +#include "nsPresContext.h" 1.25 +#include "nsIPresShell.h" 1.26 +#include "nsIScriptGlobalObject.h" 1.27 +#include "nsPIDOMWindow.h" 1.28 +#include "nsPIWindowRoot.h" 1.29 +#include "nsRDFCID.h" 1.30 +#include "nsXULCommandDispatcher.h" 1.31 +#include "prlog.h" 1.32 +#include "nsContentUtils.h" 1.33 +#include "nsReadableUtils.h" 1.34 +#include "nsCRT.h" 1.35 +#include "nsError.h" 1.36 +#include "nsDOMClassInfoID.h" 1.37 +#include "mozilla/BasicEvents.h" 1.38 +#include "mozilla/EventDispatcher.h" 1.39 +#include "mozilla/dom/Element.h" 1.40 + 1.41 +using namespace mozilla; 1.42 + 1.43 +#ifdef PR_LOGGING 1.44 +static PRLogModuleInfo* gCommandLog; 1.45 +#endif 1.46 + 1.47 +//////////////////////////////////////////////////////////////////////// 1.48 + 1.49 +nsXULCommandDispatcher::nsXULCommandDispatcher(nsIDocument* aDocument) 1.50 + : mDocument(aDocument), mUpdaters(nullptr) 1.51 +{ 1.52 + 1.53 +#ifdef PR_LOGGING 1.54 + if (! gCommandLog) 1.55 + gCommandLog = PR_NewLogModule("nsXULCommandDispatcher"); 1.56 +#endif 1.57 +} 1.58 + 1.59 +nsXULCommandDispatcher::~nsXULCommandDispatcher() 1.60 +{ 1.61 + Disconnect(); 1.62 +} 1.63 + 1.64 +// QueryInterface implementation for nsXULCommandDispatcher 1.65 + 1.66 +DOMCI_DATA(XULCommandDispatcher, nsXULCommandDispatcher) 1.67 + 1.68 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULCommandDispatcher) 1.69 + NS_INTERFACE_MAP_ENTRY(nsIDOMXULCommandDispatcher) 1.70 + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 1.71 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMXULCommandDispatcher) 1.72 + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULCommandDispatcher) 1.73 +NS_INTERFACE_MAP_END 1.74 + 1.75 +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULCommandDispatcher) 1.76 +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULCommandDispatcher) 1.77 + 1.78 +NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULCommandDispatcher) 1.79 + 1.80 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULCommandDispatcher) 1.81 + tmp->Disconnect(); 1.82 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.83 + 1.84 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULCommandDispatcher) 1.85 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) 1.86 + Updater* updater = tmp->mUpdaters; 1.87 + while (updater) { 1.88 + cb.NoteXPCOMChild(updater->mElement); 1.89 + updater = updater->mNext; 1.90 + } 1.91 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.92 + 1.93 +void 1.94 +nsXULCommandDispatcher::Disconnect() 1.95 +{ 1.96 + while (mUpdaters) { 1.97 + Updater* doomed = mUpdaters; 1.98 + mUpdaters = mUpdaters->mNext; 1.99 + delete doomed; 1.100 + } 1.101 + mDocument = nullptr; 1.102 +} 1.103 + 1.104 +already_AddRefed<nsPIWindowRoot> 1.105 +nsXULCommandDispatcher::GetWindowRoot() 1.106 +{ 1.107 + if (mDocument) { 1.108 + nsCOMPtr<nsPIDOMWindow> window(mDocument->GetWindow()); 1.109 + if (window) { 1.110 + return window->GetTopWindowRoot(); 1.111 + } 1.112 + } 1.113 + 1.114 + return nullptr; 1.115 +} 1.116 + 1.117 +nsIContent* 1.118 +nsXULCommandDispatcher::GetRootFocusedContentAndWindow(nsPIDOMWindow** aWindow) 1.119 +{ 1.120 + *aWindow = nullptr; 1.121 + 1.122 + if (mDocument) { 1.123 + nsCOMPtr<nsPIDOMWindow> win = mDocument->GetWindow(); 1.124 + if (win) { 1.125 + nsCOMPtr<nsPIDOMWindow> rootWindow = win->GetPrivateRoot(); 1.126 + if (rootWindow) { 1.127 + return nsFocusManager::GetFocusedDescendant(rootWindow, true, aWindow); 1.128 + } 1.129 + } 1.130 + } 1.131 + 1.132 + return nullptr; 1.133 +} 1.134 + 1.135 +NS_IMETHODIMP 1.136 +nsXULCommandDispatcher::GetFocusedElement(nsIDOMElement** aElement) 1.137 +{ 1.138 + *aElement = nullptr; 1.139 + 1.140 + nsCOMPtr<nsPIDOMWindow> focusedWindow; 1.141 + nsIContent* focusedContent = 1.142 + GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow)); 1.143 + if (focusedContent) { 1.144 + CallQueryInterface(focusedContent, aElement); 1.145 + 1.146 + // Make sure the caller can access the focused element. 1.147 + if (!nsContentUtils::CanCallerAccess(*aElement)) { 1.148 + // XXX This might want to return null, but we use that return value 1.149 + // to mean "there is no focused element," so to be clear, throw an 1.150 + // exception. 1.151 + NS_RELEASE(*aElement); 1.152 + return NS_ERROR_DOM_SECURITY_ERR; 1.153 + } 1.154 + } 1.155 + 1.156 + return NS_OK; 1.157 +} 1.158 + 1.159 +NS_IMETHODIMP 1.160 +nsXULCommandDispatcher::GetFocusedWindow(nsIDOMWindow** aWindow) 1.161 +{ 1.162 + *aWindow = nullptr; 1.163 + 1.164 + nsCOMPtr<nsPIDOMWindow> window; 1.165 + GetRootFocusedContentAndWindow(getter_AddRefs(window)); 1.166 + if (!window) 1.167 + return NS_OK; 1.168 + 1.169 + // Make sure the caller can access this window. The caller can access this 1.170 + // window iff it can access the document. 1.171 + nsCOMPtr<nsIDOMDocument> domdoc; 1.172 + nsresult rv = window->GetDocument(getter_AddRefs(domdoc)); 1.173 + NS_ENSURE_SUCCESS(rv, rv); 1.174 + 1.175 + // Note: If there is no document, then this window has been cleared and 1.176 + // there's nothing left to protect, so let the window pass through. 1.177 + if (domdoc && !nsContentUtils::CanCallerAccess(domdoc)) 1.178 + return NS_ERROR_DOM_SECURITY_ERR; 1.179 + 1.180 + CallQueryInterface(window, aWindow); 1.181 + return NS_OK; 1.182 +} 1.183 + 1.184 +NS_IMETHODIMP 1.185 +nsXULCommandDispatcher::SetFocusedElement(nsIDOMElement* aElement) 1.186 +{ 1.187 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.188 + NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE); 1.189 + 1.190 + if (aElement) 1.191 + return fm->SetFocus(aElement, 0); 1.192 + 1.193 + // if aElement is null, clear the focus in the currently focused child window 1.194 + nsCOMPtr<nsPIDOMWindow> focusedWindow; 1.195 + GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow)); 1.196 + return fm->ClearFocus(focusedWindow); 1.197 +} 1.198 + 1.199 +NS_IMETHODIMP 1.200 +nsXULCommandDispatcher::SetFocusedWindow(nsIDOMWindow* aWindow) 1.201 +{ 1.202 + NS_ENSURE_TRUE(aWindow, NS_OK); // do nothing if set to null 1.203 + 1.204 + nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow)); 1.205 + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); 1.206 + 1.207 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.208 + NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE); 1.209 + 1.210 + // get the containing frame for the window, and set it as focused. This will 1.211 + // end up focusing whatever is currently focused inside the frame. Since 1.212 + // setting the command dispatcher's focused window doesn't raise the window, 1.213 + // setting it to a top-level window doesn't need to do anything. 1.214 + nsCOMPtr<nsIDOMElement> frameElement = 1.215 + do_QueryInterface(window->GetFrameElementInternal()); 1.216 + if (frameElement) 1.217 + return fm->SetFocus(frameElement, 0); 1.218 + 1.219 + return NS_OK; 1.220 +} 1.221 + 1.222 +NS_IMETHODIMP 1.223 +nsXULCommandDispatcher::AdvanceFocus() 1.224 +{ 1.225 + return AdvanceFocusIntoSubtree(nullptr); 1.226 +} 1.227 + 1.228 +NS_IMETHODIMP 1.229 +nsXULCommandDispatcher::RewindFocus() 1.230 +{ 1.231 + nsCOMPtr<nsPIDOMWindow> win; 1.232 + GetRootFocusedContentAndWindow(getter_AddRefs(win)); 1.233 + 1.234 + nsCOMPtr<nsIDOMElement> result; 1.235 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.236 + if (fm) 1.237 + return fm->MoveFocus(win, nullptr, nsIFocusManager::MOVEFOCUS_BACKWARD, 1.238 + 0, getter_AddRefs(result)); 1.239 + return NS_OK; 1.240 +} 1.241 + 1.242 +NS_IMETHODIMP 1.243 +nsXULCommandDispatcher::AdvanceFocusIntoSubtree(nsIDOMElement* aElt) 1.244 +{ 1.245 + nsCOMPtr<nsPIDOMWindow> win; 1.246 + GetRootFocusedContentAndWindow(getter_AddRefs(win)); 1.247 + 1.248 + nsCOMPtr<nsIDOMElement> result; 1.249 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.250 + if (fm) 1.251 + return fm->MoveFocus(win, aElt, nsIFocusManager::MOVEFOCUS_FORWARD, 1.252 + 0, getter_AddRefs(result)); 1.253 + return NS_OK; 1.254 +} 1.255 + 1.256 +NS_IMETHODIMP 1.257 +nsXULCommandDispatcher::AddCommandUpdater(nsIDOMElement* aElement, 1.258 + const nsAString& aEvents, 1.259 + const nsAString& aTargets) 1.260 +{ 1.261 + NS_PRECONDITION(aElement != nullptr, "null ptr"); 1.262 + if (! aElement) 1.263 + return NS_ERROR_NULL_POINTER; 1.264 + 1.265 + NS_ENSURE_TRUE(mDocument, NS_ERROR_UNEXPECTED); 1.266 + 1.267 + nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, aElement); 1.268 + 1.269 + if (NS_FAILED(rv)) { 1.270 + return rv; 1.271 + } 1.272 + 1.273 + Updater* updater = mUpdaters; 1.274 + Updater** link = &mUpdaters; 1.275 + 1.276 + while (updater) { 1.277 + if (updater->mElement == aElement) { 1.278 + 1.279 +#ifdef DEBUG 1.280 + if (PR_LOG_TEST(gCommandLog, PR_LOG_NOTICE)) { 1.281 + nsAutoCString eventsC, targetsC, aeventsC, atargetsC; 1.282 + eventsC.AssignWithConversion(updater->mEvents); 1.283 + targetsC.AssignWithConversion(updater->mTargets); 1.284 + CopyUTF16toUTF8(aEvents, aeventsC); 1.285 + CopyUTF16toUTF8(aTargets, atargetsC); 1.286 + PR_LOG(gCommandLog, PR_LOG_NOTICE, 1.287 + ("xulcmd[%p] replace %p(events=%s targets=%s) with (events=%s targets=%s)", 1.288 + this, aElement, 1.289 + eventsC.get(), 1.290 + targetsC.get(), 1.291 + aeventsC.get(), 1.292 + atargetsC.get())); 1.293 + } 1.294 +#endif 1.295 + 1.296 + // If the updater was already in the list, then replace 1.297 + // (?) the 'events' and 'targets' filters with the new 1.298 + // specification. 1.299 + updater->mEvents = aEvents; 1.300 + updater->mTargets = aTargets; 1.301 + return NS_OK; 1.302 + } 1.303 + 1.304 + link = &(updater->mNext); 1.305 + updater = updater->mNext; 1.306 + } 1.307 +#ifdef DEBUG 1.308 + if (PR_LOG_TEST(gCommandLog, PR_LOG_NOTICE)) { 1.309 + nsAutoCString aeventsC, atargetsC; 1.310 + CopyUTF16toUTF8(aEvents, aeventsC); 1.311 + CopyUTF16toUTF8(aTargets, atargetsC); 1.312 + 1.313 + PR_LOG(gCommandLog, PR_LOG_NOTICE, 1.314 + ("xulcmd[%p] add %p(events=%s targets=%s)", 1.315 + this, aElement, 1.316 + aeventsC.get(), 1.317 + atargetsC.get())); 1.318 + } 1.319 +#endif 1.320 + 1.321 + // If we get here, this is a new updater. Append it to the list. 1.322 + updater = new Updater(aElement, aEvents, aTargets); 1.323 + if (! updater) 1.324 + return NS_ERROR_OUT_OF_MEMORY; 1.325 + 1.326 + *link = updater; 1.327 + return NS_OK; 1.328 +} 1.329 + 1.330 +NS_IMETHODIMP 1.331 +nsXULCommandDispatcher::RemoveCommandUpdater(nsIDOMElement* aElement) 1.332 +{ 1.333 + NS_PRECONDITION(aElement != nullptr, "null ptr"); 1.334 + if (! aElement) 1.335 + return NS_ERROR_NULL_POINTER; 1.336 + 1.337 + Updater* updater = mUpdaters; 1.338 + Updater** link = &mUpdaters; 1.339 + 1.340 + while (updater) { 1.341 + if (updater->mElement == aElement) { 1.342 +#ifdef DEBUG 1.343 + if (PR_LOG_TEST(gCommandLog, PR_LOG_NOTICE)) { 1.344 + nsAutoCString eventsC, targetsC; 1.345 + eventsC.AssignWithConversion(updater->mEvents); 1.346 + targetsC.AssignWithConversion(updater->mTargets); 1.347 + PR_LOG(gCommandLog, PR_LOG_NOTICE, 1.348 + ("xulcmd[%p] remove %p(events=%s targets=%s)", 1.349 + this, aElement, 1.350 + eventsC.get(), 1.351 + targetsC.get())); 1.352 + } 1.353 +#endif 1.354 + 1.355 + *link = updater->mNext; 1.356 + delete updater; 1.357 + return NS_OK; 1.358 + } 1.359 + 1.360 + link = &(updater->mNext); 1.361 + updater = updater->mNext; 1.362 + } 1.363 + 1.364 + // Hmm. Not found. Oh well. 1.365 + return NS_OK; 1.366 +} 1.367 + 1.368 +NS_IMETHODIMP 1.369 +nsXULCommandDispatcher::UpdateCommands(const nsAString& aEventName) 1.370 +{ 1.371 + nsAutoString id; 1.372 + nsCOMPtr<nsIDOMElement> element; 1.373 + GetFocusedElement(getter_AddRefs(element)); 1.374 + if (element) { 1.375 + nsresult rv = element->GetAttribute(NS_LITERAL_STRING("id"), id); 1.376 + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get element's id"); 1.377 + if (NS_FAILED(rv)) return rv; 1.378 + } 1.379 + 1.380 + nsCOMArray<nsIContent> updaters; 1.381 + 1.382 + for (Updater* updater = mUpdaters; updater != nullptr; updater = updater->mNext) { 1.383 + // Skip any nodes that don't match our 'events' or 'targets' 1.384 + // filters. 1.385 + if (! Matches(updater->mEvents, aEventName)) 1.386 + continue; 1.387 + 1.388 + if (! Matches(updater->mTargets, id)) 1.389 + continue; 1.390 + 1.391 + nsCOMPtr<nsIContent> content = do_QueryInterface(updater->mElement); 1.392 + NS_ASSERTION(content != nullptr, "not an nsIContent"); 1.393 + if (! content) 1.394 + return NS_ERROR_UNEXPECTED; 1.395 + 1.396 + updaters.AppendObject(content); 1.397 + } 1.398 + 1.399 + for (int32_t u = 0; u < updaters.Count(); u++) { 1.400 + nsIContent* content = updaters[u]; 1.401 + 1.402 + nsCOMPtr<nsIDocument> document = content->GetDocument(); 1.403 + 1.404 + NS_ASSERTION(document != nullptr, "element has no document"); 1.405 + if (! document) 1.406 + continue; 1.407 + 1.408 +#ifdef DEBUG 1.409 + if (PR_LOG_TEST(gCommandLog, PR_LOG_NOTICE)) { 1.410 + nsAutoCString aeventnameC; 1.411 + CopyUTF16toUTF8(aEventName, aeventnameC); 1.412 + PR_LOG(gCommandLog, PR_LOG_NOTICE, 1.413 + ("xulcmd[%p] update %p event=%s", 1.414 + this, content, 1.415 + aeventnameC.get())); 1.416 + } 1.417 +#endif 1.418 + 1.419 + nsCOMPtr<nsIPresShell> shell = document->GetShell(); 1.420 + if (shell) { 1.421 + // Retrieve the context in which our DOM event will fire. 1.422 + nsRefPtr<nsPresContext> context = shell->GetPresContext(); 1.423 + 1.424 + // Handle the DOM event 1.425 + nsEventStatus status = nsEventStatus_eIgnore; 1.426 + 1.427 + WidgetEvent event(true, NS_XUL_COMMAND_UPDATE); 1.428 + 1.429 + EventDispatcher::Dispatch(content, context, &event, nullptr, &status); 1.430 + } 1.431 + } 1.432 + return NS_OK; 1.433 +} 1.434 + 1.435 +bool 1.436 +nsXULCommandDispatcher::Matches(const nsString& aList, 1.437 + const nsAString& aElement) 1.438 +{ 1.439 + if (aList.EqualsLiteral("*")) 1.440 + return true; // match _everything_! 1.441 + 1.442 + int32_t indx = aList.Find(PromiseFlatString(aElement)); 1.443 + if (indx == -1) 1.444 + return false; // not in the list at all 1.445 + 1.446 + // okay, now make sure it's not a substring snafu; e.g., 'ur' 1.447 + // found inside of 'blur'. 1.448 + if (indx > 0) { 1.449 + char16_t ch = aList[indx - 1]; 1.450 + if (! nsCRT::IsAsciiSpace(ch) && ch != char16_t(',')) 1.451 + return false; 1.452 + } 1.453 + 1.454 + if (indx + aElement.Length() < aList.Length()) { 1.455 + char16_t ch = aList[indx + aElement.Length()]; 1.456 + if (! nsCRT::IsAsciiSpace(ch) && ch != char16_t(',')) 1.457 + return false; 1.458 + } 1.459 + 1.460 + return true; 1.461 +} 1.462 + 1.463 +NS_IMETHODIMP 1.464 +nsXULCommandDispatcher::GetControllers(nsIControllers** aResult) 1.465 +{ 1.466 + nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot(); 1.467 + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); 1.468 + 1.469 + return root->GetControllers(aResult); 1.470 +} 1.471 + 1.472 +NS_IMETHODIMP 1.473 +nsXULCommandDispatcher::GetControllerForCommand(const char *aCommand, nsIController** _retval) 1.474 +{ 1.475 + nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot(); 1.476 + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); 1.477 + 1.478 + return root->GetControllerForCommand(aCommand, _retval); 1.479 +} 1.480 + 1.481 +NS_IMETHODIMP 1.482 +nsXULCommandDispatcher::GetSuppressFocusScroll(bool* aSuppressFocusScroll) 1.483 +{ 1.484 + *aSuppressFocusScroll = false; 1.485 + return NS_OK; 1.486 +} 1.487 + 1.488 +NS_IMETHODIMP 1.489 +nsXULCommandDispatcher::SetSuppressFocusScroll(bool aSuppressFocusScroll) 1.490 +{ 1.491 + return NS_OK; 1.492 +} 1.493 +