Bug 978918 - Filter hidden <br> when get content editable text length. r=yxl

This commit is contained in:
Wei Deng 2014-03-20 18:37:00 +08:00
parent 2705a9a693
commit 22eb57c00c
5 changed files with 167 additions and 12 deletions

View File

@ -85,7 +85,9 @@ this.Keyboard = {
mm.addMessageListener('Forms:GetText:Result:OK', this);
mm.addMessageListener('Forms:GetText:Result:Error', this);
mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
mm.addMessageListener('Forms:SetSelectionRange:Result:Error', this);
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:Error', this);
mm.addMessageListener('Forms:SendKey:Result:OK', this);
mm.addMessageListener('Forms:SendKey:Result:Error', this);
mm.addMessageListener('Forms:SequenceError', this);
@ -146,6 +148,8 @@ this.Keyboard = {
case 'Forms:GetContext:Result:OK':
case 'Forms:SetComposition:Result:OK':
case 'Forms:EndComposition:Result:OK':
case 'Forms:SetSelectionRange:Result:Error':
case 'Forms:ReplaceSurroundingText:Result:Error':
let name = msg.name.replace(/^Forms/, 'Keyboard');
this.forwardEvent(name, msg);
break;

View File

@ -24,6 +24,12 @@ XPCOMUtils.defineLazyGetter(this, "domWindowUtils", function () {
});
const RESIZE_SCROLL_DELAY = 20;
// In content editable node, when there are hidden elements such as <br>, it
// may need more than one (usually less than 3 times) move/extend operations
// to change the selection range. If we cannot change the selection range
// with more than 20 opertations, we are likely being blocked and cannot change
// the selection range any more.
const MAX_BLOCKED_COUNT = 20;
let HTMLDocument = Ci.nsIDOMHTMLDocument;
let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
@ -570,7 +576,17 @@ let FormAssistant = {
let start = json.selectionStart;
let end = json.selectionEnd;
setSelectionRange(target, start, end);
if (!setSelectionRange(target, start, end)) {
if (json.requestId) {
sendAsyncMessage("Forms:SetSelectionRange:Result:Error", {
requestId: json.requestId,
error: "failed"
});
}
break;
}
this.updateSelection();
if (json.requestId) {
@ -586,12 +602,20 @@ let FormAssistant = {
CompositionManager.endComposition('');
let selectionRange = getSelectionRange(target);
replaceSurroundingText(target,
json.text,
selectionRange[0],
selectionRange[1],
json.offset,
json.length);
if (!replaceSurroundingText(target,
json.text,
selectionRange[0],
selectionRange[1],
json.offset,
json.length)) {
if (json.requestId) {
sendAsyncMessage("Forms:ReplaceSurroundingText:Result:Error", {
requestId: json.requestId,
error: "failed"
});
}
break;
}
if (json.requestId) {
sendAsyncMessage("Forms:ReplaceSurroundingText:Result:OK", {
@ -911,6 +935,7 @@ function getDocumentEncoder(element) {
.createInstance(Ci.nsIDocumentEncoder);
let flags = Ci.nsIDocumentEncoder.SkipInvisibleContent |
Ci.nsIDocumentEncoder.OutputRaw |
Ci.nsIDocumentEncoder.OutputDropInvisibleBreak |
// Bug 902847. Don't trim trailing spaces of a line.
Ci.nsIDocumentEncoder.OutputDontRemoveLineEndingSpaces |
Ci.nsIDocumentEncoder.OutputLFLineBreak |
@ -978,7 +1003,7 @@ function setSelectionRange(element, start, end) {
if (!isTextField && !isContentEditable(element)) {
// Skip HTMLOptionElement and HTMLSelectElement elements, as they don't
// support the operation of setSelectionRange
return;
return false;
}
let text = isTextField ? element.value : getContentEditableText(element);
@ -996,6 +1021,7 @@ function setSelectionRange(element, start, end) {
if (isTextField) {
// Set the selection range of <input> and <textarea> elements
element.setSelectionRange(start, end, "forward");
return true;
} else {
// set the selection range of contenteditable elements
let win = element.ownerDocument.defaultView;
@ -1007,8 +1033,22 @@ function setSelectionRange(element, start, end) {
sel.modify("move", "forward", "character");
}
while (getContentEditableSelectionStart(element, sel) < start) {
// Avoid entering infinite loop in case we cannot change the selection
// range. See bug https://bugzilla.mozilla.org/show_bug.cgi?id=978918
let oldStart = getContentEditableSelectionStart(element, sel);
let counter = 0;
while (oldStart < start) {
sel.modify("move", "forward", "character");
let newStart = getContentEditableSelectionStart(element, sel);
if (oldStart == newStart) {
counter++;
if (counter > MAX_BLOCKED_COUNT) {
return false;
}
} else {
counter = 0;
oldStart = newStart;
}
}
// Extend the selection to the end position
@ -1016,10 +1056,25 @@ function setSelectionRange(element, start, end) {
sel.modify("extend", "forward", "character");
}
// Avoid entering infinite loop in case we cannot change the selection
// range. See bug https://bugzilla.mozilla.org/show_bug.cgi?id=978918
counter = 0;
let selectionLength = end - start;
while (getContentEditableSelectionLength(element, sel) < selectionLength) {
let oldSelectionLength = getContentEditableSelectionLength(element, sel);
while (oldSelectionLength < selectionLength) {
sel.modify("extend", "forward", "character");
let newSelectionLength = getContentEditableSelectionLength(element, sel);
if (oldSelectionLength == newSelectionLength ) {
counter++;
if (counter > MAX_BLOCKED_COUNT) {
return false;
}
} else {
counter = 0;
oldSelectionLength = newSelectionLength;
}
}
return true;
}
}
@ -1068,7 +1123,7 @@ function replaceSurroundingText(element, text, selectionStart, selectionEnd,
offset, length) {
let editor = FormAssistant.editor;
if (!editor) {
return;
return false;
}
// Check the parameters.
@ -1083,7 +1138,9 @@ function replaceSurroundingText(element, text, selectionStart, selectionEnd,
if (selectionStart != start || selectionEnd != end) {
// Change selection range before replacing.
setSelectionRange(element, start, end);
if (!setSelectionRange(element, start, end)) {
return false;
}
}
if (start != end) {
@ -1098,6 +1155,7 @@ function replaceSurroundingText(element, text, selectionStart, selectionEnd,
// Insert the text to be replaced with.
editor.insertText(text);
}
return true;
}
let CompositionManager = {

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<body>
<div id="messages-input" x-inputmode="-moz-sms" contenteditable="true"
autofocus="autofocus">Httvb<br></div>
<script type="application/javascript;version=1.7">
let input = document.getElementById('messages-input');
input.focus();
</script>
</body>
</html>
</div>
</body>
</html>

View File

@ -5,11 +5,13 @@ support-files =
file_inputmethod.html
file_test_app.html
file_test_sendkey_cancel.html
file_test_sms_app.html
[test_basic.html]
[test_bug944397.html]
skip-if = toolkit == 'gonk'
[test_bug949059.html]
[test_bug978918.html]
[test_delete_focused_element.html]
skip-if = toolkit == 'gonk'
[test_sendkey_cancel.html]

View File

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=978918
-->
<head>
<title>Basic test for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978918">Mozilla Bug 978918</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
// The input context.
var gContext = null;
inputmethod_setup(function() {
runTest();
});
function runTest() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
test_setSelectionRange();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_sms_app.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
}
function test_setSelectionRange() {
gContext.setSelectionRange(0, 100).then(function() {
is(gContext.selectionStart, 0, 'selectionStart was set successfully.');
is(gContext.selectionEnd, 5, 'selectionEnd was set successfully.');
test_replaceSurroundingText();
}, function(e) {
ok(false, 'setSelectionRange failed:' + e.name);
inputmethod_cleanup();
});
}
function test_replaceSurroundingText() {
// Replace 'Httvb' with 'Hito'.
gContext.replaceSurroundingText('Hito', 0, 100).then(function() {
ok(true, 'replaceSurroundingText finished');
inputmethod_cleanup();
}, function(e) {
ok(false, 'replaceSurroundingText failed: ' + e.name);
inputmethod_cleanup();
});
}
</script>
</pre>
</body>
</html>