Bug 717772 - Delay autocomplete of pasted value.

r=gavin
This commit is contained in:
Marco Bonardo 2012-01-19 12:31:29 +01:00
parent c1a836106d
commit a8878b767c
3 changed files with 163 additions and 4 deletions

View File

@ -88,6 +88,7 @@ _TEST_FILES = findbar_window.xul \
test_autocomplete2.xul \
test_autocomplete3.xul \
test_autocomplete4.xul \
test_autocomplete_delayOnPaste.xul \
test_keys.xul \
window_keys.xul \
test_showcaret.xul \

View File

@ -0,0 +1,123 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Autocomplete Widget Test 4"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTest();">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="chrome://global/content/globalOverlay.js"/>
<textbox id="autocomplete"
type="autocomplete"
completedefaultindex="true"
onsearchcomplete="searchComplete();"
timeout="0"
autocompletesearch="simple"/>
<script class="testbody" type="application/javascript">
<![CDATA[
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
function autoCompleteSimpleResult(aString) {
this.searchString = aString;
this.searchResult = Components.interfaces.nsIAutoCompleteResult.RESULT_SUCCESS;
this.matchCount = 1;
this._param = "Result";
}
autoCompleteSimpleResult.prototype = {
_param: "",
searchString: null,
searchResult: Components.interfaces.nsIAutoCompleteResult.RESULT_FAILURE,
defaultIndex: 0,
errorDescription: null,
matchCount: 0,
getValueAt: function() { return this._param; },
getCommentAt: function() { return null; },
getStyleAt: function() { return null; },
getImageAt: function() { return null; },
getLabelAt: function() { return null; },
removeValueAt: function() {}
};
// A basic autocomplete implementation that returns one result.
let autoCompleteSimple = {
classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"),
contractID: "@mozilla.org/autocomplete/search;1?name=simple",
QueryInterface: XPCOMUtils.generateQI([
Components.interfaces.nsIFactory,
Components.interfaces.nsIAutoCompleteSearch
]),
createInstance: function (outer, iid) {
return this.QueryInterface(iid);
},
registerFactory: function () {
let registrar =
Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
registrar.registerFactory(this.classID, "Test Simple Autocomplete",
this.contractID, this);
},
unregisterFactory: function () {
let registrar =
Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
registrar.unregisterFactory(this.classID, this);
},
startSearch: function (aString, aParam, aResult, aListener) {
let result = new autoCompleteSimpleResult(aString);
aListener.onSearchResult(this, result);
},
stopSearch: function () {}
};
let gACTimer;
let gAutoComplete = $("autocomplete");
function searchComplete() {
is(gAutoComplete.value, "result", "Value should be autocompleted now");
ok(Date.now() - gACTimer > 500, "There should be a delay before autocomplete");
// Unregister the factory so that we don't get in the way of other tests
autoCompleteSimple.unregisterFactory();
SimpleTest.finish();
}
function runTest() {
SimpleTest.waitForExplicitFinish();
autoCompleteSimple.registerFactory();
const SEARCH_STRING = "res";
function cbCallback() {
gAutoComplete.focus();
synthesizeKey("v", { accelKey: true });
is(gAutoComplete.value, SEARCH_STRING, "Value should not be autocompleted immediately");
}
SimpleTest.waitForClipboard(SEARCH_STRING, function () {
gACTimer = Date.now();
Components.classes["@mozilla.org/widget/clipboardhelper;1"]
.getService(Components.interfaces.nsIClipboardHelper)
.copyStringToClipboard(SEARCH_STRING, Components.interfaces.nsIClipboard.kGlobalClipboard);
}, cbCallback, cbCallback);
}
]]>
</script>
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display">
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</window>

View File

@ -94,8 +94,15 @@
<constructor><![CDATA[
mController = Components.classes["@mozilla.org/autocomplete/controller;1"].
getService(Components.interfaces.nsIAutoCompleteController);
// For security reasons delay searches on pasted values.
this.inputField.controllers.insertControllerAt(0, this._pasteController);
]]></constructor>
<destructor><![CDATA[
this.inputField.controllers.removeController(this._pasteController);
]]></destructor>
<!-- =================== nsIAccessibleProvider =================== -->
<property name="accessibleType" readonly="true">
@ -165,8 +172,18 @@
onget="return this.getAttribute('showimagecolumn') == 'true';"/>
<property name="timeout"
onset="this.setAttribute('timeout', val); return val;"
onget="var t = parseInt(this.getAttribute('timeout')); return isNaN(t) ? 50 : t;"/>
onset="this.setAttribute('timeout', val); return val;">
<getter><![CDATA[
// For security reasons delay searches on pasted values.
if (this._valueIsPasted) {
let t = parseInt(this.getAttribute('pastetimeout'));
return isNaN(t) ? 1000 : t;
}
let t = parseInt(this.getAttribute('timeout'));
return isNaN(t) ? 50 : t;
]]></getter>
</property>
<property name="searchParam"
onget="return this.getAttribute('autocompletesearchparam') || '';"
@ -563,13 +580,31 @@
]]></body>
</method>
<field name="_valueIsPasted">false</field>
<field name="_pasteController"><![CDATA[
({
_autocomplete: this,
_kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
supportsCommand: function(aCommand) aCommand == "cmd_paste",
doCommand: function(aCommand) {
this._autocomplete._valueIsPasted = true;
this._autocomplete.editor.paste(this._kGlobalClipboard);
this._autocomplete._valueIsPasted = false;
},
isCommandEnabled: function(aCommand) {
return this._autocomplete.editor.isSelectionEditable &&
this._autocomplete.editor.canPaste(this._kGlobalClipboard);
},
onEvent: function() {}
})
]]></field>
</implementation>
<handlers>
<handler event="input"><![CDATA[
if (!this.mIgnoreInput && this.mController.input == this) {
this.valueIsTyped = true;
this.mController.handleText(true);
this.mController.handleText();
}
this.resetActionType();
]]></handler>
@ -1127,7 +1162,7 @@
}
// set these attributes before we set the class
// so that we can use them from the contructor
// so that we can use them from the constructor
item.setAttribute("image", controller.getImageAt(this._currentIndex));
item.setAttribute("url", url);
item.setAttribute("title", controller.getCommentAt(this._currentIndex));