/* Any copyright is dedicated to the public domain. http://creativecommons.org/publicdomain/zero/1.0/ */ // Bug 905573 - Add setInputMethodActive to browser elements to allow gaia // system set the active IME app. 'use strict'; SimpleTest.waitForExplicitFinish(); browserElementTestHelpers.setEnabledPref(true); // We'll need to get the appId from the current document, // it's either SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID when // we are not running inside an app (e.g. Firefox Desktop), // or the appId of Mochitest app when we are running inside that app // (e.g. Emulator). var currentAppId = SpecialPowers.wrap(document).nodePrincipal.appId; var inApp = currentAppId !== SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID; // We will also need the manifest URL and set it on iframes. var currentAppManifestURL; if (inApp) { let appsService = SpecialPowers.Cc["@mozilla.org/AppsService;1"] .getService(SpecialPowers.Ci.nsIAppsService); currentAppManifestURL = appsService.getManifestURLByLocalId(currentAppId); }; info('appId=' + currentAppId); info('manifestURL=' + currentAppManifestURL); function setup() { let appInfo = SpecialPowers.Cc['@mozilla.org/xre/app-info;1'] .getService(SpecialPowers.Ci.nsIXULAppInfo); if (appInfo.name != 'B2G') { SpecialPowers.Cu.import("resource://gre/modules/Keyboard.jsm", window); } SpecialPowers.setBoolPref("dom.mozInputMethod.enabled", true); SpecialPowers.addPermission('input-manage', true, document); } function tearDown() { SpecialPowers.setBoolPref("dom.mozInputMethod.enabled", false); SpecialPowers.removePermission('input-manage', document); SimpleTest.finish(); } function runTest() { createFrames(); } var gInputMethodFrames = []; var gInputFrame; function createFrames() { // Create two input method iframes. let loadendCount = 0; let countLoadend = function() { loadendCount++; if (loadendCount === 3) { setPermissions(); } }; // Create an input field to receive string from input method iframes. gInputFrame = document.createElement('iframe'); gInputFrame.setAttribute('mozbrowser', 'true'); gInputFrame.src = 'file_browserElement_SetInputMethodActive.html'; document.body.appendChild(gInputFrame); gInputFrame.addEventListener('mozbrowserloadend', countLoadend); for (let i = 0; i < 2; i++) { let frame = gInputMethodFrames[i] = document.createElement('iframe'); frame.setAttribute('mozbrowser', 'true'); if (currentAppManifestURL) { frame.setAttribute('mozapp', currentAppManifestURL); } frame.addEventListener('mozbrowserloadend', countLoadend); frame.src = 'file_empty.html#' + i; document.body.appendChild(frame); } } function setPermissions() { let permissions = [{ type: 'input', allow: true, context: { url: SimpleTest.getTestFileURL('/file_empty.html'), originAttributes: { appId: currentAppId, inIsolatedMozBrowser: true } } }]; if (inApp) { // The current document would also need to be given access for IPC to // recognize our permission (why)? permissions.push({ type: 'input', allow: true, context: document }); } SpecialPowers.pushPermissions(permissions, SimpleTest.waitForFocus.bind(SimpleTest, startTest)); } function startTest() { // The frame script running in the frame where the input is hosted. let appFrameScript = function appFrameScript() { let input = content.document.body.firstElementChild; input.oninput = function() { sendAsyncMessage('test:InputMethod:oninput', { from: 'input', value: this.value }); }; input.onblur = function() { // "Expected" lost of focus since the test is finished. if (input.value === 'hello#0#1') { return; } sendAsyncMessage('test:InputMethod:oninput', { from: 'input', error: true, value: 'Unexpected lost of focus on the input frame!' }); }; input.focus(); }; // Inject frame script to receive input. let mm = SpecialPowers.getBrowserFrameMessageManager(gInputFrame); mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); mm.addMessageListener("test:InputMethod:oninput", next); gInputMethodFrames.forEach((frame) => { ok(frame.setInputMethodActive, 'Can access setInputMethodActive.'); // The frame script running in the input method frames. let appFrameScript = function appFrameScript() { let im = content.navigator.mozInputMethod; im.oninputcontextchange = function() { let ctx = im.inputcontext; // Report back to parent frame on status of ctx gotten. // (A setTimeout() here to ensure this always happens after // DOMRequest succeed.) content.setTimeout(() => { sendAsyncMessage('test:InputMethod:imFrameMessage', { from: 'im', value: content.location.hash + !!ctx }); }); // If there is a context, send out the hash. if (ctx) { ctx.replaceSurroundingText(content.location.hash); } }; }; // Inject frame script to receive message. let mm = SpecialPowers.getBrowserFrameMessageManager(frame); mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); mm.addMessageListener("test:InputMethod:imFrameMessage", next); }); // Set focus to the input field and wait for input methods' inputting. SpecialPowers.DOMWindowUtils.focus(gInputFrame); let req0 = gInputMethodFrames[0].setInputMethodActive(true); req0.onsuccess = function() { ok(true, 'setInputMethodActive succeeded (0).'); }; req0.onerror = function() { ok(false, 'setInputMethodActive failed (0): ' + this.error.name); }; } var gCount = 0; var gFrameMsgCounts = { 'input': 0, 'im0': 0, 'im1': 0 }; function next(msg) { let wrappedMsg = SpecialPowers.wrap(msg); let from = wrappedMsg.data.from; let value = wrappedMsg.data.value; if (wrappedMsg.data.error) { ok(false, wrappedMsg.data.value); return; } let fromId = from; if (from === 'im') { fromId += value[1]; } gFrameMsgCounts[fromId]++; // The texts sent from the first and the second input method are '#0' and // '#1' respectively. switch (gCount) { case 0: switch (fromId) { case 'im0': if (gFrameMsgCounts.im0 === 1) { is(value, '#0true', 'First frame should get the context first.'); } else { ok(false, 'Unexpected multiple messages from im0.') } break; case 'im1': is(false, 'Shouldn\'t be hearing anything from second frame.'); break; case 'input': if (gFrameMsgCounts.input === 1) { is(value, '#0hello', 'Failed to get correct input from the first iframe.'); } else { ok(false, 'Unexpected multiple messages from input.') } break; } if (gFrameMsgCounts.input !== 1 || gFrameMsgCounts.im0 !== 1 || gFrameMsgCounts.im1 !== 0) { return; } gCount++; let req0 = gInputMethodFrames[0].setInputMethodActive(false); req0.onsuccess = function() { ok(true, 'setInputMethodActive succeeded (0).'); }; req0.onerror = function() { ok(false, 'setInputMethodActive failed (0): ' + this.error.name); }; let req1 = gInputMethodFrames[1].setInputMethodActive(true); req1.onsuccess = function() { ok(true, 'setInputMethodActive succeeded (1).'); }; req1.onerror = function() { ok(false, 'setInputMethodActive failed (1): ' + this.error.name); }; break; case 1: switch (fromId) { case 'im0': if (gFrameMsgCounts.im0 === 2) { is(value, '#0false', 'First frame should have the context removed.'); } else { ok(false, 'Unexpected multiple messages from im0.') } break; case 'im1': if (gFrameMsgCounts.im1 === 1) { is(value, '#1true', 'Second frame should get the context.'); } else { ok(false, 'Unexpected multiple messages from im0.') } break; case 'input': if (gFrameMsgCounts.input === 2) { is(value, '#0#1hello', 'Failed to get correct input from the second iframe.'); } else { ok(false, 'Unexpected multiple messages from input.') } break; } if (gFrameMsgCounts.input !== 2 || gFrameMsgCounts.im0 !== 2 || gFrameMsgCounts.im1 !== 1) { return; } gCount++; // Receive the second input from the second iframe. // Deactive the second iframe. let req3 = gInputMethodFrames[1].setInputMethodActive(false); req3.onsuccess = function() { ok(true, 'setInputMethodActive(false) succeeded (2).'); }; req3.onerror = function() { ok(false, 'setInputMethodActive(false) failed (2): ' + this.error.name); }; break; case 2: is(fromId, 'im1', 'Message sequence unexpected (3).'); is(value, '#1false', 'Second frame should have the context removed.'); tearDown(); break; } } setup(); addEventListener('testready', runTest);