author | Yuan Xulei <xyuan@mozilla.com> |
Tue, 01 Jan 2013 19:26:49 +0800 | |
changeset 123132 | 4cf6c8896c93f37fb8da0bcdf5b0da7fedd1949b |
parent 123131 | b082c2abd269d560772cdb59af0eddfecd5e6539 |
child 123133 | 25a2e989162c95c4f1296e2977dedcbf9e49c8bb |
push id | 24372 |
push user | emorley@mozilla.com |
push date | Wed, 27 Feb 2013 13:22:59 +0000 |
treeherder | mozilla-central@0a91da5f5eab [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | djf |
bugs | 818893 |
milestone | 22.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/b2g/chrome/content/forms.js +++ b/b2g/chrome/content/forms.js @@ -194,16 +194,17 @@ let FormAssistant = { 'button', 'file', 'checkbox', 'radio', 'reset', 'submit', 'image' ]), isKeyboardOpened: false, selectionStart: 0, selectionEnd: 0, scrollIntoViewTimeout: null, _focusedElement: null, + _documentEncoder: null, get focusedElement() { if (this._focusedElement && Cu.isDeadWrapper(this._focusedElement)) this._focusedElement = null; return this._focusedElement; }, @@ -218,28 +219,37 @@ let FormAssistant = { if (this.focusedElement) { this.focusedElement.removeEventListener('mousedown', this); this.focusedElement.removeEventListener('mouseup', this); if (!element) { this.focusedElement.blur(); } } + this._documentEncoder = null; + if (element) { element.addEventListener('mousedown', this); element.addEventListener('mouseup', this); + if (isContentEditable(element)) { + this._documentEncoder = getDocumentEncoder(element); + } } this.focusedElement = element; }, + get documentEncoder() { + return this._documentEncoder; + }, + handleEvent: function fa_handleEvent(evt) { - let focusedElement = this.focusedElement; let target = evt.target; + let range = null; switch (evt.type) { case "focus": if (!target) { break; } if (target instanceof HTMLDocument || target == content) { break; @@ -259,27 +269,29 @@ let FormAssistant = { case "pagehide": if (this.focusedElement) this.hideKeyboard(); break; case 'mousedown': // We only listen for this event on the currently focused element. // When the mouse goes down, note the cursor/selection position - this.selectionStart = this.focusedElement.selectionStart; - this.selectionEnd = this.focusedElement.selectionEnd; + range = getSelectionRange(this.focusedElement); + this.selectionStart = range[0]; + this.selectionEnd = range[1]; break; case 'mouseup': // We only listen for this event on the currently focused element. // When the mouse goes up, see if the cursor has moved (or the // selection changed) since the mouse went down. If it has, we // need to tell the keyboard about it - if (this.focusedElement.selectionStart !== this.selectionStart || - this.focusedElement.selectionEnd !== this.selectionEnd) { + range = getSelectionRange(this.focusedElement); + if (range[0] !== this.selectionStart || + range[1] !== this.selectionEnd) { this.sendKeyboardState(this.focusedElement); } break; case "resize": if (!this.isKeyboardOpened) return; @@ -354,21 +366,21 @@ let FormAssistant = { showKeyboard: function fa_showKeyboard(target) { if (this.isKeyboardOpened) return; if (target instanceof HTMLOptionElement) target = target.parentNode; + this.setFocusedElement(target); + let kbOpened = this.sendKeyboardState(target); if (this.isTextInputElement(target)) this.isKeyboardOpened = kbOpened; - - this.setFocusedElement(target); }, hideKeyboard: function fa_hideKeyboard() { sendAsyncMessage("Forms:Input", { "type": "blur" }); this.isKeyboardOpened = false; this.setFocusedElement(null); }, @@ -425,17 +437,16 @@ let FormAssistant = { sendAsyncMessage("Forms:Input", getJSON(element)); return true; } }; FormAssistant.init(); - function isContentEditable(element) { if (element.isContentEditable || element.designMode == "on") return true; // If a body element is editable and the body is the child of an // iframe we can assume this is an advanced HTML editor if (element instanceof HTMLIFrameElement && element.contentDocument && @@ -443,22 +454,22 @@ function isContentEditable(element) { element.contentDocument.designMode == "on")) return true; return element.ownerDocument && element.ownerDocument.designMode == "on"; } function getJSON(element) { let type = element.type || ""; - let value = element.value || "" + let value = element.value || ""; - // Treat contenteditble element as a special text field + // Treat contenteditble element as a special text area field if (isContentEditable(element)) { - type = "text"; - value = element.textContent; + type = "textarea"; + value = getContentEditableText(element); } // Until the input type=date/datetime/range have been implemented // let's return their real type even if the platform returns 'text' let attributeType = element.getAttribute("type") || ""; if (attributeType) { var typeLowerCase = attributeType.toLowerCase(); @@ -479,23 +490,25 @@ function getJSON(element) { // solution will be find. let inputmode = element.getAttribute('x-inputmode'); if (inputmode) { inputmode = inputmode.toLowerCase(); } else { inputmode = ''; } + let range = getSelectionRange(element); + return { "type": type.toLowerCase(), "choices": getListForElement(element), "value": value, "inputmode": inputmode, - "selectionStart": element.selectionStart, - "selectionEnd": element.selectionEnd + "selectionStart": range[0], + "selectionEnd": range[1] }; } function getListForElement(element) { if (!(element instanceof HTMLSelectElement)) return null; let optionIndex = 0; @@ -541,8 +554,56 @@ function getListForElement(element) { "optionIndex": optionIndex++ }); } } return result; }; +// Create a plain text document encode from the focused element. +function getDocumentEncoder(element) { + let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"] + .createInstance(Ci.nsIDocumentEncoder); + let flags = Ci.nsIDocumentEncoder.SkipInvisibleContent | + Ci.nsIDocumentEncoder.OutputRaw | + Ci.nsIDocumentEncoder.OutputLFLineBreak | + Ci.nsIDocumentEncoder.OutputDropInvisibleBreak; + encoder.init(element.ownerDocument, "text/plain", flags); + return encoder; +} + +// Get the visible content text of a content editable element +function getContentEditableText(element) { + let doc = element.ownerDocument; + let range = doc.createRange(); + range.selectNodeContents(element); + let encoder = FormAssistant.documentEncoder; + encoder.setRange(range); + return encoder.encodeToString(); +} + +function getSelectionRange(element) { + let start = 0; + let end = 0; + if (element instanceof HTMLInputElement || + element instanceof HTMLTextAreaElement) { + // Get the selection range of <input> and <textarea> elements + start = element.selectionStart; + end = element.selectionEnd; + } else { + // Get the selection range of contenteditable elements + let win = element.ownerDocument.defaultView; + let sel = win.getSelection(); + + let range = win.document.createRange(); + range.setStart(element, 0); + range.setEnd(sel.anchorNode, sel.anchorOffset); + let encoder = FormAssistant.documentEncoder; + + encoder.setRange(range); + start = encoder.encodeToString().length; + + encoder.setRange(sel.getRangeAt(0)); + end = start + encoder.encodeToString().length; + } + return [start, end]; +}