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