1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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 #include "nsFormFillController.h"
8
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/ErrorResult.h"
11 #include "mozilla/EventListenerManager.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/Element.h"
14 #include "mozilla/dom/Event.h" // for Event
15 #include "mozilla/dom/HTMLInputElement.h"
16 #include "mozilla/dom/KeyboardEvent.h"
17 #include "mozilla/dom/KeyboardEventBinding.h"
18 #include "mozilla/dom/MouseEvent.h"
19 #include "mozilla/dom/PageTransitionEvent.h"
20 #include "mozilla/Logging.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/Services.h"
23 #include "mozilla/StaticPrefs_ui.h"
24 #include "nsCRT.h"
25 #include "nsIFormAutoComplete.h"
26 #include "nsIInputListAutoComplete.h"
27 #include "nsIAutoCompleteSimpleResult.h"
28 #include "nsString.h"
29 #include "nsReadableUtils.h"
30 #include "nsIInterfaceRequestor.h"
31 #include "nsIInterfaceRequestorUtils.h"
32 #include "nsPIDOMWindow.h"
33 #include "nsIContent.h"
34 #include "nsRect.h"
35 #include "nsToolkitCompsCID.h"
36 #include "nsEmbedCID.h"
37 #include "nsContentUtils.h"
38 #include "nsGenericHTMLElement.h"
39 #include "nsILoadContext.h"
40 #include "nsIFrame.h"
41 #include "nsIScriptSecurityManager.h"
42 #include "nsFocusManager.h"
43 #include "nsQueryActor.h"
44 #include "nsQueryObject.h"
45 #include "nsServiceManagerUtils.h"
46 #include "xpcpublic.h"
47
48 using namespace mozilla;
49 using namespace mozilla::dom;
50 using mozilla::ErrorResult;
51 using mozilla::LogLevel;
52
53 static mozilla::LazyLogModule sLogger("satchel");
54
GetFormAutoComplete()55 static nsIFormAutoComplete* GetFormAutoComplete() {
56 static nsCOMPtr<nsIFormAutoComplete> sInstance;
57 static bool sInitialized = false;
58 if (!sInitialized) {
59 nsresult rv;
60 sInstance = do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
61
62 if (NS_SUCCEEDED(rv)) {
63 ClearOnShutdown(&sInstance);
64 sInitialized = true;
65 }
66 }
67 return sInstance;
68 }
69
NS_IMPL_CYCLE_COLLECTION(nsFormFillController,mController,mLoginManagerAC,mLoginReputationService,mFocusedPopup,mPopups,mLastListener,mLastFormAutoComplete)70 NS_IMPL_CYCLE_COLLECTION(nsFormFillController, mController, mLoginManagerAC,
71 mLoginReputationService, mFocusedPopup, mPopups,
72 mLastListener, mLastFormAutoComplete)
73
74 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
75 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFormFillController)
76 NS_INTERFACE_MAP_ENTRY(nsIFormFillController)
77 NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteInput)
78 NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch)
79 NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
80 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
81 NS_INTERFACE_MAP_ENTRY(nsIObserver)
82 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
83 NS_INTERFACE_MAP_END
84
85 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController)
86 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
87
88 nsFormFillController::nsFormFillController()
89 : mFocusedInput(nullptr),
90 mListNode(nullptr),
91 // The amount of time a context menu event supresses showing a
92 // popup from a focus event in ms. This matches the threshold in
93 // toolkit/components/passwordmgr/LoginManagerChild.jsm.
94 mFocusAfterRightClickThreshold(400),
95 mTimeout(50),
96 mMinResultsForPopup(1),
97 mMaxRows(0),
98 mLastRightClickTimeStamp(TimeStamp()),
99 mDisableAutoComplete(false),
100 mCompleteDefaultIndex(false),
101 mCompleteSelectedIndex(false),
102 mForceComplete(false),
103 mSuppressOnInput(false),
104 mPasswordPopupAutomaticallyOpened(false) {
105 mController = do_GetService("@mozilla.org/autocomplete/controller;1");
106 MOZ_ASSERT(mController);
107
108 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
109 MOZ_ASSERT(obs);
110
111 obs->AddObserver(this, "chrome-event-target-created", false);
112 obs->AddObserver(this, "autofill-fill-starting", false);
113 obs->AddObserver(this, "autofill-fill-complete", false);
114 }
115
~nsFormFillController()116 nsFormFillController::~nsFormFillController() {
117 if (mListNode) {
118 mListNode->RemoveMutationObserver(this);
119 mListNode = nullptr;
120 }
121 if (mFocusedInput) {
122 MaybeRemoveMutationObserver(mFocusedInput);
123 mFocusedInput = nullptr;
124 }
125 RemoveForDocument(nullptr);
126 }
127
128 /* static */
GetSingleton()129 already_AddRefed<nsFormFillController> nsFormFillController::GetSingleton() {
130 static RefPtr<nsFormFillController> sSingleton;
131 if (!sSingleton) {
132 sSingleton = new nsFormFillController();
133 ClearOnShutdown(&sSingleton);
134 }
135 return do_AddRef(sSingleton);
136 }
137
138 ////////////////////////////////////////////////////////////////////////
139 //// nsIMutationObserver
140 //
141
142 MOZ_CAN_RUN_SCRIPT_BOUNDARY
AttributeChanged(mozilla::dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)143 void nsFormFillController::AttributeChanged(mozilla::dom::Element* aElement,
144 int32_t aNameSpaceID,
145 nsAtom* aAttribute,
146 int32_t aModType,
147 const nsAttrValue* aOldValue) {
148 if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
149 aAttribute == nsGkAtoms::autocomplete) &&
150 aNameSpaceID == kNameSpaceID_None) {
151 RefPtr<HTMLInputElement> focusedInput(mFocusedInput);
152 // Reset the current state of the controller, unconditionally.
153 StopControllingInput();
154 // Then restart based on the new values. We have to delay this
155 // to avoid ending up in an endless loop due to re-registering our
156 // mutation observer (which would notify us again for *this* event).
157 nsCOMPtr<nsIRunnable> event =
158 mozilla::NewRunnableMethod<RefPtr<HTMLInputElement>>(
159 "nsFormFillController::MaybeStartControllingInput", this,
160 &nsFormFillController::MaybeStartControllingInput, focusedInput);
161 aElement->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
162 }
163
164 if (mListNode && mListNode->Contains(aElement)) {
165 RevalidateDataList();
166 }
167 }
168
169 MOZ_CAN_RUN_SCRIPT_BOUNDARY
ContentAppended(nsIContent * aChild)170 void nsFormFillController::ContentAppended(nsIContent* aChild) {
171 if (mListNode && mListNode->Contains(aChild->GetParent())) {
172 RevalidateDataList();
173 }
174 }
175
176 MOZ_CAN_RUN_SCRIPT_BOUNDARY
ContentInserted(nsIContent * aChild)177 void nsFormFillController::ContentInserted(nsIContent* aChild) {
178 if (mListNode && mListNode->Contains(aChild->GetParent())) {
179 RevalidateDataList();
180 }
181 }
182
183 MOZ_CAN_RUN_SCRIPT_BOUNDARY
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)184 void nsFormFillController::ContentRemoved(nsIContent* aChild,
185 nsIContent* aPreviousSibling) {
186 if (mListNode && mListNode->Contains(aChild->GetParent())) {
187 RevalidateDataList();
188 }
189 }
190
CharacterDataWillChange(nsIContent * aContent,const CharacterDataChangeInfo &)191 void nsFormFillController::CharacterDataWillChange(
192 nsIContent* aContent, const CharacterDataChangeInfo&) {}
193
CharacterDataChanged(nsIContent * aContent,const CharacterDataChangeInfo &)194 void nsFormFillController::CharacterDataChanged(
195 nsIContent* aContent, const CharacterDataChangeInfo&) {}
196
AttributeWillChange(mozilla::dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)197 void nsFormFillController::AttributeWillChange(mozilla::dom::Element* aElement,
198 int32_t aNameSpaceID,
199 nsAtom* aAttribute,
200 int32_t aModType) {}
201
NativeAnonymousChildListChange(nsIContent * aContent,bool aIsRemove)202 void nsFormFillController::NativeAnonymousChildListChange(nsIContent* aContent,
203 bool aIsRemove) {}
204
ParentChainChanged(nsIContent * aContent)205 void nsFormFillController::ParentChainChanged(nsIContent* aContent) {}
206
207 MOZ_CAN_RUN_SCRIPT_BOUNDARY
NodeWillBeDestroyed(const nsINode * aNode)208 void nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode) {
209 MOZ_LOG(sLogger, LogLevel::Verbose, ("NodeWillBeDestroyed: %p", aNode));
210 mPwmgrInputs.Remove(aNode);
211 mAutofillInputs.Remove(aNode);
212 if (aNode == mListNode) {
213 mListNode = nullptr;
214 RevalidateDataList();
215 } else if (aNode == mFocusedInput) {
216 mFocusedInput = nullptr;
217 }
218 }
219
MaybeRemoveMutationObserver(nsINode * aNode)220 void nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode) {
221 // Nodes being tracked in mPwmgrInputs will have their observers removed when
222 // they stop being tracked.
223 if (!mPwmgrInputs.Get(aNode) && !mAutofillInputs.Get(aNode)) {
224 aNode->RemoveMutationObserver(this);
225 }
226 }
227
228 ////////////////////////////////////////////////////////////////////////
229 //// nsIFormFillController
230
231 NS_IMETHODIMP
AttachPopupElementToDocument(Document * aDocument,dom::Element * aPopupEl)232 nsFormFillController::AttachPopupElementToDocument(Document* aDocument,
233 dom::Element* aPopupEl) {
234 if (!xpc::IsInAutomation()) {
235 return NS_ERROR_NOT_AVAILABLE;
236 }
237
238 MOZ_LOG(sLogger, LogLevel::Debug,
239 ("AttachPopupElementToDocument for document %p with popup %p",
240 aDocument, aPopupEl));
241 NS_ENSURE_TRUE(aDocument && aPopupEl, NS_ERROR_ILLEGAL_VALUE);
242
243 nsCOMPtr<nsIAutoCompletePopup> popup = aPopupEl->AsAutoCompletePopup();
244 NS_ENSURE_STATE(popup);
245
246 mPopups.InsertOrUpdate(aDocument, popup);
247 return NS_OK;
248 }
249
250 NS_IMETHODIMP
DetachFromDocument(Document * aDocument)251 nsFormFillController::DetachFromDocument(Document* aDocument) {
252 if (!xpc::IsInAutomation()) {
253 return NS_ERROR_NOT_AVAILABLE;
254 }
255 mPopups.Remove(aDocument);
256 return NS_OK;
257 }
258
259 NS_IMETHODIMP
MarkAsLoginManagerField(HTMLInputElement * aInput)260 nsFormFillController::MarkAsLoginManagerField(HTMLInputElement* aInput) {
261 /*
262 * The Login Manager can supply autocomplete results for username fields,
263 * when a user has multiple logins stored for a site. It uses this
264 * interface to indicate that the form manager shouldn't handle the
265 * autocomplete. The form manager also checks for this tag when saving
266 * form history (so it doesn't save usernames).
267 */
268 NS_ENSURE_STATE(aInput);
269
270 // If the field was already marked, we don't want to show the popup again.
271 if (mPwmgrInputs.Get(aInput)) {
272 return NS_OK;
273 }
274
275 mPwmgrInputs.InsertOrUpdate(aInput, true);
276 aInput->AddMutationObserverUnlessExists(this);
277
278 nsFocusManager* fm = nsFocusManager::GetFocusManager();
279 if (fm) {
280 nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
281 if (focusedContent == aInput) {
282 if (!mFocusedInput) {
283 MaybeStartControllingInput(aInput);
284 }
285 }
286 }
287
288 if (!mLoginManagerAC) {
289 mLoginManagerAC =
290 do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
291 }
292
293 return NS_OK;
294 }
295
296 NS_IMETHODIMP
MarkAsAutofillField(HTMLInputElement * aInput)297 nsFormFillController::MarkAsAutofillField(HTMLInputElement* aInput) {
298 /*
299 * Support other components implementing form autofill and handle autocomplete
300 * for the field.
301 */
302 NS_ENSURE_STATE(aInput);
303
304 MOZ_LOG(sLogger, LogLevel::Verbose,
305 ("MarkAsAutofillField: aInput = %p", aInput));
306
307 if (mAutofillInputs.Get(aInput)) {
308 return NS_OK;
309 }
310
311 mAutofillInputs.InsertOrUpdate(aInput, true);
312 aInput->AddMutationObserverUnlessExists(this);
313
314 aInput->EnablePreview();
315
316 nsFocusManager* fm = nsFocusManager::GetFocusManager();
317 if (fm) {
318 nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
319 if (focusedContent == aInput) {
320 MaybeStartControllingInput(aInput);
321 }
322 }
323
324 return NS_OK;
325 }
326
327 NS_IMETHODIMP
GetFocusedInput(HTMLInputElement ** aInput)328 nsFormFillController::GetFocusedInput(HTMLInputElement** aInput) {
329 *aInput = mFocusedInput;
330 NS_IF_ADDREF(*aInput);
331 return NS_OK;
332 }
333
334 ////////////////////////////////////////////////////////////////////////
335 //// nsIAutoCompleteInput
336
337 NS_IMETHODIMP
GetPopup(nsIAutoCompletePopup ** aPopup)338 nsFormFillController::GetPopup(nsIAutoCompletePopup** aPopup) {
339 *aPopup = mFocusedPopup;
340 NS_IF_ADDREF(*aPopup);
341 return NS_OK;
342 }
343
344 NS_IMETHODIMP
GetPopupElement(Element ** aPopup)345 nsFormFillController::GetPopupElement(Element** aPopup) {
346 return NS_ERROR_NOT_IMPLEMENTED;
347 }
348
349 NS_IMETHODIMP
GetController(nsIAutoCompleteController ** aController)350 nsFormFillController::GetController(nsIAutoCompleteController** aController) {
351 *aController = mController;
352 NS_IF_ADDREF(*aController);
353 return NS_OK;
354 }
355
356 NS_IMETHODIMP
GetPopupOpen(bool * aPopupOpen)357 nsFormFillController::GetPopupOpen(bool* aPopupOpen) {
358 if (mFocusedPopup) {
359 mFocusedPopup->GetPopupOpen(aPopupOpen);
360 } else {
361 *aPopupOpen = false;
362 }
363 return NS_OK;
364 }
365
366 NS_IMETHODIMP
SetPopupOpen(bool aPopupOpen)367 nsFormFillController::SetPopupOpen(bool aPopupOpen) {
368 if (mFocusedPopup) {
369 if (aPopupOpen) {
370 // make sure input field is visible before showing popup (bug 320938)
371 nsCOMPtr<nsIContent> content = mFocusedInput;
372 NS_ENSURE_STATE(content);
373 nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
374 NS_ENSURE_STATE(docShell);
375 RefPtr<PresShell> presShell = docShell->GetPresShell();
376 NS_ENSURE_STATE(presShell);
377 presShell->ScrollContentIntoView(
378 content, ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
379 ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
380 ScrollFlags::ScrollOverflowHidden);
381 // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug
382 // 420089
383 if (mFocusedPopup) {
384 mFocusedPopup->OpenAutocompletePopup(this, mFocusedInput);
385 }
386 } else {
387 mFocusedPopup->ClosePopup();
388 mPasswordPopupAutomaticallyOpened = false;
389 }
390 }
391
392 return NS_OK;
393 }
394
395 NS_IMETHODIMP
GetDisableAutoComplete(bool * aDisableAutoComplete)396 nsFormFillController::GetDisableAutoComplete(bool* aDisableAutoComplete) {
397 *aDisableAutoComplete = mDisableAutoComplete;
398 return NS_OK;
399 }
400
401 NS_IMETHODIMP
SetDisableAutoComplete(bool aDisableAutoComplete)402 nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete) {
403 mDisableAutoComplete = aDisableAutoComplete;
404 return NS_OK;
405 }
406
407 NS_IMETHODIMP
GetCompleteDefaultIndex(bool * aCompleteDefaultIndex)408 nsFormFillController::GetCompleteDefaultIndex(bool* aCompleteDefaultIndex) {
409 *aCompleteDefaultIndex = mCompleteDefaultIndex;
410 return NS_OK;
411 }
412
413 NS_IMETHODIMP
SetCompleteDefaultIndex(bool aCompleteDefaultIndex)414 nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex) {
415 mCompleteDefaultIndex = aCompleteDefaultIndex;
416 return NS_OK;
417 }
418
419 NS_IMETHODIMP
GetCompleteSelectedIndex(bool * aCompleteSelectedIndex)420 nsFormFillController::GetCompleteSelectedIndex(bool* aCompleteSelectedIndex) {
421 *aCompleteSelectedIndex = mCompleteSelectedIndex;
422 return NS_OK;
423 }
424
425 NS_IMETHODIMP
SetCompleteSelectedIndex(bool aCompleteSelectedIndex)426 nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex) {
427 mCompleteSelectedIndex = aCompleteSelectedIndex;
428 return NS_OK;
429 }
430
431 NS_IMETHODIMP
GetForceComplete(bool * aForceComplete)432 nsFormFillController::GetForceComplete(bool* aForceComplete) {
433 *aForceComplete = mForceComplete;
434 return NS_OK;
435 }
436
SetForceComplete(bool aForceComplete)437 NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete) {
438 mForceComplete = aForceComplete;
439 return NS_OK;
440 }
441
442 NS_IMETHODIMP
GetMinResultsForPopup(uint32_t * aMinResultsForPopup)443 nsFormFillController::GetMinResultsForPopup(uint32_t* aMinResultsForPopup) {
444 *aMinResultsForPopup = mMinResultsForPopup;
445 return NS_OK;
446 }
447
SetMinResultsForPopup(uint32_t aMinResultsForPopup)448 NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(
449 uint32_t aMinResultsForPopup) {
450 mMinResultsForPopup = aMinResultsForPopup;
451 return NS_OK;
452 }
453
454 NS_IMETHODIMP
GetMaxRows(uint32_t * aMaxRows)455 nsFormFillController::GetMaxRows(uint32_t* aMaxRows) {
456 *aMaxRows = mMaxRows;
457 return NS_OK;
458 }
459
460 NS_IMETHODIMP
SetMaxRows(uint32_t aMaxRows)461 nsFormFillController::SetMaxRows(uint32_t aMaxRows) {
462 mMaxRows = aMaxRows;
463 return NS_OK;
464 }
465
466 NS_IMETHODIMP
GetTimeout(uint32_t * aTimeout)467 nsFormFillController::GetTimeout(uint32_t* aTimeout) {
468 *aTimeout = mTimeout;
469 return NS_OK;
470 }
471
SetTimeout(uint32_t aTimeout)472 NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout) {
473 mTimeout = aTimeout;
474 return NS_OK;
475 }
476
477 NS_IMETHODIMP
SetSearchParam(const nsAString & aSearchParam)478 nsFormFillController::SetSearchParam(const nsAString& aSearchParam) {
479 return NS_ERROR_NOT_IMPLEMENTED;
480 }
481
482 NS_IMETHODIMP
GetSearchParam(nsAString & aSearchParam)483 nsFormFillController::GetSearchParam(nsAString& aSearchParam) {
484 if (!mFocusedInput) {
485 NS_WARNING(
486 "mFocusedInput is null for some reason! avoiding a crash. should find "
487 "out why... - ben");
488 return NS_ERROR_FAILURE; // XXX why? fix me.
489 }
490
491 mFocusedInput->GetName(aSearchParam);
492 if (aSearchParam.IsEmpty()) {
493 mFocusedInput->GetId(aSearchParam);
494 }
495
496 return NS_OK;
497 }
498
499 NS_IMETHODIMP
GetSearchCount(uint32_t * aSearchCount)500 nsFormFillController::GetSearchCount(uint32_t* aSearchCount) {
501 *aSearchCount = 1;
502 return NS_OK;
503 }
504
505 NS_IMETHODIMP
GetSearchAt(uint32_t index,nsACString & _retval)506 nsFormFillController::GetSearchAt(uint32_t index, nsACString& _retval) {
507 if (mAutofillInputs.Get(mFocusedInput)) {
508 MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: autofill-profiles field"));
509 nsCOMPtr<nsIAutoCompleteSearch> profileSearch = do_GetService(
510 "@mozilla.org/autocomplete/search;1?name=autofill-profiles");
511 if (profileSearch) {
512 _retval.AssignLiteral("autofill-profiles");
513 return NS_OK;
514 }
515 }
516
517 MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: form-history field"));
518 _retval.AssignLiteral("form-history");
519 return NS_OK;
520 }
521
522 NS_IMETHODIMP
GetTextValue(nsAString & aTextValue)523 nsFormFillController::GetTextValue(nsAString& aTextValue) {
524 if (mFocusedInput) {
525 mFocusedInput->GetValue(aTextValue, CallerType::System);
526 } else {
527 aTextValue.Truncate();
528 }
529 return NS_OK;
530 }
531
532 NS_IMETHODIMP
SetTextValue(const nsAString & aTextValue)533 nsFormFillController::SetTextValue(const nsAString& aTextValue) {
534 if (mFocusedInput) {
535 mSuppressOnInput = true;
536 mFocusedInput->SetUserInput(aTextValue,
537 *nsContentUtils::GetSystemPrincipal());
538 mSuppressOnInput = false;
539 }
540
541 return NS_OK;
542 }
543
544 NS_IMETHODIMP
SetTextValueWithReason(const nsAString & aTextValue,uint16_t aReason)545 nsFormFillController::SetTextValueWithReason(const nsAString& aTextValue,
546 uint16_t aReason) {
547 return SetTextValue(aTextValue);
548 }
549
550 NS_IMETHODIMP
GetSelectionStart(int32_t * aSelectionStart)551 nsFormFillController::GetSelectionStart(int32_t* aSelectionStart) {
552 if (!mFocusedInput) {
553 return NS_ERROR_UNEXPECTED;
554 }
555 ErrorResult rv;
556 *aSelectionStart = mFocusedInput->GetSelectionStartIgnoringType(rv);
557 return rv.StealNSResult();
558 }
559
560 NS_IMETHODIMP
GetSelectionEnd(int32_t * aSelectionEnd)561 nsFormFillController::GetSelectionEnd(int32_t* aSelectionEnd) {
562 if (!mFocusedInput) {
563 return NS_ERROR_UNEXPECTED;
564 }
565 ErrorResult rv;
566 *aSelectionEnd = mFocusedInput->GetSelectionEndIgnoringType(rv);
567 return rv.StealNSResult();
568 }
569
570 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
SelectTextRange(int32_t aStartIndex,int32_t aEndIndex)571 nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex) {
572 if (!mFocusedInput) {
573 return NS_ERROR_UNEXPECTED;
574 }
575 RefPtr<HTMLInputElement> focusedInput(mFocusedInput);
576 ErrorResult rv;
577 focusedInput->SetSelectionRange(aStartIndex, aEndIndex, Optional<nsAString>(),
578 rv);
579 return rv.StealNSResult();
580 }
581
582 NS_IMETHODIMP
OnSearchBegin()583 nsFormFillController::OnSearchBegin() { return NS_OK; }
584
585 NS_IMETHODIMP
OnSearchComplete()586 nsFormFillController::OnSearchComplete() { return NS_OK; }
587
588 NS_IMETHODIMP
OnTextEntered(Event * aEvent,bool itemWasSelected,bool * aPrevent)589 nsFormFillController::OnTextEntered(Event* aEvent, bool itemWasSelected,
590 bool* aPrevent) {
591 NS_ENSURE_ARG(aPrevent);
592 NS_ENSURE_TRUE(mFocusedInput, NS_OK);
593
594 /**
595 * This function can get called when text wasn't actually entered
596 * into the field (e.g. if an autocomplete item wasn't selected) so
597 * we don't fire DOMAutoComplete in that case since nothing
598 * was actually autocompleted.
599 */
600 if (!itemWasSelected) {
601 return NS_OK;
602 }
603
604 // Fire off a DOMAutoComplete event
605
606 IgnoredErrorResult ignored;
607 RefPtr<Event> event = mFocusedInput->OwnerDoc()->CreateEvent(
608 u"Events"_ns, CallerType::System, ignored);
609 NS_ENSURE_STATE(event);
610
611 event->InitEvent(u"DOMAutoComplete"_ns, true, true);
612
613 // XXXjst: We mark this event as a trusted event, it's up to the
614 // callers of this to ensure that it's only called from trusted
615 // code.
616 event->SetTrusted(true);
617
618 bool defaultActionEnabled =
619 mFocusedInput->DispatchEvent(*event, CallerType::System, IgnoreErrors());
620 *aPrevent = !defaultActionEnabled;
621 return NS_OK;
622 }
623
624 NS_IMETHODIMP
OnTextReverted(bool * _retval)625 nsFormFillController::OnTextReverted(bool* _retval) {
626 mPasswordPopupAutomaticallyOpened = false;
627 return NS_OK;
628 }
629
630 NS_IMETHODIMP
GetConsumeRollupEvent(bool * aConsumeRollupEvent)631 nsFormFillController::GetConsumeRollupEvent(bool* aConsumeRollupEvent) {
632 *aConsumeRollupEvent = false;
633 return NS_OK;
634 }
635
636 NS_IMETHODIMP
GetInPrivateContext(bool * aInPrivateContext)637 nsFormFillController::GetInPrivateContext(bool* aInPrivateContext) {
638 if (!mFocusedInput) {
639 *aInPrivateContext = false;
640 return NS_OK;
641 }
642
643 RefPtr<Document> doc = mFocusedInput->OwnerDoc();
644 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
645 *aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
646 return NS_OK;
647 }
648
649 NS_IMETHODIMP
GetNoRollupOnCaretMove(bool * aNoRollupOnCaretMove)650 nsFormFillController::GetNoRollupOnCaretMove(bool* aNoRollupOnCaretMove) {
651 *aNoRollupOnCaretMove = false;
652 return NS_OK;
653 }
654
655 NS_IMETHODIMP
GetNoRollupOnEmptySearch(bool * aNoRollupOnEmptySearch)656 nsFormFillController::GetNoRollupOnEmptySearch(bool* aNoRollupOnEmptySearch) {
657 if (mFocusedInput && (mPwmgrInputs.Get(mFocusedInput) ||
658 mFocusedInput->HasBeenTypePassword())) {
659 // Don't close the login popup when the field is cleared (bug 1534896).
660 *aNoRollupOnEmptySearch = true;
661 } else {
662 *aNoRollupOnEmptySearch = false;
663 }
664 return NS_OK;
665 }
666
667 NS_IMETHODIMP
GetUserContextId(uint32_t * aUserContextId)668 nsFormFillController::GetUserContextId(uint32_t* aUserContextId) {
669 *aUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
670 return NS_OK;
671 }
672
673 ////////////////////////////////////////////////////////////////////////
674 //// nsIAutoCompleteSearch
675
676 NS_IMETHODIMP
StartSearch(const nsAString & aSearchString,const nsAString & aSearchParam,nsIAutoCompleteResult * aPreviousResult,nsIAutoCompleteObserver * aListener,nsIPropertyBag2 * aOptions)677 nsFormFillController::StartSearch(const nsAString& aSearchString,
678 const nsAString& aSearchParam,
679 nsIAutoCompleteResult* aPreviousResult,
680 nsIAutoCompleteObserver* aListener,
681 nsIPropertyBag2* aOptions) {
682 MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch for %p", mFocusedInput));
683
684 nsresult rv;
685
686 // If the login manager has indicated it's responsible for this field, let it
687 // handle the autocomplete. Otherwise, handle with form history.
688 // This method is sometimes called in unit tests and from XUL without a
689 // focused node.
690 if (mFocusedInput && (mPwmgrInputs.Get(mFocusedInput) ||
691 mFocusedInput->HasBeenTypePassword())) {
692 MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: login field"));
693
694 // Handle the case where a password field is focused but
695 // MarkAsLoginManagerField wasn't called because password manager is
696 // disabled.
697 if (!mLoginManagerAC) {
698 mLoginManagerAC =
699 do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
700 }
701
702 if (NS_WARN_IF(!mLoginManagerAC)) {
703 return NS_ERROR_FAILURE;
704 }
705
706 // XXX aPreviousResult shouldn't ever be a historyResult type, since we're
707 // not letting satchel manage the field?
708 mLastListener = aListener;
709 rv = mLoginManagerAC->StartSearch(aSearchString, aPreviousResult,
710 mFocusedInput, this);
711 NS_ENSURE_SUCCESS(rv, rv);
712 } else {
713 MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: non-login field"));
714 mLastListener = aListener;
715
716 nsCOMPtr<nsIAutoCompleteResult> datalistResult;
717 if (mFocusedInput) {
718 rv = PerformInputListAutoComplete(aSearchString,
719 getter_AddRefs(datalistResult));
720 NS_ENSURE_SUCCESS(rv, rv);
721 }
722
723 auto formAutoComplete = GetFormAutoComplete();
724 NS_ENSURE_TRUE(formAutoComplete, NS_ERROR_FAILURE);
725
726 formAutoComplete->AutoCompleteSearchAsync(aSearchParam, aSearchString,
727 mFocusedInput, aPreviousResult,
728 datalistResult, this, aOptions);
729 mLastFormAutoComplete = formAutoComplete;
730 }
731
732 return NS_OK;
733 }
734
PerformInputListAutoComplete(const nsAString & aSearch,nsIAutoCompleteResult ** aResult)735 nsresult nsFormFillController::PerformInputListAutoComplete(
736 const nsAString& aSearch, nsIAutoCompleteResult** aResult) {
737 // If an <input> is focused, check if it has a list="<datalist>" which can
738 // provide the list of suggestions.
739
740 MOZ_ASSERT(!mPwmgrInputs.Get(mFocusedInput));
741 nsresult rv;
742
743 nsCOMPtr<nsIInputListAutoComplete> inputListAutoComplete =
744 do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
745 NS_ENSURE_SUCCESS(rv, rv);
746 rv = inputListAutoComplete->AutoCompleteSearch(aSearch, mFocusedInput,
747 aResult);
748 NS_ENSURE_SUCCESS(rv, rv);
749
750 if (mFocusedInput) {
751 Element* list = mFocusedInput->GetList();
752
753 // Add a mutation observer to check for changes to the items in the
754 // <datalist> and update the suggestions accordingly.
755 if (mListNode != list) {
756 if (mListNode) {
757 mListNode->RemoveMutationObserver(this);
758 mListNode = nullptr;
759 }
760 if (list) {
761 list->AddMutationObserverUnlessExists(this);
762 mListNode = list;
763 }
764 }
765 }
766
767 return NS_OK;
768 }
769
RevalidateDataList()770 void nsFormFillController::RevalidateDataList() {
771 if (!mLastListener) {
772 return;
773 }
774
775 nsCOMPtr<nsIAutoCompleteController> controller(
776 do_QueryInterface(mLastListener));
777 if (!controller) {
778 return;
779 }
780
781 controller->StartSearch(mLastSearchString);
782 }
783
784 NS_IMETHODIMP
StopSearch()785 nsFormFillController::StopSearch() {
786 // Make sure to stop and clear this, otherwise the controller will prevent
787 // mLastFormAutoComplete from being deleted.
788 if (mLastFormAutoComplete) {
789 mLastFormAutoComplete->StopAutoCompleteSearch();
790 mLastFormAutoComplete = nullptr;
791 } else if (mLoginManagerAC) {
792 mLoginManagerAC->StopSearch();
793 }
794 return NS_OK;
795 }
796
StartQueryLoginReputation(HTMLInputElement * aInput)797 nsresult nsFormFillController::StartQueryLoginReputation(
798 HTMLInputElement* aInput) {
799 return NS_OK;
800 }
801
802 ////////////////////////////////////////////////////////////////////////
803 //// nsIFormAutoCompleteObserver
804
805 NS_IMETHODIMP
OnSearchCompletion(nsIAutoCompleteResult * aResult)806 nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult* aResult) {
807 nsAutoString searchString;
808 aResult->GetSearchString(searchString);
809
810 mLastSearchString = searchString;
811
812 if (mLastListener) {
813 nsCOMPtr<nsIAutoCompleteObserver> lastListener = mLastListener;
814 lastListener->OnSearchResult(this, aResult);
815 }
816
817 return NS_OK;
818 }
819
820 ////////////////////////////////////////////////////////////////////////
821 //// nsIObserver
822
823 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)824 nsFormFillController::Observe(nsISupports* aSubject, const char* aTopic,
825 const char16_t* aData) {
826 if (!nsCRT::strcmp(aTopic, "chrome-event-target-created")) {
827 if (RefPtr<EventTarget> eventTarget = do_QueryObject(aSubject)) {
828 AttachListeners(eventTarget);
829 }
830 } else if (!nsCRT::strcmp(aTopic, "autofill-fill-starting")) {
831 mAutoCompleteActive = true;
832 } else if (!nsCRT::strcmp(aTopic, "autofill-fill-complete")) {
833 mAutoCompleteActive = false;
834 }
835 return NS_OK;
836 }
837
838 ////////////////////////////////////////////////////////////////////////
839 //// nsIDOMEventListener
840
841 NS_IMETHODIMP
HandleEvent(Event * aEvent)842 nsFormFillController::HandleEvent(Event* aEvent) {
843 EventTarget* target = aEvent->GetOriginalTarget();
844 NS_ENSURE_STATE(target);
845
846 nsCOMPtr<nsPIDOMWindowInner> inner =
847 do_QueryInterface(target->GetOwnerGlobal());
848 NS_ENSURE_STATE(inner);
849
850 if (!inner->GetBrowsingContext()->IsContent()) {
851 return NS_OK;
852 }
853
854 WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
855 NS_ENSURE_STATE(internalEvent);
856
857 switch (internalEvent->mMessage) {
858 case eFocus:
859 return Focus(aEvent);
860 case eMouseDown:
861 return MouseDown(aEvent);
862 case eKeyDown:
863 return KeyDown(aEvent);
864 case eEditorInput: {
865 if (!(mAutoCompleteActive || mSuppressOnInput)) {
866 nsCOMPtr<nsINode> input =
867 do_QueryInterface(aEvent->GetComposedTarget());
868 if (IsTextControl(input) && IsFocusedInputControlled()) {
869 nsCOMPtr<nsIAutoCompleteController> controller = mController;
870 bool unused = false;
871 return controller->HandleText(&unused);
872 }
873 }
874 return NS_OK;
875 }
876 case eBlur:
877 if (mFocusedInput && !StaticPrefs::ui_popup_disable_autohide()) {
878 StopControllingInput();
879 }
880 return NS_OK;
881 case eCompositionStart:
882 NS_ASSERTION(mController, "should have a controller!");
883 if (IsFocusedInputControlled()) {
884 nsCOMPtr<nsIAutoCompleteController> controller = mController;
885 controller->HandleStartComposition();
886 }
887 return NS_OK;
888 case eCompositionEnd:
889 NS_ASSERTION(mController, "should have a controller!");
890 if (IsFocusedInputControlled()) {
891 nsCOMPtr<nsIAutoCompleteController> controller = mController;
892 controller->HandleEndComposition();
893 }
894 return NS_OK;
895 case eContextMenu:
896 if (mFocusedPopup) {
897 mFocusedPopup->ClosePopup();
898 }
899 return NS_OK;
900 case ePageHide: {
901 nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget());
902 if (!doc) {
903 return NS_OK;
904 }
905
906 if (mFocusedInput && doc == mFocusedInput->OwnerDoc()) {
907 StopControllingInput();
908 }
909
910 // Only remove the observer notifications and marked autofill and password
911 // manager fields if the page isn't going to be persisted (i.e. it's being
912 // unloaded) so that appropriate autocomplete handling works with bfcache.
913 bool persisted = aEvent->AsPageTransitionEvent()->Persisted();
914 if (!persisted) {
915 RemoveForDocument(doc);
916 }
917 } break;
918 default:
919 // Handling the default case to shut up stupid -Wswitch warnings.
920 // One day compilers will be smarter...
921 break;
922 }
923
924 return NS_OK;
925 }
926
AttachListeners(EventTarget * aEventTarget)927 void nsFormFillController::AttachListeners(EventTarget* aEventTarget) {
928 EventListenerManager* elm = aEventTarget->GetOrCreateListenerManager();
929 NS_ENSURE_TRUE_VOID(elm);
930
931 elm->AddEventListenerByType(this, u"focus"_ns, TrustedEventsAtCapture());
932 elm->AddEventListenerByType(this, u"blur"_ns, TrustedEventsAtCapture());
933 elm->AddEventListenerByType(this, u"pagehide"_ns, TrustedEventsAtCapture());
934 elm->AddEventListenerByType(this, u"mousedown"_ns, TrustedEventsAtCapture());
935 elm->AddEventListenerByType(this, u"input"_ns, TrustedEventsAtCapture());
936 elm->AddEventListenerByType(this, u"keydown"_ns, TrustedEventsAtCapture());
937 elm->AddEventListenerByType(this, u"keypress"_ns,
938 TrustedEventsAtSystemGroupCapture());
939 elm->AddEventListenerByType(this, u"compositionstart"_ns,
940 TrustedEventsAtCapture());
941 elm->AddEventListenerByType(this, u"compositionend"_ns,
942 TrustedEventsAtCapture());
943 elm->AddEventListenerByType(this, u"contextmenu"_ns,
944 TrustedEventsAtCapture());
945 }
946
RemoveForDocument(Document * aDoc)947 void nsFormFillController::RemoveForDocument(Document* aDoc) {
948 MOZ_LOG(sLogger, LogLevel::Verbose, ("RemoveForDocument: %p", aDoc));
949 for (auto iter = mPwmgrInputs.Iter(); !iter.Done(); iter.Next()) {
950 const nsINode* key = iter.Key();
951 if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
952 // mFocusedInput's observer is tracked separately, so don't remove it
953 // here.
954 if (key != mFocusedInput) {
955 const_cast<nsINode*>(key)->RemoveMutationObserver(this);
956 }
957 iter.Remove();
958 }
959 }
960
961 for (auto iter = mAutofillInputs.Iter(); !iter.Done(); iter.Next()) {
962 const nsINode* key = iter.Key();
963 if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
964 // mFocusedInput's observer is tracked separately, so don't remove it
965 // here.
966 if (key != mFocusedInput) {
967 const_cast<nsINode*>(key)->RemoveMutationObserver(this);
968 }
969 iter.Remove();
970 }
971 }
972 }
973
IsTextControl(nsINode * aNode)974 bool nsFormFillController::IsTextControl(nsINode* aNode) {
975 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aNode);
976 return formControl && formControl->IsSingleLineTextControl(false);
977 }
978
MaybeStartControllingInput(HTMLInputElement * aInput)979 void nsFormFillController::MaybeStartControllingInput(
980 HTMLInputElement* aInput) {
981 MOZ_LOG(sLogger, LogLevel::Verbose,
982 ("MaybeStartControllingInput for %p", aInput));
983 if (!aInput) {
984 return;
985 }
986
987 if (!IsTextControl(aInput)) {
988 return;
989 }
990
991 bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
992
993 bool hasList = !!aInput->GetList();
994
995 bool isPwmgrInput = false;
996 if (mPwmgrInputs.Get(aInput) || aInput->HasBeenTypePassword()) {
997 isPwmgrInput = true;
998 }
999
1000 bool isAutofillInput = false;
1001 if (mAutofillInputs.Get(aInput)) {
1002 isAutofillInput = true;
1003 }
1004
1005 if (isAutofillInput || isPwmgrInput || hasList || autocomplete) {
1006 StartControllingInput(aInput);
1007 }
1008
1009 #ifdef NIGHTLY_BUILD
1010 // Trigger an asynchronous login reputation query when user focuses on the
1011 // password field.
1012 if (aInput->HasBeenTypePassword()) {
1013 StartQueryLoginReputation(aInput);
1014 }
1015 #endif
1016 }
1017
HandleFocus(HTMLInputElement * aInput)1018 nsresult nsFormFillController::HandleFocus(HTMLInputElement* aInput) {
1019 MaybeStartControllingInput(aInput);
1020
1021 // Bail if we didn't start controlling the input.
1022 if (!mFocusedInput) {
1023 return NS_OK;
1024 }
1025
1026 // If this focus doesn't follow a right click within our specified
1027 // threshold then show the autocomplete popup for all password fields.
1028 // This is done to avoid showing both the context menu and the popup
1029 // at the same time.
1030 // We use a timestamp instead of a bool to avoid complexity when dealing with
1031 // multiple input forms and the fact that a mousedown into an already focused
1032 // field does not trigger another focus.
1033
1034 if (!mFocusedInput->HasBeenTypePassword()) {
1035 return NS_OK;
1036 }
1037
1038 // If we have not seen a right click yet, just show the popup.
1039 if (mLastRightClickTimeStamp.IsNull()) {
1040 mPasswordPopupAutomaticallyOpened = true;
1041 ShowPopup();
1042 return NS_OK;
1043 }
1044
1045 uint64_t timeDiff =
1046 (TimeStamp::Now() - mLastRightClickTimeStamp).ToMilliseconds();
1047 if (timeDiff > mFocusAfterRightClickThreshold) {
1048 mPasswordPopupAutomaticallyOpened = true;
1049 ShowPopup();
1050 }
1051
1052 return NS_OK;
1053 }
1054
Focus(Event * aEvent)1055 nsresult nsFormFillController::Focus(Event* aEvent) {
1056 nsCOMPtr<nsIContent> input = do_QueryInterface(aEvent->GetComposedTarget());
1057 return HandleFocus(MOZ_KnownLive(HTMLInputElement::FromNodeOrNull(input)));
1058 }
1059
KeyDown(Event * aEvent)1060 nsresult nsFormFillController::KeyDown(Event* aEvent) {
1061 NS_ASSERTION(mController, "should have a controller!");
1062
1063 mPasswordPopupAutomaticallyOpened = false;
1064
1065 if (!IsFocusedInputControlled()) {
1066 return NS_OK;
1067 }
1068
1069 RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
1070 if (!keyEvent) {
1071 return NS_ERROR_FAILURE;
1072 }
1073
1074 bool cancel = false;
1075 bool unused = false;
1076
1077 uint32_t k = keyEvent->KeyCode();
1078 switch (k) {
1079 case KeyboardEvent_Binding::DOM_VK_RETURN: {
1080 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1081 controller->HandleEnter(false, aEvent, &cancel);
1082 break;
1083 }
1084 case KeyboardEvent_Binding::DOM_VK_DELETE:
1085 #ifndef XP_MACOSX
1086 {
1087 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1088 controller->HandleDelete(&cancel);
1089 break;
1090 }
1091 case KeyboardEvent_Binding::DOM_VK_BACK_SPACE: {
1092 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1093 controller->HandleText(&unused);
1094 break;
1095 }
1096 #else
1097 case KeyboardEvent_Binding::DOM_VK_BACK_SPACE: {
1098 if (keyEvent->ShiftKey()) {
1099 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1100 controller->HandleDelete(&cancel);
1101 } else {
1102 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1103 controller->HandleText(&unused);
1104 }
1105 break;
1106 }
1107 #endif
1108 case KeyboardEvent_Binding::DOM_VK_PAGE_UP:
1109 case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN: {
1110 if (keyEvent->CtrlKey() || keyEvent->AltKey() || keyEvent->MetaKey()) {
1111 break;
1112 }
1113 }
1114 [[fallthrough]];
1115 case KeyboardEvent_Binding::DOM_VK_UP:
1116 case KeyboardEvent_Binding::DOM_VK_DOWN:
1117 case KeyboardEvent_Binding::DOM_VK_LEFT:
1118 case KeyboardEvent_Binding::DOM_VK_RIGHT: {
1119 // Get the writing-mode of the relevant input element,
1120 // so that we can remap arrow keys if necessary.
1121 mozilla::WritingMode wm;
1122 if (mFocusedInput) {
1123 nsIFrame* frame = mFocusedInput->GetPrimaryFrame();
1124 if (frame) {
1125 wm = frame->GetWritingMode();
1126 }
1127 }
1128 if (wm.IsVertical()) {
1129 switch (k) {
1130 case KeyboardEvent_Binding::DOM_VK_LEFT:
1131 k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_UP
1132 : KeyboardEvent_Binding::DOM_VK_DOWN;
1133 break;
1134 case KeyboardEvent_Binding::DOM_VK_RIGHT:
1135 k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_DOWN
1136 : KeyboardEvent_Binding::DOM_VK_UP;
1137 break;
1138 case KeyboardEvent_Binding::DOM_VK_UP:
1139 k = KeyboardEvent_Binding::DOM_VK_LEFT;
1140 break;
1141 case KeyboardEvent_Binding::DOM_VK_DOWN:
1142 k = KeyboardEvent_Binding::DOM_VK_RIGHT;
1143 break;
1144 }
1145 }
1146 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1147 controller->HandleKeyNavigation(k, &cancel);
1148 break;
1149 }
1150 case KeyboardEvent_Binding::DOM_VK_ESCAPE: {
1151 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1152 controller->HandleEscape(&cancel);
1153 break;
1154 }
1155 case KeyboardEvent_Binding::DOM_VK_TAB: {
1156 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1157 controller->HandleTab();
1158 cancel = false;
1159 break;
1160 }
1161 }
1162
1163 if (cancel) {
1164 aEvent->PreventDefault();
1165 // Don't let the page see the RETURN event when the popup is open
1166 // (indicated by cancel=true) so sites don't manually submit forms
1167 // (e.g. via submit.click()) without the autocompleted value being filled.
1168 // Bug 286933 will fix this for other key events.
1169 if (k == KeyboardEvent_Binding::DOM_VK_RETURN) {
1170 aEvent->StopPropagation();
1171 }
1172 }
1173
1174 return NS_OK;
1175 }
1176
MouseDown(Event * aEvent)1177 nsresult nsFormFillController::MouseDown(Event* aEvent) {
1178 MouseEvent* mouseEvent = aEvent->AsMouseEvent();
1179 if (!mouseEvent) {
1180 return NS_ERROR_FAILURE;
1181 }
1182
1183 nsCOMPtr<nsINode> targetNode = do_QueryInterface(aEvent->GetComposedTarget());
1184 if (!HTMLInputElement::FromNodeOrNull(targetNode)) {
1185 return NS_OK;
1186 }
1187
1188 int16_t button = mouseEvent->Button();
1189
1190 // In case of a right click we set a timestamp that
1191 // will be checked in Focus() to avoid showing
1192 // both contextmenu and popup at the same time.
1193 if (button == 2) {
1194 mLastRightClickTimeStamp = TimeStamp::Now();
1195 return NS_OK;
1196 }
1197
1198 if (button != 0) {
1199 return NS_OK;
1200 }
1201
1202 return ShowPopup();
1203 }
1204
1205 NS_IMETHODIMP
ShowPopup()1206 nsFormFillController::ShowPopup() {
1207 bool isOpen = false;
1208 GetPopupOpen(&isOpen);
1209 if (isOpen) {
1210 return SetPopupOpen(false);
1211 }
1212
1213 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1214
1215 nsCOMPtr<nsIAutoCompleteInput> input;
1216 controller->GetInput(getter_AddRefs(input));
1217 if (!input) {
1218 return NS_OK;
1219 }
1220
1221 nsAutoString value;
1222 input->GetTextValue(value);
1223 if (value.Length() > 0) {
1224 // Show the popup with a filtered result set
1225 controller->SetSearchString(u""_ns);
1226 bool unused = false;
1227 controller->HandleText(&unused);
1228 } else {
1229 // Show the popup with the complete result set. Can't use HandleText()
1230 // because it doesn't display the popup if the input is blank.
1231 bool cancel = false;
1232 controller->HandleKeyNavigation(KeyboardEvent_Binding::DOM_VK_DOWN,
1233 &cancel);
1234 }
1235
1236 return NS_OK;
1237 }
1238
GetPasswordPopupAutomaticallyOpened(bool * _retval)1239 NS_IMETHODIMP nsFormFillController::GetPasswordPopupAutomaticallyOpened(
1240 bool* _retval) {
1241 *_retval = mPasswordPopupAutomaticallyOpened;
1242 return NS_OK;
1243 }
1244
StartControllingInput(HTMLInputElement * aInput)1245 void nsFormFillController::StartControllingInput(HTMLInputElement* aInput) {
1246 MOZ_LOG(sLogger, LogLevel::Verbose, ("StartControllingInput for %p", aInput));
1247 // Make sure we're not still attached to an input
1248 StopControllingInput();
1249
1250 if (!mController || !aInput) {
1251 return;
1252 }
1253
1254 nsCOMPtr<nsIAutoCompletePopup> popup = mPopups.Get(aInput->OwnerDoc());
1255 if (!popup) {
1256 popup = do_QueryActor("AutoComplete", aInput->OwnerDoc());
1257 if (!popup) {
1258 return;
1259 }
1260 }
1261
1262 mFocusedPopup = popup;
1263
1264 aInput->AddMutationObserverUnlessExists(this);
1265 mFocusedInput = aInput;
1266
1267 if (Element* list = mFocusedInput->GetList()) {
1268 list->AddMutationObserverUnlessExists(this);
1269 mListNode = list;
1270 }
1271
1272 if (!mFocusedInput->ReadOnly()) {
1273 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1274 controller->SetInput(this);
1275 }
1276 }
1277
IsFocusedInputControlled() const1278 bool nsFormFillController::IsFocusedInputControlled() const {
1279 return mFocusedInput && mController && !mFocusedInput->ReadOnly();
1280 }
1281
StopControllingInput()1282 void nsFormFillController::StopControllingInput() {
1283 mPasswordPopupAutomaticallyOpened = false;
1284
1285 if (mListNode) {
1286 mListNode->RemoveMutationObserver(this);
1287 mListNode = nullptr;
1288 }
1289
1290 if (nsCOMPtr<nsIAutoCompleteController> controller = mController) {
1291 // Reset the controller's input, but not if it has been switched
1292 // to another input already, which might happen if the user switches
1293 // focus by clicking another autocomplete textbox
1294 nsCOMPtr<nsIAutoCompleteInput> input;
1295 controller->GetInput(getter_AddRefs(input));
1296 if (input == this) {
1297 MOZ_LOG(sLogger, LogLevel::Verbose,
1298 ("StopControllingInput: Nulled controller input for %p", this));
1299 controller->SetInput(nullptr);
1300 }
1301 }
1302
1303 MOZ_LOG(sLogger, LogLevel::Verbose,
1304 ("StopControllingInput: Stopped controlling %p", mFocusedInput));
1305 if (mFocusedInput) {
1306 MaybeRemoveMutationObserver(mFocusedInput);
1307 mFocusedInput = nullptr;
1308 }
1309
1310 if (mFocusedPopup) {
1311 mFocusedPopup->ClosePopup();
1312 }
1313 mFocusedPopup = nullptr;
1314 }
1315
GetDocShellForInput(HTMLInputElement * aInput)1316 nsIDocShell* nsFormFillController::GetDocShellForInput(
1317 HTMLInputElement* aInput) {
1318 NS_ENSURE_TRUE(aInput, nullptr);
1319
1320 nsCOMPtr<nsPIDOMWindowOuter> win = aInput->OwnerDoc()->GetWindow();
1321 NS_ENSURE_TRUE(win, nullptr);
1322
1323 return win->GetDocShell();
1324 }
1325