mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-09 17:31:47 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
562
dom/inputmethod/HardwareKeyHandler.cpp
Normal file
562
dom/inputmethod/HardwareKeyHandler.cpp
Normal 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
|
||||
224
dom/inputmethod/HardwareKeyHandler.h
Normal file
224
dom/inputmethod/HardwareKeyHandler.h
Normal 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_
|
||||
2
dom/inputmethod/InputMethod.manifest
Normal file
2
dom/inputmethod/InputMethod.manifest
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
component {4607330d-e7d2-40a4-9eb8-43967eae0142} MozKeyboard.js
|
||||
contract @mozilla.org/b2g-inputmethod;1 {4607330d-e7d2-40a4-9eb8-43967eae0142}
|
||||
644
dom/inputmethod/Keyboard.jsm
Normal file
644
dom/inputmethod/Keyboard.jsm
Normal 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();
|
||||
1255
dom/inputmethod/MozKeyboard.js
Normal file
1255
dom/inputmethod/MozKeyboard.js
Normal file
File diff suppressed because it is too large
Load diff
1561
dom/inputmethod/forms.js
Normal file
1561
dom/inputmethod/forms.js
Normal file
File diff suppressed because it is too large
Load diff
6
dom/inputmethod/jar.mn
Normal file
6
dom/inputmethod/jar.mn
Normal 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)
|
||||
267
dom/inputmethod/mochitest/bug1110030_helper.js
Normal file
267
dom/inputmethod/mochitest/bug1110030_helper.js
Normal 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;
|
||||
}
|
||||
},
|
||||
};
|
||||
52
dom/inputmethod/mochitest/chrome.ini
Normal file
52
dom/inputmethod/mochitest/chrome.ini
Normal 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]
|
||||
4
dom/inputmethod/mochitest/file_blank.html
Normal file
4
dom/inputmethod/mochitest/file_blank.html
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<html>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
25
dom/inputmethod/mochitest/file_inputmethod.html
Normal file
25
dom/inputmethod/mochitest/file_inputmethod.html
Normal 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>
|
||||
11
dom/inputmethod/mochitest/file_test_app.html
Normal file
11
dom/inputmethod/mochitest/file_test_app.html
Normal 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>
|
||||
6
dom/inputmethod/mochitest/file_test_bug1066515.html
Normal file
6
dom/inputmethod/mochitest/file_test_bug1066515.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<div id="text" contenteditable>Jan Jongboom</div>
|
||||
</body>
|
||||
</html>
|
||||
6
dom/inputmethod/mochitest/file_test_bug1137557.html
Normal file
6
dom/inputmethod/mochitest/file_test_bug1137557.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<textarea rows=30 cols=30></textarea>
|
||||
</body>
|
||||
</html>
|
||||
1
dom/inputmethod/mochitest/file_test_bug1175399.html
Normal file
1
dom/inputmethod/mochitest/file_test_bug1175399.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<html><body><input value="First" readonly></body></html>
|
||||
10
dom/inputmethod/mochitest/file_test_empty_app.html
Normal file
10
dom/inputmethod/mochitest/file_test_empty_app.html
Normal 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>
|
||||
|
|
@ -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>
|
||||
14
dom/inputmethod/mochitest/file_test_sendkey_cancel.html
Normal file
14
dom/inputmethod/mochitest/file_test_sendkey_cancel.html
Normal 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>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<html><body>
|
||||
<input type="text">
|
||||
<input type="number">
|
||||
<input type="password">
|
||||
</body></html>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<html><body><input type="text"></body></html>
|
||||
14
dom/inputmethod/mochitest/file_test_sms_app.html
Normal file
14
dom/inputmethod/mochitest/file_test_sms_app.html
Normal 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>
|
||||
14
dom/inputmethod/mochitest/file_test_sms_app_1066515.html
Normal file
14
dom/inputmethod/mochitest/file_test_sms_app_1066515.html
Normal 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>
|
||||
1
dom/inputmethod/mochitest/file_test_sync_edit.html
Normal file
1
dom/inputmethod/mochitest/file_test_sync_edit.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<html><body><input value="First"></body></html>
|
||||
1
dom/inputmethod/mochitest/file_test_two_inputs.html
Normal file
1
dom/inputmethod/mochitest/file_test_two_inputs.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<html><body><input value="First"><input value="Second"></body></html>
|
||||
1
dom/inputmethod/mochitest/file_test_two_selects.html
Normal file
1
dom/inputmethod/mochitest/file_test_two_selects.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<html><body><select><option>First</option></select><select><option>Second</option></select></html>
|
||||
1
dom/inputmethod/mochitest/file_test_unload.html
Normal file
1
dom/inputmethod/mochitest/file_test_unload.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<html><body><form id="form"><input value="First"><input type="submit"></form></body></html>
|
||||
1
dom/inputmethod/mochitest/file_test_unload_action.html
Normal file
1
dom/inputmethod/mochitest/file_test_unload_action.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<html><body><input value="Second"></body></html>
|
||||
24
dom/inputmethod/mochitest/inputmethod_common.js
Normal file
24
dom/inputmethod/mochitest/inputmethod_common.js
Normal 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();
|
||||
}
|
||||
212
dom/inputmethod/mochitest/test_basic.html
Normal file
212
dom/inputmethod/mochitest/test_basic.html
Normal 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>
|
||||
|
||||
101
dom/inputmethod/mochitest/test_bug1026997.html
Normal file
101
dom/inputmethod/mochitest/test_bug1026997.html
Normal 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>
|
||||
|
||||
183
dom/inputmethod/mochitest/test_bug1043828.html
Normal file
183
dom/inputmethod/mochitest/test_bug1043828.html
Normal 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>
|
||||
|
||||
87
dom/inputmethod/mochitest/test_bug1059163.html
Normal file
87
dom/inputmethod/mochitest/test_bug1059163.html
Normal 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>
|
||||
|
||||
93
dom/inputmethod/mochitest/test_bug1066515.html
Normal file
93
dom/inputmethod/mochitest/test_bug1066515.html
Normal 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>
|
||||
1799
dom/inputmethod/mochitest/test_bug1137557.html
Normal file
1799
dom/inputmethod/mochitest/test_bug1137557.html
Normal file
File diff suppressed because it is too large
Load diff
62
dom/inputmethod/mochitest/test_bug1175399.html
Normal file
62
dom/inputmethod/mochitest/test_bug1175399.html
Normal 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>
|
||||
|
||||
107
dom/inputmethod/mochitest/test_bug944397.html
Normal file
107
dom/inputmethod/mochitest/test_bug944397.html
Normal 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>
|
||||
|
||||
40
dom/inputmethod/mochitest/test_bug949059.html
Normal file
40
dom/inputmethod/mochitest/test_bug949059.html
Normal 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>
|
||||
|
||||
52
dom/inputmethod/mochitest/test_bug953044.html
Normal file
52
dom/inputmethod/mochitest/test_bug953044.html
Normal 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>
|
||||
|
||||
108
dom/inputmethod/mochitest/test_bug960946.html
Normal file
108
dom/inputmethod/mochitest/test_bug960946.html
Normal 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>
|
||||
|
||||
77
dom/inputmethod/mochitest/test_bug978918.html
Normal file
77
dom/inputmethod/mochitest/test_bug978918.html
Normal 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>
|
||||
|
||||
199
dom/inputmethod/mochitest/test_focus_blur_manage_events.html
Normal file
199
dom/inputmethod/mochitest/test_focus_blur_manage_events.html
Normal 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>
|
||||
|
||||
149
dom/inputmethod/mochitest/test_forward_hardware_key_to_ime.html
Normal file
149
dom/inputmethod/mochitest/test_forward_hardware_key_to_ime.html
Normal 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>
|
||||
251
dom/inputmethod/mochitest/test_input_registry_events.html
Normal file
251
dom/inputmethod/mochitest/test_input_registry_events.html
Normal 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>
|
||||
67
dom/inputmethod/mochitest/test_sendkey_cancel.html
Normal file
67
dom/inputmethod/mochitest/test_sendkey_cancel.html
Normal 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>
|
||||
|
||||
130
dom/inputmethod/mochitest/test_setSupportsSwitching.html
Normal file
130
dom/inputmethod/mochitest/test_setSupportsSwitching.html
Normal 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>
|
||||
154
dom/inputmethod/mochitest/test_simple_manage_events.html
Normal file
154
dom/inputmethod/mochitest/test_simple_manage_events.html
Normal 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>
|
||||
|
||||
81
dom/inputmethod/mochitest/test_sync_edit.html
Normal file
81
dom/inputmethod/mochitest/test_sync_edit.html
Normal 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>
|
||||
|
||||
184
dom/inputmethod/mochitest/test_two_inputs.html
Normal file
184
dom/inputmethod/mochitest/test_two_inputs.html
Normal 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>
|
||||
|
||||
182
dom/inputmethod/mochitest/test_two_selects.html
Normal file
182
dom/inputmethod/mochitest/test_two_selects.html
Normal 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>
|
||||
|
||||
167
dom/inputmethod/mochitest/test_unload.html
Normal file
167
dom/inputmethod/mochitest/test_unload.html
Normal 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
41
dom/inputmethod/moz.build
Normal 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']
|
||||
142
dom/inputmethod/nsIHardwareKeyHandler.idl
Normal file
142
dom/inputmethod/nsIHardwareKeyHandler.idl
Normal 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;
|
||||
%}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue