toolkit/components/satchel/nsFormFillController.cpp

branch
TOR_BUG_9701
changeset 14
925c144e1f1f
equal deleted inserted replaced
-1:000000000000 0:7cc8a81dd6f3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "nsFormFillController.h"
7
8 #include "mozilla/dom/Element.h"
9 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
10 #include "nsIFormAutoComplete.h"
11 #include "nsIInputListAutoComplete.h"
12 #include "nsIAutoCompleteSimpleResult.h"
13 #include "nsString.h"
14 #include "nsReadableUtils.h"
15 #include "nsIServiceManager.h"
16 #include "nsIInterfaceRequestor.h"
17 #include "nsIInterfaceRequestorUtils.h"
18 #include "nsIDocShellTreeItem.h"
19 #include "nsPIDOMWindow.h"
20 #include "nsIWebNavigation.h"
21 #include "nsIContentViewer.h"
22 #include "nsIDOMKeyEvent.h"
23 #include "nsIDOMDocument.h"
24 #include "nsIDOMElement.h"
25 #include "nsIFormControl.h"
26 #include "nsIDocument.h"
27 #include "nsIContent.h"
28 #include "nsIPresShell.h"
29 #include "nsRect.h"
30 #include "nsIDOMHTMLFormElement.h"
31 #include "nsILoginManager.h"
32 #include "nsIDOMMouseEvent.h"
33 #include "mozilla/ModuleUtils.h"
34 #include "nsToolkitCompsCID.h"
35 #include "nsEmbedCID.h"
36 #include "nsIDOMNSEditableElement.h"
37 #include "nsContentUtils.h"
38 #include "nsILoadContext.h"
39
40 using namespace mozilla::dom;
41
42 NS_IMPL_ISUPPORTS(nsFormFillController,
43 nsIFormFillController,
44 nsIAutoCompleteInput,
45 nsIAutoCompleteSearch,
46 nsIDOMEventListener,
47 nsIFormAutoCompleteObserver,
48 nsIMutationObserver)
49
50 nsFormFillController::nsFormFillController() :
51 mFocusedInput(nullptr),
52 mFocusedInputNode(nullptr),
53 mListNode(nullptr),
54 mTimeout(50),
55 mMinResultsForPopup(1),
56 mMaxRows(0),
57 mDisableAutoComplete(false),
58 mCompleteDefaultIndex(false),
59 mCompleteSelectedIndex(false),
60 mForceComplete(false),
61 mSuppressOnInput(false)
62 {
63 mController = do_GetService("@mozilla.org/autocomplete/controller;1");
64 }
65
66 struct PwmgrInputsEnumData
67 {
68 PwmgrInputsEnumData(nsFormFillController* aFFC, nsIDocument* aDoc)
69 : mFFC(aFFC), mDoc(aDoc) {}
70
71 nsFormFillController* mFFC;
72 nsCOMPtr<nsIDocument> mDoc;
73 };
74
75 nsFormFillController::~nsFormFillController()
76 {
77 if (mListNode) {
78 mListNode->RemoveMutationObserver(this);
79 mListNode = nullptr;
80 }
81 if (mFocusedInputNode) {
82 MaybeRemoveMutationObserver(mFocusedInputNode);
83 mFocusedInputNode = nullptr;
84 mFocusedInput = nullptr;
85 }
86 PwmgrInputsEnumData ed(this, nullptr);
87 mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed);
88
89 // Remove ourselves as a focus listener from all cached docShells
90 uint32_t count = mDocShells.Length();
91 for (uint32_t i = 0; i < count; ++i) {
92 nsCOMPtr<nsIDOMWindow> domWindow = GetWindowForDocShell(mDocShells[i]);
93 RemoveWindowListeners(domWindow);
94 }
95 }
96
97 ////////////////////////////////////////////////////////////////////////
98 //// nsIMutationObserver
99 //
100
101 void
102 nsFormFillController::AttributeChanged(nsIDocument* aDocument,
103 mozilla::dom::Element* aElement,
104 int32_t aNameSpaceID,
105 nsIAtom* aAttribute, int32_t aModType)
106 {
107 if (mListNode && mListNode->Contains(aElement)) {
108 RevalidateDataList();
109 }
110 }
111
112 void
113 nsFormFillController::ContentAppended(nsIDocument* aDocument,
114 nsIContent* aContainer,
115 nsIContent* aChild,
116 int32_t aIndexInContainer)
117 {
118 if (mListNode && mListNode->Contains(aContainer)) {
119 RevalidateDataList();
120 }
121 }
122
123 void
124 nsFormFillController::ContentInserted(nsIDocument* aDocument,
125 nsIContent* aContainer,
126 nsIContent* aChild,
127 int32_t aIndexInContainer)
128 {
129 if (mListNode && mListNode->Contains(aContainer)) {
130 RevalidateDataList();
131 }
132 }
133
134 void
135 nsFormFillController::ContentRemoved(nsIDocument* aDocument,
136 nsIContent* aContainer,
137 nsIContent* aChild,
138 int32_t aIndexInContainer,
139 nsIContent* aPreviousSibling)
140 {
141 if (mListNode && mListNode->Contains(aContainer)) {
142 RevalidateDataList();
143 }
144 }
145
146 void
147 nsFormFillController::CharacterDataWillChange(nsIDocument* aDocument,
148 nsIContent* aContent,
149 CharacterDataChangeInfo* aInfo)
150 {
151 }
152
153 void
154 nsFormFillController::CharacterDataChanged(nsIDocument* aDocument,
155 nsIContent* aContent,
156 CharacterDataChangeInfo* aInfo)
157 {
158 }
159
160 void
161 nsFormFillController::AttributeWillChange(nsIDocument* aDocument,
162 mozilla::dom::Element* aElement,
163 int32_t aNameSpaceID,
164 nsIAtom* aAttribute, int32_t aModType)
165 {
166 }
167
168 void
169 nsFormFillController::ParentChainChanged(nsIContent* aContent)
170 {
171 }
172
173 void
174 nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode)
175 {
176 mPwmgrInputs.Remove(aNode);
177 if (aNode == mListNode) {
178 mListNode = nullptr;
179 RevalidateDataList();
180 } else if (aNode == mFocusedInputNode) {
181 mFocusedInputNode = nullptr;
182 mFocusedInput = nullptr;
183 }
184 }
185
186 void
187 nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode)
188 {
189 // Nodes being tracked in mPwmgrInputs will have their observers removed when
190 // they stop being tracked.
191 bool dummy;
192 if (!mPwmgrInputs.Get(aNode, &dummy)) {
193 aNode->RemoveMutationObserver(this);
194 }
195 }
196
197 ////////////////////////////////////////////////////////////////////////
198 //// nsIFormFillController
199
200 NS_IMETHODIMP
201 nsFormFillController::AttachToBrowser(nsIDocShell *aDocShell, nsIAutoCompletePopup *aPopup)
202 {
203 NS_ENSURE_TRUE(aDocShell && aPopup, NS_ERROR_ILLEGAL_VALUE);
204
205 mDocShells.AppendElement(aDocShell);
206 mPopups.AppendElement(aPopup);
207
208 // Listen for focus events on the domWindow of the docShell
209 nsCOMPtr<nsIDOMWindow> domWindow = GetWindowForDocShell(aDocShell);
210 AddWindowListeners(domWindow);
211
212 return NS_OK;
213 }
214
215 NS_IMETHODIMP
216 nsFormFillController::DetachFromBrowser(nsIDocShell *aDocShell)
217 {
218 int32_t index = GetIndexOfDocShell(aDocShell);
219 NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE);
220
221 // Stop listening for focus events on the domWindow of the docShell
222 nsCOMPtr<nsIDOMWindow> domWindow =
223 GetWindowForDocShell(mDocShells.SafeElementAt(index));
224 RemoveWindowListeners(domWindow);
225
226 mDocShells.RemoveElementAt(index);
227 mPopups.RemoveElementAt(index);
228
229 return NS_OK;
230 }
231
232
233 NS_IMETHODIMP
234 nsFormFillController::MarkAsLoginManagerField(nsIDOMHTMLInputElement *aInput)
235 {
236 /*
237 * The Login Manager can supply autocomplete results for username fields,
238 * when a user has multiple logins stored for a site. It uses this
239 * interface to indicate that the form manager shouldn't handle the
240 * autocomplete. The form manager also checks for this tag when saving
241 * form history (so it doesn't save usernames).
242 */
243 nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
244 NS_ENSURE_STATE(node);
245 mPwmgrInputs.Put(node, true);
246 node->AddMutationObserverUnlessExists(this);
247
248 if (!mLoginManager)
249 mLoginManager = do_GetService("@mozilla.org/login-manager;1");
250
251 return NS_OK;
252 }
253
254
255 ////////////////////////////////////////////////////////////////////////
256 //// nsIAutoCompleteInput
257
258 NS_IMETHODIMP
259 nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup)
260 {
261 *aPopup = mFocusedPopup;
262 NS_IF_ADDREF(*aPopup);
263 return NS_OK;
264 }
265
266 NS_IMETHODIMP
267 nsFormFillController::GetController(nsIAutoCompleteController **aController)
268 {
269 *aController = mController;
270 NS_IF_ADDREF(*aController);
271 return NS_OK;
272 }
273
274 NS_IMETHODIMP
275 nsFormFillController::GetPopupOpen(bool *aPopupOpen)
276 {
277 if (mFocusedPopup)
278 mFocusedPopup->GetPopupOpen(aPopupOpen);
279 else
280 *aPopupOpen = false;
281 return NS_OK;
282 }
283
284 NS_IMETHODIMP
285 nsFormFillController::SetPopupOpen(bool aPopupOpen)
286 {
287 if (mFocusedPopup) {
288 if (aPopupOpen) {
289 // make sure input field is visible before showing popup (bug 320938)
290 nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
291 NS_ENSURE_STATE(content);
292 nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
293 NS_ENSURE_STATE(docShell);
294 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
295 NS_ENSURE_STATE(presShell);
296 presShell->ScrollContentIntoView(content,
297 nsIPresShell::ScrollAxis(
298 nsIPresShell::SCROLL_MINIMUM,
299 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
300 nsIPresShell::ScrollAxis(
301 nsIPresShell::SCROLL_MINIMUM,
302 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
303 nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
304 // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug 420089
305 if (mFocusedPopup) {
306 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
307 mFocusedPopup->OpenAutocompletePopup(this, element);
308 }
309 } else
310 mFocusedPopup->ClosePopup();
311 }
312
313 return NS_OK;
314 }
315
316 NS_IMETHODIMP
317 nsFormFillController::GetDisableAutoComplete(bool *aDisableAutoComplete)
318 {
319 *aDisableAutoComplete = mDisableAutoComplete;
320 return NS_OK;
321 }
322
323 NS_IMETHODIMP
324 nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete)
325 {
326 mDisableAutoComplete = aDisableAutoComplete;
327 return NS_OK;
328 }
329
330 NS_IMETHODIMP
331 nsFormFillController::GetCompleteDefaultIndex(bool *aCompleteDefaultIndex)
332 {
333 *aCompleteDefaultIndex = mCompleteDefaultIndex;
334 return NS_OK;
335 }
336
337 NS_IMETHODIMP
338 nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex)
339 {
340 mCompleteDefaultIndex = aCompleteDefaultIndex;
341 return NS_OK;
342 }
343
344 NS_IMETHODIMP
345 nsFormFillController::GetCompleteSelectedIndex(bool *aCompleteSelectedIndex)
346 {
347 *aCompleteSelectedIndex = mCompleteSelectedIndex;
348 return NS_OK;
349 }
350
351 NS_IMETHODIMP
352 nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex)
353 {
354 mCompleteSelectedIndex = aCompleteSelectedIndex;
355 return NS_OK;
356 }
357
358 NS_IMETHODIMP
359 nsFormFillController::GetForceComplete(bool *aForceComplete)
360 {
361 *aForceComplete = mForceComplete;
362 return NS_OK;
363 }
364
365 NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete)
366 {
367 mForceComplete = aForceComplete;
368 return NS_OK;
369 }
370
371 NS_IMETHODIMP
372 nsFormFillController::GetMinResultsForPopup(uint32_t *aMinResultsForPopup)
373 {
374 *aMinResultsForPopup = mMinResultsForPopup;
375 return NS_OK;
376 }
377
378 NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(uint32_t aMinResultsForPopup)
379 {
380 mMinResultsForPopup = aMinResultsForPopup;
381 return NS_OK;
382 }
383
384 NS_IMETHODIMP
385 nsFormFillController::GetMaxRows(uint32_t *aMaxRows)
386 {
387 *aMaxRows = mMaxRows;
388 return NS_OK;
389 }
390
391 NS_IMETHODIMP
392 nsFormFillController::SetMaxRows(uint32_t aMaxRows)
393 {
394 mMaxRows = aMaxRows;
395 return NS_OK;
396 }
397
398 NS_IMETHODIMP
399 nsFormFillController::GetShowImageColumn(bool *aShowImageColumn)
400 {
401 *aShowImageColumn = false;
402 return NS_OK;
403 }
404
405 NS_IMETHODIMP nsFormFillController::SetShowImageColumn(bool aShowImageColumn)
406 {
407 return NS_ERROR_NOT_IMPLEMENTED;
408 }
409
410
411 NS_IMETHODIMP
412 nsFormFillController::GetShowCommentColumn(bool *aShowCommentColumn)
413 {
414 *aShowCommentColumn = false;
415 return NS_OK;
416 }
417
418 NS_IMETHODIMP nsFormFillController::SetShowCommentColumn(bool aShowCommentColumn)
419 {
420 return NS_ERROR_NOT_IMPLEMENTED;
421 }
422
423 NS_IMETHODIMP
424 nsFormFillController::GetTimeout(uint32_t *aTimeout)
425 {
426 *aTimeout = mTimeout;
427 return NS_OK;
428 }
429
430 NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout)
431 {
432 mTimeout = aTimeout;
433 return NS_OK;
434 }
435
436 NS_IMETHODIMP
437 nsFormFillController::SetSearchParam(const nsAString &aSearchParam)
438 {
439 return NS_ERROR_NOT_IMPLEMENTED;
440 }
441
442 NS_IMETHODIMP
443 nsFormFillController::GetSearchParam(nsAString &aSearchParam)
444 {
445 if (!mFocusedInput) {
446 NS_WARNING("mFocusedInput is null for some reason! avoiding a crash. should find out why... - ben");
447 return NS_ERROR_FAILURE; // XXX why? fix me.
448 }
449
450 mFocusedInput->GetName(aSearchParam);
451 if (aSearchParam.IsEmpty()) {
452 nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(mFocusedInput);
453 element->GetId(aSearchParam);
454 }
455
456 return NS_OK;
457 }
458
459 NS_IMETHODIMP
460 nsFormFillController::GetSearchCount(uint32_t *aSearchCount)
461 {
462 *aSearchCount = 1;
463 return NS_OK;
464 }
465
466 NS_IMETHODIMP
467 nsFormFillController::GetSearchAt(uint32_t index, nsACString & _retval)
468 {
469 _retval.Assign("form-history");
470 return NS_OK;
471 }
472
473 NS_IMETHODIMP
474 nsFormFillController::GetTextValue(nsAString & aTextValue)
475 {
476 if (mFocusedInput) {
477 mFocusedInput->GetValue(aTextValue);
478 } else {
479 aTextValue.Truncate();
480 }
481 return NS_OK;
482 }
483
484 NS_IMETHODIMP
485 nsFormFillController::SetTextValue(const nsAString & aTextValue)
486 {
487 nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(mFocusedInput);
488 if (editable) {
489 mSuppressOnInput = true;
490 editable->SetUserInput(aTextValue);
491 mSuppressOnInput = false;
492 }
493 return NS_OK;
494 }
495
496 NS_IMETHODIMP
497 nsFormFillController::GetSelectionStart(int32_t *aSelectionStart)
498 {
499 if (mFocusedInput)
500 mFocusedInput->GetSelectionStart(aSelectionStart);
501 return NS_OK;
502 }
503
504 NS_IMETHODIMP
505 nsFormFillController::GetSelectionEnd(int32_t *aSelectionEnd)
506 {
507 if (mFocusedInput)
508 mFocusedInput->GetSelectionEnd(aSelectionEnd);
509 return NS_OK;
510 }
511
512 NS_IMETHODIMP
513 nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex)
514 {
515 if (mFocusedInput)
516 mFocusedInput->SetSelectionRange(aStartIndex, aEndIndex, EmptyString());
517 return NS_OK;
518 }
519
520 NS_IMETHODIMP
521 nsFormFillController::OnSearchBegin()
522 {
523 return NS_OK;
524 }
525
526 NS_IMETHODIMP
527 nsFormFillController::OnSearchComplete()
528 {
529 return NS_OK;
530 }
531
532 NS_IMETHODIMP
533 nsFormFillController::OnTextEntered(bool* aPrevent)
534 {
535 NS_ENSURE_ARG(aPrevent);
536 NS_ENSURE_TRUE(mFocusedInput, NS_OK);
537 // Fire off a DOMAutoComplete event
538 nsCOMPtr<nsIDOMDocument> domDoc;
539 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
540 element->GetOwnerDocument(getter_AddRefs(domDoc));
541 NS_ENSURE_STATE(domDoc);
542
543 nsCOMPtr<nsIDOMEvent> event;
544 domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
545 NS_ENSURE_STATE(event);
546
547 event->InitEvent(NS_LITERAL_STRING("DOMAutoComplete"), true, true);
548
549 // XXXjst: We mark this event as a trusted event, it's up to the
550 // callers of this to ensure that it's only called from trusted
551 // code.
552 event->SetTrusted(true);
553
554 nsCOMPtr<EventTarget> targ = do_QueryInterface(mFocusedInput);
555
556 bool defaultActionEnabled;
557 targ->DispatchEvent(event, &defaultActionEnabled);
558 *aPrevent = !defaultActionEnabled;
559 return NS_OK;
560 }
561
562 NS_IMETHODIMP
563 nsFormFillController::OnTextReverted(bool *_retval)
564 {
565 return NS_OK;
566 }
567
568 NS_IMETHODIMP
569 nsFormFillController::GetConsumeRollupEvent(bool *aConsumeRollupEvent)
570 {
571 *aConsumeRollupEvent = false;
572 return NS_OK;
573 }
574
575 NS_IMETHODIMP
576 nsFormFillController::GetInPrivateContext(bool *aInPrivateContext)
577 {
578 if (!mFocusedInput) {
579 *aInPrivateContext = false;
580 return NS_OK;
581 }
582
583 nsCOMPtr<nsIDOMDocument> inputDoc;
584 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
585 element->GetOwnerDocument(getter_AddRefs(inputDoc));
586 nsCOMPtr<nsIDocument> doc = do_QueryInterface(inputDoc);
587 nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
588 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
589 *aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
590 return NS_OK;
591 }
592
593
594 ////////////////////////////////////////////////////////////////////////
595 //// nsIAutoCompleteSearch
596
597 NS_IMETHODIMP
598 nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
599 nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
600 {
601 nsresult rv;
602 nsCOMPtr<nsIAutoCompleteResult> result;
603
604 // If the login manager has indicated it's responsible for this field, let it
605 // handle the autocomplete. Otherwise, handle with form history.
606 bool dummy;
607 if (mPwmgrInputs.Get(mFocusedInputNode, &dummy)) {
608 // XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
609 // satchel manage the field?
610 rv = mLoginManager->AutoCompleteSearch(aSearchString,
611 aPreviousResult,
612 mFocusedInput,
613 getter_AddRefs(result));
614 NS_ENSURE_SUCCESS(rv, rv);
615 if (aListener) {
616 aListener->OnSearchResult(this, result);
617 }
618 } else {
619 mLastListener = aListener;
620
621 // It appears that mFocusedInput is always null when we are focusing a XUL
622 // element. Scary :)
623 if (!mFocusedInput || nsContentUtils::IsAutocompleteEnabled(mFocusedInput)) {
624 nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
625 do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
626 NS_ENSURE_SUCCESS(rv, rv);
627
628 formAutoComplete->AutoCompleteSearchAsync(aSearchParam,
629 aSearchString,
630 mFocusedInput,
631 aPreviousResult,
632 this);
633 mLastFormAutoComplete = formAutoComplete;
634 } else {
635 mLastSearchString = aSearchString;
636
637 // Even if autocomplete is disabled, handle the inputlist anyway as that was
638 // specifically requested by the page. This is so a field can have the default
639 // autocomplete disabled and replaced with a custom inputlist autocomplete.
640 return PerformInputListAutoComplete(aPreviousResult);
641 }
642 }
643
644 return NS_OK;
645 }
646
647 nsresult
648 nsFormFillController::PerformInputListAutoComplete(nsIAutoCompleteResult* aPreviousResult)
649 {
650 // If an <input> is focused, check if it has a list="<datalist>" which can
651 // provide the list of suggestions.
652
653 nsresult rv;
654 nsCOMPtr<nsIAutoCompleteResult> result;
655
656 nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
657 do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
658 NS_ENSURE_SUCCESS(rv, rv);
659 rv = inputListAutoComplete->AutoCompleteSearch(aPreviousResult,
660 mLastSearchString,
661 mFocusedInput,
662 getter_AddRefs(result));
663 NS_ENSURE_SUCCESS(rv, rv);
664
665 if (mFocusedInput) {
666 nsCOMPtr<nsIDOMHTMLElement> list;
667 mFocusedInput->GetList(getter_AddRefs(list));
668
669 // Add a mutation observer to check for changes to the items in the <datalist>
670 // and update the suggestions accordingly.
671 nsCOMPtr<nsINode> node = do_QueryInterface(list);
672 if (mListNode != node) {
673 if (mListNode) {
674 mListNode->RemoveMutationObserver(this);
675 mListNode = nullptr;
676 }
677 if (node) {
678 node->AddMutationObserverUnlessExists(this);
679 mListNode = node;
680 }
681 }
682 }
683
684 if (mLastListener) {
685 mLastListener->OnSearchResult(this, result);
686 }
687
688 return NS_OK;
689 }
690
691 class UpdateSearchResultRunnable : public nsRunnable
692 {
693 public:
694 UpdateSearchResultRunnable(nsIAutoCompleteObserver* aObserver,
695 nsIAutoCompleteSearch* aSearch,
696 nsIAutoCompleteResult* aResult)
697 : mObserver(aObserver)
698 , mSearch(aSearch)
699 , mResult(aResult)
700 {}
701
702 NS_IMETHOD Run() {
703 NS_ASSERTION(mObserver, "You shouldn't call this runnable with a null observer!");
704
705 mObserver->OnUpdateSearchResult(mSearch, mResult);
706 return NS_OK;
707 }
708
709 private:
710 nsCOMPtr<nsIAutoCompleteObserver> mObserver;
711 nsCOMPtr<nsIAutoCompleteSearch> mSearch;
712 nsCOMPtr<nsIAutoCompleteResult> mResult;
713 };
714
715 void nsFormFillController::RevalidateDataList()
716 {
717 if (!mLastListener) {
718 return;
719 }
720 nsresult rv;
721 nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
722 do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
723
724 nsCOMPtr<nsIAutoCompleteResult> result;
725
726 rv = inputListAutoComplete->AutoCompleteSearch(mLastSearchResult,
727 mLastSearchString,
728 mFocusedInput,
729 getter_AddRefs(result));
730
731 nsCOMPtr<nsIRunnable> event =
732 new UpdateSearchResultRunnable(mLastListener, this, result);
733 NS_DispatchToCurrentThread(event);
734 }
735
736 NS_IMETHODIMP
737 nsFormFillController::StopSearch()
738 {
739 // Make sure to stop and clear this, otherwise the controller will prevent
740 // mLastFormAutoComplete from being deleted.
741 if (mLastFormAutoComplete) {
742 mLastFormAutoComplete->StopAutoCompleteSearch();
743 mLastFormAutoComplete = nullptr;
744 }
745 return NS_OK;
746 }
747
748 ////////////////////////////////////////////////////////////////////////
749 //// nsIFormAutoCompleteObserver
750
751 NS_IMETHODIMP
752 nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult *aResult)
753 {
754 nsCOMPtr<nsIAutoCompleteResult> resultParam = do_QueryInterface(aResult);
755
756 nsAutoString searchString;
757 resultParam->GetSearchString(searchString);
758 mLastSearchResult = aResult;
759 mLastSearchString = searchString;
760
761 return PerformInputListAutoComplete(resultParam);
762 }
763
764 ////////////////////////////////////////////////////////////////////////
765 //// nsIDOMEventListener
766
767 NS_IMETHODIMP
768 nsFormFillController::HandleEvent(nsIDOMEvent* aEvent)
769 {
770 nsAutoString type;
771 aEvent->GetType(type);
772
773 if (type.EqualsLiteral("focus")) {
774 return Focus(aEvent);
775 }
776 if (type.EqualsLiteral("mousedown")) {
777 return MouseDown(aEvent);
778 }
779 if (type.EqualsLiteral("keypress")) {
780 return KeyPress(aEvent);
781 }
782 if (type.EqualsLiteral("input")) {
783 return (!mSuppressOnInput && mController && mFocusedInput) ?
784 mController->HandleText() : NS_OK;
785 }
786 if (type.EqualsLiteral("blur")) {
787 if (mFocusedInput)
788 StopControllingInput();
789 return NS_OK;
790 }
791 if (type.EqualsLiteral("compositionstart")) {
792 NS_ASSERTION(mController, "should have a controller!");
793 if (mController && mFocusedInput)
794 mController->HandleStartComposition();
795 return NS_OK;
796 }
797 if (type.EqualsLiteral("compositionend")) {
798 NS_ASSERTION(mController, "should have a controller!");
799 if (mController && mFocusedInput)
800 mController->HandleEndComposition();
801 return NS_OK;
802 }
803 if (type.EqualsLiteral("contextmenu")) {
804 if (mFocusedPopup)
805 mFocusedPopup->ClosePopup();
806 return NS_OK;
807 }
808 if (type.EqualsLiteral("pagehide")) {
809
810 nsCOMPtr<nsIDocument> doc = do_QueryInterface(
811 aEvent->InternalDOMEvent()->GetTarget());
812 if (!doc)
813 return NS_OK;
814
815 if (mFocusedInput) {
816 if (doc == mFocusedInputNode->OwnerDoc())
817 StopControllingInput();
818 }
819
820 PwmgrInputsEnumData ed(this, doc);
821 mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed);
822 }
823
824 return NS_OK;
825 }
826
827
828 /* static */ PLDHashOperator
829 nsFormFillController::RemoveForDocumentEnumerator(const nsINode* aKey,
830 bool& aEntry,
831 void* aUserData)
832 {
833 PwmgrInputsEnumData* ed = static_cast<PwmgrInputsEnumData*>(aUserData);
834 if (aKey && (!ed->mDoc || aKey->OwnerDoc() == ed->mDoc)) {
835 // mFocusedInputNode's observer is tracked separately, don't remove it here.
836 if (aKey != ed->mFFC->mFocusedInputNode) {
837 const_cast<nsINode*>(aKey)->RemoveMutationObserver(ed->mFFC);
838 }
839 return PL_DHASH_REMOVE;
840 }
841 return PL_DHASH_NEXT;
842 }
843
844 nsresult
845 nsFormFillController::Focus(nsIDOMEvent* aEvent)
846 {
847 nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(
848 aEvent->InternalDOMEvent()->GetTarget());
849 nsCOMPtr<nsINode> inputNode = do_QueryInterface(input);
850 if (!inputNode)
851 return NS_OK;
852
853 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(input);
854 if (!formControl || !formControl->IsSingleLineTextControl(true))
855 return NS_OK;
856
857 bool isReadOnly = false;
858 input->GetReadOnly(&isReadOnly);
859 if (isReadOnly)
860 return NS_OK;
861
862 bool autocomplete = nsContentUtils::IsAutocompleteEnabled(input);
863
864 nsCOMPtr<nsIDOMHTMLElement> datalist;
865 input->GetList(getter_AddRefs(datalist));
866 bool hasList = datalist != nullptr;
867
868 bool dummy;
869 bool isPwmgrInput = false;
870 if (mPwmgrInputs.Get(inputNode, &dummy))
871 isPwmgrInput = true;
872
873 if (isPwmgrInput || hasList || autocomplete) {
874 StartControllingInput(input);
875 }
876
877 return NS_OK;
878 }
879
880 nsresult
881 nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
882 {
883 NS_ASSERTION(mController, "should have a controller!");
884 if (!mFocusedInput || !mController)
885 return NS_OK;
886
887 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
888 if (!keyEvent)
889 return NS_ERROR_FAILURE;
890
891 bool cancel = false;
892
893 uint32_t k;
894 keyEvent->GetKeyCode(&k);
895 switch (k) {
896 case nsIDOMKeyEvent::DOM_VK_DELETE:
897 #ifndef XP_MACOSX
898 mController->HandleDelete(&cancel);
899 break;
900 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
901 mController->HandleText();
902 break;
903 #else
904 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
905 {
906 bool isShift = false;
907 keyEvent->GetShiftKey(&isShift);
908
909 if (isShift)
910 mController->HandleDelete(&cancel);
911 else
912 mController->HandleText();
913
914 break;
915 }
916 #endif
917 case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
918 case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
919 {
920 bool isCtrl, isAlt, isMeta;
921 keyEvent->GetCtrlKey(&isCtrl);
922 keyEvent->GetAltKey(&isAlt);
923 keyEvent->GetMetaKey(&isMeta);
924 if (isCtrl || isAlt || isMeta)
925 break;
926 }
927 /* fall through */
928 case nsIDOMKeyEvent::DOM_VK_UP:
929 case nsIDOMKeyEvent::DOM_VK_DOWN:
930 case nsIDOMKeyEvent::DOM_VK_LEFT:
931 case nsIDOMKeyEvent::DOM_VK_RIGHT:
932 mController->HandleKeyNavigation(k, &cancel);
933 break;
934 case nsIDOMKeyEvent::DOM_VK_ESCAPE:
935 mController->HandleEscape(&cancel);
936 break;
937 case nsIDOMKeyEvent::DOM_VK_TAB:
938 mController->HandleTab();
939 cancel = false;
940 break;
941 case nsIDOMKeyEvent::DOM_VK_RETURN:
942 mController->HandleEnter(false, &cancel);
943 break;
944 }
945
946 if (cancel) {
947 aEvent->PreventDefault();
948 }
949
950 return NS_OK;
951 }
952
953 nsresult
954 nsFormFillController::MouseDown(nsIDOMEvent* aEvent)
955 {
956 nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aEvent));
957 if (!mouseEvent)
958 return NS_ERROR_FAILURE;
959
960 nsCOMPtr<nsIDOMHTMLInputElement> targetInput = do_QueryInterface(
961 aEvent->InternalDOMEvent()->GetTarget());
962 if (!targetInput)
963 return NS_OK;
964
965 int16_t button;
966 mouseEvent->GetButton(&button);
967 if (button != 0)
968 return NS_OK;
969
970 bool isOpen = false;
971 GetPopupOpen(&isOpen);
972 if (isOpen)
973 return NS_OK;
974
975 nsCOMPtr<nsIAutoCompleteInput> input;
976 mController->GetInput(getter_AddRefs(input));
977 if (!input)
978 return NS_OK;
979
980 nsAutoString value;
981 input->GetTextValue(value);
982 if (value.Length() > 0) {
983 // Show the popup with a filtered result set
984 mController->SetSearchString(EmptyString());
985 mController->HandleText();
986 } else {
987 // Show the popup with the complete result set. Can't use HandleText()
988 // because it doesn't display the popup if the input is blank.
989 bool cancel = false;
990 mController->HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
991 }
992
993 return NS_OK;
994 }
995
996 ////////////////////////////////////////////////////////////////////////
997 //// nsFormFillController
998
999 void
1000 nsFormFillController::AddWindowListeners(nsIDOMWindow *aWindow)
1001 {
1002 if (!aWindow)
1003 return;
1004
1005 nsCOMPtr<nsPIDOMWindow> privateDOMWindow(do_QueryInterface(aWindow));
1006 EventTarget* target = nullptr;
1007 if (privateDOMWindow)
1008 target = privateDOMWindow->GetChromeEventHandler();
1009
1010 if (!target)
1011 return;
1012
1013 target->AddEventListener(NS_LITERAL_STRING("focus"), this,
1014 true, false);
1015 target->AddEventListener(NS_LITERAL_STRING("blur"), this,
1016 true, false);
1017 target->AddEventListener(NS_LITERAL_STRING("pagehide"), this,
1018 true, false);
1019 target->AddEventListener(NS_LITERAL_STRING("mousedown"), this,
1020 true, false);
1021 target->AddEventListener(NS_LITERAL_STRING("input"), this,
1022 true, false);
1023 target->AddEventListener(NS_LITERAL_STRING("compositionstart"), this,
1024 true, false);
1025 target->AddEventListener(NS_LITERAL_STRING("compositionend"), this,
1026 true, false);
1027 target->AddEventListener(NS_LITERAL_STRING("contextmenu"), this,
1028 true, false);
1029
1030 // Note that any additional listeners added should ensure that they ignore
1031 // untrusted events, which might be sent by content that's up to no good.
1032 }
1033
1034 void
1035 nsFormFillController::RemoveWindowListeners(nsIDOMWindow *aWindow)
1036 {
1037 if (!aWindow)
1038 return;
1039
1040 StopControllingInput();
1041
1042 nsCOMPtr<nsIDOMDocument> domDoc;
1043 aWindow->GetDocument(getter_AddRefs(domDoc));
1044 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
1045 PwmgrInputsEnumData ed(this, doc);
1046 mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed);
1047
1048 nsCOMPtr<nsPIDOMWindow> privateDOMWindow(do_QueryInterface(aWindow));
1049 EventTarget* target = nullptr;
1050 if (privateDOMWindow)
1051 target = privateDOMWindow->GetChromeEventHandler();
1052
1053 if (!target)
1054 return;
1055
1056 target->RemoveEventListener(NS_LITERAL_STRING("focus"), this, true);
1057 target->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
1058 target->RemoveEventListener(NS_LITERAL_STRING("pagehide"), this, true);
1059 target->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
1060 target->RemoveEventListener(NS_LITERAL_STRING("input"), this, true);
1061 target->RemoveEventListener(NS_LITERAL_STRING("compositionstart"), this,
1062 true);
1063 target->RemoveEventListener(NS_LITERAL_STRING("compositionend"), this,
1064 true);
1065 target->RemoveEventListener(NS_LITERAL_STRING("contextmenu"), this, true);
1066 }
1067
1068 void
1069 nsFormFillController::AddKeyListener(nsINode* aInput)
1070 {
1071 aInput->AddEventListener(NS_LITERAL_STRING("keypress"), this,
1072 true, false);
1073 }
1074
1075 void
1076 nsFormFillController::RemoveKeyListener()
1077 {
1078 if (!mFocusedInputNode)
1079 return;
1080
1081 mFocusedInputNode->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
1082 }
1083
1084 void
1085 nsFormFillController::StartControllingInput(nsIDOMHTMLInputElement *aInput)
1086 {
1087 // Make sure we're not still attached to an input
1088 StopControllingInput();
1089
1090 // Find the currently focused docShell
1091 nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(aInput);
1092 int32_t index = GetIndexOfDocShell(docShell);
1093 if (index < 0)
1094 return;
1095
1096 // Cache the popup for the focused docShell
1097 mFocusedPopup = mPopups.SafeElementAt(index);
1098
1099 nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
1100 if (!node) {
1101 return;
1102 }
1103
1104 AddKeyListener(node);
1105
1106 node->AddMutationObserverUnlessExists(this);
1107 mFocusedInputNode = node;
1108 mFocusedInput = aInput;
1109
1110 nsCOMPtr<nsIDOMHTMLElement> list;
1111 mFocusedInput->GetList(getter_AddRefs(list));
1112 nsCOMPtr<nsINode> listNode = do_QueryInterface(list);
1113 if (listNode) {
1114 listNode->AddMutationObserverUnlessExists(this);
1115 mListNode = listNode;
1116 }
1117
1118 // Now we are the autocomplete controller's bitch
1119 mController->SetInput(this);
1120 }
1121
1122 void
1123 nsFormFillController::StopControllingInput()
1124 {
1125 RemoveKeyListener();
1126
1127 if (mListNode) {
1128 mListNode->RemoveMutationObserver(this);
1129 mListNode = nullptr;
1130 }
1131
1132 // Reset the controller's input, but not if it has been switched
1133 // to another input already, which might happen if the user switches
1134 // focus by clicking another autocomplete textbox
1135 nsCOMPtr<nsIAutoCompleteInput> input;
1136 mController->GetInput(getter_AddRefs(input));
1137 if (input == this)
1138 mController->SetInput(nullptr);
1139
1140 if (mFocusedInputNode) {
1141 MaybeRemoveMutationObserver(mFocusedInputNode);
1142 mFocusedInputNode = nullptr;
1143 mFocusedInput = nullptr;
1144 }
1145 mFocusedPopup = nullptr;
1146 }
1147
1148 nsIDocShell *
1149 nsFormFillController::GetDocShellForInput(nsIDOMHTMLInputElement *aInput)
1150 {
1151 nsCOMPtr<nsIDOMDocument> domDoc;
1152 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aInput);
1153 element->GetOwnerDocument(getter_AddRefs(domDoc));
1154 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
1155 NS_ENSURE_TRUE(doc, nullptr);
1156 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(doc->GetWindow());
1157 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webNav);
1158 return docShell;
1159 }
1160
1161 nsIDOMWindow *
1162 nsFormFillController::GetWindowForDocShell(nsIDocShell *aDocShell)
1163 {
1164 nsCOMPtr<nsIContentViewer> contentViewer;
1165 aDocShell->GetContentViewer(getter_AddRefs(contentViewer));
1166 NS_ENSURE_TRUE(contentViewer, nullptr);
1167
1168 nsCOMPtr<nsIDOMDocument> domDoc;
1169 contentViewer->GetDOMDocument(getter_AddRefs(domDoc));
1170 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
1171 NS_ENSURE_TRUE(doc, nullptr);
1172
1173 return doc->GetWindow();
1174 }
1175
1176 int32_t
1177 nsFormFillController::GetIndexOfDocShell(nsIDocShell *aDocShell)
1178 {
1179 if (!aDocShell)
1180 return -1;
1181
1182 // Loop through our cached docShells looking for the given docShell
1183 uint32_t count = mDocShells.Length();
1184 for (uint32_t i = 0; i < count; ++i) {
1185 if (mDocShells[i] == aDocShell)
1186 return i;
1187 }
1188
1189 // Recursively check the parent docShell of this one
1190 nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(aDocShell);
1191 nsCOMPtr<nsIDocShellTreeItem> parentItem;
1192 treeItem->GetParent(getter_AddRefs(parentItem));
1193 if (parentItem) {
1194 nsCOMPtr<nsIDocShell> parentShell = do_QueryInterface(parentItem);
1195 return GetIndexOfDocShell(parentShell);
1196 }
1197
1198 return -1;
1199 }
1200
1201 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormFillController)
1202
1203 NS_DEFINE_NAMED_CID(NS_FORMFILLCONTROLLER_CID);
1204
1205 static const mozilla::Module::CIDEntry kSatchelCIDs[] = {
1206 { &kNS_FORMFILLCONTROLLER_CID, false, nullptr, nsFormFillControllerConstructor },
1207 { nullptr }
1208 };
1209
1210 static const mozilla::Module::ContractIDEntry kSatchelContracts[] = {
1211 { "@mozilla.org/satchel/form-fill-controller;1", &kNS_FORMFILLCONTROLLER_CID },
1212 { NS_FORMHISTORYAUTOCOMPLETE_CONTRACTID, &kNS_FORMFILLCONTROLLER_CID },
1213 { nullptr }
1214 };
1215
1216 static const mozilla::Module kSatchelModule = {
1217 mozilla::Module::kVersion,
1218 kSatchelCIDs,
1219 kSatchelContracts
1220 };
1221
1222 NSMODULE_DEFN(satchel) = &kSatchelModule;

mercurial