Bug 802073 - Receive input event twice from input tag type:time and type:date r=vingtetun a=blocking-basecamp

This commit is contained in:
Tim Taubert 2012-11-07 12:53:24 +01:00
parent 0d699ad09b
commit e057906f3d
5 changed files with 193 additions and 98 deletions

View File

@ -18,6 +18,13 @@ XPCOMUtils.defineLazyServiceGetter(Services, "fm",
"@mozilla.org/focus-manager;1",
"nsIFocusManager");
XPCOMUtils.defineLazyGetter(this, "domWindowUtils", function () {
return content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
});
const FOCUS_CHANGE_DELAY = 20;
let HTMLInputElement = Ci.nsIDOMHTMLInputElement;
let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
@ -28,7 +35,6 @@ let FormAssistant = {
init: function fa_init() {
addEventListener("focus", this, true, false);
addEventListener("blur", this, true, false);
addEventListener("keypress", this, true, false);
addEventListener("resize", this, true, false);
addMessageListener("Forms:Select:Choice", this);
addMessageListener("Forms:Input:Value", this);
@ -37,6 +43,10 @@ let FormAssistant = {
Services.obs.addObserver(this, "xpcom-shutdown", false);
},
ignoredInputTypes: new Set([
'button', 'file', 'checkbox', 'radio', 'reset', 'submit', 'image'
]),
isKeyboardOpened: false,
selectionStart: 0,
selectionEnd: 0,
@ -80,43 +90,24 @@ let FormAssistant = {
switch (evt.type) {
case "focus":
if (this.isKeyboardOpened)
if (this.isIMEDisabled())
return;
let ignore = {
button: true,
file: true,
checkbox: true,
radio: true,
reset: true,
submit: true,
image: true
};
if (target instanceof HTMLSelectElement) {
content.setTimeout(function showIMEForSelect() {
sendAsyncMessage("Forms:Input", getJSON(target));
});
this.setFocusedElement(target);
} else if (target instanceof HTMLOptionElement &&
target.parentNode instanceof HTMLSelectElement) {
target = target.parentNode;
content.setTimeout(function showIMEForSelect() {
sendAsyncMessage("Forms:Input", getJSON(target));
});
this.setFocusedElement(target);
} else if ((target instanceof HTMLInputElement && !ignore[target.type]) ||
target instanceof HTMLTextAreaElement) {
this.isKeyboardOpened = this.tryShowIme(target);
this.setFocusedElement(target);
if (target && this.isFocusableElement(target)) {
if (this.blurTimeout) {
this.blurTimeout = content.clearTimeout(this.blurTimeout);
this.handleIMEStateDisabled();
}
this.handleIMEStateEnabled(target);
}
break;
case "blur":
if (this.focusedElement) {
sendAsyncMessage("Forms:Input", { "type": "blur" });
this.setFocusedElement(null);
this.isKeyboardOpened = false;
this.blurTimeout = content.setTimeout(function () {
this.blurTimeout = null;
this.handleIMEStateDisabled();
}.bind(this), FOCUS_CHANGE_DELAY);
}
break;
@ -146,17 +137,6 @@ let FormAssistant = {
this.focusedElement.scrollIntoView(false);
}
break;
case "keypress":
if (evt.keyCode != evt.DOM_VK_ESCAPE || !this.isKeyboardOpened)
return;
sendAsyncMessage("Forms:Input", { "type": "blur" });
this.isKeyboardOpened = false;
evt.preventDefault();
evt.stopPropagation();
break;
}
},
@ -213,21 +193,17 @@ let FormAssistant = {
observe: function fa_observe(subject, topic, data) {
switch (topic) {
case "ime-enabled-state-changed":
let isOpen = this.isKeyboardOpened;
let shouldOpen = parseInt(data);
if (shouldOpen && !isOpen) {
let target = Services.fm.focusedElement;
let target = Services.fm.focusedElement;
if (!target)
return;
if (!target || !this.tryShowIme(target)) {
this.setFocusedElement(null);
return;
} else {
this.setFocusedElement(target);
}
} else if (!shouldOpen && isOpen) {
sendAsyncMessage("Forms:Input", { "type": "blur" });
if (shouldOpen) {
if (!this.focusedElement && this.isFocusableElement(target))
this.handleIMEStateEnabled(target);
} else if (this._focusedElement == target) {
this.handleIMEStateDisabled();
}
this.isKeyboardOpened = shouldOpen;
break;
case "xpcom-shutdown":
@ -239,11 +215,50 @@ let FormAssistant = {
}
},
tryShowIme: function(element) {
if (!element) {
return;
}
isIMEDisabled: function fa_isIMEDisabled() {
let disabled = false;
try {
disabled = domWindowUtils.IMEStatus == domWindowUtils.IME_STATUS_DISABLED;
} catch (e) {}
return disabled;
},
handleIMEStateEnabled: function fa_handleIMEStateEnabled(target) {
if (this.isKeyboardOpened)
return;
if (target instanceof HTMLOptionElement)
target = target.parentNode;
let kbOpened = this.tryShowIme(target);
if (target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement)
this.isKeyboardOpened = kbOpened;
this.setFocusedElement(target);
},
handleIMEStateDisabled: function fa_handleIMEStateDisabled() {
sendAsyncMessage("Forms:Input", { "type": "blur" });
this.isKeyboardOpened = false;
this.setFocusedElement(null);
},
isFocusableElement: function fa_isFocusableElement(element) {
if (element instanceof HTMLSelectElement ||
element instanceof HTMLTextAreaElement)
return true;
if (element instanceof HTMLOptionElement &&
element.parentNode instanceof HTMLSelectElement)
return true;
return (element instanceof HTMLInputElement &&
!this.ignoredInputTypes.has(element.type));
},
tryShowIme: function(element) {
// FIXME/bug 729623: work around apparent bug in the IME manager
// in gecko.
let readonly = element.getAttribute("readonly");

View File

@ -17,6 +17,7 @@ Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
Cu.import('resource://gre/modules/Payment.jsm');
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
Cu.import('resource://gre/modules/Keyboard.jsm');
#ifdef MOZ_B2G_RIL
Cu.import('resource://gre/modules/NetworkStatsService.jsm');
#endif

101
b2g/components/Keyboard.jsm Normal file
View File

@ -0,0 +1,101 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
this.EXPORTED_SYMBOLS = ['Keyboard'];
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const kFormsFrameScript = 'chrome://browser/content/forms.js';
Cu.import('resource://gre/modules/Services.jsm');
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
let Keyboard = {
_messageManager: null,
_messageNames: [
'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions'
],
get messageManager() {
if (this._messageManager && !Cu.isDeadWrapper(this._messageManager))
return this._messageManager;
throw Error('no message manager set');
},
set messageManager(mm) {
this._messageManager = mm;
},
init: function keyboardInit() {
Services.obs.addObserver(this, 'in-process-browser-frame-shown', false);
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
for (let name of this._messageNames)
ppmm.addMessageListener('Keyboard:' + name, this);
},
observe: function keyboardObserve(subject, topic, data) {
let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
let mm = frameLoader.messageManager;
mm.addMessageListener('Forms:Input', this);
try {
mm.loadFrameScript(kFormsFrameScript, true);
} catch (e) {
dump('Error loading ' + kFormsFrameScript + ' as frame script: ' + e + '\n');
}
},
receiveMessage: function keyboardReceiveMessage(msg) {
switch (msg.name) {
case 'Forms:Input':
this.handleFormsInput(msg);
break;
case 'Keyboard:SetValue':
this.setValue(msg);
break;
case 'Keyboard:RemoveFocus':
this.removeFocus();
break;
case 'Keyboard:SetSelectedOption':
this.setSelectedOption(msg);
break;
case 'Keyboard:SetSelectedOptions':
this.setSelectedOption(msg);
break;
}
},
handleFormsInput: function keyboardHandleFormsInput(msg) {
this.messageManager = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
ppmm.broadcastAsyncMessage('Keyboard:FocusChange', msg.data);
},
setSelectedOption: function keyboardSetSelectedOption(msg) {
this.messageManager.sendAsyncMessage('Forms:Select:Choice', msg.data);
},
setSelectedOptions: function keyboardSetSelectedOptions(msg) {
this.messageManager.sendAsyncMessage('Forms:Select:Choice', msg.data);
},
setValue: function keyboardSetValue(msg) {
this.messageManager.sendAsyncMessage('Forms:Input:Value', msg.data);
},
removeFocus: function keyboardRemoveFocus() {
this.messageManager.sendAsyncMessage('Forms:Select:Blur', {});
}
};
Keyboard.init();

View File

@ -34,6 +34,7 @@ EXTRA_PP_COMPONENTS = \
$(NULL)
EXTRA_JS_MODULES = \
Keyboard.jsm \
TelURIParser.jsm \
$(NULL)

View File

@ -4,15 +4,16 @@
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const kFormsFrameScript = "chrome://browser/content/forms.js";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender");
// -----------------------------------------------------------------------
// MozKeyboard
// -----------------------------------------------------------------------
@ -36,20 +37,19 @@ MozKeyboard.prototype = {
init: function mozKeyboardInit(win) {
Services.obs.addObserver(this, "inner-window-destroyed", false);
Services.obs.addObserver(this, 'in-process-browser-frame-shown', false);
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
cpmm.addMessageListener('Keyboard:FocusChange', this);
this._window = win;
this._utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
this.innerWindowID = this._utils.currentInnerWindowID;
this._focusHandler = null;
},
uninit: function mozKeyboardUninit() {
Services.obs.removeObserver(this, "inner-window-destroyed");
this._messageManager = null;
cpmm.removeMessageListener('Keyboard:FocusChange', this);
this._window = null;
this._utils = null;
this._focusHandler = null;
@ -63,25 +63,25 @@ MozKeyboard.prototype = {
},
setSelectedOption: function mozKeyboardSetSelectedOption(index) {
this._messageManager.sendAsyncMessage("Forms:Select:Choice", {
"index": index
cpmm.sendAsyncMessage('Keyboard:SetSelectedOption', {
'index': index
});
},
setValue: function mozKeyboardSetValue(value) {
this._messageManager.sendAsyncMessage("Forms:Input:Value", {
"value": value
cpmm.sendAsyncMessage('Keyboard:SetValue', {
'value': value
});
},
setSelectedOptions: function mozKeyboardSetSelectedOptions(indexes) {
this._messageManager.sendAsyncMessage("Forms:Select:Choice", {
"indexes": indexes || []
cpmm.sendAsyncMessage('Keyboard:SetSelectedOptions', {
'indexes': indexes
});
},
removeFocus: function mozKeyboardRemoveFocus() {
this._messageManager.sendAsyncMessage("Forms:Select:Blur", {});
cpmm.sendAsyncMessage('Keyboard:RemoveFocus', {});
},
set onfocuschange(val) {
@ -92,7 +92,7 @@ MozKeyboard.prototype = {
return this._focusHandler;
},
handleMessage: function mozKeyboardHandleMessage(msg) {
receiveMessage: function mozKeyboardReceiveMessage(msg) {
let handler = this._focusHandler;
if (!handler || !(handler instanceof Ci.nsIDOMEventListener))
return;
@ -107,32 +107,9 @@ MozKeyboard.prototype = {
},
observe: function mozKeyboardObserve(subject, topic, data) {
switch (topic) {
case "inner-window-destroyed": {
let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (wId == this.innerWindowID) {
this.uninit();
}
break;
}
case 'remote-browser-frame-shown':
case 'in-process-browser-frame-shown': {
let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
let mm = frameLoader.messageManager;
mm.addMessageListener("Forms:Input", (function receiveMessage(msg) {
// Need to save mm here so later the message can be sent back to the
// correct app in the methods called by the value selector.
this._messageManager = mm;
this.handleMessage(msg);
}).bind(this));
try {
mm.loadFrameScript(kFormsFrameScript, true);
} catch (e) {
dump('Error loading ' + kFormsFrameScript + ' as frame script: ' + e + '\n');
}
break;
}
}
let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (wId == this.innerWindowID)
this.uninit();
}
};