Relanding bug 178607 - Keychain cannot save multiple passwords for the same host. Patch by Bryan Atwood <batwood.bugs@gmail.com>, r=smorgan, sr=mento.

This commit is contained in:
alqahira%ardisson.org 2008-01-22 22:03:35 +00:00
Родитель 8f07d7f084
Коммит 0007b3b331
17 изменённых файлов: 2151 добавлений и 116 удалений

Просмотреть файл

@ -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 = "<group>"; };
@ -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;
};

Просмотреть файл

@ -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

Просмотреть файл

@ -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);

Просмотреть файл

@ -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<nsIContent> content = do_QueryInterface(aElement);
if (!content)
return PR_FALSE;
nsCOMPtr<nsIDocument> doc = content->GetDocument();
if (!doc)
return PR_FALSE;
nsCOMPtr<nsIPresShell> 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<nsIContent> content = do_QueryInterface(aElement);
if (!content)
return;
nsCOMPtr<nsIDocument> doc = content->GetDocument();
if (!doc)
return;
nsCOMPtr<nsIPresShell> presShell = doc->GetPrimaryShell();
if (!presShell)
return;
presShell->ScrollContentIntoView(content, NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,
NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE);
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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 <Cocoa/Cocoa.h>
// 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<AutoCompleteListener>)listener;
@end

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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 <Cocoa/Cocoa.h>
#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 <AutoCompleteListener>
{
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

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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<nsIDOMWindow> 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<nsIDOMWindow> 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<nsPIDOMWindow> privateDOMWindow = do_QueryInterface(aWindow);
if (!privateDOMWindow)
return;
nsPIDOMEventTarget* chromeEventHandler = privateDOMWindow->GetChromeEventHandler();
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(chromeEventHandler);
if (!target)
return;
target->AddEventListener(NS_LITERAL_STRING("focus"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->AddEventListener(NS_LITERAL_STRING("blur"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->AddEventListener(NS_LITERAL_STRING("unload"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->AddEventListener(NS_LITERAL_STRING("submit"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->AddEventListener(NS_LITERAL_STRING("input"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->AddEventListener(NS_LITERAL_STRING("keypress"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
}
- (void)removeWindowListeners:(nsIDOMWindow*)aWindow
{
[self stopControllingInputElement];
nsCOMPtr<nsPIDOMWindow> privateDOMWindow = do_QueryInterface(aWindow);
if (!privateDOMWindow)
return;
nsPIDOMEventTarget* chromeEventHandler = privateDOMWindow->GetChromeEventHandler();
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(chromeEventHandler);
if (!target)
return;
target->RemoveEventListener(NS_LITERAL_STRING("focus"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("blur"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("unload"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("submit"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("input"),
static_cast<nsIDOMEventListener*>(mListener),
PR_TRUE);
target->RemoveEventListener(NS_LITERAL_STRING("keypress"),
static_cast<nsIDOMEventListener*>(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<nsIDOMNSHTMLInputElement> 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<nsIDOMNSHTMLInputElement> 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<nsIDOMDocument> domDoc;
mFocusedInputElement->GetOwnerDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDOMDocumentEvent> doc = do_QueryInterface(domDoc);
if (!doc)
return;
nsCOMPtr<nsIDOMEvent> event;
doc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
if (!privateEvent)
return;
event->InitEvent(NS_LITERAL_STRING("DOMAutoComplete"), PR_TRUE, PR_TRUE);
privateEvent->SetTrusted(PR_TRUE);
nsCOMPtr<nsIDOMEventTarget> 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<nsIDOMEventTarget> target;
aEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMHTMLInputElement> 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<nsIDOMHTMLFormElement> 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<nsIDOMEventTarget> target;
aEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMDocument> eventDoc = do_QueryInterface(target);
nsCOMPtr<nsIDOMDocument> 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<nsIDOMEventTarget> target;
aEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMHTMLInputElement> 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<nsIDOMKeyEvent> 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<nsIDOMNSHTMLInputElement> nsInput = do_QueryInterface(mFocusedInputElement);
if (!nsInput)
return NO;
PRInt32 selectStart;
nsInput->GetSelectionStart(&selectStart);
PRInt32 textLength;
nsInput->GetTextLength(&textLength);
return (selectStart == textLength);
}
@end

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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 <Cocoa/Cocoa.h>
@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

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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 <Cocoa/Cocoa.h>
#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<nsIDOMHTMLInputElement> mUsernameElement; // strong
nsCOMPtr<nsIDOMHTMLInputElement> mPasswordElement; // strong
};
@interface KeychainAutoCompleteSession : NSObject<AutoCompleteSession>
{
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

Просмотреть файл

@ -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 <bryan.h.atwood@gmail.com>
*
* 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<nsIDOMEventTarget> target;
if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target))))
return NS_OK;
nsCOMPtr<nsIDOMHTMLInputElement> 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<nsIDOMDocument> domDoc;
nsresult rv = mUsernameElement->GetOwnerDocument(getter_AddRefs(domDoc));
if (NS_FAILED(rv) || !domDoc)
return;
nsCOMPtr<nsIDocument> 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<nsIDOMHTMLInputElement> 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<nsIDOMEventTarget> 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<AutoCompleteListener>)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<nsIDOMHTMLFormElement> 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<nsIDOMHTMLInputElement> 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<nsIDOMDocument> domDoc;
nsresult rv = aElement->GetOwnerDocument(getter_AddRefs(domDoc));
if (NS_FAILED(rv))
return;
nsCOMPtr<nsIDocument> 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()];
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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)

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<nsIDOMNSHTMLInputElement> 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;