import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,562 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "HardwareKeyHandler.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/KeyboardEvent.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/TextEvents.h"
#include "nsDeque.h"
#include "nsFocusManager.h"
#include "nsFrameLoader.h"
#include "nsIContent.h"
#include "nsIDOMHTMLDocument.h"
#include "nsIDOMHTMLElement.h"
#include "nsPIDOMWindow.h"
#include "nsPresContext.h"
#include "nsPresShell.h"
namespace mozilla {
using namespace dom;
NS_IMPL_ISUPPORTS(HardwareKeyHandler, nsIHardwareKeyHandler)
StaticRefPtr<HardwareKeyHandler> HardwareKeyHandler::sInstance;
HardwareKeyHandler::HardwareKeyHandler()
: mInputMethodAppConnected(false)
{
}
HardwareKeyHandler::~HardwareKeyHandler()
{
}
NS_IMETHODIMP
HardwareKeyHandler::OnInputMethodAppConnected()
{
if (NS_WARN_IF(mInputMethodAppConnected)) {
return NS_ERROR_UNEXPECTED;
}
mInputMethodAppConnected = true;
return NS_OK;
}
NS_IMETHODIMP
HardwareKeyHandler::OnInputMethodAppDisconnected()
{
if (NS_WARN_IF(!mInputMethodAppConnected)) {
return NS_ERROR_UNEXPECTED;
}
mInputMethodAppConnected = false;
return NS_OK;
}
NS_IMETHODIMP
HardwareKeyHandler::RegisterListener(nsIHardwareKeyEventListener* aListener)
{
// Make sure the listener is not nullptr and there is no available
// hardwareKeyEventListener now
if (NS_WARN_IF(!aListener)) {
return NS_ERROR_NULL_POINTER;
}
if (NS_WARN_IF(mHardwareKeyEventListener)) {
return NS_ERROR_ALREADY_INITIALIZED;
}
mHardwareKeyEventListener = do_GetWeakReference(aListener);
if (NS_WARN_IF(!mHardwareKeyEventListener)) {
return NS_ERROR_NULL_POINTER;
}
return NS_OK;
}
NS_IMETHODIMP
HardwareKeyHandler::UnregisterListener()
{
// Clear the HardwareKeyEventListener
mHardwareKeyEventListener = nullptr;
return NS_OK;
}
bool
HardwareKeyHandler::ForwardKeyToInputMethodApp(nsINode* aTarget,
WidgetKeyboardEvent* aEvent,
nsEventStatus* aEventStatus)
{
MOZ_ASSERT(aTarget, "No target provided");
MOZ_ASSERT(aEvent, "No event provided");
// No need to forward hardware key event to IME
// if key's defaultPrevented is true
if (aEvent->mFlags.mDefaultPrevented) {
return false;
}
// No need to forward hardware key event to IME if IME is disabled
if (!mInputMethodAppConnected) {
return false;
}
// No need to forward hardware key event to IME
// if this key event is generated by IME itself(from nsITextInputProcessor)
if (aEvent->mIsSynthesizedByTIP) {
return false;
}
// No need to forward hardware key event to IME
// if the key event is handling or already handled
if (aEvent->mInputMethodAppState != WidgetKeyboardEvent::eNotHandled) {
return false;
}
// No need to forward hardware key event to IME
// if there is no nsIHardwareKeyEventListener in use
nsCOMPtr<nsIHardwareKeyEventListener>
keyHandler(do_QueryReferent(mHardwareKeyEventListener));
if (!keyHandler) {
return false;
}
// Set the flags to specify the keyboard event is in forwarding phase.
aEvent->mInputMethodAppState = WidgetKeyboardEvent::eHandling;
// For those keypress events coming after their heading keydown's reply
// already arrives, they should be dispatched directly instead of
// being stored into the event queue. Otherwise, without the heading keydown
// in the event queue, the stored keypress will never be withdrawn to be fired.
if (aEvent->mMessage == eKeyPress && mEventQueue.IsEmpty()) {
DispatchKeyPress(aTarget, *aEvent, *aEventStatus);
return true;
}
// Push the key event into queue for reuse when its reply arrives.
KeyboardInfo* copiedInfo =
new KeyboardInfo(aTarget,
*aEvent,
aEventStatus ? *aEventStatus : nsEventStatus_eIgnore);
// No need to forward hardware key event to IME if the event queue is full
if (!mEventQueue.Push(copiedInfo)) {
delete copiedInfo;
return false;
}
// We only forward keydown and keyup event to input-method-app
// because input-method-app will generate keypress by itself.
if (aEvent->mMessage == eKeyPress) {
return true;
}
// Create a keyboard event to pass into
// nsIHardwareKeyEventListener.onHardwareKey
nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
nsPresContext* presContext = GetPresContext(aTarget);
RefPtr<KeyboardEvent> keyboardEvent =
NS_NewDOMKeyboardEvent(eventTarget, presContext, aEvent->AsKeyboardEvent());
// Duplicate the internal event data in the heap for the keyboardEvent,
// or the internal data from |aEvent| in the stack may be destroyed by others.
keyboardEvent->DuplicatePrivateData();
// Forward the created keyboard event to input-method-app
bool isSent = false;
keyHandler->OnHardwareKey(keyboardEvent, &isSent);
// Pop the pending key event if it can't be forwarded
if (!isSent) {
mEventQueue.RemoveFront();
}
return isSent;
}
NS_IMETHODIMP
HardwareKeyHandler::OnHandledByInputMethodApp(const nsAString& aType,
uint16_t aDefaultPrevented)
{
// We can not handle this reply because the pending events had been already
// removed from the forwarding queue before this reply arrives.
if (mEventQueue.IsEmpty()) {
return NS_OK;
}
RefPtr<KeyboardInfo> keyInfo = mEventQueue.PopFront();
// Only allow keydown and keyup to call this method
if (NS_WARN_IF(aType.EqualsLiteral("keydown") &&
keyInfo->mEvent.mMessage != eKeyDown) ||
NS_WARN_IF(aType.EqualsLiteral("keyup") &&
keyInfo->mEvent.mMessage != eKeyUp)) {
return NS_ERROR_INVALID_ARG;
}
// The value of defaultPrevented depends on whether or not
// the key is consumed by input-method-app
SetDefaultPrevented(keyInfo->mEvent, aDefaultPrevented);
// Set the flag to specify the reply phase
keyInfo->mEvent.mInputMethodAppState = WidgetKeyboardEvent::eHandled;
// Check whether the event is still valid to be fired
if (CanDispatchEvent(keyInfo->mTarget, keyInfo->mEvent)) {
// If the key's defaultPrevented is true, it means that the
// input-method-app has already consumed this key,
// so we can dispatch |mozbrowserafterkey*| directly if
// preference "dom.beforeAfterKeyboardEvent.enabled" is enabled.
if (keyInfo->mEvent.mFlags.mDefaultPrevented) {
DispatchAfterKeyEvent(keyInfo->mTarget, keyInfo->mEvent);
// Otherwise, it means that input-method-app doesn't handle this key,
// so we need to dispatch it to its current event target.
} else {
DispatchToTargetApp(keyInfo->mTarget,
keyInfo->mEvent,
keyInfo->mStatus);
}
}
// No need to do further processing if the event is not keydown
if (keyInfo->mEvent.mMessage != eKeyDown) {
return NS_OK;
}
// Update the latest keydown data:
// Release the holding reference to the previous keydown's data and
// add a reference count to the current keydown's data.
mLatestKeyDownInfo = keyInfo;
// Handle the pending keypress event once keydown's reply arrives:
// It may have many keypress events per keydown on some platforms,
// so we use loop to dispatch keypress events.
// (But Gonk dispatch only one keypress per keydown)
// However, if there is no keypress after this keydown,
// then those following keypress will be handled in
// ForwardKeyToInputMethodApp directly.
for (KeyboardInfo* keypressInfo;
!mEventQueue.IsEmpty() &&
(keypressInfo = mEventQueue.PeekFront()) &&
keypressInfo->mEvent.mMessage == eKeyPress;
mEventQueue.RemoveFront()) {
DispatchKeyPress(keypressInfo->mTarget,
keypressInfo->mEvent,
keypressInfo->mStatus);
}
return NS_OK;
}
bool
HardwareKeyHandler::DispatchKeyPress(nsINode* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus)
{
MOZ_ASSERT(aTarget, "No target provided");
MOZ_ASSERT(aEvent.mMessage == eKeyPress, "Event is not keypress");
// No need to dispatch keypress to the event target
// if the keydown event is consumed by the input-method-app.
if (mLatestKeyDownInfo &&
mLatestKeyDownInfo->mEvent.mFlags.mDefaultPrevented) {
return false;
}
// No need to dispatch keypress to the event target
// if the previous keydown event is modifier key's
if (mLatestKeyDownInfo &&
mLatestKeyDownInfo->mEvent.IsModifierKeyEvent()) {
return false;
}
// No need to dispatch keypress to the event target
// if it's invalid to be dispatched
if (!CanDispatchEvent(aTarget, aEvent)) {
return false;
}
// Set the flag to specify the reply phase.
aEvent.mInputMethodAppState = WidgetKeyboardEvent::eHandled;
// Dispatch the pending keypress event
bool ret = DispatchToTargetApp(aTarget, aEvent, aStatus);
// Re-trigger EventStateManager::PostHandleKeyboardEvent for keypress
PostHandleKeyboardEvent(aTarget, aEvent, aStatus);
return ret;
}
void
HardwareKeyHandler::DispatchAfterKeyEvent(nsINode* aTarget,
WidgetKeyboardEvent& aEvent)
{
MOZ_ASSERT(aTarget, "No target provided");
if (!PresShell::BeforeAfterKeyboardEventEnabled() ||
aEvent.mMessage == eKeyPress) {
return;
}
nsCOMPtr<nsIPresShell> presShell = GetPresShell(aTarget);
if (NS_WARN_IF(presShell)) {
presShell->DispatchAfterKeyboardEvent(aTarget,
aEvent,
aEvent.mFlags.mDefaultPrevented);
}
}
bool
HardwareKeyHandler::DispatchToTargetApp(nsINode* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus)
{
MOZ_ASSERT(aTarget, "No target provided");
// Get current focused element as the event target
nsCOMPtr<nsIContent> currentTarget = GetCurrentTarget();
if (NS_WARN_IF(!currentTarget)) {
return false;
}
// The event target should be set to the current focused element.
// However, it might have security issue if the event is dispatched to
// the unexpected application, and it might cause unexpected operation
// in the new app.
nsCOMPtr<nsPIDOMWindowOuter> originalRootWindow = GetRootWindow(aTarget);
nsCOMPtr<nsPIDOMWindowOuter> currentRootWindow = GetRootWindow(currentTarget);
if (currentRootWindow != originalRootWindow) {
NS_WARNING("The root window is changed during the event is dispatching");
return false;
}
// If the current focused element is still in the same app,
// then we can use it as the current target to dispatch event.
nsCOMPtr<nsIPresShell> presShell = GetPresShell(currentTarget);
if (!presShell) {
return false;
}
if (!presShell->CanDispatchEvent(&aEvent)) {
return false;
}
// In-process case: the event target is in the current process
if (!PresShell::IsTargetIframe(currentTarget)) {
DispatchToCurrentProcess(presShell, currentTarget, aEvent, aStatus);
if (presShell->CanDispatchEvent(&aEvent)) {
DispatchAfterKeyEvent(aTarget, aEvent);
}
return true;
}
// OOP case: the event target is in the child process
return DispatchToCrossProcess(aTarget, aEvent);
// After the oop target receives the event from TabChild::RecvRealKeyEvent
// and return the result through TabChild::SendDispatchAfterKeyboardEvent,
// the |mozbrowserafterkey*| will be fired from
// TabParent::RecvDispatchAfterKeyboardEvent, so we don't need to dispatch
// |mozbrowserafterkey*| by ourselves in this module.
}
void
HardwareKeyHandler::DispatchToCurrentProcess(nsIPresShell* presShell,
nsIContent* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus)
{
EventDispatcher::Dispatch(aTarget, presShell->GetPresContext(),
&aEvent, nullptr, &aStatus, nullptr);
}
bool
HardwareKeyHandler::DispatchToCrossProcess(nsINode* aTarget,
WidgetKeyboardEvent& aEvent)
{
nsCOMPtr<nsIFrameLoaderOwner> remoteLoaderOwner = do_QueryInterface(aTarget);
if (NS_WARN_IF(!remoteLoaderOwner)) {
return false;
}
RefPtr<nsFrameLoader> remoteFrameLoader =
remoteLoaderOwner->GetFrameLoader();
if (NS_WARN_IF(!remoteFrameLoader)) {
return false;
}
uint32_t eventMode;
remoteFrameLoader->GetEventMode(&eventMode);
if (eventMode == nsIFrameLoader::EVENT_MODE_DONT_FORWARD_TO_CHILD) {
return false;
}
PBrowserParent* remoteBrowser = remoteFrameLoader->GetRemoteBrowser();
TabParent* remote = static_cast<TabParent*>(remoteBrowser);
if (NS_WARN_IF(!remote)) {
return false;
}
return remote->SendRealKeyEvent(aEvent);
}
void
HardwareKeyHandler::PostHandleKeyboardEvent(nsINode* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus)
{
MOZ_ASSERT(aTarget, "No target provided");
nsPresContext* presContext = GetPresContext(aTarget);
RefPtr<mozilla::EventStateManager> esm = presContext->EventStateManager();
bool dispatchedToChildProcess = PresShell::IsTargetIframe(aTarget);
esm->PostHandleKeyboardEvent(&aEvent, aStatus, dispatchedToChildProcess);
}
void
HardwareKeyHandler::SetDefaultPrevented(WidgetKeyboardEvent& aEvent,
uint16_t aDefaultPrevented) {
if (aDefaultPrevented & DEFAULT_PREVENTED) {
aEvent.mFlags.mDefaultPrevented = true;
}
if (aDefaultPrevented & DEFAULT_PREVENTED_BY_CHROME) {
aEvent.mFlags.mDefaultPreventedByChrome = true;
}
if (aDefaultPrevented & DEFAULT_PREVENTED_BY_CONTENT) {
aEvent.mFlags.mDefaultPreventedByContent = true;
}
}
bool
HardwareKeyHandler::CanDispatchEvent(nsINode* aTarget,
WidgetKeyboardEvent& aEvent)
{
nsCOMPtr<nsIPresShell> presShell = GetPresShell(aTarget);
if (NS_WARN_IF(!presShell)) {
return false;
}
return presShell->CanDispatchEvent(&aEvent);
}
already_AddRefed<nsPIDOMWindowOuter>
HardwareKeyHandler::GetRootWindow(nsINode* aNode)
{
// Get nsIPresShell's pointer first
nsCOMPtr<nsIPresShell> presShell = GetPresShell(aNode);
if (NS_WARN_IF(!presShell)) {
return nullptr;
}
nsCOMPtr<nsPIDOMWindowOuter> rootWindow = presShell->GetRootWindow();
return rootWindow.forget();
}
already_AddRefed<nsIContent>
HardwareKeyHandler::GetCurrentTarget()
{
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (NS_WARN_IF(!fm)) {
return nullptr;
}
nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
if (NS_WARN_IF(!focusedWindow)) {
return nullptr;
}
auto* ourWindow = nsPIDOMWindowOuter::From(focusedWindow);
nsCOMPtr<nsPIDOMWindowOuter> rootWindow = ourWindow->GetPrivateRoot();
if (NS_WARN_IF(!rootWindow)) {
return nullptr;
}
nsCOMPtr<nsPIDOMWindowOuter> focusedFrame;
nsCOMPtr<nsIContent> focusedContent =
fm->GetFocusedDescendant(rootWindow, true, getter_AddRefs(focusedFrame));
// If there is no focus, then we use document body instead
if (NS_WARN_IF(!focusedContent || !focusedContent->GetPrimaryFrame())) {
nsIDocument* document = ourWindow->GetExtantDoc();
if (NS_WARN_IF(!document)) {
return nullptr;
}
focusedContent = document->GetRootElement();
nsCOMPtr<nsIDOMHTMLDocument> htmlDocument = do_QueryInterface(document);
if (htmlDocument) {
nsCOMPtr<nsIDOMHTMLElement> body;
htmlDocument->GetBody(getter_AddRefs(body));
nsCOMPtr<nsIContent> bodyContent = do_QueryInterface(body);
if (bodyContent) {
focusedContent = bodyContent;
}
}
}
return focusedContent ? focusedContent.forget() : nullptr;
}
nsPresContext*
HardwareKeyHandler::GetPresContext(nsINode* aNode)
{
// Get nsIPresShell's pointer first
nsCOMPtr<nsIPresShell> presShell = GetPresShell(aNode);
if (NS_WARN_IF(!presShell)) {
return nullptr;
}
// then use nsIPresShell to get nsPresContext's pointer
return presShell->GetPresContext();
}
already_AddRefed<nsIPresShell>
HardwareKeyHandler::GetPresShell(nsINode* aNode)
{
nsIDocument* doc = aNode->OwnerDoc();
if (NS_WARN_IF(!doc)) {
return nullptr;
}
nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
if (NS_WARN_IF(!presShell)) {
return nullptr;
}
return presShell.forget();
}
/* static */
already_AddRefed<HardwareKeyHandler>
HardwareKeyHandler::GetInstance()
{
if (!XRE_IsParentProcess()) {
return nullptr;
}
if (!sInstance) {
sInstance = new HardwareKeyHandler();
ClearOnShutdown(&sInstance);
}
RefPtr<HardwareKeyHandler> service = sInstance.get();
return service.forget();
}
} // namespace mozilla

View file

@ -0,0 +1,224 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_HardwareKeyHandler_h_
#define mozilla_HardwareKeyHandler_h_
#include "mozilla/EventForwards.h" // for nsEventStatus
#include "mozilla/StaticPtr.h"
#include "mozilla/TextEvents.h"
#include "nsCOMPtr.h"
#include "nsDeque.h"
#include "nsIHardwareKeyHandler.h"
#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
class nsIContent;
class nsINode;
class nsIPresShell;
class nsPIDOMWindowOuter;
class nsPresContext;
namespace mozilla {
// This module will copy the events' data into its event queue for reuse
// after receiving input-method-app's reply, so we use the following struct
// for storing these information.
// RefCounted<T> is a helper class for adding reference counting mechanism.
struct KeyboardInfo : public RefCounted<KeyboardInfo>
{
MOZ_DECLARE_REFCOUNTED_TYPENAME(KeyboardInfo)
nsINode* mTarget;
WidgetKeyboardEvent mEvent;
nsEventStatus mStatus;
KeyboardInfo(nsINode* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus aStatus)
: mTarget(aTarget)
, mEvent(aEvent)
, mStatus(aStatus)
{
}
};
// The following is the type-safe wrapper around nsDeque
// for storing events' data.
// The T must be one class that supports reference counting mechanism.
// The EventQueueDeallocator will be called in nsDeque::~nsDeque() or
// nsDeque::Erase() to deallocate the objects. nsDeque::Erase() will remove
// and delete all items in the queue. See more from nsDeque.h.
template <class T>
class EventQueueDeallocator : public nsDequeFunctor
{
virtual void* operator() (void* aObject)
{
RefPtr<T> releaseMe = dont_AddRef(static_cast<T*>(aObject));
return nullptr;
}
};
// The type-safe queue to be used to store the KeyboardInfo data
template <class T>
class EventQueue : private nsDeque
{
public:
EventQueue()
: nsDeque(new EventQueueDeallocator<T>())
{
};
~EventQueue()
{
Clear();
}
inline size_t GetSize()
{
return nsDeque::GetSize();
}
bool IsEmpty()
{
return !nsDeque::GetSize();
}
inline bool Push(T* aItem)
{
MOZ_ASSERT(aItem);
NS_ADDREF(aItem);
size_t sizeBefore = GetSize();
nsDeque::Push(aItem);
if (GetSize() != sizeBefore + 1) {
NS_RELEASE(aItem);
return false;
}
return true;
}
inline already_AddRefed<T> PopFront()
{
RefPtr<T> rv = dont_AddRef(static_cast<T*>(nsDeque::PopFront()));
return rv.forget();
}
inline void RemoveFront()
{
RefPtr<T> releaseMe = PopFront();
}
inline T* PeekFront()
{
return static_cast<T*>(nsDeque::PeekFront());
}
void Clear()
{
while (GetSize() > 0) {
RemoveFront();
}
}
};
class HardwareKeyHandler : public nsIHardwareKeyHandler
{
public:
HardwareKeyHandler();
NS_DECL_ISUPPORTS
NS_DECL_NSIHARDWAREKEYHANDLER
static already_AddRefed<HardwareKeyHandler> GetInstance();
virtual bool ForwardKeyToInputMethodApp(nsINode* aTarget,
WidgetKeyboardEvent* aEvent,
nsEventStatus* aEventStatus) override;
private:
virtual ~HardwareKeyHandler();
// Return true if the keypress is successfully dispatched.
// Otherwise, return false.
bool DispatchKeyPress(nsINode* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus);
void DispatchAfterKeyEvent(nsINode* aTarget, WidgetKeyboardEvent& aEvent);
void DispatchToCurrentProcess(nsIPresShell* aPresShell,
nsIContent* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus);
bool DispatchToCrossProcess(nsINode* aTarget, WidgetKeyboardEvent& aEvent);
// This method will dispatch not only key* event to its event target,
// no mather it's in the current process or in its child process,
// but also mozbrowserafterkey* to the corresponding target if it needs.
// Return true if the key is successfully dispatched.
// Otherwise, return false.
bool DispatchToTargetApp(nsINode* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus);
// This method will be called after dispatching keypress to its target,
// if the input-method-app doesn't handle the key.
// In normal dispatching path, EventStateManager::PostHandleKeyboardEvent
// will be called when event is keypress.
// However, the ::PostHandleKeyboardEvent mentioned above will be aborted
// when we try to forward key event to the input-method-app.
// If the input-method-app consumes the key, then we don't need to do anything
// because the input-method-app will generate a new key event by itself.
// On the other hand, if the input-method-app doesn't consume the key,
// then we need to dispatch the key event by ourselves
// and call ::PostHandleKeyboardEvent again after the event is forwarded.
// Note that the EventStateManager::PreHandleEvent is already called before
// forwarding, so we don't need to call it in this module.
void PostHandleKeyboardEvent(nsINode* aTarget,
WidgetKeyboardEvent& aEvent,
nsEventStatus& aStatus);
void SetDefaultPrevented(WidgetKeyboardEvent& aEvent,
uint16_t aDefaultPrevented);
// Check whether the event is valid to be fired.
// This method should be called every time before dispatching next event.
bool CanDispatchEvent(nsINode* aTarget,
WidgetKeyboardEvent& aEvent);
already_AddRefed<nsPIDOMWindowOuter> GetRootWindow(nsINode* aNode);
already_AddRefed<nsIContent> GetCurrentTarget();
nsPresContext* GetPresContext(nsINode* aNode);
already_AddRefed<nsIPresShell> GetPresShell(nsINode* aNode);
static StaticRefPtr<HardwareKeyHandler> sInstance;
// The event queue is used to store the forwarded keyboard events.
// Those stored events will be dispatched if input-method-app doesn't
// consume them.
EventQueue<KeyboardInfo> mEventQueue;
// Hold the pointer to the latest keydown's data
RefPtr<KeyboardInfo> mLatestKeyDownInfo;
// input-method-app needs to register a listener by
// |nsIHardwareKeyHandler.registerListener| to receive
// the hardware keyboard event, and |nsIHardwareKeyHandler.registerListener|
// will set an nsIHardwareKeyEventListener to mHardwareKeyEventListener.
// Then, mHardwareKeyEventListener is used to forward the event
// to the input-method-app.
nsWeakPtr mHardwareKeyEventListener;
// To keep tracking the input-method-app is active or disabled.
bool mInputMethodAppConnected;
};
} // namespace mozilla
#endif // #ifndef mozilla_HardwareKeyHandler_h_

View file

@ -0,0 +1,2 @@
component {4607330d-e7d2-40a4-9eb8-43967eae0142} MozKeyboard.js
contract @mozilla.org/b2g-inputmethod;1 {4607330d-e7d2-40a4-9eb8-43967eae0142}

View file

@ -0,0 +1,644 @@
/* 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;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
XPCOMUtils.defineLazyGetter(this, "appsService", function() {
return Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
});
XPCOMUtils.defineLazyGetter(this, "hardwareKeyHandler", function() {
#ifdef MOZ_B2G
return Cc["@mozilla.org/HardwareKeyHandler;1"]
.getService(Ci.nsIHardwareKeyHandler);
#else
return null;
#endif
});
var Utils = {
getMMFromMessage: function u_getMMFromMessage(msg) {
let mm;
try {
mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
} catch(e) {
mm = msg.target;
}
return mm;
},
checkPermissionForMM: function u_checkPermissionForMM(mm, permName) {
return mm.assertPermission(permName);
}
};
this.Keyboard = {
#ifdef MOZ_B2G
// For receving keyboard event fired from hardware before it's dispatched,
// |this| object is used to be the listener to get the forwarded event.
// As the listener, |this| object must implement nsIHardwareKeyEventListener
// and nsSupportsWeakReference.
// Please see nsIHardwareKeyHandler.idl to get more information.
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIHardwareKeyEventListener,
Ci.nsISupportsWeakReference
]),
#endif
_isConnectedToHardwareKeyHandler: false,
_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.
_systemMMs: [], // The message managers registered to handle system async
// messages.
_supportsSwitchingTypes: [],
_systemMessageNames: [
'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions',
'SetSupportsSwitchingTypes', 'RegisterSync', 'Unregister'
],
_messageNames: [
'RemoveFocus',
'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
'SwitchToNextInputMethod', 'HideInputMethod',
'SendKey', 'GetContext',
'SetComposition', 'EndComposition',
'RegisterSync', 'Unregister',
'ReplyHardwareKeyEvent'
],
get formMM() {
if (this._formMM && !Cu.isDeadWrapper(this._formMM))
return this._formMM;
return null;
},
set formMM(mm) {
this._formMM = mm;
},
sendToForm: function(name, data) {
if (!this.formMM) {
dump("Keyboard.jsm: Attempt to send message " + name +
" to form but no message manager exists.\n");
return;
}
try {
this.formMM.sendAsyncMessage(name, data);
} catch(e) { }
},
sendToKeyboard: function(name, data) {
try {
this._keyboardMM.sendAsyncMessage(name, data);
} catch(e) {
return false;
}
return true;
},
sendToSystem: function(name, data) {
if (!this._systemMMs.length) {
dump("Keyboard.jsm: Attempt to send message " + name +
" to system but no message manager registered.\n");
return;
}
this._systemMMs.forEach((mm, i) => {
data.inputManageId = i;
mm.sendAsyncMessage(name, data);
});
},
init: function keyboardInit() {
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
Services.obs.addObserver(this, 'remote-browser-shown', false);
Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
Services.obs.addObserver(this, 'message-manager-close', false);
// For receiving the native hardware keyboard event
if (hardwareKeyHandler) {
hardwareKeyHandler.registerListener(this);
}
for (let name of this._messageNames) {
ppmm.addMessageListener('Keyboard:' + name, this);
}
for (let name of this._systemMessageNames) {
ppmm.addMessageListener('System:' + name, this);
}
this.inputRegistryGlue = new InputRegistryGlue();
},
// This method will be registered into nsIHardwareKeyHandler:
// Send the initialized dictionary retrieved from the native keyboard event
// to input-method-app for generating a new event.
onHardwareKey: function onHardwareKeyReceived(evt) {
return this.sendToKeyboard('Keyboard:ReceiveHardwareKeyEvent', {
type: evt.type,
keyDict: evt.initDict
});
},
observe: function keyboardObserve(subject, topic, data) {
let frameLoader = null;
let mm = null;
if (topic == 'message-manager-close') {
mm = subject;
} else {
frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
mm = frameLoader.messageManager;
}
if (topic == 'oop-frameloader-crashed' ||
topic == 'message-manager-close') {
if (this.formMM == mm) {
// The application has been closed unexpectingly. Let's tell the
// keyboard app that the focus has been lost.
this.sendToKeyboard('Keyboard:Blur', {});
// Notify system app to hide keyboard.
this.sendToSystem('System:Blur', {});
// XXX: To be removed when content migrate away from mozChromeEvents.
SystemAppProxy.dispatchEvent({
type: 'inputmethod-contextchange',
inputType: 'blur'
});
this.formMM = null;
}
} else {
// Ignore notifications that aren't from a BrowserOrApp
if (!frameLoader.ownerIsMozBrowserOrAppFrame) {
return;
}
this.initFormsFrameScript(mm);
}
},
initFormsFrameScript: function(mm) {
mm.addMessageListener('Forms:Focus', this);
mm.addMessageListener('Forms:Blur', this);
mm.addMessageListener('Forms:SelectionChange', this);
mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
mm.addMessageListener('Forms:SetSelectionRange:Result:Error', this);
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:Error', this);
mm.addMessageListener('Forms:SendKey:Result:OK', this);
mm.addMessageListener('Forms:SendKey:Result:Error', this);
mm.addMessageListener('Forms:SequenceError', this);
mm.addMessageListener('Forms:GetContext:Result:OK', this);
mm.addMessageListener('Forms:SetComposition:Result:OK', this);
mm.addMessageListener('Forms:EndComposition:Result:OK', this);
},
receiveMessage: function keyboardReceiveMessage(msg) {
// If we get a 'Keyboard:XXX'/'System:XXX' message, check that the sender
// has the required permission.
let mm;
// Assert the permission based on the prefix of the message.
let permName;
if (msg.name.startsWith("Keyboard:")) {
permName = "input";
} else if (msg.name.startsWith("System:")) {
permName = "input-manage";
}
// There is no permission to check (nor we need to get the mm)
// for Form: messages.
if (permName) {
mm = Utils.getMMFromMessage(msg);
if (!mm) {
dump("Keyboard.jsm: Message " + msg.name + " has no message manager.");
return;
}
if (!Utils.checkPermissionForMM(mm, permName)) {
dump("Keyboard.jsm: Message " + msg.name +
" from a content process with no '" + permName + "' privileges.\n");
return;
}
}
// 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:RegisterSync' !== msg.name && this._keyboardID !== kbID)
) {
return;
}
switch (msg.name) {
case 'Forms:Focus':
this.handleFocus(msg);
break;
case 'Forms:Blur':
this.handleBlur(msg);
break;
case 'Forms:SelectionChange':
case 'Forms:SetSelectionRange:Result:OK':
case 'Forms:ReplaceSurroundingText:Result:OK':
case 'Forms:SendKey:Result:OK':
case 'Forms:SendKey:Result:Error':
case 'Forms:SequenceError':
case 'Forms:GetContext:Result:OK':
case 'Forms:SetComposition:Result:OK':
case 'Forms:EndComposition:Result:OK':
case 'Forms:SetSelectionRange:Result:Error':
case 'Forms:ReplaceSurroundingText:Result:Error':
let name = msg.name.replace(/^Forms/, 'Keyboard');
this.forwardEvent(name, msg);
break;
case 'System:SetValue':
this.setValue(msg);
break;
case 'Keyboard:RemoveFocus':
case 'System:RemoveFocus':
this.removeFocus();
break;
case 'System:RegisterSync': {
if (this._systemMMs.length !== 0) {
dump('Keyboard.jsm Warning: There are more than one content page ' +
'with input-manage permission. There will be undeterministic ' +
'responses to addInput()/removeInput() if both content pages are ' +
'trying to respond to the same request event.\n');
}
let id = this._systemMMs.length;
this._systemMMs.push(mm);
return id;
}
case 'System:Unregister':
this._systemMMs.splice(msg.data.id, 1);
break;
case 'System:SetSelectedOption':
this.setSelectedOption(msg);
break;
case 'System:SetSelectedOptions':
this.setSelectedOption(msg);
break;
case 'System:SetSupportsSwitchingTypes':
this.setSupportsSwitchingTypes(msg);
break;
case 'Keyboard:SetSelectionRange':
this.setSelectionRange(msg);
break;
case 'Keyboard:ReplaceSurroundingText':
this.replaceSurroundingText(msg);
break;
case 'Keyboard:SwitchToNextInputMethod':
this.switchToNextInputMethod();
break;
case 'Keyboard:ShowInputMethodPicker':
this.showInputMethodPicker();
break;
case 'Keyboard:SendKey':
this.sendKey(msg);
break;
case 'Keyboard:GetContext':
this.getContext(msg);
break;
case 'Keyboard:SetComposition':
this.setComposition(msg);
break;
case 'Keyboard:EndComposition':
this.endComposition(msg);
break;
case 'Keyboard:RegisterSync':
this._keyboardMM = mm;
if (kbID) {
// 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;
case 'Keyboard:ReplyHardwareKeyEvent':
if (hardwareKeyHandler) {
let reply = msg.data;
hardwareKeyHandler.onHandledByInputMethodApp(reply.type,
reply.defaultPrevented);
}
break;
}
},
handleFocus: function keyboardHandleFocus(msg) {
// Set the formMM to the new message manager received.
let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
this.formMM = mm;
// Notify the nsIHardwareKeyHandler that the input-method-app is active now.
if (hardwareKeyHandler && !this._isConnectedToHardwareKeyHandler) {
this._isConnectedToHardwareKeyHandler = true;
hardwareKeyHandler.onInputMethodAppConnected();
}
// Notify the current active input app to gain focus.
this.forwardEvent('Keyboard:Focus', msg);
// Notify System app, used also to render value selectors for now;
// that's why we need the info about choices / min / max here as well...
this.sendToSystem('System:Focus', msg.data);
// XXX: To be removed when content migrate away from mozChromeEvents.
SystemAppProxy.dispatchEvent({
type: 'inputmethod-contextchange',
inputType: msg.data.inputType,
value: msg.data.value,
choices: JSON.stringify(msg.data.choices),
min: msg.data.min,
max: msg.data.max
});
},
handleBlur: function keyboardHandleBlur(msg) {
let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
// A blur message can't be sent to the keyboard if the focus has
// already been taken away at first place.
// This check is here to prevent problem caused by out-of-order
// ipc messages from two processes.
if (mm !== this.formMM) {
return;
}
// unset formMM
this.formMM = null;
// Notify the nsIHardwareKeyHandler that
// the input-method-app is disabled now.
if (hardwareKeyHandler && this._isConnectedToHardwareKeyHandler) {
this._isConnectedToHardwareKeyHandler = false;
hardwareKeyHandler.onInputMethodAppDisconnected();
}
this.forwardEvent('Keyboard:Blur', msg);
this.sendToSystem('System:Blur', {});
// XXX: To be removed when content migrate away from mozChromeEvents.
SystemAppProxy.dispatchEvent({
type: 'inputmethod-contextchange',
inputType: 'blur'
});
},
forwardEvent: function keyboardForwardEvent(newEventName, msg) {
this.sendToKeyboard(newEventName, msg.data);
},
setSelectedOption: function keyboardSetSelectedOption(msg) {
this.sendToForm('Forms:Select:Choice', msg.data);
},
setSelectedOptions: function keyboardSetSelectedOptions(msg) {
this.sendToForm('Forms:Select:Choice', msg.data);
},
setSelectionRange: function keyboardSetSelectionRange(msg) {
this.sendToForm('Forms:SetSelectionRange', msg.data);
},
setValue: function keyboardSetValue(msg) {
this.sendToForm('Forms:Input:Value', msg.data);
},
removeFocus: function keyboardRemoveFocus() {
if (!this.formMM) {
return;
}
this.sendToForm('Forms:Select:Blur', {});
},
replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
this.sendToForm('Forms:ReplaceSurroundingText', msg.data);
},
showInputMethodPicker: function keyboardShowInputMethodPicker() {
this.sendToSystem('System:ShowAll', {});
// XXX: To be removed with mozContentEvent support from shell.js
SystemAppProxy.dispatchEvent({
type: "inputmethod-showall"
});
},
switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
this.sendToSystem('System:Next', {});
// XXX: To be removed with mozContentEvent support from shell.js
SystemAppProxy.dispatchEvent({
type: "inputmethod-next"
});
},
sendKey: function keyboardSendKey(msg) {
this.sendToForm('Forms:Input:SendKey', msg.data);
},
getContext: function keyboardGetContext(msg) {
if (!this.formMM) {
return;
}
this.sendToKeyboard('Keyboard:SupportsSwitchingTypesChange', {
types: this._supportsSwitchingTypes
});
this.sendToForm('Forms:GetContext', msg.data);
},
setComposition: function keyboardSetComposition(msg) {
this.sendToForm('Forms:SetComposition', msg.data);
},
endComposition: function keyboardEndComposition(msg) {
this.sendToForm('Forms:EndComposition', msg.data);
},
setSupportsSwitchingTypes: function setSupportsSwitchingTypes(msg) {
this._supportsSwitchingTypes = msg.data.types;
this.sendToKeyboard('Keyboard:SupportsSwitchingTypesChange', msg.data);
},
// XXX: To be removed with mozContentEvent support from shell.js
setLayouts: function keyboardSetLayouts(layouts) {
// The input method plugins may not have loaded yet,
// cache the layouts so on init we can respond immediately instead
// of going back and forth between keyboard_manager
var types = [];
Object.keys(layouts).forEach((type) => {
if (layouts[type] > 1) {
types.push(type);
}
});
this._supportsSwitchingTypes = types;
this.sendToKeyboard('Keyboard:SupportsSwitchingTypesChange', {
types: types
});
}
};
function InputRegistryGlue() {
this._messageId = 0;
this._msgMap = new Map();
ppmm.addMessageListener('InputRegistry:Add', this);
ppmm.addMessageListener('InputRegistry:Remove', this);
ppmm.addMessageListener('System:InputRegistry:Add:Done', this);
ppmm.addMessageListener('System:InputRegistry:Remove:Done', this);
};
InputRegistryGlue.prototype.receiveMessage = function(msg) {
let mm = Utils.getMMFromMessage(msg);
let permName = msg.name.startsWith("System:") ? "input-mgmt" : "input";
if (!Utils.checkPermissionForMM(mm, permName)) {
dump("InputRegistryGlue message " + msg.name +
" from a content process with no " + permName + " privileges.");
return;
}
switch (msg.name) {
case 'InputRegistry:Add':
this.addInput(msg, mm);
break;
case 'InputRegistry:Remove':
this.removeInput(msg, mm);
break;
case 'System:InputRegistry:Add:Done':
case 'System:InputRegistry:Remove:Done':
this.returnMessage(msg.data);
break;
}
};
InputRegistryGlue.prototype.addInput = function(msg, mm) {
let msgId = this._messageId++;
this._msgMap.set(msgId, {
mm: mm,
requestId: msg.data.requestId
});
let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId);
Keyboard.sendToSystem('System:InputRegistry:Add', {
id: msgId,
manifestURL: manifestURL,
inputId: msg.data.inputId,
inputManifest: msg.data.inputManifest
});
// XXX: To be removed when content migrate away from mozChromeEvents.
SystemAppProxy.dispatchEvent({
type: 'inputregistry-add',
id: msgId,
manifestURL: manifestURL,
inputId: msg.data.inputId,
inputManifest: msg.data.inputManifest
});
};
InputRegistryGlue.prototype.removeInput = function(msg, mm) {
let msgId = this._messageId++;
this._msgMap.set(msgId, {
mm: mm,
requestId: msg.data.requestId
});
let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId);
Keyboard.sendToSystem('System:InputRegistry:Remove', {
id: msgId,
manifestURL: manifestURL,
inputId: msg.data.inputId
});
// XXX: To be removed when content migrate away from mozChromeEvents.
SystemAppProxy.dispatchEvent({
type: 'inputregistry-remove',
id: msgId,
manifestURL: manifestURL,
inputId: msg.data.inputId
});
};
InputRegistryGlue.prototype.returnMessage = function(detail) {
if (!this._msgMap.has(detail.id)) {
dump('InputRegistryGlue: Ignoring already handled message response. ' +
'id=' + detail.id + '\n');
return;
}
let { mm, requestId } = this._msgMap.get(detail.id);
this._msgMap.delete(detail.id);
if (Cu.isDeadWrapper(mm)) {
dump('InputRegistryGlue: Message manager has already died.\n');
return;
}
if (!('error' in detail)) {
mm.sendAsyncMessage('InputRegistry:Result:OK', {
requestId: requestId
});
} else {
mm.sendAsyncMessage('InputRegistry:Result:Error', {
error: detail.error,
requestId: requestId
});
}
};
this.Keyboard.init();

File diff suppressed because it is too large Load diff

1561
dom/inputmethod/forms.js Normal file

File diff suppressed because it is too large Load diff

6
dom/inputmethod/jar.mn Normal file
View file

@ -0,0 +1,6 @@
# 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/.
toolkit.jar:
content/global/forms.js (forms.js)

View file

@ -0,0 +1,267 @@
// ***********************************
// * Global variables
// ***********************************
const kIsWin = navigator.platform.indexOf("Win") == 0;
// Bit value for the keyboard events
const kKeyDown = 0x01;
const kKeyPress = 0x02;
const kKeyUp = 0x04;
// Pair the event name to its bit value
const kEventCode = {
'keydown' : kKeyDown,
'keypress' : kKeyPress,
'keyup' : kKeyUp
};
// Holding the current test case's infomation:
var gCurrentTest;
// The current used input method of this test
var gInputMethod;
// ***********************************
// * Utilities
// ***********************************
function addKeyEventListeners(eventTarget, handler)
{
Object.keys(kEventCode).forEach(function(type) {
eventTarget.addEventListener(type, handler);
});
}
function eventToCode(type)
{
return kEventCode[type];
}
// To test key events that will be generated by input method here,
// we need to convert alphabets to native key code.
// (Our input method for testing will handle alphabets)
// On the other hand, to test key events that will not be generated by IME,
// we use 0-9 for such case in our testing.
function guessNativeKeyCode(key)
{
let nativeCodeName = (kIsWin)? 'WIN_VK_' : 'MAC_VK_ANSI_';
if (/^[A-Z]$/.test(key)) {
nativeCodeName += key;
} else if (/^[a-z]$/.test(key)) {
nativeCodeName += key.toUpperCase();
} else if (/^[0-9]$/.test(key)) {
nativeCodeName += key.toString();
} else {
return 0;
}
return eval(nativeCodeName);
}
// ***********************************
// * Frame loader and frame scripts
// ***********************************
function frameScript()
{
function handler(e) {
sendAsyncMessage("forwardevent", { type: e.type, key: e.key });
}
function notifyFinish(e) {
if (e.type != 'keyup') return;
sendAsyncMessage("finish");
}
let input = content.document.getElementById('test-input');
input.addEventListener('keydown', handler);
input.addEventListener('keypress', handler);
input.addEventListener('keyup', handler);
input.addEventListener('keyup', notifyFinish);
}
function loadTestFrame(goNext) {
let iframe = document.createElement('iframe');
iframe.src = 'file_test_empty_app.html';
iframe.setAttribute('mozbrowser', true);
iframe.addEventListener("mozbrowserloadend", function onloadend() {
iframe.removeEventListener("mozbrowserloadend", onloadend);
iframe.focus();
var mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
mm.addMessageListener("forwardevent", function(msg) {
inputtextEventReceiver(msg.json);
});
mm.addMessageListener("finish", function(msg) {
if(goNext) {
goNext();
}
});
mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false);
return;
});
document.body.appendChild(iframe);
}
// ***********************************
// * Event firer and listeners
// ***********************************
function fireEvent(callback)
{
let key = gCurrentTest.key;
synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, guessNativeKeyCode(key), {},
key, key, (callback) ? callback : null);
}
function hardwareEventReceiver(evt)
{
if (!gCurrentTest) {
return;
}
gCurrentTest.hardwareinput.receivedEvents |= eventToCode(evt.type);
gCurrentTest.hardwareinput.receivedKeys += evt.key;
}
function inputtextEventReceiver(evt)
{
if (!gCurrentTest) {
return;
}
gCurrentTest.inputtext.receivedEvents |= eventToCode(evt.type);
gCurrentTest.inputtext.receivedKeys += evt.key;
}
// ***********************************
// * Event verifier
// ***********************************
function verifyResults(test)
{
// Verify results received from inputcontent.hardwareinput
is(test.hardwareinput.receivedEvents,
test.hardwareinput.expectedEvents,
"received events from inputcontent.hardwareinput are wrong");
is(test.hardwareinput.receivedKeys,
test.hardwareinput.expectedKeys,
"received keys from inputcontent.hardwareinput are wrong");
// Verify results received from actual input text
is(test.inputtext.receivedEvents,
test.inputtext.expectedEvents,
"received events from input text are wrong");
is(test.inputtext.receivedKeys,
test.inputtext.expectedKeys,
"received keys from input text are wrong");
}
function areEventsSame(test)
{
return (test.hardwareinput.receivedEvents ==
test.hardwareinput.expectedEvents) &&
(test.inputtext.receivedEvents ==
test.inputtext.expectedEvents);
}
// ***********************************
// * Input Method
// ***********************************
// The method input used in this test
// only handles alphabets
function InputMethod(inputContext)
{
this._inputContext = inputContext;
this.init();
}
InputMethod.prototype = {
init: function im_init() {
this._setKepMap();
},
handler: function im_handler(evt) {
// Ignore the key if the event is defaultPrevented
if (evt.defaultPrevented) {
return;
}
// Finish if there is no _inputContext
if (!this._inputContext) {
return;
}
// Generate the keyDict for inputcontext.keydown/keyup
let keyDict = this._generateKeyDict(evt);
// Ignore the key if IME doesn't want to handle it
if (!keyDict) {
return;
}
// Call preventDefault if the key will be handled.
evt.preventDefault();
// Call inputcontext.keydown/keyup
this._inputContext[evt.type](keyDict);
},
mapKey: function im_keymapping(key) {
if (!this._mappingTable) {
return;
}
return this._mappingTable[key];
},
_setKepMap: function im_setKeyMap() {
// A table to map characters:
// {
// 'A': 'B'
// 'a': 'b'
// 'B': 'C'
// 'b': 'c'
// ..
// ..
// 'Z': 'A',
// 'z': 'a',
// }
this._mappingTable = {};
let rotation = 1;
for (let i = 0 ; i < 26 ; i++) {
// Convert 'A' to 'B', 'B' to 'C', ..., 'Z' to 'A'
this._mappingTable[String.fromCharCode(i + 'A'.charCodeAt(0))] =
String.fromCharCode((i+rotation)%26 + 'A'.charCodeAt(0));
// Convert 'a' to 'b', 'b' to 'c', ..., 'z' to 'a'
this._mappingTable[String.fromCharCode(i + 'a'.charCodeAt(0))] =
String.fromCharCode((i+rotation)%26 + 'a'.charCodeAt(0));
}
},
_generateKeyDict: function im_generateKeyDict(evt) {
let mappedKey = this.mapKey(evt.key);
if (!mappedKey) {
return;
}
let keyDict = {
key: mappedKey,
code: this._guessCodeFromKey(mappedKey),
repeat: evt.repeat,
};
return keyDict;
},
_guessCodeFromKey: function im_guessCodeFromKey(key) {
if (/^[A-Z]$/.test(key)) {
return "Key" + key;
} else if (/^[a-z]$/.test(key)) {
return "Key" + key.toUpperCase();
} else if (/^[0-9]$/.test(key)) {
return "Digit" + key.toString();
} else {
return 0;
}
},
};

View file

@ -0,0 +1,52 @@
[DEFAULT]
# dom/inputmethod only made sense on B2G
skip-if = true
support-files =
bug1110030_helper.js
inputmethod_common.js
file_inputmethod.html
file_blank.html
file_test_app.html
file_test_bug1066515.html
file_test_bug1137557.html
file_test_bug1175399.html
file_test_empty_app.html
file_test_focus_blur_manage_events.html
file_test_sendkey_cancel.html
file_test_setSupportsSwitching.html
file_test_simple_manage_events.html
file_test_sms_app.html
file_test_sms_app_1066515.html
file_test_sync_edit.html
file_test_two_inputs.html
file_test_two_selects.html
file_test_unload.html
file_test_unload_action.html
[test_basic.html]
[test_bug944397.html]
[test_bug949059.html]
[test_bug953044.html]
[test_bug960946.html]
[test_bug978918.html]
[test_bug1026997.html]
[test_bug1043828.html]
[test_bug1059163.html]
disabled = fails because receiving bad values
[test_bug1066515.html]
[test_bug1137557.html]
[test_bug1175399.html]
[test_focus_blur_manage_events.html]
disabled = fails because receiving bad events # also depends on bug 1254823
[test_forward_hardware_key_to_ime.html]
skip-if = true # Test only ran on Mulet
[test_input_registry_events.html]
disabled = timeout on pine
[test_sendkey_cancel.html]
[test_setSupportsSwitching.html]
[test_simple_manage_events.html]
disabled = fails because receiving bad events
[test_sync_edit.html]
[test_two_inputs.html]
[test_two_selects.html]
[test_unload.html]

View file

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

View file

@ -0,0 +1,25 @@
<html>
<body>
<script>
var im = navigator.mozInputMethod;
if (im) {
im.oninputcontextchange = onIcc;
if (im.inputcontext) {
onIcc();
}
}
function onIcc() {
var ctx = im.inputcontext;
if (ctx) {
ctx.replaceSurroundingText(location.hash).then(function() {
/* Happy flow */
}, function(err) {
dump('ReplaceSurroundingText failed ' + err + '\n');
});
}
}
</script>
</body>
</html>

View file

@ -0,0 +1,11 @@
<!DOCTYPE HTML>
<html>
<body>
<input id="test-input" type="text" value="Yuan" x-inputmode="verbatim" lang="zh"/>
<script type="application/javascript;version=1.7">
let input = document.getElementById('test-input');
input.focus();
dump('file_test_app.html was loaded.');
</script>
</body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE HTML>
<html>
<body>
<div id="text" contenteditable>Jan Jongboom</div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE HTML>
<html>
<body>
<textarea rows=30 cols=30></textarea>
</body>
</html>

View file

@ -0,0 +1 @@
<html><body><input value="First" readonly></body></html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html>
<body>
<input id="test-input" type="text" value=""/>
<script type="application/javascript;version=1.7">
let input = document.getElementById('test-input');
input.focus();
</script>
</body>
</html>

View file

@ -0,0 +1,22 @@
<html><body>
<input type="text">
<input type="search">
<textarea></textarea>
<p contenteditable></p>
<input type="number">
<input type="tel">
<input type="url">
<input type="email">
<input type="password">
<input type="datetime">
<input type="date" value="2015-08-03" min="1990-01-01" max="2020-01-01">
<input type="month">
<input type="week">
<input type="time">
<input type="datetime-local">
<input type="color">
<select><option selected>foo</option><option disabled>bar</option>
<optgroup label="group"><option>baz</option></optgroup></select>
<select multiple><option selected>foo</option><option disabled>bar</option>
<optgroup label="group"><option>baz</option></optgroup></select>
</body></html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<body>
<input id="test-input" type="text" value="Yolo"/>
<script type="application/javascript;version=1.7">
let input = document.getElementById('test-input');
input.focus();
input.addEventListener('keydown', function(e) {
e.preventDefault();
});
</script>
</body>
</html>

View file

@ -0,0 +1,5 @@
<html><body>
<input type="text">
<input type="number">
<input type="password">
</body></html>

View file

@ -0,0 +1 @@
<html><body><input type="text"></body></html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<body>
<div id="messages-input" x-inputmode="-moz-sms" contenteditable="true"
autofocus="autofocus">Httvb<br></div>
<script type="application/javascript;version=1.7">
let input = document.getElementById('messages-input');
input.focus();
</script>
</body>
</html>
</div>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<body>
<div id="messages-input" x-inputmode="-moz-sms" contenteditable="true"
autofocus="autofocus">fxos<br>hello <b>world</b></div>
<script type="application/javascript;version=1.7">
let input = document.getElementById('messages-input');
input.focus();
</script>
</body>
</html>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
<html><body><input value="First"></body></html>

View file

@ -0,0 +1 @@
<html><body><input value="First"><input value="Second"></body></html>

View file

@ -0,0 +1 @@
<html><body><select><option>First</option></select><select><option>Second</option></select></html>

View file

@ -0,0 +1 @@
<html><body><form id="form"><input value="First"><input type="submit"></form></body></html>

View file

@ -0,0 +1 @@
<html><body><input value="Second"></body></html>

View file

@ -0,0 +1,24 @@
function inputmethod_setup(callback) {
SimpleTest.waitForExplicitFinish();
SimpleTest.requestCompleteLog();
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", this);
}
let prefs = [
['dom.mozBrowserFramesEnabled', true],
['network.disable.ipc.security', true],
// Enable navigator.mozInputMethod.
['dom.mozInputMethod.enabled', true]
];
SpecialPowers.pushPrefEnv({set: prefs}, function() {
SimpleTest.waitForFocus(callback);
});
}
function inputmethod_cleanup() {
SpecialPowers.wrap(navigator.mozInputMethod).setActive(false);
SimpleTest.finish();
}

View file

@ -0,0 +1,212 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=932145
-->
<head>
<title>Basic test for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932145">Mozilla Bug 932145</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
SimpleTest.requestFlakyTimeout("untriaged");
// The input context.
var gContext = null;
inputmethod_setup(function() {
runTest();
});
function runTest() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
is(gContext.type, 'input', 'The input context type should match.');
is(gContext.inputType, 'text', 'The inputType should match.');
is(gContext.inputMode, 'verbatim', 'The inputMode should match.');
is(gContext.lang, 'zh', 'The language should match.');
is(gContext.text, 'Yuan', 'Should get the text.');
is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yuan',
'Should get the text around the cursor.');
test_setSelectionRange();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_app.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
}
function test_setSelectionRange() {
// Move cursor position to 2.
gContext.setSelectionRange(2, 0).then(function() {
is(gContext.selectionStart, 2, 'selectionStart was set successfully.');
is(gContext.selectionEnd, 2, 'selectionEnd was set successfully.');
test_sendKey();
}, function(e) {
ok(false, 'setSelectionRange failed:' + e.name);
console.error(e);
inputmethod_cleanup();
});
}
function test_sendKey() {
// Add '-' to current cursor posistion and move the cursor position to 3.
gContext.sendKey(0, '-'.charCodeAt(0), 0).then(function() {
is(gContext.text, 'Yu-an',
'sendKey should changed the input field correctly.');
is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yu-an',
'sendKey should changed the input field correctly.');
test_deleteSurroundingText();
}, function(e) {
ok(false, 'sendKey failed:' + e.name);
inputmethod_cleanup();
});
}
function test_deleteSurroundingText() {
// Remove one character before current cursor position and move the cursor
// position back to 2.
gContext.deleteSurroundingText(-1, 1).then(function() {
ok(true, 'deleteSurroundingText finished');
is(gContext.text, 'Yuan',
'deleteSurroundingText should changed the input field correctly.');
is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yuan',
'deleteSurroundingText should changed the input field correctly.');
test_replaceSurroundingText();
}, function(e) {
ok(false, 'deleteSurroundingText failed:' + e.name);
inputmethod_cleanup();
});
}
function test_replaceSurroundingText() {
// Replace 'Yuan' with 'Xulei'.
gContext.replaceSurroundingText('Xulei', -2, 4).then(function() {
ok(true, 'replaceSurroundingText finished');
is(gContext.text, 'Xulei',
'replaceSurroundingText changed the input field correctly.');
is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Xulei',
'replaceSurroundingText changed the input field correctly.');
test_setComposition();
}, function(e) {
ok(false, 'replaceSurroundingText failed: ' + e.name);
inputmethod_cleanup();
});
}
function test_setComposition() {
gContext.setComposition('XXX').then(function() {
ok(true, 'setComposition finished');
test_endComposition();
}, function(e) {
ok(false, 'setComposition failed: ' + e.name);
inputmethod_cleanup();
});
}
function test_endComposition() {
gContext.endComposition('2013').then(function() {
is(gContext.text, 'Xulei2013',
'endComposition changed the input field correctly.');
is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Xulei2013',
'endComposition changed the input field correctly.');
test_onSelectionChange();
}, function (e) {
ok(false, 'endComposition failed: ' + e.name);
inputmethod_cleanup();
});
}
function test_onSelectionChange() {
var sccTimeout = setTimeout(function() {
ok(false, 'selectionchange event not fired');
cleanup(true);
}, 3000);
function cleanup(failed) {
gContext.onselectionchange = null;
clearTimeout(sccTimeout);
if (failed) {
inputmethod_cleanup();
}
else {
test_onSurroundingTextChange();
}
}
gContext.onselectionchange = function(evt) {
ok(true, 'onselectionchange fired');
is(evt.detail.selectionStart, 10);
is(evt.detail.selectionEnd, 10);
ok(evt.detail.ownAction);
};
gContext.sendKey(0, 'j'.charCodeAt(0), 0).then(function() {
cleanup();
}, function(e) {
ok(false, 'sendKey failed: ' + e.name);
cleanup(true);
});
}
function test_onSurroundingTextChange() {
var sccTimeout = setTimeout(function() {
ok(false, 'surroundingtextchange event not fired');
cleanup(true);
}, 3000);
function cleanup(failed) {
gContext.onsurroundingtextchange = null;
clearTimeout(sccTimeout);
if (failed) {
inputmethod_cleanup();
}
else {
// in case we want more tests leave this
inputmethod_cleanup();
}
}
gContext.onsurroundingtextchange = function(evt) {
ok(true, 'onsurroundingtextchange fired');
is(evt.detail.text, 'Xulei2013jj');
is(evt.detail.textBeforeCursor, 'Xulei2013jj');
is(evt.detail.textAfterCursor, '');
ok(evt.detail.ownAction);
};
gContext.sendKey(0, 'j'.charCodeAt(0), 0).then(function() {
cleanup();
}, function(e) {
ok(false, 'sendKey failed: ' + e.name);
cleanup(true);
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,101 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1026997
-->
<head>
<title>SelectionChange on InputMethod API.</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1026997">Mozilla Bug 1026997</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
// The frame script running in file_test_app.html.
function appFrameScript() {
let input = content.document.getElementById('test-input');
input.focus();
function next(start, end) {
input.setSelectionRange(start, end);
}
addMessageListener("test:KeyBoard:nextSelection", function(event) {
let json = event.json;
next(json[0], json[1]);
});
}
function runTest() {
let actions = [
[0, 4],
[1, 1],
[3, 3],
[2, 3]
];
let counter = 0;
let mm = null;
let ic = null;
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
ic = im.inputcontext;
if (!ic) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
ic.onselectionchange = function() {
is(ic.selectionStart, actions[counter][0], "start");
is(ic.selectionEnd, actions[counter][1], "end");
if (++counter === actions.length) {
inputmethod_cleanup();
return;
}
next();
};
next();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
// Create an app frame to recieve keyboard inputs.
let app = document.createElement('iframe');
app.src = 'file_test_app.html';
app.setAttribute('mozbrowser', true);
document.body.appendChild(app);
app.addEventListener('mozbrowserloadend', function() {
mm = SpecialPowers.getBrowserFrameMessageManager(app);
mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
next();
});
function next() {
if (ic && mm) {
mm.sendAsyncMessage('test:KeyBoard:nextSelection', actions[counter]);
}
}
}
</script>
</pre>
</body>
</html>

View file

@ -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="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/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">
SimpleTest.requestFlakyTimeout("untriaged");
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_blank.html';
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() {
info('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() {
info('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() {
info('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() {
info('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() {
info('step5');
mmKeyboardB.sendAsyncMessage('test:InputMethod:getText:Do');
}
step1();
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1059163
-->
<head>
<title>Basic test for repeat sendKey events</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1059163">Mozilla Bug 1059163</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
// The frame script running in the file
function appFrameScript() {
let document = content.document;
let window = content.document.defaultView;
let t = document.getElementById('text');
t.focus();
let range = document.createRange();
range.selectNodeContents(t);
range.collapse(false);
let selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
addMessageListener('test:InputMethod:clear', function() {
t.innerHTML = '';
});
}
function runTest() {
let im = navigator.mozInputMethod;
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
// Create an app frame to recieve keyboard inputs.
let app = document.createElement('iframe');
app.src = 'file_test_bug1066515.html';
app.setAttribute('mozbrowser', true);
document.body.appendChild(app);
app.addEventListener('mozbrowserloadend', function() {
let mm = SpecialPowers.getBrowserFrameMessageManager(app);
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
im.oninputcontextchange = function() {
is(im.inputcontext.type, 'contenteditable', 'type');
is(im.inputcontext.inputType, 'textarea', 'inputType');
if (im.inputcontext) {
im.oninputcontextchange = null;
register();
}
};
function register() {
im.inputcontext.onselectionchange = function() {
im.inputcontext.onselectionchange = null;
is(im.inputcontext.textBeforeCursor, '', 'textBeforeCursor');
is(im.inputcontext.textAfterCursor, '', 'textAfterCursor');
is(im.inputcontext.selectionStart, 0, 'selectionStart');
is(im.inputcontext.selectionEnd, 0, 'selectionEnd');
inputmethod_cleanup();
};
mm.sendAsyncMessage('test:InputMethod:clear');
}
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,93 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1066515
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1066515</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1066515">Mozilla Bug 1066515</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
// The input context.
var gContext = null;
inputmethod_setup(function() {
runTest();
});
function runTest() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
test_replaceSurroundingTextWithinTextNode();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_sms_app_1066515.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
}
function test_replaceSurroundingTextWithinTextNode() {
// Set cursor position after 'f'.
gContext.setSelectionRange(1, 0);
// Replace 'fxos' to 'Hitooo' which the range is within current text node.
gContext.replaceSurroundingText('Hitooo', -1, 4).then(function() {
gContext.getText().then(function(text) {
is(text, 'Hitooo\nhello world', 'replaceSurroundingText successfully.');
test_replaceSurroundingTextSpanMultipleNodes();
}, function(e) {
ok(false, 'getText failed: ' + e.name);
inputmethod_cleanup();
});
}, function(e) {
ok(false, 'replaceSurroundingText failed: ' + e.name);
inputmethod_cleanup();
});
}
function test_replaceSurroundingTextSpanMultipleNodes() {
// Set cursor position to the beginning.
gContext.setSelectionRange(0, 0);
// Replace whole content editable element to 'abc'.
gContext.replaceSurroundingText('abc', 0, 100).then(function() {
gContext.getText().then(function(text) {
is(text, 'abc', 'replaceSurroundingText successfully.');
inputmethod_cleanup();
}, function(e) {
ok(false, 'getText failed: ' + e.name);
inputmethod_cleanup();
});
}, function(e) {
ok(false, 'replaceSurroundingText failed: ' + e.name);
inputmethod_cleanup();
});
}
</script>
</pre>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1175399
-->
<head>
<title>Test focus when page unloads</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175399">Mozilla Bug 1175399</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
let appFrameScript = function appFrameScript() {
let input = content.document.body.firstElementChild;
input.focus();
content.setTimeout(function() {
sendAsyncMessage('test:step');
});
};
function runTest() {
let im = navigator.mozInputMethod;
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_bug1175399.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
im.oninputcontextchange = function() {
is(false, 'should not receive inputcontextchange event');
};
iframe.addEventListener('mozbrowserloadend', function() {
mm.addMessageListener('test:step', function() {
let inputcontext = navigator.mozInputMethod.inputcontext;
is(inputcontext, null, 'inputcontext is null');
inputmethod_cleanup();
});
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,107 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=944397
-->
<head>
<title>Basic test for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944397">Mozilla Bug 944397</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
SimpleTest.requestFlakyTimeout("untriaged");
inputmethod_setup(function() {
runTest();
});
// The frame script running in file_test_app.html.
function appFrameScript() {
let input = content.document.getElementById('test-input');
input.oninput = function() {
sendAsyncMessage('test:InputMethod:oninput', {
value: input.value
});
};
}
function runTest() {
let app, keyboard;
/**
* So this test does the following:
* 1. Create a mozbrowser iframe with a text field in it, and focus the text field
* 2. 100ms. after loading we create new keyboard iframe, that will try to execute
* replaceSurroundingText on the current active inputcontext
* 3. That should trigger 'input' event on the said text field
* 4. And if that happens we know everything is OK
*/
let path = location.pathname;
let basePath = location.protocol + '//' + location.host +
path.substring(0, path.lastIndexOf('/'));
// STEP 1: Create an app frame to recieve keyboard inputs.
function step1() {
app = document.createElement('iframe');
app.src = basePath + '/file_test_app.html';
app.setAttribute('mozbrowser', true);
document.body.appendChild(app);
app.addEventListener('mozbrowserloadend', function() {
let mm = SpecialPowers.getBrowserFrameMessageManager(app);
mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
mm.addMessageListener("test:InputMethod:oninput", function(ev) {
step4(SpecialPowers.wrap(ev).json.value);
});
step2();
});
}
function step2() {
// STEP 2a: Create a browser frame to load the input method app.
keyboard = document.createElement('iframe');
keyboard.setAttribute('mozbrowser', true);
document.body.appendChild(keyboard);
// STEP 2b: Grant input privileges to the keyboard iframe
let imeUrl = basePath + '/file_inputmethod.html#data';
// STEP 2c: Tell Gecko to use this iframe as its keyboard app
let req = keyboard.setInputMethodActive(true);
req.onsuccess = function() {
ok(true, 'setInputMethodActive succeeded.');
};
req.onerror = function() {
ok(false, 'setInputMethodActive failed: ' + this.error.name);
inputmethod_cleanup();
};
// STEP 3: Loads the input method app to the browser frame after a delay.
setTimeout(function() {
keyboard.src = imeUrl;
}, 100);
}
function step4(val) {
ok(true, 'Keyboard input was received.');
is(val, '#dataYuan', 'Input value');
inputmethod_cleanup();
}
step1();
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=949059
-->
<head>
<title>Test "mgmt" property of MozInputMethod.</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949059">Mozilla Bug 949059</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
function runTest() {
let im = navigator.mozInputMethod;
// Treat current page as an input method and activate it.
SpecialPowers.wrap(im).setActive(true);
ok(im.mgmt, 'The mgmt property should not be null.');
// Deactivate current page.
SpecialPowers.wrap(im).setActive(false);
ok(im.mgmt, 'The mgmt property should not be null.');
inputmethod_cleanup();
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=953044
-->
<head>
<title>Basic test for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=953044">Mozilla Bug 953044</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
function runTest() {
// Create an app frame to recieve keyboard inputs.
let app = document.createElement('iframe');
app.src = 'file_test_app.html';
app.setAttribute('mozbrowser', true);
document.body.appendChild(app);
// Create a browser frame to load the input method app.
let keyboard = document.createElement('iframe');
keyboard.setAttribute('mozbrowser', true);
document.body.appendChild(keyboard);
// Bug 953044 setInputMethodActive(false) before input method app loads should
// always succeed.
let req = keyboard.setInputMethodActive(false);
req.onsuccess = function() {
ok(true, 'setInputMethodActive before loading succeeded.');
inputmethod_cleanup();
};
req.onerror = function() {
ok(false, 'setInputMethodActive before loading failed: ' + this.error.name);
inputmethod_cleanup();
};
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,108 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=960946
-->
<head>
<title>Basic test for repeat sendKey events</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=960946">Mozilla Bug 960946</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
// The input context.
var gContext = null;
var gCounter = 0;
var gBackSpaceCounter = 0;
var result = ["keydown", "keypress", "keydown","keypress",
"keydown", "keypress", "keyup"
];
inputmethod_setup(function() {
runTest();
});
var input;
// The frame script running in file_test_backspace_event.html.
function appFrameScript() {
let input = content.document.getElementById('test-input');
input.onkeydown = input.onkeypress = input.onkeyup = function(event) {
dump('key event was fired in file_test_backspace_event.html: ' + event.type +'\n');
sendAsyncMessage('test:KeyBoard:keyEvent', {'type':event.type});
};
}
function runTest() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
test_sendKey();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
// Create an app frame to recieve keyboard inputs.
let app = document.createElement('iframe');
app.src = 'file_test_app.html';
app.setAttribute('mozbrowser', true);
document.body.appendChild(app);
app.addEventListener('mozbrowserloadend', function() {
let mm = SpecialPowers.getBrowserFrameMessageManager(app);
mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
mm.addMessageListener("test:KeyBoard:keyEvent", function(event) {
ok(true, 'Keyboard input was received.');
is(SpecialPowers.wrap(event).json.type, result[gCounter], "expected event");
gCounter++;
});
});
}
function test_sendKey() {
// Move cursor position to 4.
gContext.setSelectionRange(4, 0).then(function() {
is(gContext.selectionStart, 4, 'selectionStart was set successfully.');
is(gContext.selectionEnd, 4, 'selectionEnd was set successfully.');
for(let i = 0; i < 2; i++) {
test_sendBackspace(true);
}
test_sendBackspace(false);
}, function(e) {
ok(false, 'setSelectionRange failed:' + e.name);
inputmethod_cleanup();
});
}
function test_sendBackspace(repeat) {
// Send backspace
gContext.sendKey(KeyEvent.DOM_VK_BACK_SPACE, 0, 0, repeat).then(function() {
ok(true, 'sendKey success');
gBackSpaceCounter++;
if (gBackSpaceCounter == 3) {
inputmethod_cleanup();
}
}, function(e) {
ok(false, 'sendKey failed:' + e.name);
inputmethod_cleanup();
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=978918
-->
<head>
<title>Basic test for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978918">Mozilla Bug 978918</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
// The input context.
var gContext = null;
inputmethod_setup(function() {
runTest();
});
function runTest() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
test_setSelectionRange();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_sms_app.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
}
function test_setSelectionRange() {
gContext.setSelectionRange(0, 100).then(function() {
is(gContext.selectionStart, 0, 'selectionStart was set successfully.');
is(gContext.selectionEnd, 5, 'selectionEnd was set successfully.');
test_replaceSurroundingText();
}, function(e) {
ok(false, 'setSelectionRange failed:' + e.name);
inputmethod_cleanup();
});
}
function test_replaceSurroundingText() {
// Replace 'Httvb' with 'Hito'.
gContext.replaceSurroundingText('Hito', 0, 100).then(function() {
ok(true, 'replaceSurroundingText finished');
inputmethod_cleanup();
}, function(e) {
ok(false, 'replaceSurroundingText failed: ' + e.name);
inputmethod_cleanup();
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,199 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
-->
<head>
<title>Test inputcontextfocus and inputcontextblur event</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
let contentFrameMM;
function setupTestRunner() {
info('setupTestRunner');
let im = navigator.mozInputMethod;
let expectedEventDetails = [
{ type: 'input', inputType: 'text' },
{ type: 'input', inputType: 'search' },
{ type: 'textarea', inputType: 'textarea' },
{ type: 'contenteditable', inputType: 'textarea' },
{ type: 'input', inputType: 'number' },
{ type: 'input', inputType: 'tel' },
{ type: 'input', inputType: 'url' },
{ type: 'input', inputType: 'email' },
{ type: 'input', inputType: 'password' },
{ type: 'input', inputType: 'datetime' },
{ type: 'input', inputType: 'date',
value: '2015-08-03', min: '1990-01-01', max: '2020-01-01' },
{ type: 'input', inputType: 'month' },
{ type: 'input', inputType: 'week' },
{ type: 'input', inputType: 'time' },
{ type: 'input', inputType: 'datetime-local' },
{ type: 'input', inputType: 'color' },
{ type: 'select', inputType: 'select-one',
choices: {
multiple: false,
choices: [
{ group: false, inGroup: false, text: 'foo',
disabled: false, selected: true, optionIndex: 0 },
{ group: false, inGroup: false, text: 'bar',
disabled: true, selected: false, optionIndex: 1 },
{ group: true, text: 'group', disabled: false },
{ group: false, inGroup: true, text: 'baz',
disabled: false, selected: false, optionIndex: 2 } ] }
},
{ type: 'select', inputType: 'select-multiple',
choices: {
multiple: true,
choices: [
{ group: false, inGroup: false, text: 'foo',
disabled: false, selected: true, optionIndex: 0 },
{ group: false, inGroup: false, text: 'bar',
disabled: true, selected: false, optionIndex: 1 },
{ group: true, text: 'group', disabled: false },
{ group: false, inGroup: true, text: 'baz',
disabled: false, selected: false, optionIndex: 2 } ] }
}
];
let expectBlur = false;
function deepAssertObject(obj, expectedObj, desc) {
for (let prop in expectedObj) {
if (typeof expectedObj[prop] === 'object') {
deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop);
} else {
is(obj[prop], expectedObj[prop], desc + '.' + prop);
}
}
}
im.mgmt.oninputcontextfocus =
im.mgmt.oninputcontextblur = function(evt) {
if (expectBlur) {
is(evt.type, 'inputcontextblur', 'evt.type');
evt.preventDefault();
expectBlur = false;
return;
}
let expectedEventDetail = expectedEventDetails.shift();
if (!expectedEventDetail) {
ok(false, 'Receving extra events');
inputmethod_cleanup();
return;
}
is(evt.type, 'inputcontextfocus', 'evt.type');
evt.preventDefault();
expectBlur = true;
let detail = evt.detail;
deepAssertObject(detail, expectedEventDetail, 'detail');
if (expectedEventDetails.length) {
contentFrameMM.sendAsyncMessage('test:next');
} else {
im.mgmt.oninputcontextfocus = im.mgmt.oninputcontextblur = null;
inputmethod_cleanup();
}
};
}
function setupInputAppFrame() {
info('setupInputAppFrame');
return new Promise((resolve, reject) => {
let appFrameScript = function appFrameScript() {
let im = content.navigator.mozInputMethod;
im.mgmt.oninputcontextfocus =
im.mgmt.oninputcontextblur = function(evt) {
sendAsyncMessage('text:appEvent', { type: evt.type });
};
content.document.body.textContent = 'I am a input app';
};
let path = location.pathname;
let basePath = location.protocol + '//' + location.host +
path.substring(0, path.lastIndexOf('/'));
let imeUrl = basePath + '/file_blank.html';
let inputAppFrame = document.createElement('iframe');
inputAppFrame.setAttribute('mozbrowser', true);
inputAppFrame.src = imeUrl;
document.body.appendChild(inputAppFrame);
let mm = SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
inputAppFrame.addEventListener('mozbrowserloadend', function() {
mm.addMessageListener('text:appEvent', function(msg) {
ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
});
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
// Set the input app frame to be active
let req = inputAppFrame.setInputMethodActive(true);
resolve(req);
});
});
}
function setupContentFrame() {
info('setupContentFrame');
return new Promise((resolve, reject) => {
let contentFrameScript = function contentFrameScript() {
let input = content.document.body.firstElementChild;
let i = 0;
input.focus();
addMessageListener('test:next', function() {
content.document.body.children[++i].focus();
});
};
let iframe = document.createElement('iframe');
iframe.src = 'file_test_focus_blur_manage_events.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = contentFrameMM =
SpecialPowers.getBrowserFrameMessageManager(iframe);
iframe.addEventListener('mozbrowserloadend', function() {
mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false);
resolve();
});
});
}
inputmethod_setup(function() {
Promise.resolve()
.then(() => setupTestRunner())
.then(() => setupContentFrame())
.then(() => setupInputAppFrame())
.catch((e) => {
ok(false, 'Error' + e.toString());
console.error(e);
});
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,149 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1110030
-->
<head>
<title>Forwarding Hardware Key to InputMethod</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"></script>
<script type="text/javascript" src="bug1110030_helper.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1110030">Mozilla Bug 1110030</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
// The input context.
var gContext = null;
// The test cases.
var gTests;
inputmethod_setup(function() {
setInputContext();
});
function setInputContext() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext || !gContext.hardwareinput) {
ok(false, 'Should have a non-null inputcontext.hardwareinput');
inputmethod_cleanup();
return;
}
prepareTest();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
// verifyResultsAndMoveNext will be called after input#text-input
// receives all expected key events and it will verify results
// and start next test.
loadTestFrame(verifyResultsAndMoveNext);
}
function prepareTest()
{
// Set the used input method of this test
gInputMethod = new InputMethod(gContext);
// Add listenr to hardwareinput
addKeyEventListeners(gContext.hardwareinput, function (evt) {
hardwareEventReceiver(evt);
gInputMethod.handler(evt);
});
// Set the test cases
gTests = [
// Case 1: IME handle the key input
{
key: 'z',
hardwareinput: {
expectedEvents: kKeyDown | kKeyUp,
receivedEvents: 0,
expectedKeys: 'zz', // One for keydown, the other for keyup
receivedKeys: '',
},
inputtext: {
expectedEvents: kKeyDown | kKeyPress | kKeyUp,
receivedEvents: 0,
expectedKeys: gInputMethod.mapKey('z') + // for keydown
gInputMethod.mapKey('z') + // for keypress
gInputMethod.mapKey('z'), // for keyup
receivedKeys: '',
}
},
// case 2: IME doesn't handle the key input
{
key: '7',
hardwareinput: {
expectedEvents: kKeyDown | kKeyUp,
receivedEvents: 0,
expectedKeys: '77', // One for keydown, the other for keyup
receivedKeys: '',
},
inputtext: {
expectedEvents: kKeyDown | kKeyPress | kKeyUp,
receivedEvents: 0,
expectedKeys: '777', // keydown, keypress, keyup all will receive key
receivedKeys: '',
}
},
// case 3: IME is disable
// This case is same as
// dom/events/test/test_dom_before_after_keyboard_event*.html
];
startTesting();
}
function startTesting()
{
if (gTests.length <= 0) {
finish();
return;
}
gCurrentTest = gTests.shift();
fireEvent();
}
function verifyResultsAndMoveNext()
{
verifyResults(gCurrentTest);
startTesting();
}
function finish()
{
inputmethod_cleanup();
}
function errorHandler(msg)
{
// Clear the test cases
if (gTests) {
gTests = [];
}
ok(false, msg);
inputmethod_cleanup();
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,251 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
-->
<head>
<title>Test addinputrequest and removeinputrequest event</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
let appFrameMM;
let nextStep;
function setupInputAppFrame() {
info('setupInputAppFrame');
return new Promise((resolve, reject) => {
let appFrameScript = function appFrameScript() {
let im = content.navigator.mozInputMethod;
addMessageListener('test:callAddInput', function() {
im.addInput('foo', {
launch_path: 'bar.html',
name: 'Foo',
description: 'foobar',
types: ['text', 'password']
})
.then((r) => {
sendAsyncMessage('test:resolved', { resolved: true, result: r });
}, (e) => {
sendAsyncMessage('test:rejected', { rejected: true, error: e });
});
});
addMessageListener('test:callRemoveInput', function() {
im.removeInput('foo')
.then((r) => {
sendAsyncMessage('test:resolved', { resolved: true, result: r });
}, (e) => {
sendAsyncMessage('test:rejected', { rejected: true, error: e });
});
});
im.mgmt.onaddinputrequest =
im.mgmt.onremoveinputrequest = function(evt) {
sendAsyncMessage('test:appEvent', { type: evt.type });
};
content.document.body.textContent = 'I am a input app';
};
let path = location.pathname;
let basePath = location.protocol + '//' + location.host +
path.substring(0, path.lastIndexOf('/'));
let imeUrl = basePath + '/file_blank.html';
let inputAppFrame = document.createElement('iframe');
inputAppFrame.setAttribute('mozbrowser', true);
// FIXME: Bug 1270790
inputAppFrame.setAttribute('remote', true);
inputAppFrame.src = imeUrl;
document.body.appendChild(inputAppFrame);
let mm = appFrameMM =
SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
inputAppFrame.addEventListener('mozbrowserloadend', function() {
mm.addMessageListener('test:appEvent', function(msg) {
ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
});
mm.addMessageListener('test:resolved', function(msg) {
nextStep && nextStep(msg.data);
});
mm.addMessageListener('test:rejected', function(msg) {
nextStep && nextStep(msg.data);
});
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
resolve();
});
});
}
function Deferred() {
this.promise = new Promise((res, rej) => {
this.resolve = res;
this.reject = rej;
});
return this;
}
function deepAssertObject(obj, expectedObj, desc) {
for (let prop in expectedObj) {
if (typeof expectedObj[prop] === 'object') {
deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop);
} else {
is(obj[prop], expectedObj[prop], desc + '.' + prop);
}
}
}
function setupTestRunner() {
let im = navigator.mozInputMethod;
let d;
let i = -1;
nextStep = function next(evt) {
i++;
info('Step ' + i);
switch (i) {
case 0:
appFrameMM.sendAsyncMessage('test:callAddInput');
break;
case 1:
is(evt.type, 'addinputrequest', 'evt.type');
deepAssertObject(evt.detail, {
inputId: 'foo',
manifestURL: null, // todo
inputManifest: {
launch_path: 'bar.html',
name: 'Foo',
description: 'foobar',
types: ['text', 'password']
}
}, 'detail');
d = new Deferred();
evt.detail.waitUntil(d.promise);
evt.preventDefault();
Promise.resolve().then(next);
break;
case 2:
d.resolve();
d = null;
break;
case 3:
ok(evt.resolved, 'resolved');
appFrameMM.sendAsyncMessage('test:callAddInput');
break;
case 4:
is(evt.type, 'addinputrequest', 'evt.type');
d = new Deferred();
evt.detail.waitUntil(d.promise);
evt.preventDefault();
Promise.resolve().then(next);
break;
case 5:
d.reject('Foo Error');
d = null;
break;
case 6:
ok(evt.rejected, 'rejected');
is(evt.error, 'Foo Error', 'rejected');
appFrameMM.sendAsyncMessage('test:callRemoveInput');
break;
case 7:
is(evt.type, 'removeinputrequest', 'evt.type');
deepAssertObject(evt.detail, {
inputId: 'foo',
manifestURL: null // todo
}, 'detail');
d = new Deferred();
evt.detail.waitUntil(d.promise);
evt.preventDefault();
Promise.resolve().then(next);
break;
case 8:
d.resolve();
d = null;
break;
case 9:
ok(evt.resolved, 'resolved');
appFrameMM.sendAsyncMessage('test:callRemoveInput');
break;
case 10:
is(evt.type, 'removeinputrequest', 'evt.type');
d = new Deferred();
evt.detail.waitUntil(d.promise);
evt.preventDefault();
Promise.resolve().then(next);
break;
case 11:
d.reject('Foo Error');
d = null;
break;
case 12:
ok(evt.rejected, 'rejected');
is(evt.error, 'Foo Error', 'rejected');
inputmethod_cleanup();
break;
default:
ok(false, 'received extra call.');
inputmethod_cleanup();
break;
}
}
im.mgmt.onaddinputrequest =
im.mgmt.onremoveinputrequest = nextStep;
}
inputmethod_setup(function() {
Promise.resolve()
.then(() => setupTestRunner())
.then(() => setupInputAppFrame())
.then(() => nextStep())
.catch((e) => {
ok(false, 'Error' + e.toString());
console.error(e);
});
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=952080
-->
<head>
<title>SendKey with canceled keydown test for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952080">Mozilla Bug 952080</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
// The input context.
var gContext = null;
inputmethod_setup(function() {
runTest();
});
function runTest() {
let im = navigator.mozInputMethod;
im.oninputcontextchange = function() {
ok(true, 'inputcontextchange event was fired.');
im.oninputcontextchange = null;
gContext = im.inputcontext;
if (!gContext) {
ok(false, 'Should have a non-null inputcontext.');
inputmethod_cleanup();
return;
}
test();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_sendkey_cancel.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
}
function test() {
gContext.sendKey(0, 'j', 0).then(function() {
ok(false, 'sendKey was incorrectly resolved');
inputmethod_cleanup();
}, function(e) {
ok(true, 'sendKey was rejected');
inputmethod_cleanup();
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,130 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1197682
-->
<head>
<title>Test inputcontext#inputType and MozInputMethodManager#supportsSwitching()</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1197682">Mozilla Bug 1197682</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
let appFrameScript = function appFrameScript() {
let input = content.document.body.firstElementChild;
let i = 1;
input.focus();
addMessageListener('test:next', function() {
i++;
switch (i) {
case 2:
content.document.body.children[1].focus();
i++; // keep the same count with the parent frame.
break;
case 4:
content.document.body.lastElementChild.focus();
i++; // keep the same count with the parent frame.
break;
case 6:
content.document.body.lastElementChild.blur();
break;
}
});
};
function runTest() {
let im = navigator.mozInputMethod;
let i = 0;
im.oninputcontextchange = function(evt) {
var inputcontext = navigator.mozInputMethod.inputcontext;
i++;
switch (i) {
case 1:
ok(!!inputcontext, '1) Receving the input context');
is(inputcontext.inputType, 'text', '1) input type');
is(im.mgmt.supportsSwitching(), true, '1) supports switching');
mm.sendAsyncMessage('test:next');
break;
case 2:
is(inputcontext, null, '2) Receving null inputcontext');
break;
case 3:
ok(!!inputcontext, '3) Receving the input context');
is(inputcontext.inputType, 'number', '3) input type');
is(im.mgmt.supportsSwitching(), false, '3) supports switching');
mm.sendAsyncMessage('test:next');
break;
case 4:
is(inputcontext, null, '4) Receving null inputcontext');
break;
case 5:
ok(!!inputcontext, '5) Receving the input context');
is(inputcontext.inputType, 'password', '5) input type');
is(im.mgmt.supportsSwitching(), true, '5) supports switching');
mm.sendAsyncMessage('test:next');
break;
case 6:
is(inputcontext, null, '6) Receving null inputcontext');
is(im.mgmt.supportsSwitching(), false, '6) supports switching');
inputmethod_cleanup();
break;
default:
ok(false, 'Receving extra inputcontextchange calls');
inputmethod_cleanup();
break;
}
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
// Set text and password inputs as supports switching (and not supported for number type)
im.mgmt.setSupportsSwitchingTypes(['text', 'password']);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_setSupportsSwitching.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
iframe.addEventListener('mozbrowserloadend', function() {
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,154 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
-->
<head>
<title>Test simple manage notification events on MozInputMethodManager</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
let appFrameMM;
let nextStep;
function setupTestRunner() {
info('setupTestRunner');
let im = navigator.mozInputMethod;
let i = 0;
im.mgmt.onshowallrequest =
im.mgmt.onnextrequest = nextStep = function(evt) {
i++;
switch (i) {
case 1:
is(evt.type, 'inputcontextchange', '1) inputcontextchange event');
appFrameMM.sendAsyncMessage('test:callShowAll');
break;
case 2:
is(evt.type, 'showallrequest', '2) showallrequest event');
ok(evt.target, im.mgmt, '2) evt.target');
evt.preventDefault();
appFrameMM.sendAsyncMessage('test:callNext');
break;
case 3:
is(evt.type, 'nextrequest', '3) nextrequest event');
ok(evt.target, im.mgmt, '3) evt.target');
evt.preventDefault();
im.mgmt.onshowallrequest =
im.mgmt.onnextrequest = nextStep = null;
inputmethod_cleanup();
break;
default:
ok(false, 'Receving extra events');
inputmethod_cleanup();
break;
}
};
}
function setupInputAppFrame() {
info('setupInputAppFrame');
return new Promise((resolve, reject) => {
let appFrameScript = function appFrameScript() {
let im = content.navigator.mozInputMethod;
addMessageListener('test:callShowAll', function() {
im.mgmt.showAll();
});
addMessageListener('test:callNext', function() {
im.mgmt.next();
});
im.mgmt.onshowallrequest =
im.mgmt.onnextrequest = function(evt) {
sendAsyncMessage('test:appEvent', { type: evt.type });
};
im.oninputcontextchange = function(evt) {
sendAsyncMessage('test:inputcontextchange', {});
};
content.document.body.textContent = 'I am a input app';
};
let path = location.pathname;
let basePath = location.protocol + '//' + location.host +
path.substring(0, path.lastIndexOf('/'));
let imeUrl = basePath + '/file_blank.html';
let inputAppFrame = document.createElement('iframe');
inputAppFrame.setAttribute('mozbrowser', true);
inputAppFrame.src = imeUrl;
document.body.appendChild(inputAppFrame);
let mm = appFrameMM =
SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
inputAppFrame.addEventListener('mozbrowserloadend', function() {
mm.addMessageListener('test:appEvent', function(msg) {
ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
});
mm.addMessageListener('test:inputcontextchange', function(msg) {
nextStep && nextStep({ type: 'inputcontextchange' });
});
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
// Set the input app frame to be active
let req = inputAppFrame.setInputMethodActive(true);
resolve(req);
});
});
}
function setupContentFrame() {
let contentFrameScript = function contentFrameScript() {
let input = content.document.body.firstElementChild;
input.focus();
};
let iframe = document.createElement('iframe');
iframe.src = 'file_test_simple_manage_events.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
iframe.addEventListener('mozbrowserloadend', function() {
mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false);
});
}
inputmethod_setup(function() {
Promise.resolve()
.then(() => setupTestRunner())
.then(() => setupContentFrame())
.then(() => setupInputAppFrame())
.catch((e) => {
ok(false, 'Error' + e.toString());
console.error(e);
});
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,81 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1079455
-->
<head>
<title>Sync edit of an input</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1079455">Mozilla Bug 1079455</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
let appFrameScript = function appFrameScript() {
let input = content.document.body.firstElementChild;
input.focus();
input.value = 'First1';
input.blur();
};
function runTest() {
let im = navigator.mozInputMethod;
let i = 0;
im.oninputcontextchange = function() {
let inputcontext = im.inputcontext;
i++;
switch (i) {
case 1:
ok(!!inputcontext, 'Should receive inputcontext from focus().');
is(inputcontext.textAfterCursor, 'First');
break;
case 2:
ok(!!inputcontext, 'Should receive inputcontext from value change.');
is(inputcontext.textBeforeCursor, 'First1');
break;
case 3:
ok(!inputcontext, 'Should lost inputcontext from blur().');
inputmethod_cleanup();
break;
default:
ok(false, 'Unknown event count.');
inputmethod_cleanup();
}
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_sync_edit.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
iframe.addEventListener('mozbrowserloadend', function() {
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,184 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1057898
https://bugzilla.mozilla.org/show_bug.cgi?id=952741
-->
<head>
<title>Test switching between two inputs</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1057898">Mozilla Bug 1057898</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952741">Mozilla Bug 952741</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
let appFrameScript = function appFrameScript() {
let input1 = content.document.body.firstElementChild;
let input2 = content.document.body.children[1];
let i = 1;
input1.focus();
addMessageListener('test:next', function() {
i++;
switch (i) {
case 2:
input2.focus();
i++; // keep the same count with the parent frame.
break;
case 4:
input2.blur();
break;
case 5:
input2.focus();
break;
case 6:
input1.focus();
i++; // keep the same count with the parent frame.
break;
case 8:
content.document.body.removeChild(input1);
break;
case 9:
input2.focus();
break;
case 10:
content.document.body.removeChild(input2);
break;
}
});
};
function runTest() {
let im = navigator.mozInputMethod;
let i = 0;
im.oninputcontextchange = function(evt) {
var inputcontext = navigator.mozInputMethod.inputcontext;
i++;
switch (i) {
// focus on the first input receives the first input context.
case 1:
ok(!!inputcontext, '1) Receving the first input context');
is(inputcontext.textAfterCursor, 'First');
mm.sendAsyncMessage('test:next');
break;
// focus on the second input should implicitly blur the first input
case 2:
is(inputcontext, null, '2) Receving null inputcontext');
break;
// ... and results the second input context.
case 3:
ok(!!inputcontext, '3) Receving the second input context');
is(inputcontext.textAfterCursor, 'Second');
mm.sendAsyncMessage('test:next');
break;
// blur on the second input results null input context
case 4:
is(inputcontext, null, '4) Receving null inputcontext');
mm.sendAsyncMessage('test:next');
break;
// focus on the second input receives the second input context.
case 5:
ok(!!inputcontext, '5) Receving the second input context');
is(inputcontext.textAfterCursor, 'Second');
mm.sendAsyncMessage('test:next');
break;
// focus on the second input should implicitly blur the first input
case 6:
is(inputcontext, null, '6) Receving null inputcontext');
break;
// ... and results the second input context.
case 7:
ok(!!inputcontext, '7) Receving the first input context');
is(inputcontext.textAfterCursor, 'First');
mm.sendAsyncMessage('test:next');
break;
// remove on the first focused input results null input context
case 8:
is(inputcontext, null, '8) Receving null inputcontext');
mm.sendAsyncMessage('test:next');
break;
// input context for the second input.
case 9:
ok(!!inputcontext, '9) Receving the second input context');
is(inputcontext.textAfterCursor, 'Second');
mm.sendAsyncMessage('test:next');
break;
// remove on the second focused input results null input context
case 10:
is(inputcontext, null, '10) Receving null inputcontext');
inputmethod_cleanup();
break;
default:
ok(false, 'Receving extra inputcontextchange calls');
inputmethod_cleanup();
break;
}
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_two_inputs.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
iframe.addEventListener('mozbrowserloadend', function() {
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,182 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1079728
-->
<head>
<title>Test switching between two inputs</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1079728">Mozilla Bug 1079728</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
let appFrameScript = function appFrameScript() {
let select1 = content.document.body.firstElementChild;
let select2 = content.document.body.children[1];
let i = 1;
select1.focus();
addMessageListener('test:next', function() {
i++;
switch (i) {
case 2:
select2.focus();
i++; // keep the same count with the parent frame.
break;
case 4:
select2.blur();
break;
case 5:
select2.focus();
break;
case 6:
select1.focus();
i++; // keep the same count with the parent frame.
break;
case 8:
content.document.body.removeChild(select1);
break;
case 9:
select2.focus();
break;
case 10:
content.document.body.removeChild(select2);
break;
}
});
};
function runTest() {
let im = navigator.mozInputMethod;
let i = 0;
im.oninputcontextchange = function(evt) {
var inputcontext = navigator.mozInputMethod.inputcontext;
i++;
switch (i) {
// focus on the first input receives the first input context.
case 1:
ok(!!inputcontext, '1) Receving the first input context');
is(inputcontext.textAfterCursor, 'First');
mm.sendAsyncMessage('test:next');
break;
// focus on the second input should implicitly blur the first input
case 2:
is(inputcontext, null, '2) Receving null inputcontext');
break;
// ... and results the second input context.
case 3:
ok(!!inputcontext, '3) Receving the second input context');
is(inputcontext.textAfterCursor, 'Second');
mm.sendAsyncMessage('test:next');
break;
// blur on the second input results null input context
case 4:
is(inputcontext, null, '4) Receving null inputcontext');
mm.sendAsyncMessage('test:next');
break;
// focus on the second input receives the second input context.
case 5:
ok(!!inputcontext, '5) Receving the second input context');
is(inputcontext.textAfterCursor, 'Second');
mm.sendAsyncMessage('test:next');
break;
// focus on the second input should implicitly blur the first input
case 6:
is(inputcontext, null, '6) Receving null inputcontext');
break;
// ... and results the second input context.
case 7:
ok(!!inputcontext, '7) Receving the first input context');
is(inputcontext.textAfterCursor, 'First');
mm.sendAsyncMessage('test:next');
break;
// remove on the first focused input results null input context
case 8:
is(inputcontext, null, '8) Receving null inputcontext');
mm.sendAsyncMessage('test:next');
break;
// input context for the second input.
case 9:
ok(!!inputcontext, '9) Receving the second input context');
is(inputcontext.textAfterCursor, 'Second');
mm.sendAsyncMessage('test:next');
break;
// remove on the second focused input results null input context
case 10:
is(inputcontext, null, '10) Receving null inputcontext');
inputmethod_cleanup();
break;
default:
ok(false, 'Receving extra inputcontextchange calls');
inputmethod_cleanup();
break;
}
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_two_selects.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
iframe.addEventListener('mozbrowserloadend', function() {
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
});
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,167 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1122463
https://bugzilla.mozilla.org/show_bug.cgi?id=820057
-->
<head>
<title>Test focus when page unloads</title>
<script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1122463">Mozilla Bug 1122463</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=820057">Mozilla Bug 820057</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
let appFrameScript = function appFrameScript() {
let form1 = content.document.body.firstElementChild;
let input1 = form1.firstElementChild;
let submit1 = form1.lastElementChild;
let input2;
let cancelSubmit = function(evt) {
evt.preventDefault();
};
// Content of the second page.
form1.action = 'file_test_unload_action.html';
let i = 1;
input1.focus();
addMessageListener('test:next', function() {
i++;
switch (i) {
case 2:
// Click the submit button, trigger the submit event and make our
// installed event listener preventing the submission.
form1.addEventListener('submit', cancelSubmit);
submit1.click();
sendAsyncMessage('test:step');
break;
case 3:
// Actually submit the form.
form1.removeEventListener('submit', cancelSubmit);
submit1.click();
break;
case 4:
if (!content.document.body) {
content.onload = function() {
content.onload = null;
let input2 = content.document.body.firstElementChild;
input2.focus();
};
return;
}
input2 = content.document.body.firstElementChild;
input2.focus();
break;
case 5:
content.location.href = 'data:text/html,Hello!';
break;
}
});
};
function runTest() {
let im = navigator.mozInputMethod;
let i = 0;
function nextStep() {
let inputcontext = navigator.mozInputMethod.inputcontext;
i++;
switch (i) {
// focus on the first input receives the first input context.
case 1:
ok(!!inputcontext, '1) Receving the first input context');
is(inputcontext.textAfterCursor, 'First');
mm.sendAsyncMessage('test:next');
break;
// Cancelled submission should not cause us lost focus.
case 2:
ok(!!inputcontext, '2) Receving the first input context');
is(inputcontext.textAfterCursor, 'First');
mm.sendAsyncMessage('test:next');
break;
// Real submit and page transition should cause us lost focus.
// XXX: Unless we could delay the page transition, we does not know if
// the inputcontext is lost because of the submit or the pagehide/beforeload
// event.
case 3:
is(inputcontext, null, '3) Receving null inputcontext');
mm.sendAsyncMessage('test:next');
break;
// Regaining focus of input in the second page.
case 4:
ok(!!inputcontext, '4) Receving the second input context');
is(inputcontext.textAfterCursor, 'Second');
mm.sendAsyncMessage('test:next');
break;
// Page transition should cause us lost focus
case 5:
is(inputcontext, null, '5) Receving null inputcontext');
inputmethod_cleanup();
break;
}
}
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
let iframe = document.createElement('iframe');
iframe.src = 'file_test_unload.html';
iframe.setAttribute('mozbrowser', true);
document.body.appendChild(iframe);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
im.oninputcontextchange = nextStep;
let frameScriptLoaded = false;
iframe.addEventListener('mozbrowserloadend', function() {
if (frameScriptLoaded)
return;
frameScriptLoaded = true;
mm.addMessageListener('test:step', nextStep);
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
});
}
</script>
</pre>
</body>
</html>

41
dom/inputmethod/moz.build Normal file
View file

@ -0,0 +1,41 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
if CONFIG['MOZ_B2G']:
XPIDL_SOURCES += [
'nsIHardwareKeyHandler.idl',
]
XPIDL_MODULE = 'inputmethod'
EXPORTS.mozilla += [
'HardwareKeyHandler.h',
]
SOURCES += [
'HardwareKeyHandler.cpp'
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base',
'/layout/base',
]
EXTRA_COMPONENTS += [
'InputMethod.manifest',
'MozKeyboard.js',
]
EXTRA_PP_JS_MODULES += [
'Keyboard.jsm',
]
JAR_MANIFESTS += ['jar.mn']
MOCHITEST_CHROME_MANIFESTS += ['mochitest/chrome.ini']

View file

@ -0,0 +1,142 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "nsISupports.idl"
interface nsIDOMKeyEvent;
%{C++
#define NS_HARDWARE_KEY_HANDLER_CID \
{ 0xfb45921b, 0xe0a5, 0x45c6, \
{ 0x90, 0xd0, 0xa6, 0x97, 0xa7, 0x72, 0xc4, 0x2a } }
#define NS_HARDWARE_KEY_HANDLER_CONTRACTID \
"@mozilla.org/HardwareKeyHandler;1"
#include "mozilla/EventForwards.h" /* For nsEventStatus */
namespace mozilla {
class WidgetKeyboardEvent;
}
using mozilla::WidgetKeyboardEvent;
class nsINode;
%}
/**
* This interface is used to be registered to the nsIHardwareKeyHandler through
* |nsIHardwareKeyHandler.registerListener|.
*/
[scriptable, function, uuid(cd5aeee3-b4b9-459d-85e7-c0671c7a8a2e)]
interface nsIHardwareKeyEventListener : nsISupports
{
/**
* This method will be invoked by nsIHardwareKeyHandler to forward the native
* keyboard event to the active input method
*/
bool onHardwareKey(in nsIDOMKeyEvent aEvent);
};
/**
* This interface has two main roles. One is to send a hardware keyboard event
* to the active input method app and the other is to receive its reply result.
* If a keyboard event is triggered from a hardware keyboard when an editor has
* focus, the event target should be the editor. However, the text input
* processor algorithm is implemented in an input method app and it should
* handle the event earlier than the real event target to do the mapping such
* as character conversion according to the language setting or the type of a
* hardware keyboard.
*/
[scriptable, builtinclass, uuid(25b34270-caad-4d18-a910-860351690639)]
interface nsIHardwareKeyHandler : nsISupports
{
/**
* Flags used to set the defaultPrevented's result. The default result
* from input-method-app should be set to NO_DEFAULT_PREVENTED.
* (It means the forwarded event isn't consumed by input-method-app.)
* If the input-method-app consumes the forwarded event,
* then the result should be set by DEFAULT_PREVENTED* before reply.
*/
const unsigned short NO_DEFAULT_PREVENTED = 0x0000;
const unsigned short DEFAULT_PREVENTED = 0x0001;
const unsigned short DEFAULT_PREVENTED_BY_CHROME = 0x0002;
const unsigned short DEFAULT_PREVENTED_BY_CONTENT = 0x0004;
/**
* Registers a listener in input-method-app to receive
* the forwarded hardware keyboard events
*
* @param aListener Listener object to be notified for receiving
* the keyboard event fired from hardware
* @note A listener object must implement
* nsIHardwareKeyEventListener and
* nsSupportsWeakReference
* @see nsIHardwareKeyEventListener
* @see nsSupportsWeakReference
*/
void registerListener(in nsIHardwareKeyEventListener aListener);
/**
* Unregisters the current listener from input-method-app
*/
void unregisterListener();
/**
* Notifies nsIHardwareKeyHandler that input-method-app is active.
*/
void onInputMethodAppConnected();
/**
* Notifies nsIHardwareKeyHandler that input-method-app is disabled.
*/
void onInputMethodAppDisconnected();
/**
* Input-method-app will pass the processing result that the forwarded
* event is handled or not through this method, and the nsIHardwareKeyHandler
* can use this to receive the reply of |forwardKeyToInputMethodApp|
* from the active input method.
*
* The result should contain the original event type and the info whether
* the default is prevented, also, it is prevented by chrome or content.
*
* @param aEventType The type of an original event.
* @param aDefaultPrevented State that |evt.preventDefault|
* is called by content, chrome or not.
*/
void onHandledByInputMethodApp(in DOMString aType,
in unsigned short aDefaultPrevented);
/**
* Sends the native keyboard events triggered from hardware to the
* active input method before dispatching to its event target.
* This method only forwards keydown and keyup events.
* If the event isn't allowed to be forwarded, we should continue the
* normal event processing. For those forwarded keydown and keyup events
* We will pause the further event processing to wait for the completion
* of the event handling in the active input method app.
* Once |onHandledByInputMethodApp| is called by the input method app,
* the pending event processing can be resumed according to its reply.
* On the other hand, the keypress will never be sent to the input-method-app.
* Depending on whether the keydown's reply arrives before the keypress event
* comes, the keypress event will be handled directly or pushed into
* the event queue to wait for its heading keydown's reply.
*
* This implementation will call |nsIHardwareKeyEventListener.onHardwareKey|,
* which is registered through |nsIHardwareKeyEventListener.registerListener|,
* to forward the events.
*
* Returns true, if the event is handled in this module.
* Returns false, otherwise.
*
* If it returns false, we should continue the normal event processing.
*/
%{C++
virtual bool ForwardKeyToInputMethodApp(nsINode* aTarget,
WidgetKeyboardEvent* aEvent,
nsEventStatus* aEventStatus) = 0;
%}
};