Bug 860546 - [keyboard] Notify keyboard when the content of current input field is changed by JS. r=fabrice
author=?UTF-8?q?"Yuan=20Xulei(=E8=A2=81=E5=BE=90=E7=A3=8A)"?=
Fri, 03 May 2013 15:02:30 +0200
changeset 130741 5565fd265415bf8f1aabc67fd3a4a3916b8c90d9
parent 130740 85249bddf0a65860354b279a9ff8fd2181f513c0
child 130742 e6c09bae2a132c605219141f62ccc707e9c3504b
push id1579
push userphilringnalda@gmail.com
push dateSat, 04 May 2013 04:38:04 +0000
treeherderfx-team@a56432a42a41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs860546
milestone23.0a1
Bug 860546 - [keyboard] Notify keyboard when the content of current input field is changed by JS. r=fabrice
b2g/chrome/content/forms.js
--- a/b2g/chrome/content/forms.js
+++ b/b2g/chrome/content/forms.js
@@ -198,16 +198,18 @@ let FormAssistant = {
   ]),
 
   isKeyboardOpened: false,
   selectionStart: -1,
   selectionEnd: -1,
   scrollIntoViewTimeout: null,
   _focusedElement: null,
   _documentEncoder: null,
+  _editor: null,
+  _editing: false,
 
   get focusedElement() {
     if (this._focusedElement && Cu.isDeadWrapper(this._focusedElement))
       this._focusedElement = null;
 
     return this._focusedElement;
   },
 
@@ -223,32 +225,51 @@ let FormAssistant = {
       this.focusedElement.removeEventListener('mousedown', this);
       this.focusedElement.removeEventListener('mouseup', this);
       if (!element) {
         this.focusedElement.blur();
       }
     }
 
     this._documentEncoder = null;
+    if (this._editor) {
+      this._editor.removeEditorObserver(this);
+      this._editor = null;
+    }
 
     if (element) {
       element.addEventListener('mousedown', this);
       element.addEventListener('mouseup', this);
       if (isContentEditable(element)) {
         this._documentEncoder = getDocumentEncoder(element);
       }
+      this._editor = getPlaintextEditor(element);
+      if (this._editor) {
+        // Add a nsIEditorObserver to monitor the text content of the focused
+        // element.
+        this._editor.addEditorObserver(this);
+      }
     }
 
     this.focusedElement = element;
   },
 
   get documentEncoder() {
     return this._documentEncoder;
   },
 
+  // Implements nsIEditorObserver get notification when the text content of
+  // current input field has changed.
+  EditAction: function fa_editAction() {
+    if (this._editing) {
+      return;
+    }
+    this.sendKeyboardState(this.focusedElement);
+  },
+
   handleEvent: function fa_handleEvent(evt) {
     let target = evt.target;
 
     let range = null;
     switch (evt.type) {
       case "focus":
         if (!target) {
           break;
@@ -326,31 +347,36 @@ let FormAssistant = {
         break;
 
       case "input":
         // When the text content changes, notify the keyboard
         this.updateSelection();
         break;
 
       case "keydown":
+        // Don't monitor the text change resulting from key event.
+        this._editing = true;
+
         // We use 'setTimeout' to wait until the input element accomplishes the
-        // change in selection range
+        // change in selection range or text content.
         content.setTimeout(function() {
           this.updateSelection();
+          this._editing = false;
         }.bind(this), 0);
         break;
     }
   },
 
   receiveMessage: function fa_receiveMessage(msg) {
     let target = this.focusedElement;
     if (!target) {
       return;
     }
 
+    this._editing = true;
     let json = msg.json;
     switch (msg.name) {
       case "Forms:Input:Value": {
         target.value = json.value;
 
         let event = target.ownerDocument.createEvent('HTMLEvents');
         event.initEvent('input', true, false);
         target.dispatchEvent(event);
@@ -391,16 +417,18 @@ let FormAssistant = {
       case "Forms:SetSelectionRange":  {
         let start = json.selectionStart;
         let end =  json.selectionEnd;
         setSelectionRange(target, start, end);
         this.updateSelection();
         break;
       }
     }
+    this._editing = false;
+
   },
 
   showKeyboard: function fa_showKeyboard(target) {
     if (this.isKeyboardOpened)
       return;
 
     if (target instanceof HTMLOptionElement)
       target = target.parentNode;
@@ -520,17 +548,17 @@ function getJSON(element) {
     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(); 
+    var typeLowerCase = attributeType.toLowerCase();
     switch (typeLowerCase) {
       case "datetime":
       case "datetime-local":
       case "range":
         type = typeLowerCase;
         break;
     }
   }
@@ -719,8 +747,32 @@ function setSelectionRange(element, star
 
     let selectionLength = end - start;
     while (getContentEditableSelectionLength(element, sel) < selectionLength) {
       sel.modify("extend", "forward", "character");
     }
   }
 }
 
+// Get nsIPlaintextEditor object from an input field
+function getPlaintextEditor(element) {
+  let editor = null;
+  // Get nsIEditor
+  if (element instanceof HTMLInputElement ||
+      element instanceof HTMLTextAreaElement) {
+    // Get from the <input> and <textarea> elements
+    editor = element.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
+  } else if (isContentEditable(element)) {
+    // Get from content editable element
+    let win = element.ownerDocument.defaultView;
+    let editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIWebNavigation)
+                            .QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIEditingSession);
+    if (editingSession) {
+      editor = editingSession.getEditorForWindow(win);
+    }
+  }
+  if (editor) {
+    editor.QueryInterface(Ci.nsIPlaintextEditor);
+  }
+  return editor;
+}