зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1043828 - Switching IMEs doesn't always work after switching to 3rd-party keyboard and back. r=yxl
This commit is contained in:
Родитель
8655d63560
Коммит
3c89f36d03
|
@ -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>
|
||||
|
Загрузка…
Ссылка в новой задаче