Bug 1208944 - Part 9. Hook IMM32 APIs on plugin process. r=masayuki
authorMakoto Kato <m_kato@ga2.so-net.ne.jp>
Tue, 29 Dec 2015 22:57:38 +0900
changeset 313281 18186849973ce4832d7230b3ceb5627ab43dbaf2
parent 313280 a88721cadfc47b079e707237b4c302ca8fcbbf15
child 313282 e43204b34ed1e09f324b2999166eed8cfd10e683
push id5703
push userraliiev@mozilla.com
push dateMon, 07 Mar 2016 14:18:41 +0000
treeherdermozilla-beta@31e373ad5b5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1208944
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1208944 - Part 9. Hook IMM32 APIs on plugin process. r=masayuki
dom/ipc/PBrowser.ipdl
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/plugins/base/nsPluginInstanceOwner.cpp
dom/plugins/base/nsPluginInstanceOwner.h
dom/plugins/ipc/PPluginInstance.ipdl
dom/plugins/ipc/PluginInstanceChild.cpp
dom/plugins/ipc/PluginInstanceChild.h
dom/plugins/ipc/PluginInstanceParent.cpp
dom/plugins/ipc/PluginInstanceParent.h
dom/plugins/ipc/PluginQuirks.cpp
dom/plugins/ipc/PluginQuirks.h
widget/PuppetWidget.cpp
widget/PuppetWidget.h
widget/TextRange.h
widget/nsBaseWidget.h
widget/nsIWidget.h
widget/windows/IMMHandler.cpp
widget/windows/IMMHandler.h
widget/windows/WinIMEHandler.cpp
widget/windows/WinIMEHandler.h
widget/windows/nsWindow.cpp
widget/windows/nsWindow.h
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -277,16 +277,21 @@ parent:
      * a plugin (inside the widget) has the keyboard focus.  Should be sent
      * when the keyboard focus changes too or from a plugin.
      *
      * aFocused  Whether or not a plugin is focused
      */
     prio(urgent) async SetPluginFocused(bool aFocused);
 
     /**
+     * Set IME candidate window by windowless plugin if plugin has focus.
+     */
+    async SetCandidateWindowForPlugin(int32_t aX, int32_t aY);
+
+    /**
      * Request that the parent process move focus to the browser's frame. If
      * canRaise is true, the window can be raised if it is inactive.
      */
     RequestFocus(bool canRaise);
 
     /**
      * Indicate, based on the current state, that some commands are enabled and
      * some are disabled.
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2391,16 +2391,29 @@ TabParent::RecvSetPluginFocused(const bo
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return true;
   }
   widget->SetPluginFocused((bool&)aFocused);
   return true;
 }
 
+ bool
+TabParent::RecvSetCandidateWindowForPlugin(const int32_t& aX,
+                                           const int32_t& aY)
+{
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget) {
+    return true;
+  }
+
+  widget->SetCandidateWindowForPlugin(aX, aY);
+  return true;
+}
+
 bool
 TabParent::RecvGetInputContext(int32_t* aIMEEnabled,
                                int32_t* aIMEOpen)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     *aIMEEnabled = IMEState::DISABLED;
     *aIMEOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -191,16 +191,18 @@ public:
     virtual bool RecvRequestIMEToCommitComposition(const bool& aCancel,
                                                    bool* aIsCommitted,
                                                    nsString* aCommittedString) override;
     virtual bool RecvStartPluginIME(const WidgetKeyboardEvent& aKeyboardEvent,
                                     const int32_t& aPanelX,
                                     const int32_t& aPanelY,
                                     nsString* aCommitted) override;
     virtual bool RecvSetPluginFocused(const bool& aFocused) override;
+    virtual bool RecvSetCandidateWindowForPlugin(const int32_t& aX,
+                                                 const int32_t& aY) override;
     virtual bool RecvGetInputContext(int32_t* aIMEEnabled,
                                      int32_t* aIMEOpen) override;
     virtual bool RecvSetInputContext(const int32_t& aIMEEnabled,
                                      const int32_t& aIMEOpen,
                                      const nsString& aType,
                                      const nsString& aInputmode,
                                      const nsString& aActionHint,
                                      const int32_t& aCause,
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -55,16 +55,18 @@ using mozilla::DefaultXDisplay;
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/TabChild.h"
 #include "nsFrameSelection.h"
 #include "PuppetWidget.h"
 #include "nsPIWindowRoot.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/TextComposition.h"
 
 #include "nsContentCID.h"
 #include "nsWidgetsCID.h"
 static NS_DEFINE_CID(kWidgetCID, NS_CHILD_CID);
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 #ifdef XP_WIN
 #include <wtypes.h>
@@ -786,17 +788,192 @@ nsPluginInstanceOwner::SetNetscapeWindow
     NS_ASSERTION(rootWidget, "Couldn't get topmost document's widget.");
     return NS_ERROR_FAILURE;
   }
 
   rootWidget->SetNativeData(NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW,
                             reinterpret_cast<uintptr_t>(aWindowToAdopt));
   return NS_OK;
 }
-#endif
+
+bool
+nsPluginInstanceOwner::GetCompositionString(uint32_t aType,
+                                            nsTArray<uint8_t>* aDist,
+                                            int32_t* aLength)
+{
+  RefPtr<TextComposition> composition = GetTextComposition();
+  if (NS_WARN_IF(!composition)) {
+    return false;
+  }
+
+  switch(aType) {
+    case GCS_COMPSTR: {
+      if (!composition->IsComposing()) {
+        *aLength = 0;
+        return true;
+      }
+
+      uint32_t len = composition->LastData().Length() * sizeof(char16_t);
+      if (len) {
+        aDist->SetLength(len);
+        memcpy(aDist->Elements(), composition->LastData().get(), len);
+      }
+      *aLength = len;
+      return true;
+    }
+
+    case GCS_RESULTSTR: {
+      if (composition->IsComposing()) {
+        *aLength = 0;
+        return true;
+      }
+
+      uint32_t len = composition->LastData().Length() * sizeof(char16_t);
+      if (len) {
+        aDist->SetLength(len);
+        memcpy(aDist->Elements(), composition->LastData().get(), len);
+      }
+      *aLength = len;
+      return true;
+    }
+
+    case GCS_CURSORPOS: {
+      *aLength = 0;
+      TextRangeArray* ranges = composition->GetLastRanges();
+      if (!ranges) {
+        return true;
+      }
+      *aLength = ranges->GetCaretPosition();
+      if (*aLength < 0) {
+        return false;
+      }
+      return true;
+    }
+
+    case GCS_COMPATTR: {
+      TextRangeArray* ranges = composition->GetLastRanges();
+      if (!ranges || ranges->IsEmpty()) {
+        *aLength = 0;
+        return true;
+      }
+
+      aDist->SetLength(composition->LastData().Length());
+      memset(aDist->Elements(), ATTR_INPUT, aDist->Length());
+
+      for (TextRange& range : *ranges) {
+        uint8_t type = ATTR_INPUT;
+        switch(range.mRangeType) {
+          case NS_TEXTRANGE_RAWINPUT:
+            type = ATTR_INPUT;
+            break;
+          case NS_TEXTRANGE_SELECTEDRAWTEXT:
+            type = ATTR_TARGET_NOTCONVERTED;
+            break;
+          case NS_TEXTRANGE_CONVERTEDTEXT:
+            type = ATTR_CONVERTED;
+            break;
+          case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
+            type = ATTR_TARGET_CONVERTED;
+            break;
+          default:
+            continue;
+        }
+
+        size_t minLen = std::min<size_t>(range.mEndOffset, aDist->Length());
+        for (size_t i = range.mStartOffset; i < minLen; i++) {
+          (*aDist)[i] = type;
+        }
+      }
+      *aLength = aDist->Length();
+      return true;
+    }
+
+    case GCS_COMPCLAUSE: {
+      RefPtr<TextRangeArray> ranges = composition->GetLastRanges();
+      if (!ranges || ranges->IsEmpty()) {
+        aDist->SetLength(sizeof(uint32_t));
+        memset(aDist->Elements(), 0, sizeof(uint32_t));
+        *aLength = aDist->Length();
+        return true;
+      }
+      nsAutoTArray<uint32_t, 16> clauses;
+      clauses.AppendElement(0);
+      for (TextRange& range : *ranges) {
+        if (!range.IsClause()) {
+          continue;
+        }
+        clauses.AppendElement(range.mEndOffset);
+      }
+
+      aDist->SetLength(clauses.Length() * sizeof(uint32_t));
+      memcpy(aDist->Elements(), clauses.Elements(), aDist->Length());
+      *aLength = aDist->Length();
+      return true;
+    }
+
+    case GCS_RESULTREADSTR: {
+      // When returning error causes unexpected error, so we return 0 instead.
+      *aLength = 0;
+      return true;
+    }
+
+    case GCS_RESULTCLAUSE: {
+      // When returning error causes unexpected error, so we return 0 instead.
+      *aLength = 0;
+      return true;
+    }
+
+    default:
+      NS_WARNING(
+        nsPrintfCString("Unsupported type %x of ImmGetCompositionStringW hook",
+                        aType).get());
+      break;
+  }
+
+  return false;
+}
+
+bool
+nsPluginInstanceOwner::SetCandidateWindow(int32_t aX, int32_t aY)
+{
+  if (NS_WARN_IF(!mPluginFrame)) {
+    return false;
+  }
+
+  nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset();
+  if (!widget) {
+    widget = GetRootWidgetForPluginFrame(mPluginFrame);
+    if (NS_WARN_IF(!widget)) {
+      return false;
+    }
+  }
+
+  widget->SetCandidateWindowForPlugin(aX, aY);
+  return true;
+}
+
+bool
+nsPluginInstanceOwner::RequestCommitOrCancel(bool aCommitted)
+{
+  nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset();
+  if (!widget) {
+    widget = GetRootWidgetForPluginFrame(mPluginFrame);
+    if (NS_WARN_IF(!widget)) {
+      return false;
+    }
+  }
+
+  if (aCommitted) {
+    widget->NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION); 
+  } else {
+    widget->NotifyIME(widget::REQUEST_TO_CANCEL_COMPOSITION); 
+  }
+  return true;
+}
+#endif // #ifdef XP_WIN
 
 NS_IMETHODIMP nsPluginInstanceOwner::SetEventModel(int32_t eventModel)
 {
 #ifdef XP_MACOSX
   mEventModel = static_cast<NPEventModel>(eventModel);
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
@@ -1592,30 +1769,66 @@ nsresult nsPluginInstanceOwner::Dispatch
     }
     if (mouseEvent->mMessage == eMouseUp) {
       mLastMouseDownButtonType = -1;
     }
   }
   return NS_OK;
 }
 
+#ifdef XP_WIN
+already_AddRefed<TextComposition>
+nsPluginInstanceOwner::GetTextComposition()
+{
+  if (NS_WARN_IF(!mPluginFrame)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset();
+  if (!widget) {
+    widget = GetRootWidgetForPluginFrame(mPluginFrame);
+    if (NS_WARN_IF(!widget)) {
+      return nullptr;
+    }
+  }
+
+  RefPtr<TextComposition> composition =
+    IMEStateManager::GetTextCompositionFor(widget);
+  if (NS_WARN_IF(!composition)) {
+    return nullptr;
+  }
+
+  return composition.forget();
+}
+#endif
+
 nsresult
 nsPluginInstanceOwner::DispatchCompositionToPlugin(nsIDOMEvent* aEvent)
 {
 #ifdef XP_WIN
   if (!mPluginWindow) {
     // CompositionEvent isn't cancellable.  So it is unnecessary to call
     // PreventDefaults() to consume event
     return NS_OK;
   }
   WidgetCompositionEvent* compositionEvent =
     aEvent->GetInternalNSEvent()->AsCompositionEvent();
   if (NS_WARN_IF(!compositionEvent)) {
       return NS_ERROR_INVALID_ARG;
   }
+
+  if (compositionEvent->mMessage == eCompositionChange) {
+    RefPtr<TextComposition> composition = GetTextComposition();
+    if (NS_WARN_IF(!composition)) {
+      return NS_ERROR_FAILURE;
+    }
+    TextComposition::CompositionChangeEventHandlingMarker
+      compositionChangeEventHandlingMarker(composition, compositionEvent);
+  }
+
   nsEventStatus rv = ProcessEvent(*compositionEvent);
   // XXX This isn't e10s aware.
   // If the event isn't consumed, we cannot post result to chrome process.
   if (nsEventStatus_eConsumeNoDefault == rv) {
     aEvent->StopImmediatePropagation();
   }
 #endif
   return NS_OK;
--- a/dom/plugins/base/nsPluginInstanceOwner.h
+++ b/dom/plugins/base/nsPluginInstanceOwner.h
@@ -24,16 +24,17 @@
 #endif
 
 class nsIInputStream;
 class nsPluginDOMContextMenuListener;
 class nsPluginFrame;
 class nsDisplayListBuilder;
 
 namespace mozilla {
+class TextComposition;
 namespace dom {
 struct MozPluginParameter;
 } // namespace dom
 namespace widget {
 class PuppetWidget;
 } // namespace widget
 } // namespace mozilla
 
@@ -255,16 +256,21 @@ public:
   // Called from AndroidJNI when we removed the fullscreen view.
   static void ExitFullScreen(jobject view);
 #endif
 
   void NotifyHostAsyncInitFailed();
   void NotifyHostCreateWidget();
   void NotifyDestroyPending();
 
+  bool GetCompositionString(uint32_t aIndex, nsTArray<uint8_t>* aString,
+                            int32_t* aLength);
+  bool SetCandidateWindow(int32_t aX, int32_t aY);
+  bool RequestCommitOrCancel(bool aCommitted);
+
 private:
   virtual ~nsPluginInstanceOwner();
 
   // return FALSE if LayerSurface dirty (newly created and don't have valid plugin content yet)
   bool IsUpToDate()
   {
     nsIntSize size;
     return NS_SUCCEEDED(mInstance->GetImageSize(&size)) &&
@@ -277,16 +283,17 @@ private:
   void RemovePluginView();
 
   bool mFullScreen;
   void* mJavaView;
 #endif 
 
 #if defined(XP_WIN)
   nsIWidget* GetContainingWidgetIfOffset();
+  already_AddRefed<mozilla::TextComposition> GetTextComposition();
 #endif
  
   nsPluginNativeWindow       *mPluginWindow;
   RefPtr<nsNPAPIPluginInstance> mInstance;
   nsPluginFrame              *mPluginFrame;
   nsWeakPtr                   mContent; // WEAK, content owns us
   nsCString                   mDocumentBase;
   bool                        mWidgetCreationComplete;
--- a/dom/plugins/ipc/PPluginInstance.ipdl
+++ b/dom/plugins/ipc/PPluginInstance.ipdl
@@ -255,16 +255,25 @@ parent:
 
   // Notifies the parent of its NPP_New result code.
   async AsyncNPP_NewResult(NPError aResult);
 
   // Sends a native window to be adopted by the native window that would be
   // returned by NPN_GetValue_NPNVnetscapeWindow. Only used on Windows.
   async SetNetscapeWindowAsParent(NativeWindowHandle childWindow);
 
+  sync GetCompositionString(uint32_t aType)
+                            returns (uint8_t[] aDist, int32_t aLength);
+  // Set candidate window position.
+  //
+  // @param aX  x position of candidate window
+  // @param aY  y position of candidate window
+  async SetCandidateWindow(int32_t aX, int32_t aY);
+  sync RequestCommitOrCancel(bool aCommitted);
+
 both:
   async PPluginScriptableObject();
 
 child:
   /* NPP_NewStream */
   async PBrowserStream(nsCString url,
                        uint32_t length,
                        uint32_t lastmodified,
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -68,16 +68,35 @@ typedef BOOL (WINAPI *User32TrackPopupMe
                                             int y,
                                             int nReserved,
                                             HWND hWnd,
                                             CONST RECT *prcRect);
 static WindowsDllInterceptor sUser32Intercept;
 static HWND sWinlessPopupSurrogateHWND = nullptr;
 static User32TrackPopupMenu sUser32TrackPopupMenuStub = nullptr;
 
+typedef HIMC (WINAPI *Imm32ImmGetContext)(HWND hWND);
+typedef BOOL (WINAPI *Imm32ImmReleaseContext)(HWND hWND, HIMC hIMC);
+typedef LONG (WINAPI *Imm32ImmGetCompositionString)(HIMC hIMC,
+                                                    DWORD dwIndex,
+                                                    LPVOID lpBuf,
+                                                    DWORD dwBufLen);
+typedef BOOL (WINAPI *Imm32ImmSetCandidateWindow)(HIMC hIMC,
+                                                  LPCANDIDATEFORM lpCandidate);
+typedef BOOL (WINAPI *Imm32ImmNotifyIME)(HIMC hIMC, DWORD dwAction,
+                                        DWORD dwIndex, DWORD dwValue);
+static WindowsDllInterceptor sImm32Intercept;
+static Imm32ImmGetContext sImm32ImmGetContextStub = nullptr;
+static Imm32ImmReleaseContext sImm32ImmReleaseContextStub = nullptr;
+static Imm32ImmGetCompositionString sImm32ImmGetCompositionStringStub = nullptr;
+static Imm32ImmSetCandidateWindow sImm32ImmSetCandidateWindowStub = nullptr;
+static Imm32ImmNotifyIME sImm32ImmNotifyIME = nullptr;
+static PluginInstanceChild* sCurrentPluginInstance = nullptr;
+static const HIMC sHookIMC = (const HIMC)0xefefefef;
+
 using mozilla::gfx::SharedDIB;
 
 #include <windows.h>
 #include <windowsx.h>
 
 // Flash WM_USER message delay time for PostDelayedTask. Borrowed
 // from Chromium's web plugin delegate src. See 'flash msg throttling
 // helpers' section for details.
@@ -192,16 +211,17 @@ PluginInstanceChild::PluginInstanceChild
     mWsInfo.display = DefaultXDisplay();
 #endif
 #endif // MOZ_X11 && XP_UNIX && !XP_MACOSX
 #if defined(OS_WIN)
     InitPopupMenuHook();
     if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) {
         SetUnityHooks();
     }
+    InitImm32Hook();
 #endif // OS_WIN
 }
 
 PluginInstanceChild::~PluginInstanceChild()
 {
 #if defined(OS_WIN)
     NS_ASSERTION(!mPluginWindowHWND, "Destroying PluginInstanceChild without NPP_Destroy?");
     if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) {
@@ -1978,16 +1998,145 @@ PluginInstanceChild::CreateWinlessPopupS
     if (!mWinlessPopupSurrogateHWND) {
         NS_ERROR("CreateWindowEx failed for winless placeholder!");
         return;
     }
 
     SendSetNetscapeWindowAsParent(mWinlessPopupSurrogateHWND);
 }
 
+// static
+HIMC
+PluginInstanceChild::ImmGetContextProc(HWND aWND)
+{
+    if (!sCurrentPluginInstance) {
+        return sImm32ImmGetContextStub(aWND);
+    }
+
+    wchar_t szClass[21];
+    int haveClass = GetClassNameW(aWND, szClass, ArrayLength(szClass));
+    if (!haveClass || wcscmp(szClass, L"SWFlash_PlaceholderX")) {
+        NS_WARNING("We cannot recongnize hooked window class");
+        return sImm32ImmGetContextStub(aWND);
+    }
+
+    return sHookIMC;
+}
+
+// static
+BOOL
+PluginInstanceChild::ImmReleaseContextProc(HWND aWND, HIMC aIMC)
+{
+    if (aIMC == sHookIMC) {
+        return TRUE;
+    }
+
+    return sImm32ImmReleaseContextStub(aWND, aIMC);
+}
+
+// static
+LONG
+PluginInstanceChild::ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex,
+                                                 LPVOID aBuf, DWORD aLen)
+{
+    if (aIMC != sHookIMC) {
+        return sImm32ImmGetCompositionStringStub(aIMC, aIndex, aBuf, aLen);
+    }
+    if (!sCurrentPluginInstance) {
+        return IMM_ERROR_GENERAL;
+    }
+    nsAutoTArray<uint8_t, 16> dist;
+    int32_t length = 0; // IMM_ERROR_NODATA
+    sCurrentPluginInstance->SendGetCompositionString(aIndex, &dist, &length);
+    if (length == IMM_ERROR_NODATA || length == IMM_ERROR_GENERAL) {
+      return length;
+    }
+
+    if (aBuf && aLen >= static_cast<DWORD>(length)) {
+        memcpy(aBuf, dist.Elements(), length);
+    }
+    return length;
+}
+
+// staitc
+BOOL
+PluginInstanceChild::ImmSetCandidateWindowProc(HIMC aIMC, LPCANDIDATEFORM aForm)
+{
+    if (aIMC != sHookIMC) {
+        return sImm32ImmSetCandidateWindowStub(aIMC, aForm);
+    }
+
+    if (!sCurrentPluginInstance ||
+        aForm->dwIndex != 0 ||
+        !(aForm->dwStyle & CFS_CANDIDATEPOS)) {
+        // Flash only uses CFS_CANDIDATEPOS with index == 0.
+        return FALSE;
+    }
+
+    sCurrentPluginInstance->SendSetCandidateWindow(
+        aForm->ptCurrentPos.x, aForm->ptCurrentPos.y);
+    return TRUE;
+}
+
+// static
+BOOL
+PluginInstanceChild::ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex,
+                                  DWORD aValue)
+{
+    if (aIMC != sHookIMC) {
+        return sImm32ImmNotifyIME(aIMC, aAction, aIndex, aValue);
+    }
+
+    // We only supports NI_COMPOSITIONSTR because Flash uses it only
+    if (!sCurrentPluginInstance ||
+        aAction != NI_COMPOSITIONSTR ||
+        (aIndex != CPS_COMPLETE && aIndex != CPS_CANCEL)) {
+        return FALSE;
+    }
+
+    sCurrentPluginInstance->SendRequestCommitOrCancel(aAction == CPS_COMPLETE);
+    return TRUE;
+}
+
+void
+PluginInstanceChild::InitImm32Hook()
+{
+    if (!(GetQuirks() & QUIRK_WINLESS_HOOK_IME)) {
+        return;
+    }
+
+    if (sImm32ImmGetContextStub) {
+        return;
+    }
+
+    // When using windowless plugin, IMM API won't work due ot OOP.
+
+    sImm32Intercept.Init("imm32.dll");
+    sImm32Intercept.AddHook(
+        "ImmGetContext",
+        reinterpret_cast<intptr_t>(ImmGetContextProc),
+        (void**)&sImm32ImmGetContextStub);
+    sImm32Intercept.AddHook(
+        "ImmReleaseContext",
+        reinterpret_cast<intptr_t>(ImmReleaseContextProc),
+        (void**)&sImm32ImmReleaseContextStub);
+    sImm32Intercept.AddHook(
+        "ImmGetCompositionStringW",
+        reinterpret_cast<intptr_t>(ImmGetCompositionStringProc),
+        (void**)&sImm32ImmGetCompositionStringStub);
+    sImm32Intercept.AddHook(
+        "ImmSetCandidateWindow",
+        reinterpret_cast<intptr_t>(ImmSetCandidateWindowProc),
+        (void**)&sImm32ImmSetCandidateWindowStub);
+    sImm32Intercept.AddHook(
+        "ImmNotifyIME",
+        reinterpret_cast<intptr_t>(ImmNotifyIME),
+        (void**)&sImm32ImmNotifyIME);
+}
+
 void
 PluginInstanceChild::DestroyWinlessPopupSurrogate()
 {
     if (mWinlessPopupSurrogateHWND)
         DestroyWindow(mWinlessPopupSurrogateHWND);
     mWinlessPopupSurrogateHWND = nullptr;
 }
 
@@ -2011,16 +2160,23 @@ PluginInstanceChild::WinlessHandleEvent(
            event.event == WM_RBUTTONUP)) {  // silverlight
       sWinlessPopupSurrogateHWND = mWinlessPopupSurrogateHWND;
       
       // A little trick scrounged from chromium's code - set the focus
       // to our surrogate parent so keyboard nav events go to the menu. 
       focusHwnd = SetFocus(mWinlessPopupSurrogateHWND);
     }
 
+    AutoRestore<PluginInstanceChild *> pluginInstance(sCurrentPluginInstance);
+    if (event.event == WM_IME_STARTCOMPOSITION ||
+        event.event == WM_IME_COMPOSITION ||
+        event.event == WM_KILLFOCUS) {
+      sCurrentPluginInstance = this;
+    }
+
     MessageLoop* loop = MessageLoop::current();
     AutoRestore<bool> modalLoop(loop->os_modal_loop());
 
     handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&event));
 
     sWinlessPopupSurrogateHWND = nullptr;
 
     if (IsWindow(focusHwnd)) {
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -300,16 +300,17 @@ private:
     void CreateWinlessPopupSurrogate();
     void DestroyWinlessPopupSurrogate();
     void InitPopupMenuHook();
     void SetupFlashMsgThrottle();
     void UnhookWinlessFlashThrottle();
     void HookSetWindowLongPtr();
     void SetUnityHooks();
     void ClearUnityHooks();
+    void InitImm32Hook();
     static inline bool SetWindowLongHookCheck(HWND hWnd,
                                                 int nIndex,
                                                 LONG_PTR newLong);
     void FlashThrottleMessage(HWND, UINT, WPARAM, LPARAM, bool);
     static LRESULT CALLBACK DummyWindowProc(HWND hWnd,
                                             UINT message,
                                             WPARAM wParam,
                                             LPARAM lParam);
@@ -345,16 +346,25 @@ private:
     static LONG WINAPI SetWindowLongAHook(HWND hWnd,
                                           int nIndex,
                                           LONG newLong);
     static LONG WINAPI SetWindowLongWHook(HWND hWnd,
                                           int nIndex,
                                           LONG newLong);
 #endif
 
+    static HIMC WINAPI ImmGetContextProc(HWND aWND);
+    static BOOL WINAPI ImmReleaseContextProc(HWND aWND, HIMC aIMC);
+    static LONG WINAPI ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex,
+                                                   LPVOID aBuf, DWORD aLen);
+    static BOOL WINAPI ImmSetCandidateWindowProc(HIMC hIMC,
+                                                 LPCANDIDATEFORM plCandidate);
+    static BOOL WINAPI ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex,
+                                    DWORD aValue);
+
     class FlashThrottleAsyncMsg : public ChildAsyncCall
     {
       public:
         FlashThrottleAsyncMsg();
         FlashThrottleAsyncMsg(PluginInstanceChild* aInst, 
                               HWND aWnd, UINT aMsg,
                               WPARAM aWParam, LPARAM aLParam,
                               bool isWindowed)
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -1563,16 +1563,26 @@ PluginInstanceParent::NPP_HandleEvent(vo
             }
             break;
 
             case WM_WINDOWPOSCHANGED:
             {
                 // We send this in nsPluginFrame just before painting
                 return SendWindowPosChanged(npremoteevent);
             }
+
+            case WM_IME_STARTCOMPOSITION:
+            case WM_IME_COMPOSITION:
+            case WM_IME_ENDCOMPOSITION:
+                if (!(mParent->GetQuirks() & QUIRK_WINLESS_HOOK_IME)) {
+                  // IME message will be posted on allowed plugins only such as
+                  // Flash.  Because if we cannot know that plugin can handle
+                  // IME correctly.
+                  return 0;
+                }
             break;
         }
     }
 #endif
 
 #if defined(MOZ_X11)
     switch (npevent->type) {
     case GraphicsExpose:
@@ -2363,16 +2373,60 @@ PluginInstanceParent::Cast(NPP aInstance
 
     if (aSurrogate) {
         *aSurrogate = resolver->GetAsyncSurrogate();
     }
 
     return instancePtr;
 }
 
+bool
+PluginInstanceParent::RecvGetCompositionString(const uint32_t& aIndex,
+                                               nsTArray<uint8_t>* aDist,
+                                               int32_t* aLength)
+{
+#if defined(OS_WIN)
+    nsPluginInstanceOwner* owner = GetOwner();
+    if (!owner) {
+        *aLength = IMM_ERROR_GENERAL;
+        return true;
+    }
+
+    if (!owner->GetCompositionString(aIndex, aDist, aLength)) {
+        *aLength = IMM_ERROR_NODATA;
+    }
+#endif
+    return true;
+}
+
+bool
+PluginInstanceParent::RecvSetCandidateWindow(const int32_t& aX,
+                                             const int32_t& aY)
+{
+#if defined(OS_WIN)
+    nsPluginInstanceOwner* owner = GetOwner();
+    if (owner) {
+        owner->SetCandidateWindow(aX, aY);
+    }
+#endif
+    return true;
+}
+
+bool
+PluginInstanceParent::RecvRequestCommitOrCancel(const bool& aCommitted)
+{
+#if defined(OS_WIN)
+    nsPluginInstanceOwner* owner = GetOwner();
+    if (owner) {
+        owner->RequestCommitOrCancel(aCommitted);
+    }
+#endif
+    return true;
+}
+
 void
 PluginInstanceParent::RecordDrawingModel()
 {
     int mode = -1;
     switch (mWindowType) {
     case NPWindowTypeWindow:
         // We use 0=windowed since there is no specific NPDrawingModel value.
         mode = 0;
--- a/dom/plugins/ipc/PluginInstanceParent.h
+++ b/dom/plugins/ipc/PluginInstanceParent.h
@@ -347,16 +347,26 @@ public:
 
     virtual PluginAsyncSurrogate* GetAsyncSurrogate() override;
 
     virtual PluginInstanceParent* GetInstance() override { return this; }
 
     static PluginInstanceParent* Cast(NPP instance,
                                       PluginAsyncSurrogate** aSurrogate = nullptr);
 
+    // for IME hook
+    virtual bool
+    RecvGetCompositionString(const uint32_t& aIndex,
+                             nsTArray<uint8_t>* aBuffer,
+                             int32_t* aLength) override;
+    virtual bool
+    RecvSetCandidateWindow(const int32_t& aX, const int32_t& aY) override;
+    virtual bool
+    RecvRequestCommitOrCancel(const bool& aCommitted) override;
+
 private:
     // Create an appropriate platform surface for a background of size
     // |aSize|.  Return true if successful.
     bool CreateBackground(const nsIntSize& aSize);
     void DestroyBackground();
     SurfaceDescriptor BackgroundDescriptor() /*const*/;
 
     typedef mozilla::layers::ImageContainer ImageContainer;
--- a/dom/plugins/ipc/PluginQuirks.cpp
+++ b/dom/plugins/ipc/PluginQuirks.cpp
@@ -29,16 +29,17 @@ int GetQuirksFromMimeTypeAndFilename(con
     if (specialType == nsPluginHost::eSpecialType_Flash) {
         quirks |= QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN;
 #ifdef OS_WIN
         quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
         quirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS;
         quirks |= QUIRK_FLASH_HOOK_SETLONGPTR;
         quirks |= QUIRK_FLASH_HOOK_GETWINDOWINFO;
         quirks |= QUIRK_FLASH_FIXUP_MOUSE_CAPTURE;
+        quirks |= QUIRK_WINLESS_HOOK_IME;
 #endif
     }
 
 #ifdef OS_WIN
     // QuickTime plugin usually loaded with audio/mpeg mimetype
     NS_NAMED_LITERAL_CSTRING(quicktime, "npqtplugin");
     if (FindInReadable(quicktime, aPluginFilename)) {
         quirks |= QUIRK_QUICKTIME_AVOID_SETWINDOW;
@@ -57,13 +58,19 @@ int GetQuirksFromMimeTypeAndFilename(con
 #endif
 
 #ifdef OS_WIN
     if (specialType == nsPluginHost::eSpecialType_Unity) {
         quirks |= QUIRK_UNITY_FIXUP_MOUSE_CAPTURE;
     }
 #endif
 
+#ifdef OS_WIN
+    if (specialType == nsPluginHost::eSpecialType_Test) {
+        quirks |= QUIRK_WINLESS_HOOK_IME;
+    }
+#endif
+
     return quirks;
 }
 
 } /* namespace plugins */
 } /* namespace mozilla */
--- a/dom/plugins/ipc/PluginQuirks.h
+++ b/dom/plugins/ipc/PluginQuirks.h
@@ -53,16 +53,18 @@ enum PluginQuirks {
   // outside of that call.  See bug 804606.
   QUIRK_FLASH_AVOID_CGMODE_CRASHES                = 1 << 10,
   // Work around a Flash bug where it fails to check the error code of a
   // NPN_GetValue(NPNVdocumentOrigin) call before trying to dereference
   // its char* output.
   QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN        = 1 << 11,
   // Win: Addresses a Unity bug with mouse capture.
   QUIRK_UNITY_FIXUP_MOUSE_CAPTURE                 = 1 << 12,
+  // Win: Hook IMM32 API to handle IME event on windowless plugin
+  QUIRK_WINLESS_HOOK_IME                          = 1 << 13,
 };
 
 int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
                                      const nsCString& aPluginFilename);
 
 } /* namespace plugins */
 } /* namespace mozilla */
 
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -1399,10 +1399,20 @@ PuppetWidget::GetCurrentWidgetListener()
 
   if (mAttachedWidgetListener->GetView()->IsPrimaryFramePaintSuppressed()) {
     return mPreviouslyAttachedWidgetListener;
   }
 
   return mAttachedWidgetListener;
 }
 
+void
+PuppetWidget::SetCandidateWindowForPlugin(int32_t aX, int32_t aY)
+{
+  if (!mTabChild) {
+    return;
+  }
+
+  mTabChild->SendSetCandidateWindowForPlugin(aX, aY);
+}
+
 } // namespace widget
 } // namespace mozilla
--- a/widget/PuppetWidget.h
+++ b/widget/PuppetWidget.h
@@ -249,16 +249,19 @@ public:
                                               nsIObserver* aObserver) override;
   virtual nsresult SynthesizeNativeTouchTap(ScreenIntPoint aPointerScreenPoint,
                                             bool aLongTap,
                                             nsIObserver* aObserver) override;
   virtual nsresult ClearNativeTouchSequence(nsIObserver* aObserver) override;
   virtual uint32_t GetMaxTouchPoints() const override;
 
   virtual void StartAsyncScrollbarDrag(const AsyncDragMetrics& aDragMetrics) override;
+
+  virtual void SetCandidateWindowForPlugin(int32_t aX, int32_t aY) override;
+
 protected:
   virtual nsresult NotifyIMEInternal(
                      const IMENotification& aIMENotification) override;
 
 private:
   nsresult Paint();
 
   void SetChild(PuppetWidget* aChild);
--- a/widget/TextRange.h
+++ b/widget/TextRange.h
@@ -261,13 +261,23 @@ public:
   {
     for (const TextRange& range : *this) {
       if (range.mRangeType == NS_TEXTRANGE_CARETPOSITION) {
         return true;
       }
     }
     return false;
   }
+
+  uint32_t GetCaretPosition() const
+  {
+    for (const TextRange& range : *this) {
+      if (range.mRangeType == NS_TEXTRANGE_CARETPOSITION) {
+        return range.mStartOffset;
+      }
+    }
+    return UINT32_MAX;
+  }
 };
 
 } // namespace mozilla
 
 #endif // mozilla_TextRage_h_
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -212,16 +212,19 @@ public:
   virtual nsresult        ForceUpdateNativeMenuAt(const nsAString& indexString) override { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              NotifyIME(const IMENotification& aIMENotification) override final;
   NS_IMETHOD              StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
                                          int32_t aPanelX, int32_t aPanelY,
                                          nsString& aCommitted) override
                           { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              SetPluginFocused(bool& aFocused) override
                           { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual void            SetCandidateWindowForPlugin(int32_t aX,
+                                                      int32_t aY) override
+                          { }
   NS_IMETHOD              AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) override { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD_(bool)       ExecuteNativeKeyBinding(
                             NativeKeyBindingsType aType,
                             const mozilla::WidgetKeyboardEvent& aEvent,
                             DoCommandCallback aCallback,
                             void* aCallbackData) override { return false; }
   virtual bool            ComputeShouldAccelerate();
   virtual nsIMEUpdatePreference GetIMEUpdatePreference() override { return nsIMEUpdatePreference(); }
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -128,18 +128,18 @@ typedef void* nsNativeWidget;
 // set/get nsPluginNativeWindowGtk, e10s specific
 #define NS_NATIVE_PLUGIN_OBJECT_PTR    104
 #endif
 #ifdef MOZ_WIDGET_ANDROID
 #define NS_NATIVE_NEW_EGL_SURFACE      100
 #endif
 
 #define NS_IWIDGET_IID \
-{ 0xaaa79c8d, 0xc99d, 0x4fe1, \
-  { 0xa5, 0x11, 0xd3, 0xeb, 0xb1, 0x61, 0x9e, 0x26 } }
+{ 0xa3db64d2, 0x5a73, 0x425f, \
+  { 0x9b, 0xb0, 0x57, 0x7f, 0xe5, 0x56, 0x43, 0x15 } }
 
 /*
  * Window shadow styles
  * Also used for the -moz-window-shadow CSS property
  */
 
 #define NS_STYLE_WINDOW_SHADOW_NONE             0
 #define NS_STYLE_WINDOW_SHADOW_DEFAULT          1
@@ -1794,16 +1794,21 @@ public:
     /*
      * Tell the plugin has focus.  It is unnecessary to use IPC
      */
     bool PluginHasFocus() 
     {
       return GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN;
     }
 
+    /**
+     * Set IME candidate window position by windowless plugin.
+     */
+    virtual void SetCandidateWindowForPlugin(int32_t aX, int32_t aY) = 0;
+
     /*
      * Notifies the input context changes.
      */
     NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
                                       const InputContextAction& aAction) = 0;
 
     /*
      * Get current input context.
--- a/widget/windows/IMMHandler.cpp
+++ b/widget/windows/IMMHandler.cpp
@@ -2744,16 +2744,24 @@ IMMHandler::OnKeyDownEvent(nsWindow* aWi
         CancelComposition(aWindow, false);
       }
       return false;
     default:
       return false;
   }
 }
 
+// static
+void
+IMMHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
+{
+  IMEContext context(aWindow);
+  ImmSetCandidateWindow(context.get(), aForm);
+}
+
 /******************************************************************************
  * IMMHandler::Selection
  ******************************************************************************/
 
 bool
 IMMHandler::Selection::IsValid() const
 {
   if (!mIsValid || NS_WARN_IF(mOffset == UINT32_MAX)) {
--- a/widget/windows/IMMHandler.h
+++ b/widget/windows/IMMHandler.h
@@ -145,16 +145,17 @@ public:
                                 bool aIsIMMActive);
 
   static nsIMEUpdatePreference GetIMEUpdatePreference();
 
   // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by
   // IME.  Otherwise, NS_OK.
   static nsresult OnMouseButtonEvent(nsWindow* aWindow,
                                      const IMENotification& aIMENotification);
+  static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
 
 protected:
   static void EnsureHandlerInstance();
 
   static bool IsComposingOnOurEditor();
   static bool IsComposingOnPlugin();
   static bool IsComposingWindow(nsWindow* aWindow);
 
--- a/widget/windows/WinIMEHandler.cpp
+++ b/widget/windows/WinIMEHandler.cpp
@@ -943,10 +943,21 @@ IMEHandler::GetOnScreenKeyboardWindow()
   const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
   HWND osk = ::FindWindowW(kOSKClassName, nullptr);
   if (::IsWindow(osk) && ::IsWindowEnabled(osk)) {
     return osk;
   }
   return nullptr;
 }
 
+// static
+void
+IMEHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
+{
+  if (!sPluginHasFocus) {
+    return;
+  }
+
+  IMMHandler::SetCandidateWindow(aWindow, aForm);
+}
+
 } // namespace widget
 } // namespace mozilla
--- a/widget/windows/WinIMEHandler.h
+++ b/widget/windows/WinIMEHandler.h
@@ -98,16 +98,21 @@ public:
    */
   static void AssociateIMEContext(nsWindow* aWindow, bool aEnable);
 
   /**
    * Called when the window is created.
    */
   static void InitInputContext(nsWindow* aWindow, InputContext& aInputContext);
 
+  /*
+   * For windowless plugin helper.
+   */
+  static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
+
 #ifdef DEBUG
   /**
    * Returns true when current keyboard layout has IME.  Otherwise, false.
    */
   static bool CurrentKeyboardLayoutHasIME();
 #endif // #ifdef DEBUG
 
 private:
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -7746,16 +7746,28 @@ nsWindow::ComputeShouldAccelerate()
   if (mTransparencyMode == eTransparencyTransparent ||
       (IsPopup() && gfxWindowsPlatform::GetPlatform()->IsWARP()))
   {
     return false;
   }
   return nsBaseWidget::ComputeShouldAccelerate();
 }
 
+void
+nsWindow::SetCandidateWindowForPlugin(int32_t aX, int32_t aY)
+{
+  CANDIDATEFORM form;
+  form.dwIndex = 0;
+  form.dwStyle = CFS_CANDIDATEPOS;
+  form.ptCurrentPos.x = aX;
+  form.ptCurrentPos.y = aY;
+
+  IMEHandler::SetCandidateWindow(this, &form);
+}
+
 /**************************************************************
  **************************************************************
  **
  ** BLOCK: ChildWindow impl.
  **
  ** Child window overrides.
  **
  **************************************************************
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -292,16 +292,19 @@ public:
 
   bool IsPopup();
   virtual bool ShouldUseOffMainThreadCompositing() override;
 
   bool CaptureWidgetOnScreen(RefPtr<mozilla::gfx::DrawTarget> aDT) override;
 
   const IMEContext& DefaultIMC() const { return mDefaultIMC; }
 
+  virtual void SetCandidateWindowForPlugin(int32_t aX,
+                                           int32_t aY) override;
+
 protected:
   virtual ~nsWindow();
 
   virtual void WindowUsesOMTC() override;
   virtual void RegisterTouchWindow() override;
 
   virtual nsresult NotifyIMEInternal(
                      const IMENotification& aIMENotification) override;