Bug 971651 - Keyboard Should only send key press events to the webpage that uses the keyboard. r=yxl, a=1.2.x+
☠☠ backed out by 6391325ba95b ☠ ☠
authorFabrice Desré <fabrice@mozilla.com>
Thu, 13 Feb 2014 07:39:10 -0800
changeset 157045 d809af26b2786ce190953ff1396173ab1dc6a155
parent 157044 2ea6a65eea231354cf77e6cfb508e67cd6ab6543
child 157046 7560863a37427780651aa37d655ad644fb1660a2
push id455
push userryanvm@gmail.com
push dateThu, 20 Feb 2014 12:40:30 +0000
reviewersyxl, 1.2.x
bugs971651
milestone26.0
Bug 971651 - Keyboard Should only send key press events to the webpage that uses the keyboard. r=yxl, a=1.2.x+
dom/inputmethod/Keyboard.jsm
dom/inputmethod/MozKeyboard.js
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -12,39 +12,47 @@ const Ci = Components.interfaces;
 
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
   "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
 
 this.Keyboard = {
-  _messageManager: null,
+  _formMM: null,     // The current web page message manager.
+  _keyboardMM: null, // The keyboard app message manager.
   _messageNames: [
     'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions',
     'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
     'SwitchToNextInputMethod', 'HideInputMethod',
     'GetText', 'SendKey', 'GetContext',
-    'SetComposition', 'EndComposition'
+    'SetComposition', 'EndComposition',
+    'Register', 'Unregister'
   ],
 
-  get messageManager() {
-    if (this._messageManager && !Cu.isDeadWrapper(this._messageManager))
-      return this._messageManager;
+  get formMM() {
+    if (this._formMM && !Cu.isDeadWrapper(this._formMM))
+      return this._formMM;
 
     return null;
   },
 
-  set messageManager(mm) {
-    this._messageManager = mm;
+  set formMM(mm) {
+    this._formMM = mm;
   },
 
-  sendAsyncMessage: function(name, data) {
+  sendToForm: function(name, data) {
     try {
-      this.messageManager.sendAsyncMessage(name, data);
+      this.formMM.sendAsyncMessage(name, data);
+    } catch(e) { }
+  },
+
+  sendToKeyboard: function(name, data) {
+    try {
+      this._keyboardMM.sendAsyncMessage(name, data);
     } catch(e) { }
   },
 
   init: function keyboardInit() {
     Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
     Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
     Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
 
@@ -52,20 +60,20 @@ this.Keyboard = {
       ppmm.addMessageListener('Keyboard:' + name, this);
   },
 
   observe: function keyboardObserve(subject, topic, data) {
     let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
     let mm = frameLoader.messageManager;
 
     if (topic == 'oop-frameloader-crashed') {
-      if (this.messageManager == mm) {
+      if (this.formMM == mm) {
         // The application has been closed unexpectingly. Let's tell the
         // keyboard app that the focus has been lost.
-        ppmm.broadcastAsyncMessage('Keyboard:FocusChange', { 'type': 'blur' });
+        this.sendToKeyboard('Keyboard:FocusChange', { 'type': 'blur' });
       }
     } else {
       mm.addMessageListener('Forms:Input', this);
       mm.addMessageListener('Forms:SelectionChange', this);
       mm.addMessageListener('Forms:GetText:Result:OK', this);
       mm.addMessageListener('Forms:GetText:Result:Error', this);
       mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
       mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
@@ -75,36 +83,38 @@ this.Keyboard = {
       mm.addMessageListener('Forms:SetComposition:Result:OK', this);
       mm.addMessageListener('Forms:EndComposition:Result:OK', this);
     }
   },
 
   receiveMessage: function keyboardReceiveMessage(msg) {
     // If we get a 'Keyboard:XXX' message, check that the sender has the
     // input permission.
+    let mm;
+    let isKeyboardRegistration = msg.name == "Keyboard:Register" ||
+                                 msg.name == "Keyboard:Unregister";
     if (msg.name.indexOf("Keyboard:") != -1) {
-      if (!this.messageManager) {
+      if (!this.formMM && !isKeyboardRegistration) {
         return;
       }
 
-      let mm;
       try {
         mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
                        .frameLoader.messageManager;
       } catch(e) {
         mm = msg.target;
       }
 
       // That should never happen.
       if (!mm) {
         dump("!! No message manager found for " + msg.name);
         return;
       }
 
-      if (!mm.assertPermission("input")) {
+      if (!isKeyboardRegistration && !mm.assertPermission("input")) {
         dump("Keyboard message " + msg.name +
         " from a content process with no 'input' privileges.");
         return;
       }
     }
 
     switch (msg.name) {
       case 'Forms:Input':
@@ -158,24 +168,30 @@ this.Keyboard = {
         this.getContext(msg);
         break;
       case 'Keyboard:SetComposition':
         this.setComposition(msg);
         break;
       case 'Keyboard:EndComposition':
         this.endComposition(msg);
         break;
+      case 'Keyboard:Register':
+        this._keyboardMM = mm;
+        break;
+      case 'Keyboard:Unregister':
+        this._keyboardMM = null;
+        break;
     }
   },
 
   forwardEvent: function keyboardForwardEvent(newEventName, msg) {
-    this.messageManager = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
-                             .frameLoader.messageManager;
+    this.formMM = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
+                            .frameLoader.messageManager;
 
-    ppmm.broadcastAsyncMessage(newEventName, msg.data);
+    this.sendToKeyboard(newEventName, msg.data);
   },
 
   handleFocusChange: function keyboardHandleFocusChange(msg) {
     this.forwardEvent('Keyboard:FocusChange', msg);
 
     let browser = Services.wm.getMostRecentWindow("navigator:browser");
 
     // Chrome event, used also to render value selectors; that's why we need
@@ -186,37 +202,37 @@ this.Keyboard = {
       value: msg.data.value,
       choices: JSON.stringify(msg.data.choices),
       min: msg.data.min,
       max: msg.data.max
     });
   },
 
   setSelectedOption: function keyboardSetSelectedOption(msg) {
-    this.sendAsyncMessage('Forms:Select:Choice', msg.data);
+    this.sendToForm('Forms:Select:Choice', msg.data);
   },
 
   setSelectedOptions: function keyboardSetSelectedOptions(msg) {
-    this.sendAsyncMessage('Forms:Select:Choice', msg.data);
+    this.sendToForm('Forms:Select:Choice', msg.data);
   },
 
   setSelectionRange: function keyboardSetSelectionRange(msg) {
-    this.sendAsyncMessage('Forms:SetSelectionRange', msg.data);
+    this.sendToForm('Forms:SetSelectionRange', msg.data);
   },
 
   setValue: function keyboardSetValue(msg) {
-    this.sendAsyncMessage('Forms:Input:Value', msg.data);
+    this.sendToForm('Forms:Input:Value', msg.data);
   },
 
   removeFocus: function keyboardRemoveFocus() {
-    this.sendAsyncMessage('Forms:Select:Blur', {});
+    this.sendToForm('Forms:Select:Blur', {});
   },
 
   replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
-    this.sendAsyncMessage('Forms:ReplaceSurroundingText', msg.data);
+    this.sendToForm('Forms:ReplaceSurroundingText', msg.data);
   },
 
   showInputMethodPicker: function keyboardShowInputMethodPicker() {
     let browser = Services.wm.getMostRecentWindow("navigator:browser");
     browser.shell.sendChromeEvent({
       type: "inputmethod-showall"
     });
   },
@@ -224,46 +240,46 @@ this.Keyboard = {
   switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
     let browser = Services.wm.getMostRecentWindow("navigator:browser");
     browser.shell.sendChromeEvent({
       type: "inputmethod-next"
     });
   },
 
   getText: function keyboardGetText(msg) {
-    this.sendAsyncMessage('Forms:GetText', msg.data);
+    this.sendToForm('Forms:GetText', msg.data);
   },
 
   sendKey: function keyboardSendKey(msg) {
-    this.sendAsyncMessage('Forms:Input:SendKey', msg.data);
+    this.sendToForm('Forms:Input:SendKey', msg.data);
   },
 
   getContext: function keyboardGetContext(msg) {
     if (this._layouts) {
-      ppmm.broadcastAsyncMessage('Keyboard:LayoutsChange', this._layouts);
+      this.sendToKeyboard('Keyboard:LayoutsChange', this._layouts);
     }
 
-    this.sendAsyncMessage('Forms:GetContext', msg.data);
+    this.sendToForm('Forms:GetContext', msg.data);
   },
 
   setComposition: function keyboardSetComposition(msg) {
-    this.sendAsyncMessage('Forms:SetComposition', msg.data);
+    this.sendToForm('Forms:SetComposition', msg.data);
   },
 
   endComposition: function keyboardEndComposition(msg) {
-    this.sendAsyncMessage('Forms:EndComposition', msg.data);
+    this.sendToForm('Forms:EndComposition', msg.data);
   },
 
   /**
    * Get the number of keyboard layouts active from keyboard_manager
    */
   _layouts: null,
   setLayouts: function keyboardSetLayoutCount(layouts) {
     // The input method plugins may not have loaded yet,
     // cache the layouts so on init we can respond immediately instead
     // of going back and forth between keyboard_manager
     this._layouts = layouts;
 
-    ppmm.broadcastAsyncMessage('Keyboard:LayoutsChange', layouts);
+    this.sendToKeyboard('Keyboard:LayoutsChange', layouts);
   }
 };
 
 this.Keyboard.init();
--- a/dom/inputmethod/MozKeyboard.js
+++ b/dom/inputmethod/MozKeyboard.js
@@ -5,17 +5,16 @@
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/ObjectWrapper.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
   "@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "tm",
   "@mozilla.org/thread-manager;1", "nsIThreadManager");
 
@@ -170,30 +169,30 @@ MozKeyboard.prototype = {
       if (!handler || !(handler instanceof Ci.nsIDOMEventListener))
         return;
 
       let detail = {
         "detail": msgJson
       };
 
       let evt = new this._window.CustomEvent("focuschanged",
-          ObjectWrapper.wrap(detail, this._window));
+          Cu.cloneInto(detail, this._window));
       handler.handleEvent(evt);
     } else if (msg.name == "Keyboard:SelectionChange") {
       let msgJson = msg.json;
 
       this._selectionStart = msgJson.selectionStart;
       this._selectionEnd = msgJson.selectionEnd;
 
       let handler = this._selectionHandler;
       if (!handler || !(handler instanceof Ci.nsIDOMEventListener))
         return;
 
       let evt = new this._window.CustomEvent("selectionchange",
-          ObjectWrapper.wrap({}, this._window));
+          Cu.cloneInto({}, this._window));
       handler.handleEvent(evt);
     }
   },
 
   observe: function mozKeyboardObserve(subject, topic, data) {
     let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
     if (wId == this.innerWindowID)
       this.uninit();
@@ -325,16 +324,17 @@ MozInputMethod.prototype = {
     Services.obs.addObserver(this, "inner-window-destroyed", false);
     cpmm.addMessageListener('Keyboard:FocusChange', this);
     cpmm.addMessageListener('Keyboard:SelectionChange', this);
     cpmm.addMessageListener('Keyboard:GetContext:Result:OK', this);
     cpmm.addMessageListener('Keyboard:LayoutsChange', this);
   },
 
   uninit: function mozInputMethodUninit() {
+    this.setActive(false);
     Services.obs.removeObserver(this, "inner-window-destroyed");
     cpmm.removeMessageListener('Keyboard:FocusChange', this);
     cpmm.removeMessageListener('Keyboard:SelectionChange', this);
     cpmm.removeMessageListener('Keyboard:GetContext:Result:OK', this);
     cpmm.removeMessageListener('Keyboard:LayoutsChange', this);
 
     this._window = null;
     this._mgmt = null;
@@ -412,35 +412,37 @@ MozInputMethod.prototype = {
         this._layouts[data.type] > 1 :
         false;
 
       this._inputcontext = new MozInputContext(data);
       this._inputcontext.init(this._window);
     }
 
     let event = new this._window.Event("inputcontextchange",
-                                       ObjectWrapper.wrap({}, this._window));
+                                       Cu.cloneInto({}, this._window));
     this.__DOM_IMPL__.dispatchEvent(event);
   },
 
   setActive: function mozInputMethodSetActive(isActive) {
     if (WindowMap.isActive(this._window) === isActive) {
       return;
     }
 
     WindowMap.setActive(this._window, isActive);
 
     if (isActive) {
       // Activate current input method.
       // If there is already an active context, then this will trigger
       // a GetContext:Result:OK event, and we can initialize ourselves.
       // Otherwise silently ignored.
+      cpmm.sendAsyncMessage('Keyboard:Register', {});
       cpmm.sendAsyncMessage("Keyboard:GetContext", {});
     } else {
       // Deactive current input method.
+      cpmm.sendAsyncMessage('Keyboard:Unregister', {});
       if (this._inputcontext) {
         this.setInputContext(null);
       }
     }
   }
 };
 
  /**
@@ -547,17 +549,17 @@ MozInputContext.prototype = {
         resolver.resolve(json.text);
         break;
       case "Keyboard:GetText:Result:Error":
         resolver.reject(json.error);
         break;
       case "Keyboard:SetSelectionRange:Result:OK":
       case "Keyboard:ReplaceSurroundingText:Result:OK":
         resolver.resolve(
-          ObjectWrapper.wrap(json.selectioninfo, this._window));
+          Cu.cloneInto(json.selectioninfo, this._window));
         break;
       case "Keyboard:SequenceError":
         // Occurs when a new element got focus, but the inputContext was
         // not invalidated yet...
         resolver.reject("InputContext has expired");
         break;
       case "Keyboard:SetComposition:Result:OK": // Fall through.
       case "Keyboard:EndComposition:Result:OK":
@@ -601,17 +603,17 @@ MozInputContext.prototype = {
   },
 
   _fireEvent: function ic_fireEvent(eventName, aDetail) {
     let detail = {
       detail: aDetail
     };
 
     let event = new this._window.Event(eventName,
-                                       ObjectWrapper.wrap(aDetail, this._window));
+                                       Cu.cloneInto(aDetail, this._window));
     this.__DOM_IMPL__.dispatchEvent(event);
   },
 
   // tag name of the input field
   get type() {
     return this._context.type;
   },