Bug 469443 - Form Manager Storage should be a JavaScript-based component. r=gavin

This commit is contained in:
Justin Dolske 2009-06-19 13:19:18 -07:00
parent f7550df414
commit 55da1cfdc5
16 changed files with 893 additions and 170 deletions

View File

@ -197,7 +197,6 @@ pref("browser.startup.homepage", "resource:/browserconfig.properties"
pref("browser.enable_automatic_image_resizing", true);
pref("browser.chrome.site_icons", true);
pref("browser.chrome.favicons", true);
pref("browser.formfill.enable", true);
pref("browser.warnOnQuit", true);
pref("browser.warnOnRestart", true);
pref("browser.fullscreen.autohide", true);

View File

@ -295,7 +295,7 @@ function runTest(testNum) {
*/
default:
ok(false, "Unexpected invocataion of test #" + testNum);
ok(false, "Unexpected invocation of test #" + testNum);
subwindow.close();
SimpleTest.finish();
return;

View File

@ -248,6 +248,7 @@ bin/components/aboutRights.js
bin/components/aboutRobots.js
bin/components/aboutCertError.js
bin/components/nsBadCertHandler.js
bin/components/nsFormAutoComplete.js
; Modules
bin/modules/*

View File

@ -254,6 +254,7 @@ bin\components\aboutRights.js
bin\components\aboutRobots.js
bin\components\aboutCertError.js
bin\components\nsBadCertHandler.js
bin\components\nsFormAutoComplete.js
; Modules
bin\modules\*

View File

@ -2712,6 +2712,11 @@ pref("signon.SignonFileName3", "signons3.txt"); // obsolete
pref("signon.autofillForms", true);
pref("signon.debug", false); // logs to Error Console
// Satchel (Form Manager) prefs
pref("browser.formfill.enable", true);
pref("browser.formfill.debug", false);
// Zoom prefs
pref("browser.zoom.full", false);
pref("zoom.minPercent", 30);

View File

@ -617,7 +617,7 @@ function runTest(testNum) {
return;
default:
ok(false, "Unexpected invocataion of test #" + testNum);
ok(false, "Unexpected invocation of test #" + testNum);
SimpleTest.finish();
return;
}

View File

@ -47,6 +47,7 @@ MODULE = satchel
XPIDL_MODULE = satchel
XPIDLSRCS = nsIFormFillController.idl \
nsIFormAutoComplete.idl \
nsIFormHistory.idl \
$(NULL)

View File

@ -0,0 +1,52 @@
/* ***** 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 mozilla.org code.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Justin Dolske <dolske@mozilla.com> (original author)
*
* 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 ***** */
#include "nsISupports.idl"
interface nsIAutoCompleteResult;
[scriptable, uuid(2f5bb765-428e-4e33-b2d8-441eb7ddf730)]
interface nsIFormAutoComplete: nsISupports {
/**
* Generate results for a form input autocomplete menu.
*/
nsIAutoCompleteResult autoCompleteSearch(
in AString aInputName,
in AString aSearchString,
in nsIAutoCompleteResult aPreviousResult);
};

View File

@ -50,6 +50,10 @@ IS_COMPONENT = 1
LIBXUL_LIBRARY = 1
EXPORT_LIBRARY = 1
EXTRA_COMPONENTS = \
nsFormAutoComplete.js \
$(NULL)
REQUIRES = \
xpcom \
string \
@ -92,4 +96,3 @@ EXTRA_DSO_LDOPTS += \
ifdef MOZ_MORKREADER
EXTRA_DSO_LDOPTS += $(DEPTH)/db/morkreader/$(LIB_PREFIX)morkreader_s.$(LIB_SUFFIX)
endif

View File

@ -0,0 +1,311 @@
/* ***** 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 mozilla.org code.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Justin Dolske <dolske@mozilla.com> (original author)
*
* 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 ***** */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
function FormAutoComplete() {
this.init();
}
FormAutoComplete.prototype = {
classDescription: "FormAutoComplete",
contractID: "@mozilla.org/satchel/form-autocomplete;1",
classID: Components.ID("{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"),
QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormAutoComplete, Ci.nsISupportsWeakReference]),
__logService : null, // Console logging service, used for debugging.
get _logService() {
if (!this.__logService)
this.__logService = Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService);
return this.__logService;
},
__formHistory : null,
get _formHistory() {
if (!this.__formHistory)
this.__formHistory = Cc["@mozilla.org/satchel/form-history;1"].
getService(Ci.nsIFormHistory2);
return this.__formHistory;
},
__observerService : null, // Observer Service, for notifications
get _observerService() {
if (!this.__observerService)
this.__observerService = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
return this.__observerService;
},
_prefBranch : null,
_enabled : true, // mirrors browser.formfill.enable preference
_debug : false, // mirrors browser.formfill.debug
init : function() {
// Preferences. Add observer so we get notified of changes.
this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).getBranch("browser.formfill.");
this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
this._prefBranch.addObserver("", this.observer, false);
this.observer._self = this;
this._debug = this._prefBranch.getBoolPref("debug");
this._enabled = this._prefBranch.getBoolPref("enable");
this._dbStmts = [];
this._observerService.addObserver(this.observer, "xpcom-shutdown", false);
},
observer : {
_self : null,
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe : function (subject, topic, data) {
let self = this._self;
if (topic == "nsPref:changed") {
let prefName = data;
self.log("got change to " + prefName + " preference");
if (prefName == "debug") {
self._debug = self._prefBranch.getBoolPref("debug");
} else if (prefName == "enable") {
self._enabled = self._prefBranch.getBoolPref("enable");
} else {
self.log("Oops! Pref not handled, change ignored.");
}
} else if (topic == "xpcom-shutdown") {
self._dbStmts = null;
}
}
},
/*
* log
*
* Internal function for logging debug messages to the Error Console
* window
*/
log : function (message) {
if (!this._debug)
return;
dump("FormAutoComplete: " + message + "\n");
this._logService.logStringMessage("FormAutoComplete: " + message);
},
/*
* autoCompleteSearch
*
* aInputName -- |name| attribute from the form input being autocompleted.
* aSearchString -- current value of the input
* aPreviousResult -- previous search result, if any.
*
* Returns: an nsIAutoCompleteResult
*/
autoCompleteSearch : function (aInputName, aSearchString, aPreviousResult) {
if (!this._enabled)
return null;
this.log("AutoCompleteSearch invoked. Search is: " + aSearchString);
let result = null;
if (aPreviousResult) {
this.log("Using previous autocomplete result");
result = aPreviousResult;
// We have a list of results for a shorter search string, so just
// filter them further based on the new search string.
// Count backwards, because result.matchCount is decremented
// when we remove an entry.
for (let i = result.matchCount - 1; i >= 0; i--) {
let match = result.getValueAt(i);
// Remove results that are too short, or have different prefix.
// XXX bug 394604 -- .toLowerCase can be wrong for some intl chars
if (aSearchString.length > match.length ||
aSearchString.toLowerCase() !=
match.substr(0, aSearchString.length).toLowerCase())
{
this.log("Removing autocomplete entry '" + match + "'");
result.removeValueAt(i, false);
}
}
} else {
this.log("Creating new autocomplete search result.");
let entries = this.getAutoCompleteValues(aInputName, aSearchString);
result = new FormAutoCompleteResult(this._formHistory, entries, aInputName, aSearchString);
}
return result;
},
getAutoCompleteValues : function (fieldName, searchString) {
let values = [];
let query = "SELECT value FROM moz_formhistory " +
"WHERE fieldname=:fieldname AND value LIKE :valuePrefix ESCAPE '/' " +
"ORDER BY UPPER(value) ASC";
let params = {
fieldname: fieldName,
valuePrefix: null // set below...
}
let stmt;
try {
stmt = this._dbCreateStatement(query, params);
// Chicken and egg problem: Need the statement to escape the params we
// pass to the function that gives us the statement. So, fix it up now.
stmt.params.valuePrefix = stmt.escapeStringForLIKE(searchString, "/") + "%";
while (stmt.step())
values.push(stmt.row.value);
} catch (e) {
this.log("getValues failed: " + e.name + " : " + e.message);
throw "DB failed getting form autocomplete falues";
} finally {
stmt.reset();
}
return values;
},
_dbStmts : null,
_dbCreateStatement : function (query, params) {
let stmt = this._dbStmts[query];
// Memoize the statements
if (!stmt) {
this.log("Creating new statement for query: " + query);
stmt = this._formHistory.DBConnection.createStatement(query);
this._dbStmts[query] = stmt;
}
// Replace parameters, must be done 1 at a time
if (params)
for (let i in params)
stmt.params[i] = params[i];
return stmt;
}
}; // end of FormAutoComplete implementation
// nsIAutoCompleteResult implementation
function FormAutoCompleteResult (formHistory, entries, fieldName, searchString) {
this.formHistory = formHistory;
this.entries = entries;
this.fieldName = fieldName;
this.searchString = searchString;
if (entries.length > 0) {
this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
this.defaultIndex = 0;
}
}
FormAutoCompleteResult.prototype = {
QueryInterface : XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
Ci.nsISupportsWeakReference]),
// private
formHistory : null,
entries : null,
fieldName : null,
_checkIndexBounds : function (index) {
if (index < 0 || index >= this.entries.length)
Components.Exception("Index out of range.", Cr.NS_ERROR_ILLEGAL_VALUE);
},
// Interfaces from idl...
searchString : null,
searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
defaultIndex : -1,
errorDescription : "",
get matchCount() {
return this.entries.length;
},
getValueAt : function (index) {
this._checkIndexBounds(index);
return this.entries[index];
},
getCommentAt : function (index) {
this._checkIndexBounds(index);
return "";
},
getStyleAt : function (index) {
this._checkIndexBounds(index);
return "";
},
getImageAt : function (index) {
this._checkIndexBounds(index);
return "";
},
removeValueAt : function (index, removeFromDB) {
this._checkIndexBounds(index);
let [removedEntry] = this.entries.splice(index, 1);
if (this.defaultIndex > this.entries.length)
this.defaultIndex--;
if (removeFromDB)
this.formHistory.removeEntry(this.fieldName, removedEntry);
}
};
let component = [FormAutoComplete];
function NSGetModule (compMgr, fileSpec) {
return XPCOMUtils.generateModule(component);
}

View File

@ -40,6 +40,7 @@
#include "nsFormFillController.h"
#include "nsStorageFormHistory.h"
#include "nsIFormAutoComplete.h"
#include "nsIAutoCompleteSimpleResult.h"
#include "nsString.h"
#include "nsReadableUtils.h"
@ -496,6 +497,7 @@ NS_IMETHODIMP
nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
{
nsresult rv;
nsCOMPtr<nsIAutoCompleteResult> result;
// If the login manager has indicated it's responsible for this field, let it
@ -504,22 +506,21 @@ nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAStrin
if (mPwmgrInputs.Get(mFocusedInput, &dummy)) {
// XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
// satchel manage the field?
mLoginManager->AutoCompleteSearch(aSearchString,
rv = mLoginManager->AutoCompleteSearch(aSearchString,
aPreviousResult,
mFocusedInput,
getter_AddRefs(result));
} else {
nsCOMPtr<nsIAutoCompleteSimpleResult> historyResult;
historyResult = do_QueryInterface(aPreviousResult);
nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsFormHistory *history = nsFormHistory::GetInstance();
if (history) {
history->AutoCompleteSearch(aSearchParam,
aSearchString,
historyResult,
getter_AddRefs(result));
}
rv = formAutoComplete->AutoCompleteSearch(aSearchParam,
aSearchString,
aPreviousResult,
getter_AddRefs(result));
}
NS_ENSURE_SUCCESS(rv, rv);
aListener->OnSearchResult(this, result);

View File

@ -61,7 +61,6 @@
#include "nsCOMArray.h"
#include "mozStorageHelper.h"
#include "mozStorageCID.h"
#include "nsIAutoCompleteSimpleResult.h"
#include "nsTArray.h"
#include "nsIPrivateBrowsingService.h"
#include "nsNetCID.h"
@ -78,75 +77,6 @@
// Limit the number of fields saved in a form
#define MAX_FIELDS_SAVED 100
// nsFormHistoryResult is a specialized autocomplete result class that knows
// how to remove entries from the form history table.
class nsFormHistoryResult : public nsIAutoCompleteSimpleResult
{
public:
nsFormHistoryResult(const nsAString &aFieldName)
: mFieldName(aFieldName) {}
nsresult Init();
NS_DECL_ISUPPORTS
// Forward everything except RemoveValueAt to the internal result
NS_IMETHOD GetSearchString(nsAString &_result)
{ return mResult->GetSearchString(_result); }
NS_IMETHOD GetSearchResult(PRUint16 *_result)
{ return mResult->GetSearchResult(_result); }
NS_IMETHOD GetDefaultIndex(PRInt32 *_result)
{ return mResult->GetDefaultIndex(_result); }
NS_IMETHOD GetErrorDescription(nsAString &_result)
{ return mResult->GetErrorDescription(_result); }
NS_IMETHOD GetMatchCount(PRUint32 *_result)
{ return mResult->GetMatchCount(_result); }
NS_IMETHOD GetValueAt(PRInt32 aIndex, nsAString &_result)
{ return mResult->GetValueAt(aIndex, _result); }
NS_IMETHOD GetCommentAt(PRInt32 aIndex, nsAString &_result)
{ return mResult->GetCommentAt(aIndex, _result); }
NS_IMETHOD GetStyleAt(PRInt32 aIndex, nsAString &_result)
{ return mResult->GetStyleAt(aIndex, _result); }
NS_IMETHOD GetImageAt(PRInt32 aIndex, nsAString &_result)
{ return mResult->GetImageAt(aIndex, _result); }
NS_IMETHOD RemoveValueAt(PRInt32 aRowIndex, PRBool aRemoveFromDB);
NS_FORWARD_NSIAUTOCOMPLETESIMPLERESULT(mResult->)
protected:
nsCOMPtr<nsIAutoCompleteSimpleResult> mResult;
nsString mFieldName;
};
NS_IMPL_ISUPPORTS2(nsFormHistoryResult,
nsIAutoCompleteResult, nsIAutoCompleteSimpleResult)
nsresult
nsFormHistoryResult::Init()
{
nsresult rv;
mResult = do_CreateInstance(NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &rv);
return rv;
}
NS_IMETHODIMP
nsFormHistoryResult::RemoveValueAt(PRInt32 aRowIndex, PRBool aRemoveFromDB)
{
if (!aRemoveFromDB) {
return mResult->RemoveValueAt(aRowIndex, aRemoveFromDB);
}
nsAutoString value;
nsresult rv = mResult->GetValueAt(aRowIndex, value);
NS_ENSURE_SUCCESS(rv, rv);
rv = mResult->RemoveValueAt(aRowIndex, aRemoveFromDB);
NS_ENSURE_SUCCESS(rv, rv);
nsFormHistory* fh = nsFormHistory::GetInstance();
NS_ENSURE_TRUE(fh, NS_ERROR_OUT_OF_MEMORY);
return fh->RemoveEntry(mFieldName, value);
}
#define PREF_FORMFILL_BRANCH "browser.formfill."
#define PREF_FORMFILL_ENABLE "enable"
@ -642,11 +572,6 @@ nsFormHistory::CreateStatements()
getter_AddRefs(mDBFindEntryByName));
NS_ENSURE_SUCCESS(rv,rv);
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT value FROM moz_formhistory WHERE fieldname=?1 ORDER BY UPPER(value) ASC"),
getter_AddRefs(mDBGetMatchingField));
NS_ENSURE_SUCCESS(rv,rv);
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO moz_formhistory (fieldname, value, timesUsed, "
"firstUsed, lastUsed) VALUES (?1, ?2, ?3, ?4, ?5)"),
@ -880,73 +805,6 @@ nsFormHistory::dbAreExpectedColumnsPresent()
}
nsresult
nsFormHistory::AutoCompleteSearch(const nsAString &aInputName,
const nsAString &aInputValue,
nsIAutoCompleteSimpleResult *aPrevResult,
nsIAutoCompleteResult **aResult)
{
if (!FormHistoryEnabled())
return NS_OK;
nsCOMPtr<nsIAutoCompleteSimpleResult> result;
if (aPrevResult) {
result = aPrevResult;
PRUint32 matchCount;
result->GetMatchCount(&matchCount);
for (PRInt32 i = matchCount - 1; i >= 0; --i) {
nsAutoString match;
result->GetValueAt(i, match);
if (!StringBeginsWith(match, aInputValue,
nsCaseInsensitiveStringComparator())) {
result->RemoveValueAt(i, PR_FALSE);
}
}
} else {
nsCOMPtr<nsFormHistoryResult> fhResult =
new nsFormHistoryResult(aInputName);
NS_ENSURE_TRUE(fhResult, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = fhResult->Init();
NS_ENSURE_SUCCESS(rv, rv);
reinterpret_cast<nsCOMPtr<nsIAutoCompleteSimpleResult>*>(&fhResult)->swap(result);
result->SetSearchString(aInputValue);
// generates query string
mozStorageStatementScoper scope(mDBGetMatchingField);
rv = mDBGetMatchingField->BindStringParameter(0, aInputName);
NS_ENSURE_SUCCESS(rv,rv);
PRBool hasMore = PR_FALSE;
PRUint32 count = 0;
while (NS_SUCCEEDED(mDBGetMatchingField->ExecuteStep(&hasMore)) &&
hasMore) {
nsAutoString entryString;
mDBGetMatchingField->GetString(0, entryString);
// filters out irrelevant results
if(StringBeginsWith(entryString, aInputValue,
nsCaseInsensitiveStringComparator())) {
result->AppendMatch(entryString, EmptyString(), EmptyString(), EmptyString());
++count;
}
}
if (count > 0) {
result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS);
result->SetDefaultIndex(0);
} else {
result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH);
result->SetDefaultIndex(-1);
}
}
*aResult = result;
NS_IF_ADDREF(*aResult);
return NS_OK;
}
#ifdef MOZ_MORKREADER
// Columns for form history rows

View File

@ -95,19 +95,6 @@ public:
nsFormHistory();
nsresult Init();
static nsFormHistory* GetInstance()
{
if (!gFormHistory) {
nsCOMPtr<nsIFormHistory2> fh = do_GetService(NS_FORMHISTORY_CONTRACTID);
}
return gFormHistory;
}
nsresult AutoCompleteSearch(const nsAString &aInputName,
const nsAString &aInputValue,
nsIAutoCompleteSimpleResult *aPrevResult,
nsIAutoCompleteResult **aNewResult);
private:
~nsFormHistory();
@ -137,7 +124,6 @@ public:
nsCOMPtr<nsIPrefBranch> mPrefBranch;
nsCOMPtr<mozIStorageService> mStorageService;
nsCOMPtr<mozIStorageStatement> mDBGetMatchingField;
nsCOMPtr<mozIStorageStatement> mDBFindEntry;
nsCOMPtr<mozIStorageStatement> mDBFindEntryByName;
nsCOMPtr<mozIStorageStatement> mDBSelectEntries;

View File

@ -51,6 +51,7 @@ XPCSHELL_TESTS = \
$(NULL)
MOCHI_TESTS = \
test_form_autocomplete.html \
test_form_submission.html \
test_form_submission_cap.html \
test_form_submission_cap2.html \

View File

@ -65,6 +65,28 @@ function $_(formNum, name) {
return element;
}
// Mochitest gives us a sendKey(), but it's targeted to a specific element.
// This basically sends an untargeted key event, to whatever's focused.
function doKey(aKey, modifier) {
// Seems we need to enable this again, or sendKeyEvent() complaints.
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var keyName = "DOM_VK_" + aKey.toUpperCase();
var key = Components.interfaces.nsIDOMKeyEvent[keyName];
// undefined --> null
if (!modifier)
modifier = null;
// Window utils for sending fake sey events.
var wutils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
wutils.sendKeyEvent("keydown", key, 0, modifier);
wutils.sendKeyEvent("keypress", key, 0, modifier);
wutils.sendKeyEvent("keyup", key, 0, modifier);
}
function cleanUpFormHist() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var formhist = Components.classes["@mozilla.org/satchel/form-history;1"].

View File

@ -0,0 +1,482 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Form History Autocomplete</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="satchel_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Form History test: form field autocomplete
<p id="display"></p>
<!-- we presumably can't hide the content for this test. -->
<div id="content">
<!-- normal, basic form -->
<form id="form1" onsubmit="return false;">
<input type="text" name="field1">
<button type="submit">Submit</button>
</form>
<!-- normal, basic form (new fieldname) -->
<form id="form2" onsubmit="return false;">
<input type="text" name="field2">
<button type="submit">Submit</button>
</form>
<!-- form with autocomplete=off on input -->
<form id="form3" onsubmit="return false;">
<input type="text" name="field2" autocomplete="off">
<button type="submit">Submit</button>
</form>
<!-- form with autocomplete=off on form -->
<form id="form4" autocomplete="off" onsubmit="return false;">
<input type="text" name="field2">
<button type="submit">Submit</button>
</form>
<!-- normal form for testing filtering -->
<form id="form5" onsubmit="return false;">
<input type="text" name="field3">
<button type="submit">Submit</button>
</form>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Form History autocomplete **/
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var input = $_(1, "field1");
const shiftModifier = Components.interfaces.nsIDOMNSEvent.SHIFT_MASK;
// Get the form history service
var fh = Components.classes["@mozilla.org/satchel/form-history;1"].
getService(Components.interfaces.nsIFormHistory2);
ok(fh != null, "got form history service");
fh.removeAllEntries();
fh.addEntry("field1", "value1");
fh.addEntry("field1", "value2");
fh.addEntry("field1", "value3");
fh.addEntry("field1", "value4");
fh.addEntry("field2", "value1");
fh.addEntry("field3", "a");
fh.addEntry("field3", "aa");
fh.addEntry("field3", "aa\xe6"); // 0xae == latin ae pair (0xc6 == AE)
fh.addEntry("field3", "az");
fh.addEntry("field3", "z");
// Restore the form to the default state.
function restoreForm() {
input.value = "";
input.focus();
}
// Check for expected form data.
function checkForm(expectedValue) {
var formID = input.parentNode.id;
is(input.value, expectedValue, "Checking " + formID + " input");
}
/*
* Main section of test...
*
* This is a bit hacky, because the events are either being sent or
* processes asynchronously, so we need to interrupt our flow with lots of
* setTimeout() calls. The case statements are executed in order, one per
* timeout.
*/
function runTest(testNum) {
// Seems we need to enable this again, or sendKeyEvent() complaints.
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
ok(true, "Starting test #" + testNum);
switch(testNum) {
case 1:
// Make sure initial form is empty.
checkForm("");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 2:
checkMenuEntries(["value1", "value2", "value3", "value4"]);
// Check first entry
doKey("down");
checkForm(""); // value shouldn't update
doKey("return"); // not "enter"!
checkForm("value1");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 3:
// Check second entry
doKey("down");
doKey("down");
doKey("return"); // not "enter"!
checkForm("value2");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 4:
// Check third entry
doKey("down");
doKey("down");
doKey("down");
doKey("return");
checkForm("value3");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 5:
// Check fourth entry
doKey("down");
doKey("down");
doKey("down");
doKey("down");
doKey("return");
checkForm("value4");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 6:
// Check first entry (wraparound)
doKey("down");
doKey("down");
doKey("down");
doKey("down");
doKey("down"); // deselects
doKey("down");
doKey("return");
checkForm("value1");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 7:
// Check the last entry via arrow-up
doKey("up");
doKey("return");
checkForm("value4");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 8:
// Check the last entry via arrow-up
doKey("down"); // select first entry
doKey("up"); // selects nothing!
doKey("up"); // select last entry
doKey("return");
checkForm("value4");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 9:
// Check the last entry via arrow-up (wraparound)
doKey("down");
doKey("up"); // deselects
doKey("up"); // last entry
doKey("up");
doKey("up");
doKey("up"); // first entry
doKey("up"); // deselects
doKey("up"); // last entry
doKey("return");
checkForm("value4");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 10:
// Set first entry w/o triggering autocomplete
doKey("down");
doKey("right");
checkForm("value1");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 11:
// Set first entry w/o triggering autocomplete
doKey("down");
doKey("left");
checkForm("value1");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 12:
// Check first entry (page up)
doKey("down");
doKey("down");
doKey("page_up");
doKey("return");
checkForm("value1");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 13:
// Check last entry (page down)
doKey("down");
doKey("page_down");
doKey("return");
checkForm("value4");
// Trigger autocomplete popup
restoreForm();
doKey("down");
testNum = 49;
break;
/* Test removing entries from the dropdown */
case 50:
checkMenuEntries(["value1", "value2", "value3", "value4"]);
// Delete the first entry (of 4)
doKey("down");
// On OS X, shift-backspace and shift-delete work, just delete does not.
// On Win/Linux, shift-backspace does not work, delete and shift-delete do.
doKey("delete", shiftModifier);
checkForm("");
ok(!fh.entryExists("field1", "value1"), "checking that f1/v1 was deleted");
doKey("return");
checkForm("value2");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 51:
checkMenuEntries(["value2", "value3", "value4"]);
// Check the new first entry (of 3)
doKey("down");
doKey("return");
checkForm("value2");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 52:
// Delete the second entry (of 3)
doKey("down");
doKey("down");
doKey("delete", shiftModifier);
checkForm("");
ok(!fh.entryExists("field1", "value3"), "checking that f1/v3 was deleted");
doKey("return");
checkForm("value4")
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 53:
checkMenuEntries(["value2", "value4"]);
// Check the new first entry (of 2)
doKey("down");
doKey("return");
checkForm("value2");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 54:
// Delete the last entry (of 2)
doKey("down");
doKey("down");
doKey("delete", shiftModifier);
checkForm("");
ok(!fh.entryExists("field1", "value4"), "checking that f1/v4 was deleted");
doKey("return");
checkForm("value2");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 55:
checkMenuEntries(["value2"]);
// Check the new first entry (of 1)
doKey("down");
doKey("return");
checkForm("value2");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 56:
// Delete the only remaining entry
doKey("down");
doKey("delete", shiftModifier);
checkForm("");
ok(!fh.entryExists("field1", "value2"), "checking that f1/v2 was deleted");
// Look at form 2, trigger autocomplete popup
input = $_(2, "field2");
restoreForm();
doKey("down");
testNum = 99;
break;
/* Test entries with autocomplete=off */
case 100:
// Select first entry
doKey("down");
doKey("return");
checkForm("value1");
// Look at form 3, try to trigger autocomplete popup
input = $_(3, "field2");
restoreForm();
doKey("down");
break;
case 101:
// Ensure there's no autocomplete dropdown (autocomplete=off is present)
doKey("down");
doKey("return");
checkForm("");
// Look at form 4, try to trigger autocomplete popup
input = $_(4, "field2");
restoreForm();
doKey("down");
break;
case 102:
// Ensure there's no autocomplete dropdown (autocomplete=off is present)
doKey("down");
doKey("return");
checkForm("");
// Look at form 5, try to trigger autocomplete popup
input = $_(5, "field3");
restoreForm();
testNum = 199;
sendChar("a", input);
break;
/* Test filtering as characters are typed. */
case 200:
checkMenuEntries(["a", "aa", "aa\xe6", "az"]);
sendChar("a", input);
break;
case 201:
checkMenuEntries(["aa", "aa\xe6"]);
sendChar("\xc6", input);
break;
case 202:
checkMenuEntries(["aa\xe6"]);
doKey("escape");
SimpleTest.finish();
return;
default:
ok(false, "Unexpected invocation of test #" + testNum);
SimpleTest.finish();
return;
}
setTimeout(runTest, 50, testNum + 1); // XXX 40ms was too slow, why?
}
function checkMenuEntries(expectedValues) {
var actualValues = getMenuEntries();
is(actualValues.length, expectedValues.length, "Checking length of expected menu");
for (var i = 0; i < expectedValues.length; i++)
is(actualValues[i], expectedValues[i], "Checking menu entry #"+i);
}
var autocompleteMenu;
function getMenuEntries() {
var entries = [];
// Could perhaps pull values directly from the controller, but it seems
// more reliable to test the values that are actually in the tree?
var column = autocompleteMenu.tree.columns[0];
var numRows = autocompleteMenu.tree.view.rowCount;
for (var i = 0; i < numRows; i++) {
entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
}
return entries;
}
function startTest() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var Ci = Components.interfaces;
chromeWin = window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
autocompleteMenu = chromeWin.document.getElementById("PopupAutoComplete");
ok(autocompleteMenu, "Got autocomplete popup");
runTest(1);
}
window.onload = startTest;
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>