Bug 1043828 - Switching IMEs doesn't always work after switching to 3rd-party keyboard and back. r=yxl

This commit is contained in:
John Lu [:mnjul] 2014-08-07 14:44:46 +08:00
Родитель 8655d63560
Коммит 3c89f36d03
5 изменённых файлов: 295 добавлений и 19 удалений

Просмотреть файл

@ -20,8 +20,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
this.Keyboard = {
_formMM: null, // The current web page message manager.
_keyboardMM: null, // The keyboard app message manager.
_formMM: null, // The current web page message manager.
_keyboardMM: null, // The keyboard app message manager.
_keyboardID: -1, // The keyboard app's ID number. -1 = invalid
_nextKeyboardID: 0, // The ID number counter.
_systemMessageName: [
'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions'
],
@ -149,6 +151,20 @@ this.Keyboard = {
}
}
// we don't process kb messages (other than register)
// if they come from a kb that we're currently not regsitered for.
// this decision is made with the kbID kept by us and kb app
let kbID = null;
if ('kbID' in msg.data) {
kbID = msg.data.kbID;
}
if (0 === msg.name.indexOf('Keyboard:') &&
('Keyboard:Register' !== msg.name && this._keyboardID !== kbID)
) {
return;
}
switch (msg.name) {
case 'Forms:Input':
this.handleFocusChange(msg);
@ -212,9 +228,22 @@ this.Keyboard = {
break;
case 'Keyboard:Register':
this._keyboardMM = mm;
if (kbID !== null) {
// keyboard identifies itself, use its kbID
// this msg would be async, so no need to return
this._keyboardID = kbID;
}else{
// generate the id for the keyboard
this._keyboardID = this._nextKeyboardID;
this._nextKeyboardID++;
// this msg is sync,
// and we want to return the id back to inputmethod
return this._keyboardID;
}
break;
case 'Keyboard:Unregister':
this._keyboardMM = null;
this._keyboardID = -1;
break;
}
},

Просмотреть файл

@ -13,16 +13,16 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender");
"@mozilla.org/childprocessmessagemanager;1", "nsISyncMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "tm",
"@mozilla.org/thread-manager;1", "nsIThreadManager");
/*
* A WeakMap to map input method iframe window to its active status.
* A WeakMap to map input method iframe window to its active status and kbID.
*/
let WindowMap = {
// WeakMap of <window, boolean> pairs.
// WeakMap of <window, object> pairs.
_map: null,
/*
@ -32,7 +32,13 @@ let WindowMap = {
if (!this._map || !win) {
return false;
}
return this._map.get(win) || false;
let obj = this._map.get(win);
if (obj && 'active' in obj) {
return obj.active;
}else{
return false;
}
},
/*
@ -45,10 +51,50 @@ let WindowMap = {
if (!this._map) {
this._map = new WeakMap();
}
this._map.set(win, isActive);
if (!this._map.has(win)) {
this._map.set(win, {});
}
this._map.get(win).active = isActive;
},
/*
* Get the keyboard ID (assigned by Keyboard.ksm) of the given window.
*/
getKbID: function(win) {
if (!this._map || !win) {
return null;
}
let obj = this._map.get(win);
if (obj && 'kbID' in obj) {
return obj.kbID;
}else{
return null;
}
},
/*
* Set the keyboard ID (assigned by Keyboard.ksm) of the given window.
*/
setKbID: function(win, kbID) {
if (!win) {
return;
}
if (!this._map) {
this._map = new WeakMap();
}
if (!this._map.has(win)) {
this._map.set(win, {});
}
this._map.get(win).kbID = kbID;
}
};
let cpmmSendAsyncMessageWithKbID = function (self, msg, data) {
data.kbID = WindowMap.getKbID(self._window);
cpmm.sendAsyncMessage(msg, data);
};
/**
* ==============================================
* InputMethodManager
@ -70,14 +116,14 @@ MozInputMethodManager.prototype = {
if (!WindowMap.isActive(this._window)) {
return;
}
cpmm.sendAsyncMessage('Keyboard:ShowInputMethodPicker', {});
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:ShowInputMethodPicker', {});
},
next: function() {
if (!WindowMap.isActive(this._window)) {
return;
}
cpmm.sendAsyncMessage('Keyboard:SwitchToNextInputMethod', {});
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SwitchToNextInputMethod', {});
},
supportsSwitching: function() {
@ -91,7 +137,7 @@ MozInputMethodManager.prototype = {
if (!WindowMap.isActive(this._window)) {
return;
}
cpmm.sendAsyncMessage('Keyboard:RemoveFocus', {});
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:RemoveFocus', {});
}
};
@ -261,11 +307,23 @@ MozInputMethod.prototype = {
// 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", {});
// get keyboard ID from Keyboard.jsm,
// or if we already have it, get it from our map
// Note: if we need to get it from Keyboard.jsm,
// we have to use a synchronous message
var kbID = WindowMap.getKbID(this._window);
if (kbID !== null) {
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:Register', {});
}else{
let res = cpmm.sendSyncMessage('Keyboard:Register', {});
WindowMap.setKbID(this._window, res[0]);
}
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:GetContext', {});
} else {
// Deactive current input method.
cpmm.sendAsyncMessage('Keyboard:Unregister', {});
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:Unregister', {});
if (this._inputcontext) {
this.setInputContext(null);
}
@ -489,7 +547,7 @@ MozInputContext.prototype = {
getText: function ic_getText(offset, length) {
let self = this;
return this._sendPromise(function(resolverId) {
cpmm.sendAsyncMessage('Keyboard:GetText', {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:GetText', {
contextId: self._contextId,
requestId: resolverId,
offset: offset,
@ -517,7 +575,7 @@ MozInputContext.prototype = {
setSelectionRange: function ic_setSelectionRange(start, length) {
let self = this;
return this._sendPromise(function(resolverId) {
cpmm.sendAsyncMessage("Keyboard:SetSelectionRange", {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SetSelectionRange', {
contextId: self._contextId,
requestId: resolverId,
selectionStart: start,
@ -545,7 +603,7 @@ MozInputContext.prototype = {
replaceSurroundingText: function ic_replaceSurrText(text, offset, length) {
let self = this;
return this._sendPromise(function(resolverId) {
cpmm.sendAsyncMessage('Keyboard:ReplaceSurroundingText', {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:ReplaceSurroundingText', {
contextId: self._contextId,
requestId: resolverId,
text: text,
@ -562,7 +620,7 @@ MozInputContext.prototype = {
sendKey: function ic_sendKey(keyCode, charCode, modifiers, repeat) {
let self = this;
return this._sendPromise(function(resolverId) {
cpmm.sendAsyncMessage('Keyboard:SendKey', {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SendKey', {
contextId: self._contextId,
requestId: resolverId,
keyCode: keyCode,
@ -576,7 +634,7 @@ MozInputContext.prototype = {
setComposition: function ic_setComposition(text, cursor, clauses) {
let self = this;
return this._sendPromise(function(resolverId) {
cpmm.sendAsyncMessage('Keyboard:SetComposition', {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SetComposition', {
contextId: self._contextId,
requestId: resolverId,
text: text,
@ -589,7 +647,7 @@ MozInputContext.prototype = {
endComposition: function ic_endComposition(text) {
let self = this;
return this._sendPromise(function(resolverId) {
cpmm.sendAsyncMessage('Keyboard:EndComposition', {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:EndComposition', {
contextId: self._contextId,
requestId: resolverId,
text: text || ''

Просмотреть файл

@ -0,0 +1,4 @@
<html>
<body>
</body>
</html>

Просмотреть файл

@ -4,6 +4,7 @@ skip-if = (toolkit == 'android' || toolkit == 'gonk') || e10s
support-files =
inputmethod_common.js
file_inputmethod.html
file_inputmethod_1043828.html
file_test_app.html
file_test_sendkey_cancel.html
file_test_sms_app.html
@ -14,5 +15,6 @@ support-files =
[test_bug953044.html]
[test_bug960946.html]
[test_bug978918.html]
[test_bug1043828.html]
[test_delete_focused_element.html]
[test_sendkey_cancel.html]

Просмотреть файл

@ -0,0 +1,183 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1043828
-->
<head>
<title>Basic test for Switching Keyboards.</title>
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1043828">Mozilla Bug 1043828</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
// The KB frame script running in Keyboard B.
function kbFrameScript() {
function tryGetText() {
var ctx = content.navigator.mozInputMethod.inputcontext;
if (ctx) {
var p = ctx.getText();
p.then(function(){
sendAsyncMessage('test:InputMethod:getText:Resolve');
}, function(e){
sendAsyncMessage('test:InputMethod:getText:Reject');
});
}else{
dump("Could not get inputcontext") ;
}
}
addMessageListener('test:InputMethod:getText:Do', function(){
tryGetText();
});
}
function runTest() {
let app, keyboardA, keyboardB;
let getTextPromise;
let mmKeyboardA, mmKeyboardB;
/**
* Test flow:
* 1. Create two keyboard iframes & a mozbrowser iframe with a text field in it & focus the text
* field.
* 2. Set keyboard frame A as active input. Wait 200ms.
* 3. Set keyboard frame B as active input. Wait 200ms.
* 4. Set keyboard frame A as inactive. Wait 200ms.
* 5. Allow frame b to use getText() with inputcontext to get the content from the text field
* iframe. Wait 200ms.
* [Test would succeed if the Promise returned by getText() resolves correctly.
* Test would fail if otherwise]
*/
let path = location.pathname;
let basePath = location.protocol + '//' + location.host +
path.substring(0, path.lastIndexOf('/'));
const WAIT_TIME = 200;
// STEP 1: Create the frames.
function step1() {
// app
app = document.createElement('iframe');
app.src = basePath + '/file_test_app.html';
app.setAttribute('mozbrowser', true);
document.body.appendChild(app);
// keyboards
keyboardA = document.createElement('iframe');
keyboardA.setAttribute('mozbrowser', true);
document.body.appendChild(keyboardA);
keyboardB = document.createElement('iframe');
keyboardB.setAttribute('mozbrowser', true);
document.body.appendChild(keyboardB);
// simulate two different keyboard apps
let imeUrl = basePath + '/file_inputmethod_1043828.html';
SpecialPowers.pushPermissions([{
type: 'input',
allow: true,
context: imeUrl
}], function() {
keyboardA.src = imeUrl;
keyboardB.src = imeUrl;
var handler = {
handleEvent: function(){
keyboardB.removeEventListener('mozbrowserloadend', this);
mmKeyboardB = SpecialPowers.getBrowserFrameMessageManager(keyboardB);
mmKeyboardB.loadFrameScript('data:,(' + kbFrameScript.toString() + ')();', false);
mmKeyboardB.addMessageListener('test:InputMethod:getText:Resolve', function() {
ok(true, 'getText() was resolved');
inputmethod_cleanup();
});
mmKeyboardB.addMessageListener('test:InputMethod:getText:Reject', function() {
ok(false, 'getText() was rejected');
inputmethod_cleanup();
});
setTimeout(function(){
step2();
}, WAIT_TIME);
}
};
keyboardB.addEventListener('mozbrowserloadend', handler);
});
}
// STEP 2: Set keyboard A active
function step2() {
let req = keyboardA.setInputMethodActive(true);
req.onsuccess = function(){
setTimeout(function(){
step3();
}, WAIT_TIME);
};
req.onerror = function(){
ok(false, 'setInputMethodActive failed: ' + this.error.name);
inputmethod_cleanup();
};
}
// STEP 3: Set keyboard B active
function step3() {
let req = keyboardB.setInputMethodActive(true);
req.onsuccess = function(){
setTimeout(function(){
step4();
}, WAIT_TIME);
};
req.onerror = function(){
ok(false, 'setInputMethodActive failed: ' + this.error.name);
inputmethod_cleanup();
};
}
// STEP 4: Set keyboard A inactive
function step4() {
let req = keyboardA.setInputMethodActive(false);
req.onsuccess = function(){
setTimeout(function(){
step5();
}, WAIT_TIME);
};
req.onerror = function(){
ok(false, 'setInputMethodActive failed: ' + this.error.name);
inputmethod_cleanup();
};
}
// STEP 5: getText
function step5() {
mmKeyboardB.sendAsyncMessage('test:InputMethod:getText:Do');
}
step1();
}
</script>
</pre>
</body>
</html>