Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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
#include "TSFTextStoreBase.h"
#include "IMMHandler.h"
#include "TSFInputScope.h"
#include "TSFTextStore.h"
#include "TSFUtils.h"
#include "WinIMEHandler.h"
#include "WinMessages.h"
#include "WinUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPrefs_intl.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/ToString.h"
#include "nsWindow.h"
#include <comutil.h> // for _bstr_t
#include <oleauto.h> // for SysAllocString
#include <olectl.h>
// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
// big file.
// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
extern mozilla::LazyLogModule gIMELog; // defined in TSFUtils.cpp
namespace mozilla::widget {
/**
* TSF related code should log its behavior even on release build especially
* in the interface methods.
*
* In interface methods, use LogLevel::Info.
* In internal methods, use LogLevel::Debug for logging normal behavior.
* For logging error, use LogLevel::Error.
*
* When an instance method is called, start with following text:
* "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
* after that, start with:
* "0x%p TSFFoo::Bar("
* In an internal method, start with following text:
* "0x%p TSFFoo::Bar("
* When a static method is called, start with following text:
* "TSFFoo::Bar("
*/
/******************************************************************/
/* TSFTextStoreBase */
/******************************************************************/
bool TSFTextStoreBase::InitBase(nsWindow* aWidget,
const InputContext& aContext) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::InitBase(aWidget=0x%p, aContext=%s)", this,
aWidget, mozilla::ToString(aContext).c_str()));
if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InitBase() FAILED due to being "
"initialized with "
"destroyed widget",
this));
return false;
}
if (mDocumentMgr) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InitBase() FAILED due to already "
"initialized",
this));
return false;
}
mWidget = aWidget;
if (NS_WARN_IF(!mWidget)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InitBase() FAILED "
"due to aWidget is nullptr ",
this));
return false;
}
mDispatcher = mWidget->GetTextEventDispatcher();
if (NS_WARN_IF(!mDispatcher)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InitBase() FAILED "
"due to aWidget->GetTextEventDispatcher() failure",
this));
return false;
}
mInPrivateBrowsing = aContext.mInPrivateBrowsing;
SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputMode);
if (aContext.mURI) {
// We don't need the document URL if it fails, let's ignore the error.
nsAutoCString spec;
if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
CopyUTF8toUTF16(spec, mDocumentURL);
}
}
return true;
}
STDMETHODIMP TSFTextStoreBase::QueryInterface(REFIID riid, void** ppv) {
*ppv = nullptr;
if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
*ppv = static_cast<ITextStoreACP*>(this);
}
if (*ppv) {
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP TSFTextStoreBase::AdviseSink(REFIID riid, IUnknown* punk,
DWORD dwMask) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
"mSink=0x%p, mSinkMask=%s",
this, AutoRiidCString(riid).get(), punk,
AutoSinkMasksCString(dwMask).get(), mSink.get(),
AutoSinkMasksCString(mSinkMask).get()));
if (!punk) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::AdviseSink() FAILED due to the null punk",
this));
return E_UNEXPECTED;
}
if (IID_ITextStoreACPSink != riid) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::AdviseSink() FAILED due to "
"unsupported interface",
this));
return E_INVALIDARG; // means unsupported interface.
}
if (!mSink) {
// Install sink
punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
if (!mSink) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::AdviseSink() FAILED due to "
"punk not having the interface",
this));
return E_UNEXPECTED;
}
} else {
// If sink is already installed we check to see if they are the same
// Get IUnknown from both sides for comparison
RefPtr<IUnknown> comparison1, comparison2;
punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
if (comparison1 != comparison2) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::AdviseSink() FAILED due to "
"the sink being different from the stored sink",
this));
return CONNECT_E_ADVISELIMIT;
}
}
// Update mask either for a new sink or an existing sink
mSinkMask = dwMask;
return S_OK;
}
STDMETHODIMP TSFTextStoreBase::UnadviseSink(IUnknown* punk) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::UnadviseSink(punk=0x%p), mSink=0x%p", this,
punk, mSink.get()));
if (!punk) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::UnadviseSink() FAILED due to the null punk",
this));
return E_INVALIDARG;
}
if (!mSink) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::UnadviseSink() FAILED due to "
"any sink not stored",
this));
return CONNECT_E_NOCONNECTION;
}
// Get IUnknown from both sides for comparison
RefPtr<IUnknown> comparison1, comparison2;
punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
// Unadvise only if sinks are the same
if (comparison1 != comparison2) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::UnadviseSink() FAILED due to "
"the sink being different from the stored sink",
this));
return CONNECT_E_NOCONNECTION;
}
mSink = nullptr;
mSinkMask = 0;
return S_OK;
}
STDMETHODIMP TSFTextStoreBase::RequestLock(DWORD dwLockFlags,
HRESULT* phrSession) {
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
"mLock=%s, mDestroyed=%s",
this, AutoLockFlagsCString(dwLockFlags).get(), phrSession,
AutoLockFlagsCString(mLock).get(), TSFUtils::BoolToChar(mDestroyed)));
if (!mSink) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::RequestLock() FAILED due to "
"any sink not stored",
this));
return E_FAIL;
}
if (mDestroyed) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::RequestLock() FAILED due to being destroyed",
this));
return E_FAIL;
}
if (!phrSession) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::RequestLock() FAILED due to "
"null phrSession",
this));
return E_INVALIDARG;
}
if (!mLock) {
// put on lock
mLock = dwLockFlags & (~TS_LF_SYNC);
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
this, AutoLockFlagsCString(mLock).get()));
// Don't release this instance during this lock because this is called by
// TSF but they don't grab us during this call.
const RefPtr<TSFTextStoreBase> kungFuDeathGrip(this);
const RefPtr<ITextStoreACPSink> sink = mSink;
*phrSession = sink->OnLockGranted(mLock);
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
this, AutoLockFlagsCString(mLock).get()));
DidLockGranted();
while (mLockQueued) {
mLock = mLockQueued;
mLockQueued = 0;
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
">>>>>",
this, AutoLockFlagsCString(mLock).get()));
sink->OnLockGranted(mLock);
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
"<<<<<",
this, AutoLockFlagsCString(mLock).get()));
DidLockGranted();
}
// The document is now completely unlocked.
mLock = 0;
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::RequestLock() succeeded: *phrSession=%s",
this, TSFUtils::HRESULTToChar(*phrSession)));
return S_OK;
}
// only time when reentrant lock is allowed is when caller holds a
// read-only lock and is requesting an async write lock
if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
!(dwLockFlags & TS_LF_SYNC)) {
*phrSession = TS_S_ASYNC;
mLockQueued = dwLockFlags & (~TS_LF_SYNC);
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::RequestLock() stores the request in the "
"queue, *phrSession=TS_S_ASYNC",
this));
return S_OK;
}
// no more locks allowed
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::RequestLock() didn't allow to lock, "
"*phrSession=TS_E_SYNCHRONOUS",
this));
*phrSession = TS_E_SYNCHRONOUS;
return E_FAIL;
}
void TSFTextStoreBase::DispatchEvent(WidgetGUIEvent& aEvent) {
if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
return;
}
// If the event isn't a query content event, the event may be handled
// asynchronously. So, we should put off to answer from GetTextExt() etc.
if (!aEvent.AsQueryContentEvent()) {
mDeferNotifyingTSFUntilNextUpdate = true;
}
mWidget->DispatchWindowEvent(aEvent);
}
STDMETHODIMP TSFTextStoreBase::GetStatus(TS_STATUS* pdcs) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetStatus(pdcs=0x%p)", this, pdcs));
if (!pdcs) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetStatus() FAILED due to null pdcs", this));
return E_INVALIDARG;
}
// We manage on-screen keyboard by own.
pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
// we use a "flat" text model for TSF support so no hidden text
pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
return S_OK;
}
STDMETHODIMP TSFTextStoreBase::QueryInsert(LONG acpTestStart, LONG acpTestEnd,
ULONG cch, LONG* pacpResultStart,
LONG* pacpResultEnd) {
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::QueryInsert(acpTestStart=%ld, "
"acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
this, acpTestStart, acpTestEnd, cch, pacpResultStart, pacpResultEnd));
if (!pacpResultStart || !pacpResultEnd) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::QueryInsert() FAILED due to "
"the null argument",
this));
return E_INVALIDARG;
}
if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::QueryInsert() FAILED due to "
"wrong argument",
this));
return E_INVALIDARG;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::GetSelection(ULONG ulIndex, ULONG ulCount,
TS_SELECTION_ACP* pSelection,
ULONG* pcFetched) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetSelection(ulIndex=%lu, ulCount=%lu, "
"pSelection=0x%p, pcFetched=0x%p)",
this, ulIndex, ulCount, pSelection, pcFetched));
if (!IsReadLocked()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetSelection() FAILED due to not locked",
this));
return TS_E_NOLOCK;
}
if (!ulCount || !pSelection || !pcFetched) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetSelection() FAILED due to null argument",
this));
return E_INVALIDARG;
}
*pcFetched = 0;
if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetSelection() FAILED due to "
"unsupported selection",
this));
return TS_E_NOSELECTION;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::SetSelection(
ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::SetSelection(ulCount=%lu, pSelection=%s })",
this, ulCount,
pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr"));
if (!IsReadWriteLocked()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::SetSelection() FAILED due to "
"not locked (read-write)",
this));
return TS_E_NOLOCK;
}
if (ulCount != 1) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::SetSelection() FAILED due to "
"trying setting multiple selection",
this));
return E_INVALIDARG;
}
if (!pSelection) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::SetSelection() FAILED due to "
"null argument",
this));
return E_INVALIDARG;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::GetText(LONG acpStart, LONG acpEnd,
WCHAR* pchPlain, ULONG cchPlainReq,
ULONG* pcchPlainOut,
TS_RUNINFO* prgRunInfo,
ULONG ulRunInfoReq, ULONG* pulRunInfoOut,
LONG* pacpNext) {
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetText(acpStart=%ld, acpEnd=%ld, "
"pchPlain=0x%p, cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, "
"ulRunInfoReq=%lu, pulRunInfoOut=0x%p, pacpNext=0x%p)",
this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
ulRunInfoReq, pulRunInfoOut, pacpNext));
if (!IsReadLocked()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetText() FAILED due to "
"not locked (read)",
this));
return TS_E_NOLOCK;
}
if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
!cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetText() FAILED due to "
"invalid argument",
this));
return E_INVALIDARG;
}
if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetText() FAILED due to "
"invalid position",
this));
return TS_E_INVALIDPOS;
}
// Making sure to null-terminate string just to be on the safe side
*pcchPlainOut = 0;
if (pchPlain && cchPlainReq) {
*pchPlain = 0;
}
if (pulRunInfoOut) {
*pulRunInfoOut = 0;
}
if (pacpNext) {
*pacpNext = acpStart;
}
if (prgRunInfo && ulRunInfoReq) {
prgRunInfo->uCount = 0;
prgRunInfo->type = TS_RT_PLAIN;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::SetText(DWORD dwFlags, LONG acpStart,
LONG acpEnd, const WCHAR* pchText,
ULONG cch, TS_TEXTCHANGE* pChange) {
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
"pchText=0x%p \"%s\", cch=%lu, pChange=0x%p)",
this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
acpStart, acpEnd, pchText,
pchText && cch ? AutoEscapedUTF8String(pchText, cch).get() : "", cch,
pChange));
// Per SDK documentation, and since we don't have better
// ways to do this, this method acts as a helper to
// call SetSelection followed by InsertTextAtSelection
if (!IsReadWriteLocked()) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::SetText() FAILED due to not locked (read)",
this));
return TS_E_NOLOCK;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::GetFormattedText(LONG acpStart, LONG acpEnd,
IDataObject** ppDataObject) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetFormattedText() called "
"but not supported (E_NOTIMPL)",
this));
// no support for formatted text
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::GetEmbedded(LONG acpPos, REFGUID rguidService,
REFIID riid, IUnknown** ppunk) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetEmbedded() called "
"but not supported (E_NOTIMPL)",
this));
// embedded objects are not supported
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::QueryInsertEmbedded(const GUID* pguidService,
const FORMATETC* pFormatEtc,
BOOL* pfInsertable) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::QueryInsertEmbedded() called "
"but not supported, *pfInsertable=FALSE (S_OK)",
this));
// embedded objects are not supported
*pfInsertable = FALSE;
return S_OK;
}
STDMETHODIMP TSFTextStoreBase::InsertEmbedded(DWORD dwFlags, LONG acpStart,
LONG acpEnd,
IDataObject* pDataObject,
TS_TEXTCHANGE* pChange) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::InsertEmbedded() called "
"but not supported (E_NOTIMPL)",
this));
// embedded objects are not supported
return E_NOTIMPL;
}
void TSFTextStoreBase::SetInputScope(const nsString& aHTMLInputType,
const nsString& aHTMLInputMode) {
mInputScopes.Clear();
// IME may refer only first input scope, but we will append inputmode's
// input scopes too like Chrome since IME may refer it.
IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
IMEHandler::AppendInputScopeFromInputMode(aHTMLInputMode, mInputScopes);
if (mInPrivateBrowsing) {
mInputScopes.AppendElement(IS_PRIVATE);
}
}
STDMETHODIMP TSFTextStoreBase::RequestAttrsTransitioningAtPosition(
LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID* paFilterAttr,
DWORD dwFlags) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::RequestAttrsTransitioningAtPosition("
"acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
"(S_OK)",
this, acpPos, cFilterAttrs, AutoFindFlagsCString(dwFlags).get()));
// no per character attributes defined
return S_OK;
}
STDMETHODIMP TSFTextStoreBase::FindNextAttrTransition(
LONG acpStart, LONG acpHalt, ULONG cFilterAttrs,
const TS_ATTRID* paFilterAttrs, DWORD dwFlags, LONG* pacpNext,
BOOL* pfFound, LONG* plFoundOffset) {
if (!pacpNext || !pfFound || !plFoundOffset) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" 0x%p TSFTextStoreBase::FindNextAttrTransition() FAILED due to "
"null argument",
this));
return E_INVALIDARG;
}
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::FindNextAttrTransition() called "
"but not supported (S_OK)",
this));
// no per character attributes defined
*pacpNext = *plFoundOffset = acpHalt;
*pfFound = FALSE;
return S_OK;
}
// To test the document URL result, define this to out put it to the stdout
// #define DEBUG_PRINT_DOCUMENT_URL
BSTR TSFTextStoreBase::GetExposingURL() const {
const bool allowed =
StaticPrefs::intl_tsf_expose_url_allowed() &&
(!mInPrivateBrowsing ||
StaticPrefs::intl_tsf_expose_url_in_private_browsing_allowed());
if (!allowed || mDocumentURL.IsEmpty()) {
BSTR emptyString = ::SysAllocString(L"");
MOZ_ASSERT(
emptyString,
"We need to return valid BSTR pointer to notify TSF of supporting it "
"with a pointer to empty string");
return emptyString;
}
return ::SysAllocString(mDocumentURL.get());
}
void TSFTextStoreBase::PrintExposingURL(const char* aPrefix) const {
BSTR exposingURL = GetExposingURL();
printf("%s: DocumentURL=\"%s\"\n", aPrefix,
NS_ConvertUTF16toUTF8(static_cast<char16ptr_t>(_bstr_t(exposingURL)))
.get());
::SysFreeString(exposingURL);
}
STDMETHODIMP TSFTextStoreBase::GetEndACP(LONG* pacp) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetEndACP(pacp=0x%p)", this, pacp));
if (!IsReadLocked()) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetEndACP() FAILED due to not locked (read)",
this));
return TS_E_NOLOCK;
}
if (!pacp) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetEndACP() FAILED due to null argument",
this));
return E_INVALIDARG;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::GetActiveView(TsViewCookie* pvcView) {
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetActiveView(pvcView=0x%p)", this, pvcView));
if (!pvcView) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetActiveView() FAILED due to "
"null argument",
this));
return E_INVALIDARG;
}
*pvcView = TSFUtils::sDefaultView;
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetActiveView() succeeded: *pvcView=%ld",
this, *pvcView));
return S_OK;
}
STDMETHODIMP TSFTextStoreBase::GetACPFromPoint(TsViewCookie vcView,
const POINT* pt, DWORD dwFlags,
LONG* pacp) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetACPFromPoint(pvcView=%ld, pt=%p (x=%ld, "
"y=%ld), dwFlags=%s, pacp=%p, mDeferNotifyingTSFUntilNextUpdate=%s, "
"mWaitingQueryLayout=%s",
this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
AutoACPFromPointFlagsCString(dwFlags).get(), pacp,
TSFUtils::BoolToChar(mDeferNotifyingTSFUntilNextUpdate),
TSFUtils::BoolToChar(mWaitingQueryLayout)));
if (!IsReadLocked()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetACPFromPoint() FAILED due to not "
"locked (read)",
this));
return TS_E_NOLOCK;
}
if (vcView != TSFUtils::sDefaultView) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetACPFromPoint() FAILED due to called "
"with invalid view",
this));
return E_INVALIDARG;
}
if (!pt) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetACPFromPoint() FAILED due to null pt",
this));
return E_INVALIDARG;
}
if (!pacp) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetACPFromPoint() FAILED due to null pacp",
this));
return E_INVALIDARG;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::GetTextExt(TsViewCookie vcView, LONG acpStart,
LONG acpEnd, RECT* prc,
BOOL* pfClipped) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetTextExt(vcView=%ld, "
"acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
"IsHandlingCompositionInParent()=%s, "
"IsHandlingCompositionInContent()=%s,"
"mDeferNotifyingTSFUntilNextUpdate=%s, mWaitingQueryLayout=%s, "
"IMEHandler::IsA11yHandlingNativeCaret()=%s",
this, vcView, acpStart, acpEnd, prc, pfClipped,
TSFUtils::BoolToChar(IsHandlingCompositionInParent()),
TSFUtils::BoolToChar(IsHandlingCompositionInContent()),
TSFUtils::BoolToChar(mDeferNotifyingTSFUntilNextUpdate),
TSFUtils::BoolToChar(mWaitingQueryLayout),
TSFUtils::BoolToChar(IMEHandler::IsA11yHandlingNativeCaret())));
if (!IsReadLocked()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetTextExt() FAILED due to not locked "
"(read)",
this));
return TS_E_NOLOCK;
}
if (vcView != TSFUtils::sDefaultView) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetTextExt() FAILED due to called with "
"invalid view",
this));
return E_INVALIDARG;
}
if (!prc || !pfClipped) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetTextExt() FAILED due to null argument",
this));
return E_INVALIDARG;
}
// According to MSDN, ITextStoreACP::GetTextExt() should return
// TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
// > TS_E_INVALIDARG: The specified start and end character positions are
// > equal.
// However, some TIPs (including Microsoft's Chinese TIPs!) call this with
// collapsed range and if we return TS_E_INVALIDARG, they stops showing their
// owning window or shows it but odd position. So, we should just return
// error only when acpStart and/or acpEnd are really odd.
if (acpStart < 0 || acpEnd < acpStart) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetTextExt() FAILED due to invalid position",
this));
return TS_E_INVALIDPOS;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::GetScreenExt(TsViewCookie vcView, RECT* prc) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetScreenExt(vcView=%ld, prc=0x%p)", this,
vcView, prc));
if (vcView != TSFUtils::sDefaultView) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetScreenExt() FAILED due to called "
"with invalid view",
this));
return E_INVALIDARG;
}
if (!prc) {
MOZ_LOG(
gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetScreenExt() FAILED due to null argument",
this));
return E_INVALIDARG;
}
if (mDestroyed) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetScreenExt() returns empty rect "
"due to already destroyed",
this));
prc->left = prc->top = prc->right = prc->bottom = 0;
return S_OK;
}
if (!GetScreenExtInternal(*prc)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetScreenExt() FAILED due to "
"GetScreenExtInternal() failure",
this));
return E_FAIL;
}
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetScreenExt() succeeded: "
"*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
this, prc->left, prc->top, prc->right, prc->bottom));
return S_OK;
}
bool TSFTextStoreBase::GetScreenExtInternal(RECT& aScreenExt) {
MOZ_LOG(gIMELog, LogLevel::Debug,
("0x%p TSFTextStoreBase::GetScreenExtInternal()", this));
MOZ_ASSERT(!mDestroyed);
// use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, mWidget);
mWidget->InitEvent(queryEditorRectEvent);
DispatchEvent(queryEditorRectEvent);
if (queryEditorRectEvent.Failed()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetScreenExtInternal() FAILED due to "
"eQueryEditorRect failure",
this));
return false;
}
nsWindow* refWindow =
static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget
? queryEditorRectEvent.mReply->mFocusedWidget
: static_cast<nsIWidget*>(mWidget.get()));
// Result rect is in top level widget coordinates
refWindow = refWindow->GetTopLevelWindow(false);
if (!refWindow) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetScreenExtInternal() FAILED due to "
"no top level window",
this));
return false;
}
LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
boundRect.MoveTo(0, 0);
// Clip frame rect to window rect
boundRect.IntersectRect(queryEditorRectEvent.mReply->mRect, boundRect);
if (!boundRect.IsEmpty()) {
boundRect.MoveBy(refWindow->WidgetToScreenOffset());
::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
boundRect.YMost());
} else {
::SetRectEmpty(&aScreenExt);
}
MOZ_LOG(gIMELog, LogLevel::Debug,
("0x%p TSFTextStoreBase::GetScreenExtInternal() succeeded: "
"aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
aScreenExt.bottom));
return true;
}
STDMETHODIMP TSFTextStoreBase::GetWnd(TsViewCookie vcView, HWND* phwnd) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetWnd(vcView=%ld, phwnd=0x%p), "
"mWidget=0x%p",
this, vcView, phwnd, mWidget.get()));
if (vcView != TSFUtils::sDefaultView) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetWnd() FAILED due to "
"called with invalid view",
this));
return E_INVALIDARG;
}
if (!phwnd) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::GetScreenExt() FAILED due to "
"null argument",
this));
return E_INVALIDARG;
}
*phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::GetWnd() succeeded: *phwnd=0x%p", this,
static_cast<void*>(*phwnd)));
return S_OK;
}
STDMETHODIMP TSFTextStoreBase::InsertTextAtSelection(DWORD dwFlags,
const WCHAR* pchText,
ULONG cch, LONG* pacpStart,
LONG* pacpEnd,
TS_TEXTCHANGE* pChange) {
MOZ_LOG(
gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::InsertTextAtSelection(dwFlags=%s, "
"pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
"pChange=0x%p)",
this,
dwFlags == 0 ? "0"
: dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY"
: dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY"
: "Unknown",
pchText, pchText && cch ? AutoEscapedUTF8String(pchText, cch).get() : "",
cch, pacpStart, pacpEnd, pChange));
if (cch && !pchText) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InsertTextAtSelection() FAILED due to "
"null pchText",
this));
return E_INVALIDARG;
}
if (TS_IAS_QUERYONLY == dwFlags) {
if (!IsReadLocked()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InsertTextAtSelection() FAILED due to "
"not locked (read)",
this));
return TS_E_NOLOCK;
}
if (!pacpStart || !pacpEnd) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InsertTextAtSelection() FAILED due to "
"null argument",
this));
return E_INVALIDARG;
}
return E_NOTIMPL;
}
if (!IsReadWriteLocked()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InsertTextAtSelection() FAILED due to "
"not locked (read-write)",
this));
return TS_E_NOLOCK;
}
if (!pChange) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InsertTextAtSelection() FAILED due to "
"null pChange",
this));
return E_INVALIDARG;
}
if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::InsertTextAtSelection() FAILED due to "
"null argument",
this));
return E_INVALIDARG;
}
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStoreBase::InsertEmbeddedAtSelection(
DWORD dwFlags, IDataObject* pDataObject, LONG* pacpStart, LONG* pacpEnd,
TS_TEXTCHANGE* pChange) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::InsertEmbeddedAtSelection() called "
"but not supported (E_NOTIMPL)",
this));
// embedded objects are not supported
return E_NOTIMPL;
}
HRESULT TSFTextStoreBase::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
const TS_ATTRID* aFilterAttrs,
int32_t aNumOfSupportedAttrs) {
MOZ_ASSERT(aNumOfSupportedAttrs == TSFUtils::NUM_OF_SUPPORTED_ATTRS ||
aNumOfSupportedAttrs ==
TSFUtils::NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE);
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::HandleRequestAttrs(aFlags=%s, "
"aFilterCount=%lu, aNumOfSupportedAttrs=%d)",
this, AutoFindFlagsCString(aFlags).get(), aFilterCount,
aNumOfSupportedAttrs));
// This is a little weird! RequestSupportedAttrs gives us advanced notice
// of a support query via RetrieveRequestedAttrs for a specific attribute.
// RetrieveRequestedAttrs needs to return valid data for all attributes we
// support, but the text service will only want the input scope object
// returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
// TS_ATTR_FIND_WANT_VALUE.
for (const int32_t i : IntegerRange(aNumOfSupportedAttrs)) {
mRequestedAttrs[i] = false;
}
mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
for (uint32_t i : IntegerRange(aFilterCount)) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFEmptyTextStore::HandleRequestAttrs(), "
"requested attr=%s",
this, AutoGuidCString(aFilterAttrs[i]).get()));
TSFUtils::AttrIndex index =
TSFUtils::GetRequestedAttrIndex(aFilterAttrs[i]);
if (index != TSFUtils::AttrIndex::NotSupported) {
mRequestedAttrs[index] = true;
}
}
return S_OK;
}
// To test the document URL result, define this to out put it to the stdout
// #define DEBUG_PRINT_DOCUMENT_URL
HRESULT TSFTextStoreBase::RetrieveRequestedAttrsInternal(
ULONG ulCount, TS_ATTRVAL* paAttrVals, ULONG* pcFetched,
int32_t aNumOfSupportedAttrs) {
MOZ_ASSERT(aNumOfSupportedAttrs == TSFUtils::NUM_OF_SUPPORTED_ATTRS ||
aNumOfSupportedAttrs ==
TSFUtils::NUM_OF_SUPPORTED_ATTRS_IN_EMPTY_TEXT_STORE);
if (!pcFetched || !paAttrVals) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::RetrieveRequestedAttrs() FAILED due to "
"null argument",
this));
return E_INVALIDARG;
}
const ULONG expectedCount = [&]() {
ULONG count = 0;
for (int32_t i : IntegerRange(aNumOfSupportedAttrs)) {
if (mRequestedAttrs[i]) {
count++;
}
}
return count;
}();
if (ulCount < expectedCount) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p TSFTextStoreBase::RetrieveRequestedAttrs() FAILED due to "
"not enough count ulCount=%lu, expectedCount=%lu",
this, ulCount, expectedCount));
return E_INVALIDARG;
}
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::RetrieveRequestedAttrs() called "
"ulCount=%lu, mRequestedAttrValues=%s",
this, ulCount, TSFUtils::BoolToChar(mRequestedAttrValues)));
#ifdef DEBUG_PRINT_DOCUMENT_URL
PrintExposingURL("TSFTextStoreBase::RetrieveRequestedAttrs");
#endif // #ifdef DEBUG_PRINT_DOCUMENT_URL
int32_t count = 0;
for (int32_t i = 0; i < TSFUtils::NUM_OF_SUPPORTED_ATTRS; i++) {
if (!mRequestedAttrs[i]) {
continue;
}
mRequestedAttrs[i] = false;
TS_ATTRID attrID = TSFUtils::GetAttrID(static_cast<TSFUtils::AttrIndex>(i));
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p TSFTextStoreBase::RetrieveRequestedAttrs() for %s", this,
AutoGuidCString(attrID).get()));
paAttrVals[count].idAttr = attrID;
paAttrVals[count].dwOverlapId = 0;
if (!mRequestedAttrValues) {
paAttrVals[count].varValue.vt = VT_EMPTY;
} else {
switch (i) {
case TSFUtils::AttrIndex::InputScope: {
paAttrVals[count].varValue.vt = VT_UNKNOWN;
RefPtr<IUnknown> inputScope = new TSFInputScope(mInputScopes);
paAttrVals[count].varValue.punkVal = inputScope.forget().take();
break;
}
case TSFUtils::AttrIndex::DocumentURL: {
paAttrVals[count].varValue.vt = VT_BSTR;
paAttrVals[count].varValue.bstrVal = GetExposingURL();
break;
}
case TSFUtils::AttrIndex::TextVerticalWriting: {
const Maybe<WritingMode> writingMode = GetWritingMode();
paAttrVals[count].varValue.vt = VT_BOOL;
paAttrVals[count].varValue.boolVal =
writingMode.isSome() && writingMode->IsVertical() ? VARIANT_TRUE
: VARIANT_FALSE;
break;
}
case TSFUtils::AttrIndex::TextOrientation: {
const Maybe<WritingMode> writingMode = GetWritingMode();
paAttrVals[count].varValue.vt = VT_I4;
paAttrVals[count].varValue.lVal =
writingMode.isSome() && writingMode->IsVertical() ? 2700 : 0;
break;
}
default:
MOZ_CRASH("Invalid index? Or not implemented yet?");
break;
}
}
count++;
}
mRequestedAttrValues = false;
if (count) {
*pcFetched = count;
return S_OK;
}
paAttrVals->dwOverlapId = 0;
paAttrVals->varValue.vt = VT_EMPTY;
*pcFetched = 0;
return S_OK;
}
#undef DEBUG_PRINT_DOCUMENT_URL
// static
void TSFTextStoreBase::SetInputContext(nsWindow* aWindow,
const InputContext& aContext,
const InputContextAction& aAction) {
MOZ_LOG(gIMELog, LogLevel::Info,
("TSFTextStoreBase::OnSetInputContext(aWidget=%p, "
"aContext=%s, aAction.mFocusChange=%s), "
"CurrentTextStore(0x%p)={ mWidget=0x%p, mContext=0x%p }",
aWindow, mozilla::ToString(aContext).c_str(),
mozilla::ToString(aAction.mFocusChange).c_str(),
TSFUtils::GetCurrentTextStore(),
TSFUtils::GetCurrentTextStore()
? TSFUtils::GetCurrentTextStore()->GetWindow()
: nullptr,
TSFUtils::GetCurrentTextStore()
? TSFUtils::GetCurrentTextStore()->GetContext()
: nullptr));
const bool actuallyEditable =
aContext.mIMEState.IsEditable() && !aWindow->Destroyed();
switch (aAction.mFocusChange) {
case InputContextAction::WIDGET_CREATED:
// If this is called when the widget is created, there is nothing to do.
return;
case InputContextAction::FOCUS_NOT_CHANGED:
case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
// In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
// we need to reset text store for new state right now.
break;
default: {
const RefPtr<TSFTextStoreBase> textStore =
TSFUtils::GetCurrentTextStore();
if (!textStore) {
break;
}
if (NS_SUCCEEDED(
textStore->UpdateDocumentURLAndBrowsingMode(aWindow, aContext))) {
return;
}
}
}
const bool alreadyEditable = TSFUtils::GetCurrentTextStore() &&
TSFUtils::GetCurrentTextStore()->IsEditable() &&
TSFUtils::GetCurrentTextStore()->MaybeHasFocus();
if (alreadyEditable == actuallyEditable) {
if (const RefPtr<TSFTextStoreBase> textStore =
TSFUtils::GetCurrentTextStore()) {
textStore->UpdateDocumentURLAndBrowsingMode(aWindow, aContext);
}
return;
}
// If focus isn't actually changed but the enabled state is changed, we beed
// to emulate the focus move.
if (!alreadyEditable && !IMEHandler::GetFocusedWindow()) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" TSFTextStoreBase::SetInputContent() gets called to enable IME, "
"but IMEHandler has not received focus notification"));
if (const RefPtr<TSFTextStoreBase> textStore =
TSFUtils::GetCurrentTextStore()) {
textStore->UpdateDocumentURLAndBrowsingMode(aWindow, aContext);
}
return;
}
TSFUtils::OnFocusChange(
actuallyEditable ? TSFUtils::GotFocus::Yes : TSFUtils::GotFocus::No,
aWindow, aContext);
}
nsresult TSFTextStoreBase::UpdateDocumentURLAndBrowsingMode(
nsWindow* aWindow, const InputContext& aContext) {
MOZ_ASSERT(aWindow);
MOZ_LOG(gIMELog, LogLevel::Debug,
("0x%p TSFTextStoreBase::UpdateDocumentURLAndBrowsingMode(aWindow=%p "
"(Destroyed()=%s), aContext=%s), "
"mIsEditable=%s",
this, aWindow, aWindow->Destroyed() ? "true" : "false",
mozilla::ToString(aContext).c_str(),
mozilla::ToString(mIsEditable).c_str()));
const bool isEditable =
aContext.mIMEState.IsEditable() && !aWindow->Destroyed();
// If IME enabled state is changed, we need to recreate proper
// TSFTextStoreBase instance.
if (isEditable != IsEditable()) {
return NS_ERROR_FAILURE;
}
// If the editable state is not changed, we can just update the input
// scopes and the document URL.
nsAutoString oldURL(mDocumentURL);
CopyableAutoTArray<InputScope, 5> oldInputScopes(mInputScopes);
mInPrivateBrowsing = aContext.mInPrivateBrowsing;
SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputMode);
if (aContext.mURI) {
nsAutoCString spec;
if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
CopyUTF8toUTF16(spec, mDocumentURL);
} else {
mDocumentURL.Truncate();
}
} else {
mDocumentURL.Truncate();
}
// Notify TSF of the URL and InputScope changes.
const auto& changedThings = [&]() -> AttrIndices {
const bool URLChanged = mDocumentURL != oldURL;
const bool inputScopeChanged = mInputScopes != oldInputScopes;
if (URLChanged && inputScopeChanged) {
return URLAndInputScopeChanged;
}
if (URLChanged) {
return OnlyURLChanged;
}
return inputScopeChanged ? OnlyURLChanged : NothingChanged;
}();
if (changedThings != NothingChanged) {
NotifyTSFOfInputContextChange(changedThings);
}
return NS_OK;
}
void TSFTextStoreBase::NotifyTSFOfInputContextChange(AttrIndices aAttrIndices) {
MOZ_LOG(
gIMELog, LogLevel::Debug,
("0x%p TSFTextStoreBase::NotifyTSFOfInputContextChange(), mSink=0x%p, "
"DocumentURL is changed=%s, InputScope is changed=%s",
this, mSink.get(),
aAttrIndices.contains(TSFUtils::AttrIndex::DocumentURL) ? "Yes" : "No",
aAttrIndices.contains(TSFUtils::AttrIndex::InputScope) ? "Yes" : "No"));
if (!mSink) {
return;
}
AutoTArray<TS_ATTRID, 2> attrIDs;
if (aAttrIndices.contains(TSFUtils::AttrIndex::DocumentURL)) {
attrIDs.AppendElement(
TSFUtils::GetAttrID(TSFUtils::AttrIndex::DocumentURL));
}
if (aAttrIndices.contains(TSFUtils::AttrIndex::InputScope)) {
attrIDs.AppendElement(TSFUtils::GetAttrID(TSFUtils::AttrIndex::InputScope));
}
RefPtr<ITextStoreACPSink> sink(mSink);
sink->OnAttrsChange(0, 0, attrIDs.Length(), attrIDs.Elements());
}
} // namespace mozilla::widget