Bug 762362 - Block hardware key events to content in shell.js, send mozChromeEvent instead. r=vingtetun
authorDavid Flanagan <dflanagan@mozilla.com>
Tue, 17 Jul 2012 23:20:19 -0700
changeset 100305 5e2318fc6d1afb9fe9a79cd8a5b408e931fcb6b1
parent 100304 f08e8bc175f2ccdd6092d9f62a6b9421d0698942
child 100306 511943aecd6000868f5f480a63c078904ca9b267
push id23175
push useremorley@mozilla.com
push dateWed, 25 Jul 2012 15:03:49 +0000
treeherdermozilla-central@75d16b99e8ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvingtetun
bugs762362
milestone17.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
Bug 762362 - Block hardware key events to content in shell.js, send mozChromeEvent instead. r=vingtetun
b2g/chrome/content/shell.js
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -117,21 +117,24 @@ var shell = {
     document.getElementById('shell').appendChild(browserFrame);
 
     browserFrame.contentWindow
                 .QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIWebNavigation)
                 .sessionHistory = Cc["@mozilla.org/browser/shistory;1"]
                                     .createInstance(Ci.nsISHistory);
 
-    ['keydown', 'keypress', 'keyup'].forEach((function listenKey(type) {
-      window.addEventListener(type, this, false, true);
-      window.addEventListener(type, this, true, true);
-    }).bind(this));
-
+    // Capture all key events so we can filter out hardware buttons
+    // And send them to Gaia via mozChromeEvents.
+    // Ideally, hardware buttons wouldn't generate key events at all, or
+    // if they did, they would use keycodes that conform to DOM 3 Events.
+    // See discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=762362
+    window.addEventListener('keydown', this, true);
+    window.addEventListener('keypress', this, true);
+    window.addEventListener('keyup', this, true);
     window.addEventListener('MozApplicationManifest', this);
     window.addEventListener('mozfullscreenchange', this);
     window.addEventListener('sizemodechange', this);
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
 
     // Until the volume can be set from the content side, set it to a
     // a specific value when the device starts. This way the front-end
     // can display a notification when the volume change and show a volume
@@ -160,58 +163,98 @@ var shell = {
     SettingsListener.observe("debug.paint-flashing.enabled", false, function(value) {
       Services.prefs.setBoolPref("nglayout.debug.paint_flashing", value);
     });
 
     this.contentBrowser.src = homeURL;
   },
 
   stop: function shell_stop() {
-    ['keydown', 'keypress', 'keyup'].forEach((function unlistenKey(type) {
-      window.removeEventListener(type, this, false, true);
-      window.removeEventListener(type, this, true, true);
-    }).bind(this));
-
+    window.removeEventListener('keydown', this, true);
+    window.removeEventListener('keypress', this, true);
+    window.removeEventListener('keyup', this, true);
     window.removeEventListener('MozApplicationManifest', this);
     window.removeEventListener('mozfullscreenchange', this);
     window.removeEventListener('sizemodechange', this);
     this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
 
 #ifndef MOZ_WIDGET_GONK
     delete Services.audioManager;
 #endif
   },
 
-  forwardKeyToContent: function shell_forwardKeyToContent(evt) {
-    let content = shell.contentBrowser.contentWindow;
-    let generatedEvent = content.document.createEvent('KeyboardEvent');
-    generatedEvent.initKeyEvent(evt.type, true, true, evt.view, evt.ctrlKey,
-                                evt.altKey, evt.shiftKey, evt.metaKey,
-                                evt.keyCode, evt.charCode);
+  // If this key event actually represents a hardware button, filter it here
+  // and send a mozChromeEvent with detail.type set to xxx-button-press or
+  // xxx-button-release instead.
+  filterHardwareKeys: function shell_filterHardwareKeys(evt) {
+    var type;
+    switch (evt.keyCode) {
+      case evt.DOM_VK_HOME:         // Home button
+        type = 'home-button';
+        break;
+      case evt.DOM_VK_SLEEP:        // Sleep button
+        type = 'sleep-button';
+        break;
+      case evt.DOM_VK_PAGE_UP:      // Volume up button
+        type = 'volume-up-button';
+        break;
+      case evt.DOM_VK_PAGE_DOWN:    // Volume down button
+        type = 'volume-down-button';
+        break;
+      case evt.DOM_VK_ESCAPE:       // Back button (should be disabled)
+        type = 'back-button';
+        break;
+      case evt.DOM_VK_CONTEXT_MENU: // Menu button
+        type = 'menu-button';
+        break;
+      default:                      // Anything else is a real key
+        return;  // Don't filter it at all; let it propagate to Gaia
+    }
 
-    content.document.documentElement.dispatchEvent(generatedEvent);
+    // If we didn't return, then the key event represents a hardware key
+    // and we need to prevent it from propagating to Gaia
+    evt.stopImmediatePropagation();
+    evt.preventDefault(); // Prevent keypress events (when #501496 is fixed).
+
+    // If it is a key down or key up event, we send a chrome event to Gaia.
+    // If it is a keypress event we just ignore it.
+    switch (evt.type) {
+      case 'keydown':
+        type = type + '-press';
+        break;
+      case 'keyup':
+        type = type + '-release';
+        break;
+      case 'keypress':
+        return;
+    }
+  
+    // On my device, the physical hardware buttons (sleep and volume)
+    // send multiple events (press press release release), but the
+    // soft home button just sends one.  This hack is to manually
+    // "debounce" the keys. If the type of this event is the same as
+    // the type of the last one, then don't send it.  We'll never send
+    // two presses or two releases in a row.
+    // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=761067
+    if (type !== this.lastHardwareButtonEventType) {
+      this.lastHardwareButtonEventType = type;
+      this.sendChromeEvent({type: type});
+    }
   },
+  
+  lastHardwareButtonEventType: null, // property for the hack above
 
   handleEvent: function shell_handleEvent(evt) {
     let content = this.contentBrowser.contentWindow;
     switch (evt.type) {
       case 'keydown':
       case 'keyup':
       case 'keypress':
-        // Redirect the HOME key to System app and stop the applications from
-        // handling it.
-        let rootContentEvt = (evt.target.ownerDocument.defaultView == content);
-        if (!rootContentEvt && evt.eventPhase == evt.CAPTURING_PHASE &&
-            evt.keyCode == evt.DOM_VK_HOME) {
-          this.forwardKeyToContent(evt);
-          evt.preventDefault();
-          evt.stopImmediatePropagation();
-        }
+        this.filterHardwareKeys(evt);
         break;
-
       case 'mozfullscreenchange':
         // When the screen goes fullscreen make sure to set the focus to the
         // main window so noboby can prevent the ESC key to get out fullscreen
         // mode
         if (document.mozFullScreen)
           Services.fm.focusedWindow = window;
         break;
       case 'sizemodechange':
@@ -267,16 +310,19 @@ var shell = {
         }
         break;
     }
   },
   sendEvent: function shell_sendEvent(content, type, details) {
     let event = content.document.createEvent('CustomEvent');
     event.initCustomEvent(type, true, true, details ? details : {});
     content.dispatchEvent(event);
+  },
+  sendChromeEvent: function shell_sendChromeEvent(details) {
+    this.sendEvent(getContentWindow(), "mozChromeEvent", details);
   }
 };
 
 function nsBrowserAccess() {
 }
 
 nsBrowserAccess.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
@@ -305,17 +351,17 @@ nsBrowserAccess.prototype = {
     return contentWindow == window;
   }
 };
 
 // Listen for system messages and relay them to Gaia.
 Services.obs.addObserver(function onSystemMessage(subject, topic, data) {
   let msg = JSON.parse(data);
   let origin = Services.io.newURI(msg.manifest, null, null).prePath;
-  shell.sendEvent(shell.contentBrowser.contentWindow, 'mozChromeEvent', {
+  shell.sendChromeEvent({
     type: 'open-app',
     url: msg.uri,
     origin: origin,
     manifest: msg.manifest
   });
 }, 'system-messages-open-app', false);
 
 (function Repl() {
@@ -410,22 +456,32 @@ var AlertsHelper = {
   },
 
   registerListener: function alert_registerListener(cookie, alertListener) {
     let id = "alert" + this._count++;
     this._listeners[id] = { observer: alertListener, cookie: cookie };
     return id;
   },
 
-  showAlertNotification: function alert_showAlertNotification(imageUrl, title, text, textClickable,
-                                                              cookie, alertListener, name) {
+  showAlertNotification: function alert_showAlertNotification(imageUrl,
+                                                              title,
+                                                              text,
+                                                              textClickable,
+                                                              cookie,
+                                                              alertListener,
+                                                              name)
+  {
     let id = this.registerListener(cookie, alertListener);
-    let content = shell.contentBrowser.contentWindow;
-    shell.sendEvent(content, "mozChromeEvent", { type: "desktop-notification", id: id, icon: imageUrl,
-                                                 title: title, text: text } );
+    shell.sendChromeEvent({
+      type: "desktop-notification",
+      id: id,
+      icon: imageUrl,
+      title: title,
+      text: text
+    });
   }
 }
 
 var WebappsHelper = {
   _installers: {},
   _count: 0,
 
   init: function webapps_init() {
@@ -451,35 +507,38 @@ var WebappsHelper = {
         break;
       case "webapps-install-denied":
         DOMApplicationRegistry.denyInstall(installer);
         break;
     }
   },
 
   observe: function webapps_observe(subject, topic, data) {
-    let content = shell.contentBrowser.contentWindow;
     let json = JSON.parse(data);
     switch(topic) {
       case "webapps-launch":
         DOMApplicationRegistry.getManifestFor(json.origin, function(aManifest) {
           if (!aManifest)
             return;
 
           let manifest = new DOMApplicationManifest(aManifest, json.origin);
-          shell.sendEvent(content, "mozChromeEvent", {
+          shell.sendChromeEvent({
             "type": "webapps-launch",
             "url": manifest.fullLaunchPath(json.startPoint),
             "origin": json.origin
           });
         });
         break;
       case "webapps-ask-install":
         let id = this.registerInstaller(json);
-        shell.sendEvent(content, "mozChromeEvent", { type: "webapps-ask-install", id: id, app: json.app } );
+        shell.sendChromeEvent({
+          type: "webapps-ask-install",
+          id: id,
+          app: json.app
+        });
         break;
     }
   }
 }
 
 // Start the debugger server.
 function startDebugger() {
   if (!DebuggerServer.initialized) {
@@ -524,21 +583,21 @@ window.addEventListener('ContentStart', 
       var context = canvas.getContext('2d');
       var flags =
         context.DRAWWINDOW_DRAW_CARET |
         context.DRAWWINDOW_DRAW_VIEW |
         context.DRAWWINDOW_USE_WIDGET_LAYERS;
       context.drawWindow(window, 0, 0, width, height,
                          'rgb(255,255,255)', flags);
 
-      shell.sendEvent(content, 'mozChromeEvent', {
-          type: 'take-screenshot-success',
-          file: canvas.mozGetAsFile('screenshot', 'image/png')
+      shell.sendChromeEvent({
+        type: 'take-screenshot-success',
+        file: canvas.mozGetAsFile('screenshot', 'image/png')
       });
     } catch (e) {
       dump('exception while creating screenshot: ' + e + '\n');
-      shell.sendEvent(content, 'mozChromeEvent', {
+      shell.sendChromeEvent({
         type: 'take-screenshot-error',
         error: String(e)
       });
     }
   });
 });