From 2a5c21a5346f0b812d957abd827da4f5d4ef480e Mon Sep 17 00:00:00 2001 From: "alqahira%ardisson.org" Date: Tue, 22 Jan 2008 22:03:35 +0000 Subject: [PATCH] Relanding bug 178607 - Keychain cannot save multiple passwords for the same host. Patch by Bryan Atwood , r=smorgan, sr=mento. git-svn-id: svn://10.0.0.236/trunk@243756 18797224-902f-48f8-a5cc-f745e15eee43 --- .../camino/Camino.xcodeproj/project.pbxproj | 32 + mozilla/camino/src/browser/BrowserWrapper.h | 2 + mozilla/camino/src/browser/BrowserWrapper.mm | 5 + mozilla/camino/src/extensions/GeckoUtils.cpp | 46 ++ mozilla/camino/src/extensions/GeckoUtils.h | 8 + .../camino/src/formfill/AutoCompleteUtils.h | 83 ++ .../camino/src/formfill/AutoCompleteUtils.mm | 87 ++ .../camino/src/formfill/FormFillController.h | 100 +++ .../camino/src/formfill/FormFillController.mm | 748 ++++++++++++++++++ mozilla/camino/src/formfill/FormFillPopup.h | 83 ++ mozilla/camino/src/formfill/FormFillPopup.mm | 230 ++++++ .../formfill/KeychainAutoCompleteSession.h | 85 ++ .../formfill/KeychainAutoCompleteSession.mm | 369 +++++++++ mozilla/camino/src/formfill/KeychainItem.h | 1 + mozilla/camino/src/formfill/KeychainItem.m | 8 +- mozilla/camino/src/formfill/KeychainService.h | 51 +- .../camino/src/formfill/KeychainService.mm | 329 +++++--- 17 files changed, 2151 insertions(+), 116 deletions(-) create mode 100644 mozilla/camino/src/formfill/AutoCompleteUtils.h create mode 100644 mozilla/camino/src/formfill/AutoCompleteUtils.mm create mode 100644 mozilla/camino/src/formfill/FormFillController.h create mode 100644 mozilla/camino/src/formfill/FormFillController.mm create mode 100644 mozilla/camino/src/formfill/FormFillPopup.h create mode 100644 mozilla/camino/src/formfill/FormFillPopup.mm create mode 100644 mozilla/camino/src/formfill/KeychainAutoCompleteSession.h create mode 100644 mozilla/camino/src/formfill/KeychainAutoCompleteSession.mm diff --git a/mozilla/camino/Camino.xcodeproj/project.pbxproj b/mozilla/camino/Camino.xcodeproj/project.pbxproj index 40a77a6877f..0bc8c289bb7 100644 --- a/mozilla/camino/Camino.xcodeproj/project.pbxproj +++ b/mozilla/camino/Camino.xcodeproj/project.pbxproj @@ -1038,6 +1038,14 @@ DE8C62000AB67CDA00078871 /* hidemanager.tiff in Resources */ = {isa = PBXBuildFile; fileRef = DE8C61FE0AB67CDA00078871 /* hidemanager.tiff */; }; DE8EEA0C0D39A2A500BB96C1 /* dom_json.xpt in Copy Component XPTs */ = {isa = PBXBuildFile; fileRef = DE8EEA0B0D39A2A500BB96C1 /* dom_json.xpt */; }; DE8EEA0D0D39A2A500BB96C1 /* dom_json.xpt in Copy Component XPTs */ = {isa = PBXBuildFile; fileRef = DE8EEA0B0D39A2A500BB96C1 /* dom_json.xpt */; }; + DE963D190D43EFCF007D44EE /* AutoCompleteUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D110D43EFCF007D44EE /* AutoCompleteUtils.mm */; }; + DE963D1A0D43EFCF007D44EE /* FormFillPopup.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D120D43EFCF007D44EE /* FormFillPopup.mm */; }; + DE963D1B0D43EFCF007D44EE /* FormFillController.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D130D43EFCF007D44EE /* FormFillController.mm */; }; + DE963D1C0D43EFCF007D44EE /* KeychainAutoCompleteSession.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D150D43EFCF007D44EE /* KeychainAutoCompleteSession.mm */; settings = {COMPILER_FLAGS = "-DNO_NSPR_10_SUPPORT"; }; }; + DE963D1D0D43EFCF007D44EE /* AutoCompleteUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D110D43EFCF007D44EE /* AutoCompleteUtils.mm */; }; + DE963D1E0D43EFCF007D44EE /* FormFillPopup.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D120D43EFCF007D44EE /* FormFillPopup.mm */; }; + DE963D1F0D43EFCF007D44EE /* FormFillController.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D130D43EFCF007D44EE /* FormFillController.mm */; }; + DE963D200D43EFCF007D44EE /* KeychainAutoCompleteSession.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE963D150D43EFCF007D44EE /* KeychainAutoCompleteSession.mm */; settings = {COMPILER_FLAGS = "-DNO_NSPR_10_SUPPORT"; }; }; DEA5484A0A251FA900186C93 /* GeckoUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEA548490A251FA900186C93 /* GeckoUtils.cpp */; }; DEA5484B0A251FA900186C93 /* GeckoUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEA548490A251FA900186C93 /* GeckoUtils.cpp */; }; DEB608CA0CAF0964006C34F1 /* throbber-00.tiff in Resources */ = {isa = PBXBuildFile; fileRef = DEB608C90CAF0964006C34F1 /* throbber-00.tiff */; }; @@ -2472,6 +2480,14 @@ DE8AD69F0ADB4E38009D44F6 /* bm_horizontal_separator.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = bm_horizontal_separator.tiff; path = resources/images/chrome/bm_horizontal_separator.tiff; sourceTree = SOURCE_ROOT; }; DE8C61FE0AB67CDA00078871 /* hidemanager.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = hidemanager.tiff; path = resources/images/toolbar/hidemanager.tiff; sourceTree = SOURCE_ROOT; }; DE8EEA0B0D39A2A500BB96C1 /* dom_json.xpt */ = {isa = PBXFileReference; lastKnownFileType = file; name = dom_json.xpt; path = ../dist/bin/components/dom_json.xpt; sourceTree = SOURCE_ROOT; }; + DE963D110D43EFCF007D44EE /* AutoCompleteUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AutoCompleteUtils.mm; path = src/formfill/AutoCompleteUtils.mm; sourceTree = SOURCE_ROOT; }; + DE963D120D43EFCF007D44EE /* FormFillPopup.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FormFillPopup.mm; path = src/formfill/FormFillPopup.mm; sourceTree = SOURCE_ROOT; }; + DE963D130D43EFCF007D44EE /* FormFillController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FormFillController.mm; path = src/formfill/FormFillController.mm; sourceTree = SOURCE_ROOT; }; + DE963D140D43EFCF007D44EE /* KeychainAutoCompleteSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KeychainAutoCompleteSession.h; path = src/formfill/KeychainAutoCompleteSession.h; sourceTree = SOURCE_ROOT; }; + DE963D150D43EFCF007D44EE /* KeychainAutoCompleteSession.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = KeychainAutoCompleteSession.mm; path = src/formfill/KeychainAutoCompleteSession.mm; sourceTree = SOURCE_ROOT; }; + DE963D160D43EFCF007D44EE /* FormFillPopup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FormFillPopup.h; path = src/formfill/FormFillPopup.h; sourceTree = SOURCE_ROOT; }; + DE963D170D43EFCF007D44EE /* FormFillController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FormFillController.h; path = src/formfill/FormFillController.h; sourceTree = SOURCE_ROOT; }; + DE963D180D43EFCF007D44EE /* AutoCompleteUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AutoCompleteUtils.h; path = src/formfill/AutoCompleteUtils.h; sourceTree = SOURCE_ROOT; }; DEA548490A251FA900186C93 /* GeckoUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = GeckoUtils.cpp; path = src/extensions/GeckoUtils.cpp; sourceTree = SOURCE_ROOT; }; DEB608C90CAF0964006C34F1 /* throbber-00.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = "throbber-00.tiff"; path = "resources/images/throbber/throbber-00.tiff"; sourceTree = SOURCE_ROOT; }; DEB968140B0D8DF70023F8B1 /* KeychainItem.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = KeychainItem.m; path = src/formfill/KeychainItem.m; sourceTree = ""; }; @@ -3525,6 +3541,14 @@ 335639BD0C84E8B900DC4D06 /* Keychain */ = { isa = PBXGroup; children = ( + DE963D180D43EFCF007D44EE /* AutoCompleteUtils.h */, + DE963D110D43EFCF007D44EE /* AutoCompleteUtils.mm */, + DE963D170D43EFCF007D44EE /* FormFillController.h */, + DE963D130D43EFCF007D44EE /* FormFillController.mm */, + DE963D160D43EFCF007D44EE /* FormFillPopup.h */, + DE963D120D43EFCF007D44EE /* FormFillPopup.mm */, + DE963D140D43EFCF007D44EE /* KeychainAutoCompleteSession.h */, + DE963D150D43EFCF007D44EE /* KeychainAutoCompleteSession.mm */, 7BB8FC190D2D589100CC63B0 /* KeychainDenyList.h */, 7BB8FC1A0D2D589100CC63B0 /* KeychainDenyList.mm */, DEB968170B0D8E0B0023F8B1 /* KeychainItem.h */, @@ -5295,6 +5319,10 @@ C79573880D35314D0028A773 /* XMLSearchPluginParser.mm in Sources */, C795738A0D35314D0028A773 /* OpenSearchParser.mm in Sources */, 33E1EA100D34550A00910BBD /* AddSearchProviderHandler.mm in Sources */, + DE963D190D43EFCF007D44EE /* AutoCompleteUtils.mm in Sources */, + DE963D1A0D43EFCF007D44EE /* FormFillPopup.mm in Sources */, + DE963D1B0D43EFCF007D44EE /* FormFillController.mm in Sources */, + DE963D1C0D43EFCF007D44EE /* KeychainAutoCompleteSession.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5468,6 +5496,10 @@ C795738C0D35314D0028A773 /* XMLSearchPluginParser.mm in Sources */, C795738E0D35314D0028A773 /* OpenSearchParser.mm in Sources */, 33E1EA120D34550A00910BBD /* AddSearchProviderHandler.mm in Sources */, + DE963D1D0D43EFCF007D44EE /* AutoCompleteUtils.mm in Sources */, + DE963D1E0D43EFCF007D44EE /* FormFillPopup.mm in Sources */, + DE963D1F0D43EFCF007D44EE /* FormFillController.mm in Sources */, + DE963D200D43EFCF007D44EE /* KeychainAutoCompleteSession.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/mozilla/camino/src/browser/BrowserWrapper.h b/mozilla/camino/src/browser/BrowserWrapper.h index 2a1c95ca1cf..eb81e562a9b 100644 --- a/mozilla/camino/src/browser/BrowserWrapper.h +++ b/mozilla/camino/src/browser/BrowserWrapper.h @@ -40,6 +40,7 @@ @class BrowserWindowController; @class ToolTip; +@class FormFillController; @class AutoCompleteTextField; @class RolloverImageButton; @@ -149,6 +150,7 @@ class nsIArray; CHBrowserView* mBrowserView; // retained ToolTip* mToolTip; + FormFillController* mFormFillController; // strong NSMutableArray* mStatusStrings; // current status bar messages, STRONG NSMutableSet* mLoadingResources; // page resources currently loading, STRONG diff --git a/mozilla/camino/src/browser/BrowserWrapper.mm b/mozilla/camino/src/browser/BrowserWrapper.mm index c64cb861c6d..3df96bbe8ae 100644 --- a/mozilla/camino/src/browser/BrowserWrapper.mm +++ b/mozilla/camino/src/browser/BrowserWrapper.mm @@ -46,6 +46,7 @@ #import "BrowserTabView.h" #import "BrowserTabViewItem.h" #import "ToolTip.h" +#import "FormFillController.h" #import "PageProxyIcon.h" #import "KeychainService.h" #import "AutoCompleteTextField.h" @@ -166,6 +167,9 @@ enum StatusPriority { mToolTip = [[ToolTip alloc] init]; + mFormFillController = [[FormFillController alloc] init]; + [mFormFillController attachToBrowser:mBrowserView]; + //[self setSiteIconImage:[NSImage imageNamed:@"globe_ico"]]; //[self setSiteIconURI: [NSString string]]; @@ -199,6 +203,7 @@ enum StatusPriority { [mToolTip release]; [mDisplayTitle release]; + [mFormFillController release]; [mPendingURI release]; NS_IF_RELEASE(mBlockedPopups); diff --git a/mozilla/camino/src/extensions/GeckoUtils.cpp b/mozilla/camino/src/extensions/GeckoUtils.cpp index 84778517c0f..bf6b33a3a27 100644 --- a/mozilla/camino/src/extensions/GeckoUtils.cpp +++ b/mozilla/camino/src/extensions/GeckoUtils.cpp @@ -63,7 +63,11 @@ #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" +#include "nsIPresShell.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsIDOMWindowInternal.h" #include "nsIDOMNSHTMLElement.h" @@ -352,3 +356,45 @@ void GeckoUtils::GetIntrisicSize(nsIDOMWindow* aWindow, PRInt32* outWidth, PRIn return; } + +PRBool GeckoUtils::GetFrameInScreenCoordinates(nsIDOMElement* aElement, nsIntRect* aRect) +{ + nsCOMPtr content = do_QueryInterface(aElement); + if (!content) + return PR_FALSE; + + nsCOMPtr doc = content->GetDocument(); + if (!doc) + return PR_FALSE; + + nsCOMPtr presShell = doc->GetPrimaryShell(); + if (!presShell) + return PR_FALSE; + + nsIFrame* frame = presShell->GetPrimaryFrameFor(content); + if (!frame) + return PR_FALSE; + + *aRect = frame->GetScreenRectExternal(); + + return PR_TRUE; +} + +void GeckoUtils::ScrollElementIntoView(nsIDOMElement* aElement) +{ + nsCOMPtr content = do_QueryInterface(aElement); + if (!content) + return; + + nsCOMPtr doc = content->GetDocument(); + if (!doc) + return; + + nsCOMPtr presShell = doc->GetPrimaryShell(); + if (!presShell) + return; + + presShell->ScrollContentIntoView(content, NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE, + NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE); +} + diff --git a/mozilla/camino/src/extensions/GeckoUtils.h b/mozilla/camino/src/extensions/GeckoUtils.h index 333bbbd8c3a..6ba125823e2 100644 --- a/mozilla/camino/src/extensions/GeckoUtils.h +++ b/mozilla/camino/src/extensions/GeckoUtils.h @@ -42,6 +42,7 @@ #include "nsString.h" #include "jsapi.h" #include "nsIJSContextStack.h" +#include "nsRect.h" class nsIDOMWindow; class nsIDOMNode; @@ -81,6 +82,13 @@ class GeckoUtils /* Finds the preferred size (ie the minimum size where scrollbars are not needed) of the content window. */ static void GetIntrisicSize(nsIDOMWindow* aWindow, PRInt32* outWidth, PRInt32* outHeight); + + // Finds the screen location (nsIntRect) in screen coordinates of a DOM Element. + // Returns PR_FALSE if the function fails. + static PRBool GetFrameInScreenCoordinates(nsIDOMElement* aElement, nsIntRect* aRect); + + // Given a DOM Element, scroll the view so that the element is shown + static void ScrollElementIntoView(nsIDOMElement* aElement); }; /* Stack-based utility that will push a null JSContext onto the JS stack during the diff --git a/mozilla/camino/src/formfill/AutoCompleteUtils.h b/mozilla/camino/src/formfill/AutoCompleteUtils.h new file mode 100644 index 00000000000..0bc6a1fc3c6 --- /dev/null +++ b/mozilla/camino/src/formfill/AutoCompleteUtils.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + +// AutoCompleteResults +// +// Container object for generic auto complete. +// Holds the search string, array of matched objects and the default item. +// +@interface AutoCompleteResults : NSObject +{ + NSString* mSearchString; // strong + NSArray* mMatches; // strong + int mDefaultIndex; +} +- (NSString*)searchString; +- (void)setSearchString:(NSString*)string; + +- (NSArray*)matches; +- (void)setMatches:(NSArray*)matches; + +- (int)defaultIndex; +- (void)setDefaultIndex:(int)defaultIndex; + +@end + +// AutoCompleteListener +// +// This defines the protocol methods for the object that listens for auto complete +// results. |onAutoComplete| is called by the object that searches the data and +// the results are returned to the originating caller as AutoCompleteResults. +// +@protocol AutoCompleteListener +- (void)autoCompleteFoundResults:(AutoCompleteResults*)results; +@end + +// AutoCompleteSession +// +// An AutoCompleteSession object listens for search requests and searches a set of data +// |startAutoCompleteWithSearch| initiates the process. Previous results are passed in +// as well as the listener object for when the search is complete. +// +@protocol AutoCompleteSession +- (void)startAutoCompleteWithSearch:(NSString*)searchString + previousResults:(AutoCompleteResults*)previousSearchResults + listener:(id)listener; +@end diff --git a/mozilla/camino/src/formfill/AutoCompleteUtils.mm b/mozilla/camino/src/formfill/AutoCompleteUtils.mm new file mode 100644 index 00000000000..0e72fa11aca --- /dev/null +++ b/mozilla/camino/src/formfill/AutoCompleteUtils.mm @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "AutoCompleteUtils.h" + +@implementation AutoCompleteResults + +- (void)dealloc +{ + [mSearchString release]; + [mMatches release]; + + [super dealloc]; +} + +- (NSString*)searchString +{ + return mSearchString; +} + +- (void)setSearchString:(NSString*)string +{ + [mSearchString release]; + + // The caller might change the string, so keep a copy. + mSearchString = [string copy]; +} + +- (NSArray*)matches +{ + return mMatches; +} + +- (void)setMatches:(NSArray*)matches +{ + if (mMatches != matches) { + [mMatches release]; + mMatches = [matches retain]; + } +} + +- (int)defaultIndex +{ + return mDefaultIndex; +} + +- (void)setDefaultIndex:(int)index +{ + mDefaultIndex = index; +} + +@end diff --git a/mozilla/camino/src/formfill/FormFillController.h b/mozilla/camino/src/formfill/FormFillController.h new file mode 100644 index 00000000000..a482e92bddd --- /dev/null +++ b/mozilla/camino/src/formfill/FormFillController.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import +#import "AutoCompleteUtils.h" + +#include "nsIDOMEventListener.h" + +extern const int kFormFillMaxRows; + +@class KeychainAutoCompleteSession; +@class CHBrowserView; +@class FormFillPopup; +@class FormFillController; + +class nsIDOMHTMLInputElement; + +// FormFillListener +// +// The FormFillListener object listens for DOM events and the corresponding +// methods in the FormFillController are called for handling. +// +class FormFillListener : public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + FormFillListener(FormFillController* aController); + +protected: + FormFillController* mController; // weak +}; + +// FormFillController +// +// Manages the FormFillPopup windows that contain search results +// as well as sending search requests to the KeychainAutoCompleteSession +// and listening for search results. This can be extended to send +// search requests to a form history session as well. +@interface FormFillController : NSObject +{ + KeychainAutoCompleteSession* mKeychainSession; // strong + AutoCompleteResults* mResults; // strong + FormFillListener* mListener; // strong + FormFillPopup* mPopupWindow; // strong + + CHBrowserView* mBrowserView; // weak + nsIDOMHTMLInputElement* mFocusedInputElement; // weak + + // mCompleteResult determines if the current search should complete the default + // result when ready. This prevents backspace/delete from autocompleting. + BOOL mCompleteResult; + + // mUsernameFillEnabled determines whether we send searches to the Keychain + // Service. Form fill history value can be added here as well. + BOOL mUsernameFillEnabled; +} + +- (void)attachToBrowser:(CHBrowserView*)browser; + +// Callback function for when a row in the focused popup window is clicked. +- (void)popupSelected; + +@end diff --git a/mozilla/camino/src/formfill/FormFillController.mm b/mozilla/camino/src/formfill/FormFillController.mm new file mode 100644 index 00000000000..c74e5d1f228 --- /dev/null +++ b/mozilla/camino/src/formfill/FormFillController.mm @@ -0,0 +1,748 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "FormFillController.h" +#import "CHBrowserView.h" +#import "FormFillPopup.h" +#import "KeychainAutoCompleteSession.h" +#import "PreferenceManager.h" + +#import "NSString+Gecko.h" +#import "NSArray+Utils.h" + +#include "GeckoUtils.h" +#include "nsString.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMWindow.h" +#include "nsIDOMDocumentEvent.h" +#include "nsIPrivateDOMEvent.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMEvent.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMNSHTMLInputElement.h" +#include "nsIDOMKeyEvent.h" + +const int kFormFillMaxRows = 10; + +@interface FormFillController(Private) + +// Listener and autocomplete initialization and cleanup +- (void)browserResized:(NSNotification*)notification; +- (void)addResizeObserver:(NSWindow*)browserWindow; +- (void)removeResizeObserver:(NSWindow*)browserWindow; +- (void)addWindowListeners:(nsIDOMWindow*)aWindow; +- (void)removeWindowListeners:(nsIDOMWindow*)aWindow; +- (void)startControllingInputElement:(nsIDOMHTMLInputElement*)aInputElement; +- (void)stopControllingInputElement; + +// Popup window management +- (BOOL)isPopupOpen; +- (void)openPopup; +- (void)closePopup; +- (void)shiftRowSelectionBy:(int)aRows; + +// Autocomplete methods +- (void)startSearch:(NSString*)searchString; +- (void)dataReady; +- (void)autoCompleteFieldText; +- (void)filledAutoCompleteFieldText; + +// Event handlers +- (void)focus:(nsIDOMEvent*)aEvent; +- (void)blur:(nsIDOMEvent*)aEvent; +- (void)unload:(nsIDOMEvent*)aEvent; +- (void)submit:(nsIDOMEvent*)aEvent; +- (void)input:(nsIDOMEvent*)aEvent; +- (void)keyPress:(nsIDOMEvent*)aEvent; +- (BOOL)handleKeyNavigation:(int)aKey; + +// Utility methods +- (BOOL)IsCaretAtEndOfLine; + +@end + +NS_IMPL_ISUPPORTS1(FormFillListener, nsIDOMEventListener) + +FormFillListener::FormFillListener(FormFillController* aController) +: mController(aController) +{ +} + +NS_IMETHODIMP FormFillListener::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString type; + aEvent->GetType(type); + + if (type.Equals(NS_LITERAL_STRING("focus"))) + [mController focus:aEvent]; + else if (type.Equals(NS_LITERAL_STRING("blur"))) + [mController blur:aEvent]; + else if (type.Equals(NS_LITERAL_STRING("unload"))) + [mController unload:aEvent]; + else if (type.Equals(NS_LITERAL_STRING("submit"))) + [mController submit:aEvent]; + else if (type.Equals(NS_LITERAL_STRING("input"))) + [mController input:aEvent]; + else if (type.Equals(NS_LITERAL_STRING("keypress"))) + [mController keyPress:aEvent]; + return NS_OK; +} + +@implementation FormFillController + +- (id)init +{ + if ((self = [super init])) { + // mListener captures DOM and auto complete events. + mListener = new FormFillListener(self); + NS_ADDREF(mListener); + + // Initialize the password fill session. + // History form fill session can also be added when someone writes it. + mKeychainSession = [[KeychainAutoCompleteSession alloc] init]; + } + + return self; +} + +- (void)dealloc +{ + // Remove ourselves as a focus listener from cached view. + nsCOMPtr domWindow = [mBrowserView contentWindow]; + if (domWindow) + [self removeWindowListeners:domWindow]; + + [self removeResizeObserver:[mBrowserView nativeWindow]]; + + [mResults release]; + [mKeychainSession release]; + [mPopupWindow release]; + NS_IF_RELEASE(mListener); + + [super dealloc]; +} + +- (void)attachToBrowser:(CHBrowserView*)browser +{ + if (!browser) + return; + + mBrowserView = browser; + + // Listen for focus events on the domWindow of the browser view. + nsCOMPtr domWindow = [mBrowserView contentWindow]; + if (domWindow) + [self addWindowListeners:domWindow]; +} + +- (void)browserResized:(NSNotification*)notification +{ + [self closePopup]; +} + +- (void)addResizeObserver:(NSWindow*)browserWindow +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(browserResized:) + name:NSWindowDidResizeNotification + object:browserWindow]; +} + +- (void)removeResizeObserver:(NSWindow*)browserWindow +{ + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSWindowDidResizeNotification + object:browserWindow]; +} + +- (void)addWindowListeners:(nsIDOMWindow*)aWindow +{ + nsCOMPtr privateDOMWindow = do_QueryInterface(aWindow); + if (!privateDOMWindow) + return; + + nsPIDOMEventTarget* chromeEventHandler = privateDOMWindow->GetChromeEventHandler(); + + nsCOMPtr target = do_QueryInterface(chromeEventHandler); + if (!target) + return; + + target->AddEventListener(NS_LITERAL_STRING("focus"), + static_cast(mListener), + PR_TRUE); + + target->AddEventListener(NS_LITERAL_STRING("blur"), + static_cast(mListener), + PR_TRUE); + + target->AddEventListener(NS_LITERAL_STRING("unload"), + static_cast(mListener), + PR_TRUE); + + target->AddEventListener(NS_LITERAL_STRING("submit"), + static_cast(mListener), + PR_TRUE); + + target->AddEventListener(NS_LITERAL_STRING("input"), + static_cast(mListener), + PR_TRUE); + + target->AddEventListener(NS_LITERAL_STRING("keypress"), + static_cast(mListener), + PR_TRUE); +} + +- (void)removeWindowListeners:(nsIDOMWindow*)aWindow +{ + [self stopControllingInputElement]; + + nsCOMPtr privateDOMWindow = do_QueryInterface(aWindow); + if (!privateDOMWindow) + return; + + nsPIDOMEventTarget* chromeEventHandler = privateDOMWindow->GetChromeEventHandler(); + + nsCOMPtr target = do_QueryInterface(chromeEventHandler); + if (!target) + return; + + target->RemoveEventListener(NS_LITERAL_STRING("focus"), + static_cast(mListener), + PR_TRUE); + + target->RemoveEventListener(NS_LITERAL_STRING("blur"), + static_cast(mListener), + PR_TRUE); + + target->RemoveEventListener(NS_LITERAL_STRING("unload"), + static_cast(mListener), + PR_TRUE); + + target->RemoveEventListener(NS_LITERAL_STRING("submit"), + static_cast(mListener), + PR_TRUE); + + target->RemoveEventListener(NS_LITERAL_STRING("input"), + static_cast(mListener), + PR_TRUE); + + target->RemoveEventListener(NS_LITERAL_STRING("keypress"), + static_cast(mListener), + PR_TRUE); +} + +- (void)startControllingInputElement:(nsIDOMHTMLInputElement*)aInputElement +{ + // Make sure we're not still attached to an input element. + [self stopControllingInputElement]; + + // Set the autocomplete session for this input. Currently, only password autocomplete + // but other sessions like form fill history can be added here. + mUsernameFillEnabled = [mKeychainSession attachToInput:aInputElement]; + + // If this is not a valid auto-complete input, we can return now. + if (!mUsernameFillEnabled) + return; + + // Cache the input element. + mFocusedInputElement = aInputElement; + + // Create the popup window if it doesn't exist. + if (!mPopupWindow) { + mPopupWindow = [[FormFillPopup alloc] init]; + [mPopupWindow attachToController:self]; + } +} + +- (void)stopControllingInputElement +{ + if (mPopupWindow) { + [self closePopup]; + [mPopupWindow setItems:nil]; + } + + mFocusedInputElement = nsnull; + + // Stop sending search requests to auto-form fill. + mUsernameFillEnabled = NO; + + [mResults release]; + mResults = nil; +} + +-(void)autoCompleteFoundResults:(AutoCompleteResults*)results +{ + [mResults release]; + mResults = nil; + + if ([[results matches] count] > 0) { + mResults = [results retain]; + [self dataReady]; + } + else { + [mPopupWindow setItems:nil]; + [self closePopup]; + } +} + +- (BOOL)isPopupOpen +{ + return mPopupWindow ? [mPopupWindow isPopupOpen] : NO; +} + +- (void)openPopup +{ + // Only open popup if it's not already open. + if ([self isPopupOpen]) + return; + + // Make sure input field is visible before showing popup. + GeckoUtils::ScrollElementIntoView(mFocusedInputElement); + + nsIntRect inputIntRect; + if (!(GeckoUtils::GetFrameInScreenCoordinates(mFocusedInputElement, &inputIntRect))) + return; + + NSRect inputElementFrame = NSMakeRect(inputIntRect.x, inputIntRect.y, inputIntRect.width, inputIntRect.height); + + NSScreen* mainScreen = [[NSScreen screens] firstObject]; // NSArray category method + if (!mainScreen) + return; + + NSPoint origin = inputElementFrame.origin; + float width = NSWidth(inputElementFrame); + + // y-flip and subtract the control height to convert to cocoa coords + origin.y = NSMaxY([mainScreen frame]) - inputElementFrame.origin.y - inputElementFrame.size.height; + + // To account for the text box border, shift rectangle position to the right by 2 pixels + // and reduce the width by 3 pixels. + // TODO: check shift here, still not aligned sometimes. + origin.x += 2.0; + width -= 3.0; + + [mPopupWindow openPopup:[mBrowserView nativeWindow] withOrigin:origin width:width]; + + // Listen for resize events to close the popup. + [self addResizeObserver:[mBrowserView nativeWindow]]; +} + +- (void)closePopup +{ + if (mPopupWindow) { + // Deselecting the row prevents a flash when popup is opened and default is selected. + [mPopupWindow selectRow:-1]; + [mPopupWindow closePopup]; + [self removeResizeObserver:[mBrowserView nativeWindow]]; + } +} + +- (void)shiftRowSelectionBy:(int)aRows +{ + int row = [mPopupWindow selectedRow] + aRows; + + // pin result at top row + if (row < 0) + row = 0; + + // pin result at bottom row + int numRows = [mPopupWindow rowCount]; + if (row >= numRows) + row = numRows - 1; + + [mPopupWindow selectRow:row]; +} + +// +// popupSelected +// +// Called when a new item in the popup window is selected, either by mouse click +// or keyboard movement. The form field is set to the value of the selected item +// and an autocomplete event is sent for any listeners +- (void)popupSelected +{ + if (![self isPopupOpen] || !mResults) + return; + + int row = [mPopupWindow selectedRow]; + if (row < 0) + return; + + nsAutoString value; + [[mPopupWindow resultForRow:row] assignTo_nsAString:value]; + mFocusedInputElement->SetValue(value); + + // Auto-fill the input so that untyped letters are selected. + nsCOMPtr nsInput = do_QueryInterface(mFocusedInputElement); + if (nsInput) { + int searchLength = [[mResults searchString] length]; + nsInput->SetSelectionStart((PRInt32)searchLength); + } + + // Send an autocomplete DOM event any time auto fill is done. + [self filledAutoCompleteFieldText]; +} + +- (void)startSearch:(NSString*)searchString; +{ + // Check if password autocomplete is enabled. + // Form history autocomplete can be added here. + if (mUsernameFillEnabled) { + [mKeychainSession startAutoCompleteWithSearch:searchString + previousResults:mResults + listener:self]; + } +} + +- (void)dataReady +{ + [mPopupWindow setItems:[mResults matches]]; + + // Open the popup if more than one result is returned. + // Also require mCompleteResult since it prevents a backspace from opening popup + // even though we want to search on a backspace to get the larger result set. + if ([mPopupWindow rowCount] > 1 && mCompleteResult) + [self openPopup]; + else + [self closePopup]; + + // Prevents backspace from auto-completing the result we just erased. + if (mCompleteResult) + [self autoCompleteFieldText]; +} + +// +// autoCompleteFieldText +// +// Called when FormFillController should fill the text field with the default +// autocomplete result. +- (void)autoCompleteFieldText +{ + if (!mResults) + return; + + NSArray* matches = [mResults matches]; + + // Select the default if it's available. + // Otherwise, select the first username in the list. + int defaultIndex = [mResults defaultIndex]; + + nsAutoString value; + [[matches objectAtIndex:defaultIndex] assignTo_nsAString:value]; + mFocusedInputElement->SetValue(value); + + // Auto-fill the input such that untyped letters are selected. + nsCOMPtr nsInput = do_QueryInterface(mFocusedInputElement); + if (nsInput) { + int searchLength = [[mResults searchString] length]; + nsInput->SetSelectionStart((PRInt32)searchLength); + } + + // Select the default entry in the popup. + if ([self isPopupOpen]) + [mPopupWindow selectRow:defaultIndex]; + + // Send a DOM event any time auto fill is done. + [self filledAutoCompleteFieldText]; +} + +// +// filledAutoCompleteFieldText +// +// Called whenever the form element is filled. It sends a DOM event +// "DOMAutoComplete" that can be listened for, e.g., to fill a password +// when a username element is completed. +// +- (void)filledAutoCompleteFieldText +{ + if (!mFocusedInputElement) + return; + + nsCOMPtr domDoc; + mFocusedInputElement->GetOwnerDocument(getter_AddRefs(domDoc)); + + nsCOMPtr doc = do_QueryInterface(domDoc); + if (!doc) + return; + + nsCOMPtr event; + doc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event)); + nsCOMPtr privateEvent = do_QueryInterface(event); + if (!privateEvent) + return; + + event->InitEvent(NS_LITERAL_STRING("DOMAutoComplete"), PR_TRUE, PR_TRUE); + + privateEvent->SetTrusted(PR_TRUE); + + nsCOMPtr target = do_QueryInterface(mFocusedInputElement); + + PRBool defaultActionEnabled; + target->DispatchEvent(event, &defaultActionEnabled); +} + +// +// focus +// +// When a new element is selected, check whether we allow autocomplete. +// If autocomplete is allowed, start controlling the input to that element. +// +- (void)focus:(nsIDOMEvent*)aEvent +{ + nsCOMPtr target; + aEvent->GetTarget(getter_AddRefs(target)); + + nsCOMPtr inputElement = do_QueryInterface(target); + if (!inputElement) + return; + + nsAutoString type; + inputElement->GetType(type); + if (!type.LowerCaseEqualsLiteral("text")) + return; + + PRBool isReadOnly = PR_FALSE; + if (NS_FAILED(inputElement->GetReadOnly(&isReadOnly)) || isReadOnly) + return; + + PRBool autoCompleteOverride = [[PreferenceManager sharedInstance] getBooleanPref:"wallet.crypto.autocompleteoverride" withSuccess:NULL]; + + if (!autoCompleteOverride) { + nsAutoString autocomplete; + inputElement->GetAttribute(NS_LITERAL_STRING("autocomplete"), autocomplete); + + if (autocomplete.EqualsIgnoreCase("off")) + return; + + nsCOMPtr form; + inputElement->GetForm(getter_AddRefs(form)); + if (form) { + form->GetAttribute(NS_LITERAL_STRING("autocomplete"), autocomplete); + if (autocomplete.EqualsIgnoreCase("off")) + return; + } + } + + [self startControllingInputElement:inputElement]; +} + +// +// blur +// +// Anytime focus moves from an element, stop autocompleting it until next +// input element is focused. +// +- (void)blur:(nsIDOMEvent*)aEvent +{ + if (mFocusedInputElement) + [self stopControllingInputElement]; +} + +// +// unload +// +// Stop autocompleting on a page if it is unloaded. +// +- (void)unload:(nsIDOMEvent*)aEvent +{ + if (mFocusedInputElement) { + nsCOMPtr target; + aEvent->GetTarget(getter_AddRefs(target)); + + nsCOMPtr eventDoc = do_QueryInterface(target); + nsCOMPtr inputDoc; + mFocusedInputElement->GetOwnerDocument(getter_AddRefs(inputDoc)); + + if (eventDoc == inputDoc) + [self stopControllingInputElement]; + } +} + +// +// submit +// +// If a form is submitted, stop autocompleting. +// +- (void)submit:(nsIDOMEvent*)aEvent +{ + if (mFocusedInputElement) + [self stopControllingInputElement]; +} + +// +// input +// +// If an HTML input box is being controlled, do a search when input occurs. +// +- (void)input:(nsIDOMEvent*)aEvent +{ + nsCOMPtr target; + aEvent->GetTarget(getter_AddRefs(target)); + + nsCOMPtr input = do_QueryInterface(target); + + if (input && mFocusedInputElement == input) { + nsAutoString value; + mFocusedInputElement->GetValue(value); + [self startSearch:[NSString stringWith_nsAString:value]]; + } +} + +// +// keyPress +// +// This is triggered when a key is pressed but before an 'input' is triggered. +// It also handles non-input keys like arrow keys. +// +- (void)keyPress:(nsIDOMEvent*)aEvent +{ + if (!mFocusedInputElement) + return; + + nsCOMPtr keyEvent = do_QueryInterface(aEvent); + if (!keyEvent) + return; + + // By default, allow keystroke to continue to the next listener. + BOOL cancel = NO; + + // By default, autocomplete on keystrokes that later trigger 'input' events. + mCompleteResult = YES; + + PRUint32 k; + keyEvent->GetKeyCode(&k); + switch (k) { + case nsIDOMKeyEvent::DOM_VK_UP: + case nsIDOMKeyEvent::DOM_VK_DOWN: + case nsIDOMKeyEvent::DOM_VK_PAGE_UP: + case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: + cancel = [self handleKeyNavigation:k]; + break; + case nsIDOMKeyEvent::DOM_VK_ESCAPE: + case nsIDOMKeyEvent::DOM_VK_RETURN: + cancel = [self isPopupOpen]; + [self closePopup]; + break; + case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: + case nsIDOMKeyEvent::DOM_VK_DELETE: + // Don't allow autocomplete of 'input' event if it's due to a back space or + // delete. + mCompleteResult = NO; + break; + } + + if (cancel) { + aEvent->StopPropagation(); + aEvent->PreventDefault(); + } +} + +// +// handleKeyNavigation +// +// Handles key events that are for navigation of the popup window. +// Should return YES if the event should be cancelled and not propagated. +// +- (BOOL)handleKeyNavigation:(int)aKey +{ + switch (aKey) { + case nsIDOMKeyEvent::DOM_VK_UP: + if ([self isPopupOpen]) { + [self shiftRowSelectionBy:-1]; + [self popupSelected]; + return YES; + } + break; + case nsIDOMKeyEvent::DOM_VK_DOWN: + if ([self isPopupOpen]) { + [self shiftRowSelectionBy:1]; + [self popupSelected]; + return YES; + } + else if ([self IsCaretAtEndOfLine]) { + nsAutoString value; + mFocusedInputElement->GetValue(value); + [self startSearch:[NSString stringWith_nsAString:value]]; + return YES; + } + break; + case nsIDOMKeyEvent::DOM_VK_PAGE_UP: + if ([self isPopupOpen]) { + [self shiftRowSelectionBy:-kFormFillMaxRows]; + [self popupSelected]; + return YES; + } + break; + case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: + if ([self isPopupOpen]) { + [self shiftRowSelectionBy:kFormFillMaxRows]; + [self popupSelected]; + return YES; + } + else { + nsAutoString value; + mFocusedInputElement->GetValue(value); + [self startSearch:[NSString stringWith_nsAString:value]]; + return YES; + } + break; + } + + return NO; +} + +- (BOOL)IsCaretAtEndOfLine +{ + if (!mFocusedInputElement) + return NO; + + nsCOMPtr nsInput = do_QueryInterface(mFocusedInputElement); + if (!nsInput) + return NO; + + PRInt32 selectStart; + nsInput->GetSelectionStart(&selectStart); + + PRInt32 textLength; + nsInput->GetTextLength(&textLength); + + return (selectStart == textLength); +} + +@end diff --git a/mozilla/camino/src/formfill/FormFillPopup.h b/mozilla/camino/src/formfill/FormFillPopup.h new file mode 100644 index 00000000000..c44cb1fb830 --- /dev/null +++ b/mozilla/camino/src/formfill/FormFillPopup.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + +@class FormFillController; + +// +// FormFillPopupWindow +// +// The popup window needs to look like a "key" (activated) window even thought it's +// a child window. This subclass overrides |isKeyWindow| to return YES so that it is +// able to be a key window (and have activated scrollbars, etc) but not steal focus. +// +@interface FormFillPopupWindow : NSPanel +@end + +// FormFillPopup +// +// Manages the display of the popup window and receives data from the FormFillController. +// +@interface FormFillPopup : NSObject +{ + FormFillPopupWindow* mPopupWin; // strong + NSArray* mItems; // strong + + NSTableView* mTableView; // weak + FormFillController* mController; // weak +} + +- (void)attachToController:(FormFillController*)controller; + +// openPopup expects an origin point in Cocoa coordinates and a width that +// should be equal to the text box. +- (void)openPopup:(NSWindow*)browserWindow withOrigin:(NSPoint)origin width:(float)width; +- (void)resizePopup; +- (void)closePopup; +- (BOOL)isPopupOpen; + +- (int)visibleRows; +- (int)rowCount; +- (void)selectRow:(int)index; +- (int)selectedRow; + +- (void)setItems:(NSArray*)items; +- (NSString*)resultForRow:(int)index; + +@end diff --git a/mozilla/camino/src/formfill/FormFillPopup.mm b/mozilla/camino/src/formfill/FormFillPopup.mm new file mode 100644 index 00000000000..cc1b1b2a9ef --- /dev/null +++ b/mozilla/camino/src/formfill/FormFillPopup.mm @@ -0,0 +1,230 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "FormFillPopup.h" +#import "FormFillController.h" + +@implementation FormFillPopupWindow + +- (BOOL)isKeyWindow +{ + return YES; +} + +@end + +@interface FormFillPopup(Private) + +- (void)onRowClicked:(NSNotification *)aNote; + +@end + +@implementation FormFillPopup + +- (id)init +{ + if ((self = [super init])) { + // Construct and configure the popup window. + mPopupWin = [[FormFillPopupWindow alloc] initWithContentRect:NSMakeRect(0,0,0,0) + styleMask:NSNonactivatingPanelMask + backing:NSBackingStoreBuffered + defer:NO]; + + [mPopupWin setReleasedWhenClosed:NO]; + [mPopupWin setHasShadow:YES]; + [mPopupWin setAlphaValue:0.9]; + + // Construct and configure the view. + mTableView = [[[NSTableView alloc] initWithFrame:NSMakeRect(0,0,0,0)] autorelease]; + [mTableView setIntercellSpacing:NSMakeSize(1, 2)]; + [mTableView setTarget:self]; + [mTableView setAction:@selector(onRowClicked:)]; + + // Create the text column (only one). + NSTableColumn* column = [[[NSTableColumn alloc] initWithIdentifier:@"usernames"] autorelease]; + [mTableView addTableColumn:column]; + + // Hide the table header. + [mTableView setHeaderView:nil]; + + [mTableView setDataSource:self]; + + NSScrollView *scrollView = [[[NSScrollView alloc] initWithFrame:NSMakeRect(0,0,0,0)] autorelease]; + [scrollView setHasVerticalScroller:YES]; + [scrollView setAutohidesScrollers:YES]; + [[scrollView verticalScroller] setControlSize:NSSmallControlSize]; + + [scrollView setDocumentView:mTableView]; + + [mPopupWin setContentView:scrollView]; + } + + return self; +} + +- (void)dealloc +{ + [mPopupWin release]; + [mItems release]; + + [super dealloc]; +} + +- (void)attachToController:(FormFillController*)controller +{ + mController = controller; +} + +- (BOOL)isPopupOpen +{ + return [mPopupWin isVisible]; +} + +- (void)openPopup:(NSWindow*)browserWindow withOrigin:(NSPoint)origin width:(float)width +{ + // Set the size of the popup window. + NSRect tableViewFrame = [mTableView frame]; + tableViewFrame.size.width = width; + [mTableView setFrame:tableViewFrame]; + + // Size the panel correctly. + tableViewFrame.size.height = (int)([mTableView rowHeight] + [mTableView intercellSpacing].height) * [self visibleRows]; + [mPopupWin setContentSize:tableViewFrame.size]; + [mPopupWin setFrameTopLeftPoint:origin]; + + // Show the popup. + if (![mPopupWin isVisible]) { + [browserWindow addChildWindow:mPopupWin ordered:NSWindowAbove]; + [mPopupWin orderFront:nil]; + } +} + +- (void)resizePopup +{ + // Don't resize if popup isn't visible. + if (![mPopupWin isVisible]) + return; + + if ([self visibleRows] == 0) { + [self closePopup]; + return; + } + + NSRect popupWinFrame = [mPopupWin frame]; + int tableHeight = (int)([mTableView rowHeight] + [mTableView intercellSpacing].height) * [self visibleRows]; + + popupWinFrame.origin.y += NSHeight(popupWinFrame) - tableHeight; + popupWinFrame.size.height = tableHeight; + + [mPopupWin setFrame:popupWinFrame display:YES]; +} + +- (void)closePopup +{ + // We can get -closePopup even if we didn't show it. + if ([mPopupWin isVisible]) { + [[mPopupWin parentWindow] removeChildWindow:mPopupWin]; + [mPopupWin orderOut:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } +} + +- (void)onRowClicked:(NSNotification *)aNote +{ + [mController popupSelected]; + [self closePopup]; +} + +- (int)visibleRows +{ + int minRows = [self rowCount]; + return (minRows < kFormFillMaxRows) ? minRows : kFormFillMaxRows; +} + +- (int)rowCount +{ + if (!mItems) + return 0; + + return [mItems count]; +} + +- (void)selectRow:(int)index +{ + if (index == -1) + [mTableView deselectAll:self]; + else { + [mTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO]; + [mTableView scrollRowToVisible:index]; + } +} + +- (int)selectedRow +{ + return [mTableView selectedRow]; +} + +- (NSString*)resultForRow:(int)index +{ + return [mItems objectAtIndex:index]; +} + +- (void)setItems:(NSArray*)items +{ + if (items != mItems) { + [mItems release]; + mItems = [items retain]; + } + + // Update the view any time we get new data + [mTableView noteNumberOfRowsChanged]; + [self resizePopup]; +} + +// methods for table view interaction +-(int)numberOfRowsInTableView:(NSTableView*)aTableView +{ + return [self rowCount]; +} + +-(id)tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn row:(int)aRowIndex +{ + return [self resultForRow:aRowIndex]; +} + +@end diff --git a/mozilla/camino/src/formfill/KeychainAutoCompleteSession.h b/mozilla/camino/src/formfill/KeychainAutoCompleteSession.h new file mode 100644 index 00000000000..8b3968be539 --- /dev/null +++ b/mozilla/camino/src/formfill/KeychainAutoCompleteSession.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import +#import "AutoCompleteUtils.h" + +#include "nsIDOMEventListener.h" +#include "nsCOMPtr.h" + +class nsIDOMHTMLInputElement; + +// +// KeychainAutoCompleteDOMListener +// +// Listens for password fill requests from the FormFillController when a +// username is entered. +class KeychainAutoCompleteDOMListener : public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + void SetElements(nsIDOMHTMLInputElement* usernameElement, + nsIDOMHTMLInputElement* passwordElement); + +protected: + // Fills the password with the keychain data from the cached username input + // element. + void FillPassword(); + + // Pointers to the login input elements so that the form doesn't need to + // be searched when the same input element is attached again. + nsCOMPtr mUsernameElement; // strong + nsCOMPtr mPasswordElement; // strong +}; + +@interface KeychainAutoCompleteSession : NSObject +{ + KeychainAutoCompleteDOMListener* mDOMListener; // strong + NSMutableArray* mUsernames; // strong + NSString* mDefaultUser; // strong + + // Cache the username input element so that we don't reread the keychain for + // the same element. + nsIDOMHTMLInputElement* mUsernameElement; // weak +} + +- (BOOL)attachToInput:(nsIDOMHTMLInputElement*)usernameElement; + +@end diff --git a/mozilla/camino/src/formfill/KeychainAutoCompleteSession.mm b/mozilla/camino/src/formfill/KeychainAutoCompleteSession.mm new file mode 100644 index 00000000000..8c4f222f6b9 --- /dev/null +++ b/mozilla/camino/src/formfill/KeychainAutoCompleteSession.mm @@ -0,0 +1,369 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Bryan Atwood + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bryan Atwood + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#import "NSString+Gecko.h" + +#import "KeychainAutoCompleteSession.h" +#import "KeychainItem.h" +#import "KeychainService.h" + +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMEventTarget.h"; +#include "nsIDOMHTMLInputElement.h"; + +static BOOL FindPasswordField(nsIDOMHTMLInputElement* inUsername, nsIDOMHTMLInputElement** outPassword); + +static void GetFormInfoForInput(nsIDOMHTMLInputElement* aElement, + NSString** host, + NSString** asciiHost, + PRInt32* port, + NSString** scheme); + +NS_IMPL_ISUPPORTS1(KeychainAutoCompleteDOMListener, nsIDOMEventListener) + +NS_IMETHODIMP KeychainAutoCompleteDOMListener::HandleEvent(nsIDOMEvent *aEvent) +{ + nsAutoString type; + aEvent->GetType(type); + if (type.EqualsLiteral("DOMAutoComplete")) { + // Don't fill password if autofilling has been disabled. + if(![[KeychainService instance] formPasswordFillIsEnabled]) + return NS_OK; + + nsCOMPtr target; + if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) + return NS_OK; + + nsCOMPtr userElement = do_QueryInterface(target); + if (userElement && userElement == mUsernameElement) + FillPassword(); + } + + return NS_OK; +} + +void KeychainAutoCompleteDOMListener::SetElements(nsIDOMHTMLInputElement* usernameElement, + nsIDOMHTMLInputElement* passwordElement) +{ + mUsernameElement = usernameElement; + mPasswordElement = passwordElement; +} + +void KeychainAutoCompleteDOMListener::FillPassword() +{ + if (!mUsernameElement || !mPasswordElement) + return; + + // Get the entered username. + nsAutoString nsUsername; + mUsernameElement->GetValue(nsUsername); + NSString* username = [NSString stringWith_nsAString:nsUsername]; + + NSString* host; + NSString* asciiHost; + PRInt32 port; + NSString* scheme; + + GetFormInfoForInput(mUsernameElement, &host, &asciiHost, &port, &scheme); + + KeychainService* keychain = [KeychainService instance]; + KeychainItem* keychainEntry = [keychain findWebFormKeychainEntryForUsername:username + forHost:host + port:port + scheme:scheme]; + if (!keychainEntry && ![asciiHost isEqualToString:host]) { + keychainEntry = [keychain findWebFormKeychainEntryForUsername:username + forHost:asciiHost + port:port + scheme:scheme]; + } + if (!keychainEntry) + return; + + nsAutoString password; + [[keychainEntry password] assignTo_nsAString:password]; + + if (password.IsEmpty()) + return; + + mPasswordElement->SetValue(password); + + // Now that we have actually filled the password, cache the keychain entry. + nsCOMPtr domDoc; + nsresult rv = mUsernameElement->GetOwnerDocument(getter_AddRefs(domDoc)); + if (NS_FAILED(rv) || !domDoc) + return; + + nsCOMPtr doc (do_QueryInterface(domDoc)); + if (!doc) + return; + + nsIURI* docURL = doc->GetDocumentURI(); + if (!docURL) + return; + + nsCAutoString uriCAString; + rv = docURL->GetSpec(uriCAString); + if (NS_FAILED(rv)) + return; + + NSString* uri = [NSString stringWithCString:uriCAString.get()]; + if (uri) + [keychain cacheKeychainEntry:keychainEntry forKey:uri]; +} + +@implementation KeychainAutoCompleteSession + +- (id)init +{ + if ((self = [super init])) { + mUsernames = [[NSMutableArray alloc] init]; + mDOMListener = new KeychainAutoCompleteDOMListener(); + NS_IF_ADDREF(mDOMListener); + } + + return self; +} + +- (void)dealloc +{ + [mUsernames release]; + [mDefaultUser release]; + + NS_IF_RELEASE(mDOMListener); + + [super dealloc]; +} + +// +// attachToInput +// +// Sets the session to cache the usernames for a username element +// Returns NO if this element is not a valid username element and should +// be remembered by the calling function so that autocomplete requests are not sent. +- (BOOL)attachToInput:(nsIDOMHTMLInputElement*)usernameElement +{ + // Don't listen if element is empty or password fill is disabled. + KeychainService* keychain = [KeychainService instance]; + if (!usernameElement || ![keychain formPasswordFillIsEnabled]) + return NO; + + if (mUsernameElement == usernameElement) + return YES; + + // If there is no corresponding password, this element isn't listened to. + nsCOMPtr passwordElement; + if (!FindPasswordField(usernameElement, getter_AddRefs(passwordElement))) + return NO; + + // Get the host information so we can get the keychain entries below. + NSString* host; + NSString* asciiHost; + PRInt32 port; + NSString* scheme; + + GetFormInfoForInput(usernameElement, &host, &asciiHost, &port, &scheme); + + // If session is not cached, default to empty session. + [mUsernames removeAllObjects]; + [mDefaultUser release]; + mDefaultUser = nil; + mUsernameElement = usernameElement; + + // Cache all of the usernames in the object so that the keychain + // doesn't have to be read again. This should be a faster way to search and sort. + NSMutableArray* keychainEntries = + [NSMutableArray arrayWithArray:[keychain allWebFormKeychainItemsForHost:host port:port scheme:scheme]]; + + // And add the keychain items for the punycode host. + if (![asciiHost isEqualToString:host]) + [keychainEntries addObjectsFromArray:[keychain allWebFormKeychainItemsForHost:asciiHost port:port scheme:scheme]]; + + NSEnumerator* keychainEnumerator = [keychainEntries objectEnumerator]; + KeychainItem* item; + while ((item = [keychainEnumerator nextObject])) { + // Only add a username once since there may be duplicates. + NSString* username = [item username]; + if (![mUsernames containsObject:username]) + [mUsernames addObject:username]; + } + + // Get the default keychain and cache the username. + item = [keychain defaultFromKeychainItems:keychainEntries]; + if (item) + mDefaultUser = [[item username] copy]; + + // Sort usernames alphabetically. + [mUsernames sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + // Listen for DOM form fill events so that the password can + // be autofilled when a username is entered. + mDOMListener->SetElements(usernameElement, passwordElement); + nsCOMPtr targ = do_QueryInterface(usernameElement); + targ->AddEventListener(NS_LITERAL_STRING("DOMAutoComplete"), mDOMListener, PR_FALSE); + + return YES; +} + +// +// startAutoCompleteWithSearch +// +// Function that takes a search string and returns all usernames that match it. +// Assume that if this function is called that formfilling is enabled. If it's +// not enabled, don't create the session in the first place. +// +-(void)startAutoCompleteWithSearch:(NSString*)searchString + previousResults:(AutoCompleteResults*)previousSearchResults + listener:(id)listener +{ + AutoCompleteResults* results = [[AutoCompleteResults alloc] init]; + [results setSearchString:searchString]; + + // determine if we can skip searching the whole list of usernames + // and only search through the previous search results. + bool searchPrevious = (previousSearchResults) ? [searchString hasPrefix:[previousSearchResults searchString]] : NO; + + NSEnumerator* usernameEnumerator; + if (searchPrevious) + usernameEnumerator = [[previousSearchResults matches] objectEnumerator]; + else + usernameEnumerator = [mUsernames objectEnumerator]; + + NSMutableArray* resultMatches = [[NSMutableArray alloc] init]; + + NSString* username; + bool searchStringIsEmpty = (searchString && [searchString length] > 0) ? NO : YES; + while ((username = [usernameEnumerator nextObject])) { + if (searchStringIsEmpty || [username hasPrefix:searchString]) { + [resultMatches addObject:username]; + + // Check for the default. + if ([username isEqualToString:mDefaultUser]) + [results setDefaultIndex:([resultMatches count]-1)]; + } + } + + [results setMatches:resultMatches]; + [resultMatches release]; + + [listener autoCompleteFoundResults:results]; + [results release]; +} + +@end + +// +// FindPasswordField +// +// Given an HTML input element for username entry, return the corresponding password field +// or set the password input element to nsnull if there isn't one. Return YES on success. +// +BOOL FindPasswordField(nsIDOMHTMLInputElement* inUsername, nsIDOMHTMLInputElement** outPassword) +{ + if (!inUsername) + return NO; + + // Get the form node for scanning username/password pair. + nsCOMPtr formElement; + nsresult rv = inUsername->GetForm(getter_AddRefs(formElement)); + if (NS_FAILED(rv) || !formElement) + return NO; + + // Check if the input element is a username field with a password field. + nsCOMPtr usernameElement; + rv = FindUsernamePasswordFields(formElement, + getter_AddRefs(usernameElement), + outPassword, + PR_TRUE); + // Return a null password if the password was not found or if the username + // field in the form is not the same as the input. + if (NS_FAILED(rv) || usernameElement != inUsername) + return NO; + + return YES; +} + +static void GetFormInfoForInput(nsIDOMHTMLInputElement* aElement, + NSString** host, + NSString** asciiHost, + PRInt32* port, + NSString** scheme) +{ + if (!aElement) + return; + *host = nil; + *asciiHost = nil; + *port = -1; + *scheme = nil; + + nsCOMPtr domDoc; + nsresult rv = aElement->GetOwnerDocument(getter_AddRefs(domDoc)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr doc = do_QueryInterface(domDoc); + if (!doc) + return; + + nsIURI* docURL = doc->GetDocumentURI(); + if (!docURL) + return; + + nsCAutoString hostCAString; + rv = docURL->GetHost(hostCAString); + if (NS_FAILED(rv)) + return; + + *host = [NSString stringWithCString:hostCAString.get()]; + + // Get the host in punycode for keychain use. + nsCAutoString asciiHostCAString; + rv = docURL->GetAsciiHost(asciiHostCAString); + *asciiHost = NS_SUCCEEDED(rv) ? [NSString stringWithCString:asciiHostCAString.get()] + : *host; + + docURL->GetPort(port); + + nsCAutoString schemeCAString; + rv = docURL->GetScheme(schemeCAString); + if (NS_FAILED(rv)) + return; + + *scheme = [NSString stringWithCString:schemeCAString.get()]; +} diff --git a/mozilla/camino/src/formfill/KeychainItem.h b/mozilla/camino/src/formfill/KeychainItem.h index baff769e138..1ae02b99e98 100644 --- a/mozilla/camino/src/formfill/KeychainItem.h +++ b/mozilla/camino/src/formfill/KeychainItem.h @@ -58,6 +58,7 @@ // Returns the first keychain item matching the given criteria. + (KeychainItem*)keychainItemForHost:(NSString*)host + username:(NSString*)username port:(UInt16)port protocol:(SecProtocolType)protocol authenticationType:(SecAuthenticationType)authType; diff --git a/mozilla/camino/src/formfill/KeychainItem.m b/mozilla/camino/src/formfill/KeychainItem.m index f204b9f47e7..ceabb33a9c2 100644 --- a/mozilla/camino/src/formfill/KeychainItem.m +++ b/mozilla/camino/src/formfill/KeychainItem.m @@ -49,14 +49,18 @@ @implementation KeychainItem + (KeychainItem*)keychainItemForHost:(NSString*)host + username:(NSString*)username port:(UInt16)port protocol:(SecProtocolType)protocol authenticationType:(SecAuthenticationType)authType { SecKeychainItemRef itemRef; - const char* serverName = host ? [host UTF8String] : NULL; + const char* serverName = [host UTF8String]; UInt32 serverNameLength = serverName ? strlen(serverName) : 0; - OSStatus result = SecKeychainFindInternetPassword(NULL, serverNameLength, serverName, 0, NULL, 0, NULL, + const char* accountName = [username UTF8String]; + UInt32 accountNameLength = accountName ? strlen(accountName) : 0; + OSStatus result = SecKeychainFindInternetPassword(NULL, serverNameLength, serverName, + 0, NULL, accountNameLength, accountName, 0, NULL, port, protocol, authType, NULL, NULL, &itemRef); if (result != noErr) diff --git a/mozilla/camino/src/formfill/KeychainService.h b/mozilla/camino/src/formfill/KeychainService.h index 839ded6442f..e5997cd1c0b 100644 --- a/mozilla/camino/src/formfill/KeychainService.h +++ b/mozilla/camino/src/formfill/KeychainService.h @@ -48,6 +48,8 @@ #include "nsIAuthPromptWrapper.h" #include "nsIObserver.h" #include "nsIFormSubmitObserver.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsIDOMHTMLInputElement.h" class nsIPrefBranch; @@ -56,6 +58,11 @@ enum KeychainPromptResult { kSave, kDontRemember, kNeverRemember } ; @class CHBrowserView; @class KeychainItem; +nsresult FindUsernamePasswordFields(nsIDOMHTMLFormElement* inFormElement, + nsIDOMHTMLInputElement** outUsername, + nsIDOMHTMLInputElement** outPassword, + PRBool inStopWhenFound); + @interface KeychainService : NSObject { IBOutlet id mConfirmFillPasswordPanel; @@ -84,21 +91,35 @@ enum KeychainPromptResult { kSave, kDontRemember, kNeverRemember } ; inWindow:(NSWindow*)window; - (BOOL)confirmFillPassword:(NSWindow*)parent; -- (KeychainItem*)findKeychainEntryForHost:(NSString*)host - port:(PRInt32)port - scheme:(NSString*)scheme - securityDomain:(NSString*)securityDomain - isForm:(BOOL)isForm; -- (void)storeUsername:(NSString*)username - password:(NSString*)password - forHost:(NSString*)host - securityDomain:(NSString*)host - port:(PRInt32)port - scheme:(NSString*)scheme - isForm:(BOOL)isForm; -- (KeychainItem*)updateKeychainEntry:(KeychainItem*)keychainItem - withUsername:(NSString*)username - password:(NSString*)password; +- (KeychainItem*)findWebFormKeychainEntryForUsername:(NSString*)username + forHost:(NSString*)host + port:(PRInt32)port + scheme:(NSString*)scheme; +- (KeychainItem*)findDefaultWebFormKeychainEntryForHost:(NSString*)host + port:(PRInt32)port + scheme:(NSString*)scheme; +- (void)setDefaultWebFormKeychainEntry:(KeychainItem*)keychainItem; + +- (NSArray*)allWebFormKeychainItemsForHost:(NSString*)host + port:(PRInt32)port + scheme:(NSString*)scheme; +- (KeychainItem*)defaultFromKeychainItems:(NSArray*)items; + +- (KeychainItem*)findAuthKeychainEntryForHost:(NSString*)host + port:(PRInt32)port + scheme:(NSString*)scheme + securityDomain:(NSString*)securityDomain; +- (KeychainItem*)updateAuthKeychainEntry:(KeychainItem*)keychainItem + withUsername:(NSString*)username + password:(NSString*)password; +- (KeychainItem*)storeUsername:(NSString*)username + password:(NSString*)password + forHost:(NSString*)host + securityDomain:(NSString*)securityDomain + port:(PRInt32)port + scheme:(NSString*)scheme + isForm:(BOOL)isForm; + - (void)removeAllUsernamesAndPasswords; - (void)addListenerToView:(CHBrowserView*)view; diff --git a/mozilla/camino/src/formfill/KeychainService.mm b/mozilla/camino/src/formfill/KeychainService.mm index eb69dd32b4d..fd5801bfe78 100644 --- a/mozilla/camino/src/formfill/KeychainService.mm +++ b/mozilla/camino/src/formfill/KeychainService.mm @@ -47,6 +47,7 @@ #import "KeychainDenyList.h" #import "CHBrowserService.h" #import "PreferenceManager.h" +#import "AutoCompleteUtils.h" #import "KeyEquivView.h" #import "nsAlertController.h" @@ -64,10 +65,12 @@ #include "nsIDocument.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMHTMLCollection.h" -#include "nsIDOMHTMLFormElement.h" -#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMNSHTMLInputElement.h" #include "nsIDOMHTMLSelectElement.h" #include "nsIDOMHTMLOptionElement.h" +#include "nsIDOMEvent.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMEventListener.h" #include "nsIURL.h" #include "nsIDOMWindowCollection.h" #include "nsIContent.h" @@ -101,13 +104,7 @@ static const int kCacheTimeout = 120; // from CHBrowserService.h extern NSString* const XPCOMShutDownNotificationName; - -nsresult -FindUsernamePasswordFields(nsIDOMHTMLFormElement* inFormElement, nsIDOMHTMLInputElement** outUsername, - nsIDOMHTMLInputElement** outPassword, PRBool inStopWhenFound); - -NSWindow* -GetNSWindow(nsIDOMWindow* inWindow); +static NSWindow* GetNSWindow(nsIDOMWindow* inWindow); @interface KeychainService(Private) - (KeychainItem*)findLegacyKeychainEntryForHost:(NSString*)host port:(PRInt32)port; @@ -225,21 +222,167 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) return mFormPasswordFillIsEnabled; } -- (KeychainItem*)findKeychainEntryForHost:(NSString*)host - port:(PRInt32)port - scheme:(NSString*)scheme - securityDomain:(NSString*)securityDomain - isForm:(BOOL)isForm; +- (KeychainItem*)findWebFormKeychainEntryForUsername:(NSString*)username + forHost:(NSString*)host + port:(PRInt32)port + scheme:(NSString*)scheme +{ + if (port == -1) + port = kAnyPort; + SecProtocolType protocol = [scheme isEqualToString:@"https"] ? kSecProtocolTypeHTTPS : kSecProtocolTypeHTTP; + + KeychainItem* item = [KeychainItem keychainItemForHost:host + username:username + port:port + protocol:protocol + authenticationType:kSecAuthenticationTypeHTMLForm]; + + if (item && [item password] && ![[item password] isEqualToString:@" "]) + return item; + + item = [self findLegacyKeychainEntryForHost:host port:port]; + if (item && [item password] && [[item username] isEqualToString:username]) + return item; + + return nil; +} + +- (KeychainItem*)findDefaultWebFormKeychainEntryForHost:(NSString*)host + port:(PRInt32)port + scheme:(NSString*)scheme +{ + NSArray* keychainItems = [self allWebFormKeychainItemsForHost:host port:port scheme:scheme]; + + return [self defaultFromKeychainItems:keychainItems]; +} + +// +// setDefaultWebFormKeychainEntry +// +// Discussion of how the default keychain entry is chosen in Camino and Safari: +// Safari sets its default entry by entering 'default' in the keychain entry +// comment field. We designed Camino so that it can use another default +// by setting 'camino_default' in the comment field. Camino first checks for +// 'camino_default', then looks for 'default' if there isn't one. Safari will +// overwrite the comment field when setting the default. If Safari changes the +// 'camino_default' entry to 'default', Camino will still treat it as the +// default. Camino won't overwrite a comment field, meaning that the Safari +// default will be preserved if the user selects the same item in Camino. +// +// This method sets the default Camino keychain entry. The default Camino +// entry will have a comment value set to "camino_default". It first clears +// any existing default Camino entry before setting the passed in keychain to +// the new default. It doesn't modify any other comment values, meaning that +// the Safari default will preserved. +- (void)setDefaultWebFormKeychainEntry:(KeychainItem*)keychainItem +{ + NSArray* keychainArray = [KeychainItem allKeychainItemsForHost:[keychainItem host] + port:[keychainItem port] + protocol:[keychainItem protocol] + authenticationType:[keychainItem authenticationType] + creator:0]; + + NSEnumerator* keychainEnumerator = [keychainArray objectEnumerator]; + KeychainItem* keychainTemp; + while ((keychainTemp = [keychainEnumerator nextObject])) { + if ([[keychainTemp comment] isEqualToString:@"camino_default"]) + [keychainTemp setComment:@""]; + } + + if ([[keychainItem comment] isEqualToString:@""]) + [keychainItem setComment:@"camino_default"]; +} + +- (NSArray*)allWebFormKeychainItemsForHost:(NSString*)host port:(PRInt32)port scheme:(NSString*)scheme +{ + if (port == -1) + port = kAnyPort; + SecProtocolType protocol = [scheme isEqualToString:@"https"] ? kSecProtocolTypeHTTPS : kSecProtocolTypeHTTP; + + // Since there are old-style (pre-1.1) authenticationType values, retrieve them all then filter. + NSArray* keychainItems = [KeychainItem allKeychainItemsForHost:host + port:(UInt16)port + protocol:protocol + authenticationType:0 + creator:0]; + + NSMutableArray* returnItems = [[[NSMutableArray alloc] init] autorelease]; + KeychainItem* item; + NSEnumerator* keychainEnumerator = [keychainItems objectEnumerator]; + while ((item = [keychainEnumerator nextObject])) { + if ([item authenticationType] == kSecAuthenticationTypeHTMLForm || + [item authenticationType] == kSecAuthenticationTypeHTTPDigest) { + [returnItems addObject:item]; + } + else if ([item authenticationType] == kSecAuthenticationTypeHTTPDigestReversed) { + [item setAuthenticationType:kSecAuthenticationTypeHTTPDigest]; + [returnItems addObject:item]; + } + } + + return returnItems; +} + +// +// defaultFromKeychainItems +// +// Method that takes an array of web form keychain items and returns the default. +// +- (KeychainItem*)defaultFromKeychainItems:(NSArray*)items +{ + // First, check for the default Camino entry. + KeychainItem* item; + NSEnumerator* keychainEnumerator = [items objectEnumerator]; + while ((item = [keychainEnumerator nextObject])) { + if ([[item comment] isEqualToString:@"camino_default"] && [item password]) + return item; + } + + // Next, check for Safari's default. + keychainEnumerator = [items objectEnumerator]; + while ((item = [keychainEnumerator nextObject])) { + if ([[item comment] isEqualToString:@"default"]) { + // Safari doesn't bother to set kSecNegativeItemAttr on "Passwords not saved" + // items; that's just the way they roll. This fragile method is the best we can do. + if ([[item password] isEqualToString:@" "]) + continue; + if ([item password]) + return item; + } + } + + // Then check for the first new-style Camino keychain entry. + keychainEnumerator = [items objectEnumerator]; + while ((item = [keychainEnumerator nextObject])) { + if (([item creator] == kCaminoKeychainCreatorCode) && [item password]) + return item; + } + + // Finally, just arbitrarily pick the first one. + keychainEnumerator = [items objectEnumerator]; + while ((item = [keychainEnumerator nextObject])) { + if ([[item password] isEqualToString:@" "]) + continue; + if ([item password]) + return item; + } + + return nil; +} + +- (KeychainItem*)findAuthKeychainEntryForHost:(NSString*)host + port:(PRInt32)port + scheme:(NSString*)scheme + securityDomain:(NSString*)securityDomain { if (port == -1) port = kAnyPort; SecProtocolType protocol = [scheme isEqualToString:@"https"] ? kSecProtocolTypeHTTPS : kSecProtocolTypeHTTP; - SecAuthenticationType authType = isForm ? kSecAuthenticationTypeHTMLForm : kSecAuthenticationTypeDefault; NSArray* newKeychainItems = [KeychainItem allKeychainItemsForHost:host port:(UInt16)port protocol:protocol - authenticationType:authType + authenticationType:kSecAuthenticationTypeDefault creator:0]; // If we have a security domain, discard mismatched domains and re-sort the array @@ -281,22 +424,7 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) if (item && [item password]) return item; - // Finally, check for a new style entry created by something other than Camino. - // Since we don't yet have any UI for multiple accounts, we use Safari's default - // if we can find it, otherwise we just arbitrarily pick the first one that - // looks plausible. - keychainEnumerator = [newKeychainItems objectEnumerator]; - while ((item = [keychainEnumerator nextObject])) { - NSString* comment = [item comment]; - if (comment && ([comment rangeOfString:@"default"].location != NSNotFound)) { - // Safari doesn't bother to set kSecNegativeItemAttr on "Passwords not saved" - // items; that's just the way they roll. This fragile method is the best we can do. - if ([[item password] isEqualToString:@" "]) - continue; - if ([item password]) - return item; - } - } + // Finally, just arbitrarily pick the first one that looks plausible. keychainEnumerator = [newKeychainItems objectEnumerator]; while ((item = [keychainEnumerator nextObject])) { if ([[item password] isEqualToString:@" "]) @@ -315,12 +443,14 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) - (KeychainItem*)findLegacyKeychainEntryForHost:(NSString*)host port:(PRInt32)port { KeychainItem* item = [KeychainItem keychainItemForHost:host + username:nil port:(UInt16)port protocol:kSecProtocolTypeHTTP authenticationType:kSecAuthenticationTypeHTTPDigest]; if (!item) { // check for the reverse auth type item = [KeychainItem keychainItemForHost:host + username:nil port:(UInt16)port protocol:kSecProtocolTypeHTTP authenticationType:kSecAuthenticationTypeHTTPDigestReversed]; @@ -330,13 +460,13 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) return item; } -- (void)storeUsername:(NSString*)username - password:(NSString*)password - forHost:(NSString*)host - securityDomain:(NSString*)securityDomain - port:(PRInt32)port - scheme:(NSString*)scheme - isForm:(BOOL)isForm +- (KeychainItem*)storeUsername:(NSString*)username + password:(NSString*)password + forHost:(NSString*)host + securityDomain:(NSString*)securityDomain + port:(PRInt32)port + scheme:(NSString*)scheme + isForm:(BOOL)isForm { if (port == -1) port = kAnyPort; @@ -356,6 +486,8 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) else [newItem setSecurityDomain:securityDomain]; } + + return newItem; } // Stores changes to a site's stored account. Note that this may return a different item than @@ -363,9 +495,9 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) // store an acccount in Camino that isn't the one we pick up from Safari. For password updates // we update the existing item, but for username updates we make a new item if the one we were // using wasn't Camino-created. -- (KeychainItem*)updateKeychainEntry:(KeychainItem*)keychainItem - withUsername:(NSString*)username - password:(NSString*)password +- (KeychainItem*)updateAuthKeychainEntry:(KeychainItem*)keychainItem + withUsername:(NSString*)username + password:(NSString*)password { if ([username isEqualToString:[keychainItem username]] || [keychainItem creator] == kCaminoKeychainCreatorCode) { [keychainItem setUsername:username password:password]; @@ -496,13 +628,16 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) NSDictionary* loginInfo = (NSDictionary*)contextInfo; switch (returnCode) { case NSAlertFirstButtonReturn: // Save - [self storeUsername:[loginInfo objectForKey:kLoginUsernameKey] - password:[loginInfo objectForKey:kLoginPasswordKey] - forHost:[loginInfo objectForKey:kLoginHostKey] - securityDomain:[loginInfo objectForKey:kLoginSecurityDomainKey] - port:[[loginInfo objectForKey:kLoginPortKey] intValue] - scheme:[loginInfo objectForKey:kLoginSchemeKey] - isForm:YES]; + KeychainItem* keychainEntry = + [self storeUsername:[loginInfo objectForKey:kLoginUsernameKey] + password:[loginInfo objectForKey:kLoginPasswordKey] + forHost:[loginInfo objectForKey:kLoginHostKey] + securityDomain:[loginInfo objectForKey:kLoginSecurityDomainKey] + port:[[loginInfo objectForKey:kLoginPortKey] intValue] + scheme:[loginInfo objectForKey:kLoginSchemeKey] + isForm:YES]; + [self setDefaultWebFormKeychainEntry:keychainEntry]; + break; case NSAlertSecondButtonReturn: // Don't remember @@ -557,8 +692,8 @@ int KeychainPrefChangedCallback(const char* inPref, void* unused) { NSDictionary* loginInfo = (NSDictionary*)contextInfo; if (returnCode == NSAlertFirstButtonReturn) { - [self updateKeychainEntry:[loginInfo objectForKey:kLoginKeychainEntry] - withUsername:[loginInfo objectForKey:kLoginUsernameKey] + KeychainItem* keychainItem = [loginInfo objectForKey:kLoginKeychainEntry]; + [keychainItem setUsername:[loginInfo objectForKey:kLoginUsernameKey] password:[loginInfo objectForKey:kLoginPasswordKey]]; } [loginInfo release]; // balance the retain in the alert creation @@ -746,11 +881,10 @@ KeychainPrompt::PreFill(const PRUnichar *realmBlob, PRUnichar **user, PRUnichar NSString* scheme = (port == kDefaultHTTPSPort) ? @"https" : @"http"; KeychainService* keychain = [KeychainService instance]; - KeychainItem* entry = [keychain findKeychainEntryForHost:host - port:port - scheme:scheme - securityDomain:realm - isForm:NO]; + KeychainItem* entry = [keychain findAuthKeychainEntryForHost:host + port:port + scheme:scheme + securityDomain:realm]; if (entry) { [keychain cacheKeychainEntry:entry forKey:realmBlobString]; if (user) @@ -780,11 +914,10 @@ KeychainPrompt::ProcessPrompt(const PRUnichar* realmBlob, bool checked, PRUnicha KeychainService* keychain = [KeychainService instance]; KeychainItem* keychainEntry = [keychain cachedKeychainEntryForKey:realmBlobString]; if (!keychainEntry) { - keychainEntry = [keychain findKeychainEntryForHost:host - port:port - scheme:scheme - securityDomain:realm - isForm:NO]; + keychainEntry = [keychain findAuthKeychainEntryForHost:host + port:port + scheme:scheme + securityDomain:realm]; } // Update, store or remove the user/password depending on the user @@ -808,9 +941,9 @@ KeychainPrompt::ProcessPrompt(const PRUnichar* realmBlob, bool checked, PRUnicha if (![[keychainEntry username] isEqualToString:username] || ![[keychainEntry password] isEqualToString:password]) { - keychainEntry = [keychain updateKeychainEntry:keychainEntry - withUsername:username - password:password]; + keychainEntry = [keychain updateAuthKeychainEntry:keychainEntry + withUsername:username + password:password]; } // TODO: this is just to upgrade pre-1.1 HTTPAuth items; at some point in // the future remove from here... @@ -995,29 +1128,28 @@ KeychainFormSubmitObserver::Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindow if (!actionHost) actionHost = host; + // Search for an existing keychain entry with the same username. // Check the cache first, then fall back to a search in case a cached // entry expired before the user submitted. KeychainItem* keychainEntry = [keychain cachedKeychainEntryForKey:uri]; // We weren't using punycode for keychain entries up through 1.5, so try // non-punycode first to favor Camino entries. - if (!keychainEntry) { - keychainEntry = [keychain findKeychainEntryForHost:host - port:port - scheme:scheme - securityDomain:nil - isForm:YES]; + if (!keychainEntry || ![[keychainEntry username] isEqualToString:username]) { + keychainEntry = [keychain findWebFormKeychainEntryForUsername:username + forHost:host + port:port + scheme:scheme]; } if (!keychainEntry && ![asciiHost isEqualToString:host]) { - keychainEntry = [keychain findKeychainEntryForHost:asciiHost - port:port - scheme:scheme - securityDomain:nil - isForm:YES]; + keychainEntry = [keychain findWebFormKeychainEntryForUsername:username + forHost:asciiHost + port:port + scheme:scheme]; } - // If there's already an entry in the keychain, check if the username - // and password match. If not, ask the user what they want to do and replace - // it as necessary. If there's no entry, ask if they want to remember it - // and then put it into the keychain + // If there's already an entry in the keychain for the username, check if the + // password matches. If not, ask the user what they want to do and replace + // it as necessary. If there's no entry for this username, ask if they want to + // remember it and then put it into the keychain if (keychainEntry) { // If it's an old-style entry, upgrade it now that we know what it goes with. if ([keychainEntry authenticationType] == kSecAuthenticationTypeHTTPDigest) @@ -1033,17 +1165,15 @@ KeychainFormSubmitObserver::Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindow } // ... to here. - if (![[keychainEntry username] isEqualToString:username] || - ![[keychainEntry password] isEqualToString:password]) - { - // Note: this can create a new entry rather than updating the entry - // passed in, so |keychainEntry| shouldn't be used after this point. + if (![[keychainEntry password] isEqualToString:password]) { [keychain promptToUpdateKeychainItem:keychainEntry withUsername:username password:password inWindow:GetNSWindow(window)]; } + [keychain setDefaultWebFormKeychainEntry:keychainEntry]; + if ([[keychain allowedActionHostsForHost:host] count] == 0) [keychain setAllowedActionHosts:[NSArray arrayWithObject:actionHost] forHost:host]; // We don't need to look up the entry again, so remove it from the cache. @@ -1163,20 +1293,16 @@ KeychainFormSubmitObserver::Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindow // We weren't using punycode for keychain entries up through 1.5, so try // non-punycode first to favor Camino entries. - keychainEntry = [keychain findKeychainEntryForHost:host - port:port - scheme:scheme - securityDomain:nil - isForm:YES]; + keychainEntry = [keychain findDefaultWebFormKeychainEntryForHost:host + port:port + scheme:scheme]; // TODO: once a released version checks for punycode, anything found with // the above search (and with a Camino creator code) should be fixed // immediately to have the right host. if (!keychainEntry && ![asciiHost isEqualToString:host]) { - keychainEntry = [keychain findKeychainEntryForHost:asciiHost - port:port - scheme:scheme - securityDomain:nil - isForm:YES]; + keychainEntry = [keychain findDefaultWebFormKeychainEntryForHost:asciiHost + port:port + scheme:scheme]; } } // If we don't have a password for the page, don't bother looking for @@ -1220,6 +1346,11 @@ KeychainFormSubmitObserver::Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindow if (!userValue.Length() || userValue.Equals(user, nsCaseInsensitiveStringComparator())) { rv = usernameElement->SetValue(user); rv = passwordElement->SetValue(pwd); + + // Highlight the username. + nsCOMPtr nsElement = do_QueryInterface(usernameElement); + if (nsElement) + nsElement->SetSelectionStart(0); } // Now that we have actually filled the password, cache the keychain entry. @@ -1327,8 +1458,7 @@ KeychainFormSubmitObserver::Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindow // // Finds the native window for the given DOM window // -NSWindow* -GetNSWindow(nsIDOMWindow* inWindow) +NSWindow* GetNSWindow(nsIDOMWindow* inWindow) { CHBrowserView* browserView = [CHBrowserView browserViewFromDOMWindow:inWindow]; return [browserView nativeWindow]; @@ -1345,9 +1475,10 @@ GetNSWindow(nsIDOMWindow* inWindow) // "change your password" form). If we find one, null out // |outPassword| since we probably don't want to prefill this form. // -nsresult -FindUsernamePasswordFields(nsIDOMHTMLFormElement* inFormElement, nsIDOMHTMLInputElement** outUsername, - nsIDOMHTMLInputElement** outPassword, PRBool inStopWhenFound) +nsresult FindUsernamePasswordFields(nsIDOMHTMLFormElement* inFormElement, + nsIDOMHTMLInputElement** outUsername, + nsIDOMHTMLInputElement** outPassword, + PRBool inStopWhenFound) { if (!outUsername || !outPassword) return NS_ERROR_FAILURE;