diff -r 000000000000 -r 6474c204b198 widget/tests/TestWinTSF.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widget/tests/TestWinTSF.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,3194 @@ +/* -*- 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +/* This tests Mozilla's Text Services Framework implementation (bug #88831) + * + * The Mozilla implementation interacts with the TSF system through a + * system-provided COM interface, ITfThreadMgr. This tests works by swapping + * the system version of the interface with a custom version implemented in + * here. This way the Mozilla implementation thinks it's interacting with the + * system but in fact is interacting with this test program. This allows the + * test program to access and test every aspect of the Mozilla implementation. + */ + +#include +#include +#include +#include + +#include "TestHarness.h" +#include + +#define WM_USER_TSF_TEXTCHANGE (WM_USER + 0x100) + +#ifndef MOZILLA_INTERNAL_API +// some of the includes make use of internal string types +#define nsAString_h___ +#define nsString_h___ +#define nsStringFwd_h___ +#define nsReadableUtils_h___ +class nsACString; +class nsAString; +class nsAFlatString; +class nsAFlatCString; +class nsAdoptingString; +class nsAdoptingCString; +class nsXPIDLString; +template class nsReadingIterator; +#endif + +#include "nscore.h" +#include "nsWeakReference.h" +#include "nsIAppShell.h" +#include "nsWidgetsCID.h" +#include "nsIAppShellService.h" +#include "nsAppShellCID.h" +#include "nsNetUtil.h" +#include "nsIWebBrowserChrome.h" +#include "nsIXULWindow.h" +#include "nsIBaseWindow.h" +#include "nsIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIWidget.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIFrame.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDOMHTMLDocument.h" +#include "mozilla/dom/HTMLBodyElement.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsIDOMElement.h" +#include "nsISelectionController.h" +#include "nsViewManager.h" +#include "nsTArray.h" + +#ifndef MOZILLA_INTERNAL_API +#undef nsString_h___ +#undef nsAString_h___ +#undef nsReadableUtils_h___ +#endif + +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +class TSFMgrImpl; +class TSFDocumentMgrImpl; +class TSFContextImpl; +class TSFRangeImpl; +class TSFEnumRangeImpl; +class TSFAttrPropImpl; + +class TestApp : public nsIWebProgressListener, public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESSLISTENER + + TestApp() : mFailed(false) {} + ~TestApp() {} + + nsresult Run(void); + bool CheckFailed(void); + + typedef bool (TestApp::*test_type)(void); + +protected: + nsresult Init(void); + nsresult Term(void); + bool RunTest(test_type aTest, bool aLock = true); + + bool TestFocus(void); + bool TestClustering(void); + bool TestSelection(void); + bool TestText(void); + bool TestExtents(void); + bool TestComposition(void); + bool TestNotification(void); + bool TestEditMessages(void); + bool TestScrollMessages(void); + + bool TestSelectionInternal(char* aTestName, + LONG aStart, + LONG aEnd, + TsActiveSelEnd aSelEnd); + bool TestCompositionSelectionAndText(char* aTestName, + LONG aExpectedSelStart, + LONG aExpectedSelEnd, + nsString& aReferenceString); + bool TestNotificationTextChange(nsIWidget* aWidget, + uint32_t aCode, + const nsAString& aCharacter, + LONG aStart, + LONG aOldEnd, + LONG aNewEnd); + nsresult GetSelCon(nsISelectionController** aSelCon); + + bool GetWidget(nsIWidget** aWidget); + + bool mFailed; + nsString mTestString; + nsRefPtr mMgr; + nsCOMPtr mAppShell; + nsCOMPtr mWindow; + nsCOMPtr mCurrentNode; + nsCOMPtr mInput; + nsCOMPtr mTextArea; + nsCOMPtr mButton; +}; + +NS_IMETHODIMP +TestApp::OnProgressChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +TestApp::OnLocationChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + nsIURI *aLocation, + uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +TestApp::OnStatusChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + nsresult aStatus, + const char16_t *aMessage) +{ + return NS_OK; +} + +NS_IMETHODIMP +TestApp::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t aState) +{ + return NS_OK; +} + +static HRESULT +GetRegularExtent(ITfRange *aRange, LONG &aStart, LONG &aEnd) +{ + NS_ENSURE_TRUE(aRange, E_INVALIDARG); + nsRefPtr rangeACP; + HRESULT hr = aRange->QueryInterface(IID_ITfRangeACP, + getter_AddRefs(rangeACP)); + NS_ENSURE_TRUE(SUCCEEDED(hr) && rangeACP, E_FAIL); + + LONG start, length; + hr = rangeACP->GetExtent(&start, &length); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + if (length >= 0) { + aStart = start; + aEnd = start + length; + } else { + aEnd = start; + aStart = start + length; + } + return S_OK; +} + +// {3B2DFDF5-2485-4858-8185-5C6B4EFD38F5} +static const GUID GUID_COMPOSING_SELECTION_ATTR = + { 0x3b2dfdf5, 0x2485, 0x4858, + { 0x81, 0x85, 0x5c, 0x6b, 0x4e, 0xfd, 0x38, 0xf5 } }; +#define GUID_ATOM_COMPOSING_SELECTION_ATTR \ + (static_cast(0x3b2dfdf5)) + +/****************************************************************************** + * TSFRangeImpl + ******************************************************************************/ + +class TSFRangeImpl : public ITfRangeACP +{ +private: + ULONG mRefCnt; + +public: + LONG mStart; + LONG mLength; + + TSFRangeImpl(LONG aStart = 0, LONG aLength = 0) : + mRefCnt(0), mStart(aStart), mLength(aLength) + { + } + + ~TSFRangeImpl() + { + } + +public: // IUnknown + + STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk) + { + *ppUnk = nullptr; + if (IID_IUnknown == riid || IID_ITfRange == riid || IID_ITfRangeACP == riid) + *ppUnk = static_cast(this); + if (*ppUnk) + AddRef(); + return *ppUnk ? S_OK : E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ++mRefCnt; + } + + STDMETHODIMP_(ULONG) Release(void) + { + if (--mRefCnt) return mRefCnt; + delete this; + return 0; + } + +public: // ITfRange + + STDMETHODIMP GetText(TfEditCookie ec, DWORD dwFlags, WCHAR *pchText, + ULONG cchMax, ULONG *pcch) + { + NS_NOTREACHED("ITfRange::GetText"); + return E_NOTIMPL; + } + + STDMETHODIMP SetText(TfEditCookie ec, DWORD dwFlags, const WCHAR *pchText, + LONG cch) + { + NS_NOTREACHED("ITfRange::SetText"); + return E_NOTIMPL; + } + + STDMETHODIMP GetFormattedText(TfEditCookie ec, IDataObject **ppDataObject) + { + NS_NOTREACHED("ITfRange::GetFormattedText"); + return E_NOTIMPL; + } + + STDMETHODIMP GetEmbedded(TfEditCookie ec, REFGUID rguidService, REFIID riid, + IUnknown **ppunk) + { + NS_NOTREACHED("ITfRange::GetEmbedded"); + return E_NOTIMPL; + } + + STDMETHODIMP InsertEmbedded(TfEditCookie ec, DWORD dwFlags, + IDataObject *pDataObject) + { + NS_NOTREACHED("ITfRange::InsertEmbedded"); + return E_NOTIMPL; + } + + STDMETHODIMP ShiftStart(TfEditCookie ec, LONG cchReq, LONG *pcch, + const TF_HALTCOND *pHalt) + { + NS_NOTREACHED("ITfRange::ShiftStart"); + return E_NOTIMPL; + } + + STDMETHODIMP ShiftEnd(TfEditCookie ec, LONG cchReq, LONG *pcch, + const TF_HALTCOND *pHalt) + { + NS_NOTREACHED("ITfRange::ShiftEnd"); + return E_NOTIMPL; + } + + STDMETHODIMP ShiftStartToRange(TfEditCookie ec, ITfRange *pRange, + TfAnchor aPos) + { + NS_NOTREACHED("ITfRange::ShiftStartToRange"); + return E_NOTIMPL; + } + + STDMETHODIMP ShiftEndToRange(TfEditCookie ec, ITfRange *pRange, TfAnchor aPos) + { + NS_NOTREACHED("ITfRange::ShiftEndToRange"); + return E_NOTIMPL; + } + + STDMETHODIMP ShiftStartRegion(TfEditCookie ec, TfShiftDir dir, + BOOL *pfNoRegion) + { + NS_NOTREACHED("ITfRange::ShiftStartRegion"); + return E_NOTIMPL; + } + + STDMETHODIMP ShiftEndRegion(TfEditCookie ec, TfShiftDir dir, BOOL *pfNoRegion) + { + NS_NOTREACHED("ITfRange::ShiftEndRegion"); + return E_NOTIMPL; + } + + STDMETHODIMP IsEmpty(TfEditCookie ec, BOOL *pfEmpty) + { + NS_NOTREACHED("ITfRange::IsEmpty"); + return E_NOTIMPL; + } + + STDMETHODIMP Collapse(TfEditCookie ec, TfAnchor aPos) + { + NS_NOTREACHED("ITfRange::Collapse"); + return E_NOTIMPL; + } + + STDMETHODIMP IsEqualStart(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, + BOOL *pfEqual) + { + NS_NOTREACHED("ITfRange::IsEqualStart"); + return E_NOTIMPL; + } + + STDMETHODIMP IsEqualEnd(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, + BOOL *pfEqual) + { + NS_NOTREACHED("ITfRange::IsEqualEnd"); + return E_NOTIMPL; + } + + STDMETHODIMP CompareStart(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, + LONG *plResult) + { + NS_NOTREACHED("ITfRange::CompareStart"); + return E_NOTIMPL; + } + + STDMETHODIMP CompareEnd(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, + LONG *plResult) + { + NS_NOTREACHED("ITfRange::CompareEnd"); + return E_NOTIMPL; + } + + STDMETHODIMP AdjustForInsert(TfEditCookie ec, ULONG cchInsert, + BOOL *pfInsertOk) + { + NS_NOTREACHED("ITfRange::AdjustForInsert"); + return E_NOTIMPL; + } + + STDMETHODIMP GetGravity(TfGravity *pgStart, TfGravity *pgEnd) + { + NS_NOTREACHED("ITfRange::GetGravity"); + return E_NOTIMPL; + } + + STDMETHODIMP SetGravity(TfEditCookie ec, TfGravity gStart, TfGravity gEnd) + { + NS_NOTREACHED("ITfRange::SetGravity"); + return E_NOTIMPL; + } + + STDMETHODIMP Clone(ITfRange **ppClone) + { + NS_NOTREACHED("ITfRange::Clone"); + return E_NOTIMPL; + } + + STDMETHODIMP GetContext(ITfContext **ppContext) + { + NS_NOTREACHED("ITfRange::GetContext"); + return E_NOTIMPL; + } + +public: // ITfRangeACP + + STDMETHODIMP GetExtent(LONG *pacpAnchor, LONG *pcch) + { + NS_ENSURE_TRUE(pacpAnchor, E_FAIL); + NS_ENSURE_TRUE(pcch, E_FAIL); + *pacpAnchor = mStart; + *pcch = mLength; + return S_OK; + } + + STDMETHODIMP SetExtent(LONG acpAnchor, LONG cch) + { + mStart = acpAnchor; + mLength = cch; + return S_OK; + } +}; + +/****************************************************************************** + * TSFEnumRangeImpl + ******************************************************************************/ + +class TSFEnumRangeImpl : public IEnumTfRanges +{ +private: + ULONG mRefCnt; + uint32_t mCurrentIndex; + +public: + nsTArray > mRanges; + + TSFEnumRangeImpl() : + mRefCnt(0), mCurrentIndex(0) + { + } + + ~TSFEnumRangeImpl() + { + } + +public: // IUnknown + + STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk) + { + *ppUnk = nullptr; + if (IID_IUnknown == riid || IID_IEnumTfRanges == riid) + *ppUnk = static_cast(this); + if (*ppUnk) + AddRef(); + return *ppUnk ? S_OK : E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ++mRefCnt; + } + + STDMETHODIMP_(ULONG) Release(void) + { + if (--mRefCnt) return mRefCnt; + delete this; + return 0; + } + +public: // IEnumTfRanges + + STDMETHODIMP Clone(IEnumTfRanges **ppEnum) + { + NS_NOTREACHED("IEnumTfRanges::Clone"); + return E_NOTIMPL; + } + + STDMETHODIMP Next(ULONG ulCount, ITfRange **ppRange, ULONG *pcFetched) + { + NS_ENSURE_TRUE(ppRange, E_FAIL); + if (pcFetched) + *pcFetched = 0; + if (mCurrentIndex + ulCount - 1 >= mRanges.Length()) + return E_FAIL; + for (uint32_t i = 0; i < ulCount; i++) { + ppRange[i] = mRanges[mCurrentIndex++]; + ppRange[i]->AddRef(); + if (pcFetched) + (*pcFetched)++; + } + return S_OK; + } + + STDMETHODIMP Reset() + { + mCurrentIndex = 0; + return S_OK; + } + + STDMETHODIMP Skip(ULONG ulCount) + { + mCurrentIndex += ulCount; + return S_OK; + } +}; + +/****************************************************************************** + * TSFDispAttrInfoImpl + ******************************************************************************/ + +class TSFDispAttrInfoImpl : public ITfDisplayAttributeInfo +{ +private: + ULONG mRefCnt; + TF_DISPLAYATTRIBUTE mAttr; + +public: + + TSFDispAttrInfoImpl(REFGUID aGUID) : + mRefCnt(0) + { + if (aGUID == GUID_COMPOSING_SELECTION_ATTR) { + mAttr.crText.type = TF_CT_NONE; + mAttr.crBk.type = TF_CT_NONE; + mAttr.lsStyle = TF_LS_SQUIGGLE; + mAttr.fBoldLine = FALSE; + mAttr.crLine.type = TF_CT_NONE; + mAttr.bAttr = TF_ATTR_INPUT; + } else { + NS_NOTREACHED("TSFDispAttrInfoImpl::TSFDispAttrInfoImpl"); + } + } + + ~TSFDispAttrInfoImpl() + { + } + +public: // IUnknown + + STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk) + { + *ppUnk = nullptr; + if (IID_IUnknown == riid || IID_ITfDisplayAttributeInfo == riid) + *ppUnk = static_cast(this); + if (*ppUnk) + AddRef(); + return *ppUnk ? S_OK : E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ++mRefCnt; + } + + STDMETHODIMP_(ULONG) Release(void) + { + if (--mRefCnt) return mRefCnt; + delete this; + return 0; + } + +public: // ITfDisplayAttributeInfo + + STDMETHODIMP GetGUID(GUID *pguid) + { + NS_NOTREACHED("ITfDisplayAttributeInfo::GetGUID"); + return E_NOTIMPL; + } + + STDMETHODIMP GetDescription(BSTR *pbstrDesc) + { + NS_NOTREACHED("ITfDisplayAttributeInfo::GetDescription"); + return E_NOTIMPL; + } + + STDMETHODIMP GetAttributeInfo(TF_DISPLAYATTRIBUTE *pda) + { + NS_ENSURE_TRUE(pda, E_INVALIDARG); + *pda = mAttr; + return S_OK; + } + + STDMETHODIMP SetAttributeInfo(const TF_DISPLAYATTRIBUTE *pda) + { + NS_NOTREACHED("ITfDisplayAttributeInfo::SetAttributeInfo"); + return E_NOTIMPL; + } + + STDMETHODIMP Reset() + { + NS_NOTREACHED("ITfDisplayAttributeInfo::Reset"); + return E_NOTIMPL; + } +}; + +/****************************************************************************** + * TSFAttrPropImpl + ******************************************************************************/ + +class TSFAttrPropImpl : public ITfProperty +{ +private: + ULONG mRefCnt; + +public: + nsTArray > mRanges; + + TSFAttrPropImpl() : + mRefCnt(0) + { + } + + ~TSFAttrPropImpl() + { + } + +public: // IUnknown + + STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk) + { + *ppUnk = nullptr; + if (IID_IUnknown == riid || IID_ITfProperty == riid || + IID_ITfReadOnlyProperty == riid) + *ppUnk = static_cast(this); + if (*ppUnk) + AddRef(); + return *ppUnk ? S_OK : E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ++mRefCnt; + } + + STDMETHODIMP_(ULONG) Release(void) + { + if (--mRefCnt) return mRefCnt; + delete this; + return 0; + } + +public: // ITfProperty + + STDMETHODIMP FindRange(TfEditCookie ec, ITfRange *pRange, ITfRange **ppRange, + TfAnchor aPos) + { + NS_NOTREACHED("ITfProperty::FindRange"); + return E_NOTIMPL; + } + + STDMETHODIMP SetValueStore(TfEditCookie ec, ITfRange *pRange, + ITfPropertyStore *pPropStore) + { + NS_NOTREACHED("ITfProperty::SetValueStore"); + return E_NOTIMPL; + } + + STDMETHODIMP SetValue(TfEditCookie ec, ITfRange *pRange, + const VARIANT *pvarValue) + { + NS_NOTREACHED("ITfProperty::SetValue"); + return E_NOTIMPL; + } + + STDMETHODIMP Clear(TfEditCookie ec, ITfRange *pRange) + { + NS_NOTREACHED("ITfProperty::Clear"); + return E_NOTIMPL; + } + +public: // ITfReadOnlyProperty + + STDMETHODIMP GetType(GUID *pguid) + { + NS_NOTREACHED("ITfReadOnlyProperty::GetType"); + return E_NOTIMPL; + } + + STDMETHODIMP EnumRanges(TfEditCookie ec, IEnumTfRanges **ppEnum, + ITfRange *pTargetRange) + { + NS_ENSURE_TRUE(ppEnum, E_INVALIDARG); + NS_ENSURE_TRUE(pTargetRange, E_INVALIDARG); + + // XXX ec checking is not implemented yet. + + LONG targetStart = 0, targetEnd = 0; + if (pTargetRange) { + HRESULT hr = GetRegularExtent(pTargetRange, targetStart, targetEnd); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + } + nsRefPtr er = new TSFEnumRangeImpl(); + NS_ENSURE_TRUE(er, E_OUTOFMEMORY); + for (uint32_t i = 0; i < mRanges.Length(); i++) { + LONG start, end; + HRESULT hr = GetRegularExtent(mRanges[i], start, end); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + if (pTargetRange) { + // If pTargetRange is not null and the current range is not overlapped + // with it, we don't need to add range. + if (targetStart > end || targetEnd < start) + continue; + // Otherwise, shrink to the target range. + start = std::max(targetStart, start); + end = std::min(targetEnd, end); + } + nsRefPtr range = new TSFRangeImpl(start, end - start); + NS_ENSURE_TRUE(range, E_OUTOFMEMORY); + er->mRanges.AppendElement(range); + } + *ppEnum = er; + (*ppEnum)->AddRef(); + return S_OK; + } + + STDMETHODIMP GetValue(TfEditCookie ec, ITfRange *pRange, VARIANT *pvarValue) + { + NS_ENSURE_TRUE(pvarValue, E_INVALIDARG); + + LONG givenStart, givenEnd; + HRESULT hr = GetRegularExtent(pRange, givenStart, givenEnd); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + for (uint32_t i = 0; i < mRanges.Length(); i++) { + LONG start, end; + HRESULT hr = GetRegularExtent(mRanges[i], start, end); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + // pRange must be same as (or included in) a range of mRanges. + if (givenStart > start || givenEnd < end) + continue; + pvarValue->vt = VT_I4; + pvarValue->lVal = static_cast(GUID_ATOM_COMPOSING_SELECTION_ATTR); + return S_OK; + } + return E_FAIL; + } + + STDMETHODIMP GetContext(ITfContext **ppContext) + { + NS_NOTREACHED("ITfReadOnlyProperty::GetContext"); + return E_NOTIMPL; + } +}; + +/****************************************************************************** + * TSFContextImpl + ******************************************************************************/ + +class TSFContextImpl : public ITfContext, + public ITfCompositionView, public ITextStoreACPSink +{ +private: + ULONG mRefCnt; + +public: + nsRefPtr mAttrProp; + nsRefPtr mDocMgr; + bool mTextChanged; + bool mSelChanged; + TS_TEXTCHANGE mTextChangeData; + +public: + TSFContextImpl(TSFDocumentMgrImpl* aDocMgr) : + mDocMgr(aDocMgr), mRefCnt(0), mTextChanged(false), + mSelChanged(false) + { + mAttrProp = new TSFAttrPropImpl(); + if (!mAttrProp) { + NS_NOTREACHED("TSFContextImpl::TSFContextImpl (OOM)"); + return; + } + } + + ~TSFContextImpl() + { + } + +public: // IUnknown + + STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk) + { + *ppUnk = nullptr; + if (IID_IUnknown == riid || IID_ITfContext == riid) + *ppUnk = static_cast(this); + else if (IID_ITextStoreACPSink == riid) + *ppUnk = static_cast(this); + if (*ppUnk) + AddRef(); + return *ppUnk ? S_OK : E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ++mRefCnt; + } + + STDMETHODIMP_(ULONG) Release(void) + { + if (--mRefCnt) return mRefCnt; + delete this; + return 0; + } + +public: // ITfContext + + STDMETHODIMP RequestEditSession(TfClientId tid, ITfEditSession *pes, + DWORD dwFlags, HRESULT *phrSession) + { + NS_NOTREACHED("ITfContext::RequestEditSession"); + return E_NOTIMPL; + } + + STDMETHODIMP InWriteSession(TfClientId tid, BOOL *pfWriteSession) + { + NS_NOTREACHED("ITfContext::InWriteSession"); + return E_NOTIMPL; + } + + STDMETHODIMP GetSelection(TfEditCookie ec, ULONG ulIndex, ULONG ulCount, + TF_SELECTION *pSelection, ULONG *pcFetched) + { + NS_NOTREACHED("ITfContext::GetSelection"); + return E_NOTIMPL; + } + + STDMETHODIMP SetSelection(TfEditCookie ec, ULONG ulCount, + const TF_SELECTION *pSelection) + { + NS_NOTREACHED("ITfContext::SetSelection"); + return E_NOTIMPL; + } + + STDMETHODIMP GetStart(TfEditCookie ec, ITfRange **ppStart) + { + NS_NOTREACHED("ITfContext::GetStart"); + return E_NOTIMPL; + } + + STDMETHODIMP GetEnd(TfEditCookie ec, ITfRange **ppEnd) + { + NS_NOTREACHED("ITfContext::GetEnd"); + return E_NOTIMPL; + } + + STDMETHODIMP GetActiveView(ITfContextView **ppView) + { + NS_NOTREACHED("ITfContext::GetActiveView"); + return E_NOTIMPL; + } + + STDMETHODIMP EnumViews(IEnumTfContextViews **ppEnum) + { + NS_NOTREACHED("ITfContext::EnumViews"); + return E_NOTIMPL; + } + + STDMETHODIMP GetStatus(TF_STATUS *pdcs) + { + NS_NOTREACHED("ITfContext::GetStatus"); + return E_NOTIMPL; + } + + STDMETHODIMP GetProperty(REFGUID guidProp, ITfProperty **ppProp) + { + NS_ENSURE_TRUE(ppProp, E_INVALIDARG); + if (guidProp == GUID_PROP_ATTRIBUTE) { + (*ppProp) = mAttrProp; + (*ppProp)->AddRef(); + return S_OK; + } + NS_NOTREACHED("ITfContext::GetProperty"); + return E_NOTIMPL; + } + + STDMETHODIMP GetAppProperty(REFGUID guidProp, ITfReadOnlyProperty **ppProp) + { + NS_NOTREACHED("ITfContext::GetAppProperty"); + return E_NOTIMPL; + } + + STDMETHODIMP TrackProperties(const GUID **prgProp, ULONG cProp, + const GUID **prgAppProp, ULONG cAppProp, + ITfReadOnlyProperty **ppProperty) + { + NS_NOTREACHED("ITfContext::TrackProperties"); + return E_NOTIMPL; + } + + STDMETHODIMP EnumProperties(IEnumTfProperties **ppEnum) + { + NS_NOTREACHED("ITfContext::EnumProperties"); + return E_NOTIMPL; + } + + STDMETHODIMP GetDocumentMgr(ITfDocumentMgr **ppDm) + { + NS_NOTREACHED("ITfContext::GetDocumentMgr"); + return E_NOTIMPL; + } + + STDMETHODIMP CreateRangeBackup(TfEditCookie ec, ITfRange *pRange, + ITfRangeBackup **ppBackup) + { + NS_NOTREACHED("ITfContext::CreateRangeBackup"); + return E_NOTIMPL; + } + +public: // ITfCompositionView + + STDMETHODIMP GetOwnerClsid(CLSID* pclsid) + { + NS_NOTREACHED("ITfCompositionView::GetOwnerClsid"); + return E_NOTIMPL; + } + + STDMETHODIMP GetRange(ITfRange** ppRange) + { + NS_ENSURE_TRUE(ppRange, E_INVALIDARG); + NS_ENSURE_TRUE(mAttrProp->mRanges.Length() > 0, E_FAIL); + LONG start = LONG_MAX, end = 0; + for (uint32_t i = 0; i < mAttrProp->mRanges.Length(); i++) { + LONG tmpStart, tmpEnd; + HRESULT hr = GetRegularExtent(mAttrProp->mRanges[i], tmpStart, tmpEnd); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + start = std::min(start, tmpStart); + end = std::max(end, tmpEnd); + } + nsRefPtr range = new TSFRangeImpl(); + NS_ENSURE_TRUE(range, E_OUTOFMEMORY); + HRESULT hr = range->SetExtent(start, end - start); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + (*ppRange) = range; + (*ppRange)->AddRef(); + return S_OK; + } + +public: // ITextStoreACPSink + + STDMETHODIMP OnTextChange(DWORD dwFlags, const TS_TEXTCHANGE *pChange) + { + mTextChanged = true; + mTextChangeData = *pChange; + return S_OK; + } + + STDMETHODIMP OnSelectionChange(void) + { + mSelChanged = true; + return S_OK; + } + + STDMETHODIMP OnLayoutChange(TsLayoutCode lcode, TsViewCookie vcView) + { + return S_OK; + } + + STDMETHODIMP OnStatusChange(DWORD dwFlags) + { + return S_OK; + } + + STDMETHODIMP OnAttrsChange(LONG acpStart, LONG acpEnd, ULONG cAttrs, + const TS_ATTRID *paAttrs) + { + return S_OK; + } + + STDMETHODIMP OnLockGranted(DWORD dwLockFlags); + + STDMETHODIMP OnStartEditTransaction(void) + { + return S_OK; + } + + STDMETHODIMP OnEndEditTransaction(void) + { + return S_OK; + } +}; + +/****************************************************************************** + * TSFDocumentMgrImpl + ******************************************************************************/ + +class TSFDocumentMgrImpl : public ITfDocumentMgr +{ +private: + ULONG mRefCnt; + +public: + nsRefPtr mMgr; + nsRefPtr mStore; + nsRefPtr mContextBase; + nsRefPtr mContextTop; // XXX currently, we don't support this. + +public: + TSFDocumentMgrImpl(TSFMgrImpl* aMgr) : + mRefCnt(0), mMgr(aMgr) + { + } + + ~TSFDocumentMgrImpl() + { + } + +public: // IUnknown + + STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk) + { + *ppUnk = nullptr; + if (IID_IUnknown == riid || IID_ITfDocumentMgr == riid) + *ppUnk = static_cast(this); + if (*ppUnk) + AddRef(); + return *ppUnk ? S_OK : E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ++mRefCnt; + } + + STDMETHODIMP_(ULONG) Release(void); + +public: // ITfDocumentMgr + + STDMETHODIMP CreateContext(TfClientId tidOwner, DWORD dwFlags, + IUnknown *punk, ITfContext **ppic, + TfEditCookie *pecTextStore) + { + nsRefPtr context = new TSFContextImpl(this); + punk->QueryInterface(IID_ITextStoreACP, getter_AddRefs(mStore)); + NS_ENSURE_TRUE(mStore, E_FAIL); + HRESULT hr = + mStore->AdviseSink(IID_ITextStoreACPSink, + static_cast(context.get()), + TS_AS_ALL_SINKS); + if (FAILED(hr)) mStore = nullptr; + NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL); + (*ppic) = context; + (*ppic)->AddRef(); + *pecTextStore = 1; + return S_OK; + } + + STDMETHODIMP Push(ITfContext *pic) + { + if (mContextTop) { + NS_NOTREACHED("TSFDocumentMgrImpl::Push stack is already full"); + return E_FAIL; + } + if (mContextBase) { + NS_WARNING("TSFDocumentMgrImpl::Push additional context is pushed, but we don't support that yet."); + if (mContextBase == pic) { + NS_NOTREACHED("TSFDocumentMgrImpl::Push same context is pused again"); + return E_FAIL; + } + mContextTop = static_cast(pic); + return S_OK; + } + mContextBase = static_cast(pic); + return S_OK; + } + + STDMETHODIMP Pop(DWORD dwFlags) + { + if (!mStore) + return E_FAIL; + if (dwFlags == TF_POPF_ALL) { + NS_ENSURE_TRUE(mContextBase, E_FAIL); + mStore->UnadviseSink(static_cast(mContextBase.get())); + mStore = nullptr; + mContextBase = nullptr; + mContextTop = nullptr; + return S_OK; + } + if (dwFlags == 0) { + if (!mContextTop) { + NS_NOTREACHED("TSFDocumentMgrImpl::Pop there is non-base context"); + return E_FAIL; + } + mContextTop = nullptr; + return S_OK; + } + NS_NOTREACHED("TSFDocumentMgrImpl::Pop invalid flag"); + return E_FAIL; + } + + STDMETHODIMP GetTop(ITfContext **ppic) + { + (*ppic) = mContextTop ? mContextTop : mContextBase; + if (*ppic) (*ppic)->AddRef(); + return S_OK; + } + + STDMETHODIMP GetBase(ITfContext **ppic) + { + (*ppic) = mContextBase; + if (*ppic) (*ppic)->AddRef(); + return S_OK; + } + + STDMETHODIMP EnumContexts(IEnumTfContexts **ppEnum) + { + NS_NOTREACHED("ITfDocumentMgr::EnumContexts"); + return E_NOTIMPL; + } + +}; + +/****************************************************************************** + * TSFMgrImpl + ******************************************************************************/ + +class TSFMgrImpl : public ITfThreadMgr, + public ITfDisplayAttributeMgr, public ITfCategoryMgr +{ +private: + ULONG mRefCnt; + +public: + nsRefPtr mTestApp; + TestApp::test_type mTest; + bool mDeactivated; + TSFDocumentMgrImpl* mFocusedDocument; // Must be raw pointer, but strong. + int32_t mFocusCount; + + TSFMgrImpl(TestApp* test) : mTestApp(test), mTest(nullptr), mRefCnt(0), + mDeactivated(false), mFocusedDocument(nullptr), mFocusCount(0) + { + } + + ~TSFMgrImpl() + { + } + +public: // IUnknown + + STDMETHODIMP QueryInterface(REFIID riid, void** ppUnk) + { + *ppUnk = nullptr; + if (IID_IUnknown == riid || IID_ITfThreadMgr == riid) + *ppUnk = static_cast(this); + else if (IID_ITfDisplayAttributeMgr == riid) + *ppUnk = static_cast(this); + else if (IID_ITfCategoryMgr == riid) + *ppUnk = static_cast(this); + if (*ppUnk) + AddRef(); + return *ppUnk ? S_OK : E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ++mRefCnt; + } + + STDMETHODIMP_(ULONG) Release(void) + { + if (--mRefCnt) return mRefCnt; + delete this; + return 0; + } + +public: // ITfThreadMgr + + STDMETHODIMP Activate(TfClientId *ptid) + { + *ptid = 1; + return S_OK; + } + + STDMETHODIMP Deactivate(void) + { + mDeactivated = true; + return S_OK; + } + + STDMETHODIMP CreateDocumentMgr(ITfDocumentMgr **ppdim) + { + nsRefPtr docMgr = new TSFDocumentMgrImpl(this); + if (!docMgr) { + NS_NOTREACHED("TSFMgrImpl::CreateDocumentMgr (OOM)"); + return E_OUTOFMEMORY; + } + (*ppdim) = docMgr; + (*ppdim)->AddRef(); + return S_OK; + } + + STDMETHODIMP EnumDocumentMgrs(IEnumTfDocumentMgrs **ppEnum) + { + NS_NOTREACHED("ITfThreadMgr::EnumDocumentMgrs"); + return E_NOTIMPL; + } + + STDMETHODIMP GetFocus(ITfDocumentMgr **ppdimFocus) + { + (*ppdimFocus) = mFocusedDocument; + if (*ppdimFocus) (*ppdimFocus)->AddRef(); + return S_OK; + } + + STDMETHODIMP SetFocus(ITfDocumentMgr *pdimFocus) + { + if (!pdimFocus) { + NS_NOTREACHED("ITfThreadMgr::SetFocus must not be called with NULL"); + return E_FAIL; + } + mFocusCount++; + if (mFocusedDocument == pdimFocus) { + return S_OK; + } + mFocusedDocument = static_cast(pdimFocus); + mFocusedDocument->AddRef(); + return S_OK; + } + + STDMETHODIMP AssociateFocus(HWND hwnd, ITfDocumentMgr *pdimNew, + ITfDocumentMgr **ppdimPrev) + { + NS_NOTREACHED("ITfThreadMgr::AssociateFocus"); + return E_NOTIMPL; + } + + STDMETHODIMP IsThreadFocus(BOOL *pfThreadFocus) + { + *pfThreadFocus = TRUE; + return S_OK; + } + + STDMETHODIMP GetFunctionProvider(REFCLSID clsid, + ITfFunctionProvider **ppFuncProv) + { + NS_NOTREACHED("ITfThreadMgr::GetFunctionProvider"); + return E_NOTIMPL; + } + + STDMETHODIMP EnumFunctionProviders(IEnumTfFunctionProviders **ppEnum) + { + NS_NOTREACHED("ITfThreadMgr::EnumFunctionProviders"); + return E_NOTIMPL; + } + + STDMETHODIMP GetGlobalCompartment(ITfCompartmentMgr **ppCompMgr) + { + NS_NOTREACHED("ITfThreadMgr::GetGlobalCompartment"); + return E_NOTIMPL; + } + +public: // ITfCategoryMgr + + STDMETHODIMP RegisterCategory(REFCLSID rclsid, REFGUID rcatid, REFGUID rguid) + { + NS_NOTREACHED("ITfCategoryMgr::RegisterCategory"); + return E_NOTIMPL; + } + + STDMETHODIMP UnregisterCategory(REFCLSID rclsid, REFGUID rcatid, REFGUID rguid) + { + NS_NOTREACHED("ITfCategoryMgr::UnregisterCategory"); + return E_NOTIMPL; + } + + STDMETHODIMP EnumCategoriesInItem(REFGUID rguid, IEnumGUID **ppEnum) + { + NS_NOTREACHED("ITfCategoryMgr::EnumCategoriesInItem"); + return E_NOTIMPL; + } + + STDMETHODIMP EnumItemsInCategory(REFGUID rcatid, IEnumGUID **ppEnum) + { + NS_NOTREACHED("ITfCategoryMgr::EnumItemsInCategory"); + return E_NOTIMPL; + } + + STDMETHODIMP FindClosestCategory(REFGUID rguid, GUID *pcatid, + const GUID **ppcatidList, ULONG ulCount) + { + NS_NOTREACHED("ITfCategoryMgr::FindClosestCategory"); + return E_NOTIMPL; + } + + STDMETHODIMP RegisterGUIDDescription(REFCLSID rclsid, REFGUID rguid, + const WCHAR *pchDesc, ULONG cch) + { + NS_NOTREACHED("ITfCategoryMgr::RegisterGUIDDescription"); + return E_NOTIMPL; + } + + STDMETHODIMP UnregisterGUIDDescription(REFCLSID rclsid, REFGUID rguid) + { + NS_NOTREACHED("ITfCategoryMgr::UnregisterGUIDDescription"); + return E_NOTIMPL; + } + + STDMETHODIMP GetGUIDDescription(REFGUID rguid, BSTR *pbstrDesc) + { + NS_NOTREACHED("ITfCategoryMgr::GetGUIDDescription"); + return E_NOTIMPL; + } + + STDMETHODIMP RegisterGUIDDWORD(REFCLSID rclsid, REFGUID rguid, DWORD dw) + { + NS_NOTREACHED("ITfCategoryMgr::RegisterGUIDDWORD"); + return E_NOTIMPL; + } + + STDMETHODIMP UnregisterGUIDDWORD(REFCLSID rclsid, REFGUID rguid) + { + NS_NOTREACHED("ITfCategoryMgr::UnregisterGUIDDWORD"); + return E_NOTIMPL; + } + + STDMETHODIMP GetGUIDDWORD(REFGUID rguid, DWORD *pdw) + { + NS_NOTREACHED("ITfCategoryMgr::GetGUIDDWORD"); + return E_NOTIMPL; + } + + STDMETHODIMP RegisterGUID(REFGUID rguid, TfGuidAtom *pguidatom) + { + NS_NOTREACHED("ITfCategoryMgr::RegisterGUID"); + return E_NOTIMPL; + } + + STDMETHODIMP GetGUID(TfGuidAtom guidatom, GUID *pguid) + { + if (guidatom == GUID_ATOM_COMPOSING_SELECTION_ATTR) { + *pguid = GUID_COMPOSING_SELECTION_ATTR; + return S_OK; + } + NS_NOTREACHED("ITfCategoryMgr::GetGUID"); + return E_FAIL; + } + + STDMETHODIMP IsEqualTfGuidAtom(TfGuidAtom guidatom, REFGUID rguid, + BOOL *pfEqual) + { + NS_NOTREACHED("ITfCategoryMgr::IsEqualTfGuidAtom"); + return E_NOTIMPL; + } + +public: // ITfDisplayAttributeMgr + + STDMETHODIMP OnUpdateInfo() + { + NS_NOTREACHED("ITfDisplayAttributeMgr::OnUpdateInfo"); + return E_NOTIMPL; + } + + STDMETHODIMP EnumDisplayAttributeInfo(IEnumTfDisplayAttributeInfo **ppEnum) + { + NS_NOTREACHED("ITfDisplayAttributeMgr::EnumDisplayAttributeInfo"); + return E_NOTIMPL; + } + + STDMETHODIMP GetDisplayAttributeInfo(REFGUID guid, + ITfDisplayAttributeInfo **ppInfo, + CLSID *pclsidOwner) + { + NS_ENSURE_TRUE(ppInfo, E_INVALIDARG); + NS_ENSURE_TRUE(!pclsidOwner, E_INVALIDARG); + if (guid == GUID_COMPOSING_SELECTION_ATTR) { + (*ppInfo) = new TSFDispAttrInfoImpl(guid); + (*ppInfo)->AddRef(); + return S_OK; + } + NS_NOTREACHED("ITfDisplayAttributeMgr::GetDisplayAttributeInfo"); + return E_FAIL; + } + +public: + + ITextStoreACP* GetFocusedStore() + { + return mFocusedDocument ? mFocusedDocument->mStore : nullptr; + } + + TSFContextImpl* GetFocusedContext() + { + return mFocusedDocument ? mFocusedDocument->mContextBase : nullptr; + } + + TSFAttrPropImpl* GetFocusedAttrProp() + { + TSFContextImpl* context = GetFocusedContext(); + return context ? context->mAttrProp : nullptr; + } + +}; + +STDMETHODIMP +TSFContextImpl::OnLockGranted(DWORD dwLockFlags) +{ + // If we have a test, run it + if (mDocMgr->mMgr->mTest && + !((*mDocMgr->mMgr->mTestApp).*(mDocMgr->mMgr->mTest))()) + return S_FALSE; + return S_OK; +} + + +STDMETHODIMP_(ULONG) +TSFDocumentMgrImpl::Release(void) +{ + --mRefCnt; + if (mRefCnt == 1 && mMgr->mFocusedDocument == this) { + mMgr->mFocusedDocument = nullptr; + --mRefCnt; + } + if (mRefCnt) return mRefCnt; + delete this; + return 0; +} + +NS_IMPL_ISUPPORTS(TestApp, nsIWebProgressListener, + nsISupportsWeakReference) + +nsresult +TestApp::Run(void) +{ + // Create a test window + // We need a full-fledged window to test for TSF functionality + nsresult rv; + mAppShell = do_GetService(kAppShellCID); + NS_ENSURE_TRUE(mAppShell, NS_ERROR_UNEXPECTED); + + nsCOMPtr appShellService( + do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(appShellService, NS_ERROR_UNEXPECTED); + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), "about:blank", nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appShellService->CreateTopLevelWindow(nullptr, uri, + nsIWebBrowserChrome::CHROME_DEFAULT, + 800 /*nsIAppShellService::SIZE_TO_CONTENT*/, + 600 /*nsIAppShellService::SIZE_TO_CONTENT*/, + getter_AddRefs(mWindow)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr docShell; + rv = mWindow->GetDocShell(getter_AddRefs(docShell)); + NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); + nsCOMPtr progress(do_GetInterface(docShell)); + NS_ENSURE_TRUE(progress, NS_ERROR_UNEXPECTED); + rv = progress->AddProgressListener(this, + nsIWebProgress::NOTIFY_STATE_WINDOW | + nsIWebProgress::NOTIFY_STATUS); + NS_ENSURE_SUCCESS(rv, rv); + + mAppShell->Run(); + return NS_OK; +} + +bool +TestApp::CheckFailed(void) +{ + // All windows should be closed by now + if (mMgr && !mMgr->mDeactivated) { + fail("TSF not terminated properly"); + mFailed = true; + } + mMgr = nullptr; + return mFailed; +} + +nsresult +TestApp::Init(void) +{ + // Replace TSF manager pointer, category manager pointer and display + // attribute manager pointer. + nsCOMPtr baseWindow(do_QueryInterface(mWindow)); + NS_ENSURE_TRUE(baseWindow, NS_ERROR_UNEXPECTED); + nsCOMPtr widget; + nsresult rv = baseWindow->GetMainWidget(getter_AddRefs(widget)); + NS_ENSURE_TRUE(widget, NS_ERROR_UNEXPECTED); + + ITfThreadMgr **threadMgr = reinterpret_cast( + widget->GetNativeData(NS_NATIVE_TSF_THREAD_MGR)); + if (!threadMgr) { + fail("nsIWidget::GetNativeData(NS_NATIVE_TSF_THREAD_MGR) not supported"); + return NS_ERROR_FAILURE; + } + if (*threadMgr) { + (*threadMgr)->Deactivate(); + (*threadMgr)->Release(); + (*threadMgr) = nullptr; + } else { + // This is only for information. The test does not need TSF to run. + printf("TSF not initialized properly (TSF is not enabled/installed?)\n"); + } + + ITfCategoryMgr **catMgr = reinterpret_cast( + widget->GetNativeData(NS_NATIVE_TSF_CATEGORY_MGR)); + if (*catMgr) { + (*catMgr)->Release(); + (*catMgr) = nullptr; + } + ITfDisplayAttributeMgr **daMgr = reinterpret_cast( + widget->GetNativeData(NS_NATIVE_TSF_DISPLAY_ATTR_MGR)); + if (*daMgr) { + (*daMgr)->Release(); + (*daMgr) = nullptr; + } + + mMgr = new TSFMgrImpl(this); + if (!mMgr) { + return NS_ERROR_OUT_OF_MEMORY; + } + (*threadMgr) = mMgr; + (*threadMgr)->AddRef(); + (*catMgr) = mMgr; + (*catMgr)->AddRef(); + (*daMgr) = mMgr; + (*daMgr)->AddRef(); + + // Apply the change + reinterpret_cast( + widget->GetNativeData(NS_NATIVE_TSF_THREAD_MGR)); + + // Create a couple of text boxes for testing + nsCOMPtr win(do_GetInterface(mWindow)); + NS_ENSURE_TRUE(win, NS_ERROR_UNEXPECTED); + nsCOMPtr document; + rv = win->GetDocument(getter_AddRefs(document)); + NS_ENSURE_TRUE(document, NS_ERROR_UNEXPECTED); + nsCOMPtr htmlDoc(do_QueryInterface(document)); + NS_ENSURE_TRUE(htmlDoc, NS_ERROR_UNEXPECTED); + nsCOMPtr htmlBody; + rv = htmlDoc->GetBody(getter_AddRefs(htmlBody)); + NS_ENSURE_TRUE(htmlBody, NS_ERROR_UNEXPECTED); + + nsCOMPtr form; + rv = htmlDoc->CreateElementNS( + NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"), + NS_LITERAL_STRING("form"), + getter_AddRefs(form)); + nsCOMPtr elem; + rv = htmlDoc->CreateElementNS( + NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"), + NS_LITERAL_STRING("input"), + getter_AddRefs(elem)); + NS_ENSURE_SUCCESS(rv, rv); + elem->SetAttribute(NS_LITERAL_STRING("type"), + NS_LITERAL_STRING("text")); + mInput = do_QueryInterface(elem); + NS_ENSURE_TRUE(mInput, NS_ERROR_UNEXPECTED); + rv = htmlDoc->CreateElementNS( + NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"), + NS_LITERAL_STRING("textarea"), + getter_AddRefs(elem)); + NS_ENSURE_SUCCESS(rv, rv); + mTextArea = do_QueryInterface(elem); + NS_ENSURE_TRUE(mTextArea, NS_ERROR_UNEXPECTED); + rv = htmlDoc->CreateElementNS( + NS_LITERAL_STRING("http://www.w3.org/1999/xhtml"), + NS_LITERAL_STRING("input"), + getter_AddRefs(elem)); + NS_ENSURE_SUCCESS(rv, rv); + elem->SetAttribute(NS_LITERAL_STRING("type"), + NS_LITERAL_STRING("button")); + mButton = do_QueryInterface(elem); + NS_ENSURE_TRUE(mButton, NS_ERROR_UNEXPECTED); + + nsCOMPtr node; + rv = form->AppendChild(mInput, getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + rv = form->AppendChild(mTextArea, getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + rv = form->AppendChild(mButton, getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + rv = htmlBody->AppendChild(form, getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + + // set a background color manually, + // otherwise the window might be transparent + static_cast(htmlBody)-> + SetBgColor(NS_LITERAL_STRING("white")); + + widget->Show(true); + widget->SetFocus(); + return NS_OK; +} + +nsresult +TestApp::Term(void) +{ + mCurrentNode = nullptr; + mInput = nullptr; + mTextArea = nullptr; + mButton = nullptr; + + nsCOMPtr win(do_GetInterface(mWindow)); + if (win) + win->Close(); + win = nullptr; + mWindow = nullptr; + + if (mAppShell) + mAppShell->Exit(); + mAppShell = nullptr; + return NS_OK; +} + +bool +TestApp::RunTest(test_type aTest, bool aLock) +{ + bool succeeded; + if (aLock && mMgr && mMgr->GetFocusedStore()) { + mMgr->mTest = aTest; + HRESULT hr = E_FAIL; + mMgr->GetFocusedStore()->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &hr); + succeeded = hr == S_OK; + } else { + succeeded = (this->*aTest)(); + } + mFailed |= !succeeded; + return succeeded; +} + +NS_IMETHODIMP +TestApp::OnStateChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + NS_ASSERTION(aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW && + aStateFlags & nsIWebProgressListener::STATE_STOP, "wrong state"); + if (NS_SUCCEEDED(Init())) { + mCurrentNode = mTextArea; + mTextArea->Focus(); + + if (RunTest(&TestApp::TestEditMessages)) + passed("TestEditMessages"); + if (RunTest(&TestApp::TestScrollMessages)) + passed("TestScrollMessages"); + + if (RunTest(&TestApp::TestFocus, false)) + passed("TestFocus"); + + mCurrentNode = mInput; + mInput->Focus(); + if (mMgr->GetFocusedStore()) { + if (RunTest(&TestApp::TestClustering)) + passed("TestClustering"); + } else { + fail("no text store (clustering)"); + mFailed = true; + } + + printf("Testing TSF support in text input element...\n"); + mCurrentNode = mInput; + mTestString = NS_LITERAL_STRING( + "This is a test of the Text Services Framework implementation."); + mInput->SetValue(mTestString); + mInput->Focus(); + if (mMgr->GetFocusedStore()) { + if (RunTest(&TestApp::TestSelection)) + passed("TestSelection (input)"); + if (RunTest(&TestApp::TestText)) + passed("TestText (input)"); + if (RunTest(&TestApp::TestExtents)) + passed("TestExtents (input)"); + if (RunTest(&TestApp::TestComposition)) + passed("TestComposition (input)"); + if (RunTest(&TestApp::TestNotification, false)) + passed("TestNotification (input)"); + } else { + fail("no text store (input)"); + mFailed = true; + } + + printf("Testing TSF support in textarea element...\n"); + mCurrentNode = mTextArea; + mTestString = NS_LITERAL_STRING( + "This is a test of the\r\nText Services Framework\r\nimplementation."); + mTextArea->SetValue(mTestString); + mTextArea->Focus(); + if (mMgr->GetFocusedStore()) { + if (RunTest(&TestApp::TestSelection)) + passed("TestSelection (textarea)"); + if (RunTest(&TestApp::TestText)) + passed("TestText (textarea)"); + if (RunTest(&TestApp::TestExtents)) + passed("TestExtents (textarea)"); + if (RunTest(&TestApp::TestComposition)) + passed("TestComposition (textarea)"); + if (RunTest(&TestApp::TestNotification, false)) + passed("TestNotification (textarea)"); + } else { + fail("no text store (textarea)"); + mFailed = true; + } + } else { + fail("initialization"); + mFailed = true; + } + Term(); + return NS_OK; +} + +bool +TestApp::TestFocus(void) +{ + uint32_t focus = mMgr->mFocusCount; + nsresult rv; + + /* If these fail the cause is probably one or more of: + * - nsIMEStateManager::OnTextStateFocus not called by nsEventStateManager + * - nsIMEStateManager::OnTextStateBlur not called by nsEventStateManager + * - nsWindow::OnIMEFocusChange (nsIWidget) not called by nsIMEStateManager + * - nsTextStore::Create/Focus/Destroy not called by nsWindow + * - ITfThreadMgr::CreateDocumentMgr/SetFocus not called by nsTextStore + * - ITfDocumentMgr::CreateContext/Push not called by nsTextStore + */ + + rv = mInput->Focus(); + if (!(NS_SUCCEEDED(rv) && + mMgr->mFocusedDocument && + mMgr->mFocusCount - focus == 1 && + mMgr->GetFocusedStore())) { + fail("TestFocus: document focus was not set"); + return false; + } + + rv = mTextArea->Focus(); + if (!(NS_SUCCEEDED(rv) && + mMgr->mFocusedDocument && + mMgr->mFocusCount - focus == 2 && + mMgr->GetFocusedStore())) { + fail("TestFocus: document focus was not changed"); + return false; + } + + rv = mButton->Focus(); + if (!(NS_SUCCEEDED(rv) && + !mMgr->mFocusedDocument && + mMgr->mFocusCount - focus == 2 && + !mMgr->GetFocusedStore())) { + fail("TestFocus: document focus was changed"); + return false; + } + return true; +} + +bool +TestApp::TestClustering(void) +{ + // Text for testing + const uint32_t STRING_LENGTH = 2; + char16_t string[3]; + string[0] = 'e'; + string[1] = 0x0301; // U+0301 'acute accent' + string[2] = nullptr; + + if (!mMgr->GetFocusedStore()) { + fail("TestClustering: GetFocusedStore returns null #1"); + return false; + } + + // Replace entire string with our string + TS_TEXTCHANGE textChange; + HRESULT hr = + mMgr->GetFocusedStore()->SetText(0, 0, -1, string, STRING_LENGTH, + &textChange); + if (!(SUCCEEDED(hr) && + 0 == textChange.acpStart && + STRING_LENGTH == textChange.acpNewEnd)) { + fail("TestClustering: SetText"); + return false; + } + + TsViewCookie view; + RECT rectLetter, rectAccent, rectWhole, rectCombined; + BOOL clipped, nonEmpty; + + if (!mMgr->GetFocusedStore()) { + fail("TestClustering: GetFocusedStore returns null #2"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetActiveView(&view); + if (!(SUCCEEDED(hr))) { + fail("TestClustering: GetActiveView"); + return false; + } + + if (!mMgr->GetFocusedStore()) { + fail("TestClustering: GetFocusedStore returns null #3"); + return false; + } + + // Get rect of first char (the letter) + hr = mMgr->GetFocusedStore()->GetTextExt(view, 0, STRING_LENGTH / 2, + &rectLetter, &clipped); + if (!(SUCCEEDED(hr))) { + fail("TestClustering: GetTextExt (letter)"); + return false; + } + + if (!mMgr->GetFocusedStore()) { + fail("TestClustering: GetFocusedStore returns null #4"); + return false; + } + + // Get rect of second char (the accent) + hr = mMgr->GetFocusedStore()->GetTextExt(view, STRING_LENGTH / 2, + STRING_LENGTH, + &rectAccent, &clipped); + if (!(SUCCEEDED(hr))) { + fail("TestClustering: GetTextExt (accent)"); + return false; + } + + if (!mMgr->GetFocusedStore()) { + fail("TestClustering: GetFocusedStore returns null #5"); + return false; + } + + // Get rect of combined char + hr = mMgr->GetFocusedStore()->GetTextExt(view, 0, STRING_LENGTH, + &rectWhole, &clipped); + if (!(SUCCEEDED(hr))) { + fail("TestClustering: GetTextExt (whole)"); + return false; + } + + nonEmpty = ::UnionRect(&rectCombined, &rectLetter, &rectAccent); + if (!(nonEmpty && + ::EqualRect(&rectCombined, &rectWhole))) { + fail("TestClustering: unexpected combined rect"); + return false; + } + return true; +} + +bool +TestApp::TestSelectionInternal(char* aTestName, + LONG aStart, + LONG aEnd, + TsActiveSelEnd aSelEnd) +{ + bool succeeded = true, continueTest = true; + TS_SELECTION_ACP sel, testSel; + ULONG selFetched; + + if (!mMgr->GetFocusedStore()) { + fail("TestSelectionInternal: GetFocusedStore returns null #1"); + return false; + } + + sel.acpStart = aStart; + sel.acpEnd = aEnd; + sel.style.ase = aSelEnd; + sel.style.fInterimChar = FALSE; + HRESULT hr = mMgr->GetFocusedStore()->SetSelection(1, &sel); + if (!(SUCCEEDED(hr))) { + fail("TestSelection: SetSelection (%s)", aTestName); + continueTest = succeeded = false; + } + + if (continueTest) { + if (!mMgr->GetFocusedStore()) { + fail("TestSelectionInternal: GetFocusedStore returns null #2"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetSelection(TS_DEFAULT_SELECTION, 1, + &testSel, &selFetched); + if (!(SUCCEEDED(hr) && + selFetched == 1 && + !memcmp(&sel, &testSel, sizeof(sel)))) { + fail("TestSelection: unexpected GetSelection result (%s)", aTestName); + succeeded = false; + } + } + return succeeded; +} + +bool +TestApp::TestSelection(void) +{ + bool succeeded = true; + + /* If these fail the cause is probably one or more of: + * nsTextStore::GetSelection not sending NS_QUERY_SELECTED_TEXT + * NS_QUERY_SELECTED_TEXT not handled by ContentEventHandler + * Bug in NS_QUERY_SELECTED_TEXT handler + * nsTextStore::SetSelection not sending NS_SELECTION_SET + * NS_SELECTION_SET not handled by ContentEventHandler + * Bug in NS_SELECTION_SET handler + */ + + TS_SELECTION_ACP testSel; + ULONG selFetched; + + if (!mMgr->GetFocusedStore()) { + fail("TestSelection: GetFocusedStore returns null"); + return false; + } + + HRESULT hr = + mMgr->GetFocusedStore()->GetSelection(0, 1, &testSel, &selFetched); + if (!(SUCCEEDED(hr) && + selFetched == 1)) { + fail("TestSelection: GetSelection"); + succeeded = false; + } + + const LONG SELECTION1_START = 0; + const LONG SELECTION1_END = mTestString.Length(); + const TsActiveSelEnd SELECTION1_SELEND = TS_AE_END; + + if (!TestSelectionInternal("normal", + SELECTION1_START, + SELECTION1_END, + SELECTION1_SELEND)) { + succeeded = false; + } + + const LONG SELECTION2_START = mTestString.Length() / 2; + const LONG SELECTION2_END = SELECTION2_START; + const TsActiveSelEnd SELECTION2_SELEND = TS_AE_END; + + if (!TestSelectionInternal("collapsed", + SELECTION2_START, + SELECTION2_END, + SELECTION2_SELEND)) { + succeeded = false; + } + + const LONG SELECTION3_START = 12; + const LONG SELECTION3_END = mTestString.Length() - 20; + const TsActiveSelEnd SELECTION3_SELEND = TS_AE_START; + + if (!TestSelectionInternal("reversed", + SELECTION3_START, + SELECTION3_END, + SELECTION3_SELEND)) { + succeeded = false; + } + return succeeded; +} + +bool +TestApp::TestText(void) +{ + const uint32_t BUFFER_SIZE = (0x100); + const uint32_t RUNINFO_SIZE = (0x10); + + bool succeeded = true, continueTest; + char16_t buffer[BUFFER_SIZE]; + TS_RUNINFO runInfo[RUNINFO_SIZE]; + ULONG bufferRet, runInfoRet; + LONG acpRet, acpCurrent; + TS_TEXTCHANGE textChange; + HRESULT hr; + + /* If these fail the cause is probably one or more of: + * nsTextStore::GetText not sending NS_QUERY_TEXT_CONTENT + * NS_QUERY_TEXT_CONTENT not handled by ContentEventHandler + * Bug in NS_QUERY_TEXT_CONTENT handler + * nsTextStore::SetText not calling SetSelection or InsertTextAtSelection + * Bug in SetSelection or InsertTextAtSelection + * NS_SELECTION_SET bug or NS_COMPOSITION_* / NS_TEXT_TEXT bug + */ + + if (!mMgr->GetFocusedStore()) { + fail("TestText: GetFocusedStore returns null #1"); + return false; + } + + // Get all text + hr = mMgr->GetFocusedStore()->GetText(0, -1, buffer, BUFFER_SIZE, &bufferRet, + runInfo, RUNINFO_SIZE, &runInfoRet, + &acpRet); + if (!(SUCCEEDED(hr) && + bufferRet <= mTestString.Length() && + !wcsncmp(mTestString.get(), buffer, bufferRet) && + acpRet == LONG(bufferRet) && + runInfoRet > 0)) { + fail("TestText: GetText 1"); + succeeded = false; + } + + + if (!mMgr->GetFocusedStore()) { + fail("TestText: GetFocusedStore returns null #2"); + return false; + } + + // Get text from GETTEXT2_START to GETTEXT2_END + const uint32_t GETTEXT2_START = (18); + const uint32_t GETTEXT2_END = (mTestString.Length() - 16); + const uint32_t GETTEXT2_BUFFER_SIZE = (0x10); + + hr = mMgr->GetFocusedStore()->GetText(GETTEXT2_START, GETTEXT2_END, + buffer, GETTEXT2_BUFFER_SIZE, + &bufferRet, runInfo, RUNINFO_SIZE, + &runInfoRet, &acpRet); + if (!(SUCCEEDED(hr) && + bufferRet <= GETTEXT2_BUFFER_SIZE && + !wcsncmp(mTestString.get() + GETTEXT2_START, buffer, bufferRet) && + acpRet == LONG(bufferRet) + GETTEXT2_START && + runInfoRet > 0)) { + fail("TestText: GetText 2"); + succeeded = false; + } + + + if (!mMgr->GetFocusedStore()) { + fail("TestText: GetFocusedStore returns null #3"); + return false; + } + + // Replace text from SETTEXT1_START to SETTEXT1_END with insertString + const uint32_t SETTEXT1_START = (8); + const uint32_t SETTEXT1_TAIL_LENGTH = (40); + const uint32_t SETTEXT1_END = (mTestString.Length() - + SETTEXT1_TAIL_LENGTH); + NS_NAMED_LITERAL_STRING(insertString, "(Inserted string)"); + + continueTest = true; + hr = mMgr->GetFocusedStore()->SetText(0, SETTEXT1_START, SETTEXT1_END, + insertString.get(), + insertString.Length(), &textChange); + if (!(SUCCEEDED(hr) && + textChange.acpStart == SETTEXT1_START && + textChange.acpOldEnd == LONG(SETTEXT1_END) && + textChange.acpNewEnd == LONG(SETTEXT1_START + + insertString.Length()))) { + fail("TestText: SetText 1"); + continueTest = succeeded = false; + } + + const uint32_t SETTEXT1_FINAL_LENGTH = (SETTEXT1_START + + SETTEXT1_TAIL_LENGTH + + insertString.Length()); + + if (continueTest) { + acpCurrent = 0; + while (acpCurrent < LONG(SETTEXT1_FINAL_LENGTH)) { + if (!mMgr->GetFocusedStore()) { + fail("TestText: GetFocusedStore returns null #4"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetText(acpCurrent, -1, &buffer[acpCurrent], + BUFFER_SIZE, &bufferRet, runInfo, + RUNINFO_SIZE, &runInfoRet, &acpRet); + if (!(SUCCEEDED(hr) && + acpRet > acpCurrent && + bufferRet <= SETTEXT1_FINAL_LENGTH && + runInfoRet > 0)) { + fail("TestText: GetText failed after SetTest 1"); + continueTest = succeeded = false; + break; + } + acpCurrent = acpRet; + } + } + + if (continueTest) { + if (!(acpCurrent == LONG(SETTEXT1_FINAL_LENGTH) && + !wcsncmp(buffer, mTestString.get(), SETTEXT1_START) && + !wcsncmp(&buffer[SETTEXT1_START], insertString.get(), + insertString.Length()) && + !wcsncmp(&buffer[SETTEXT1_START + insertString.Length()], + mTestString.get() + SETTEXT1_END, SETTEXT1_TAIL_LENGTH))) { + fail("TestText: unexpected GetText result after SetText 1"); + succeeded = false; + } + } + + + if (!mMgr->GetFocusedStore()) { + fail("TestText: GetFocusedStore returns null #5"); + return false; + } + + // Restore entire text to original text (mTestString) + continueTest = true; + hr = mMgr->GetFocusedStore()->SetText(0, 0, -1, mTestString.get(), + mTestString.Length(), &textChange); + if (!(SUCCEEDED(hr) && + textChange.acpStart == 0 && + textChange.acpOldEnd == LONG(SETTEXT1_FINAL_LENGTH) && + textChange.acpNewEnd == LONG(mTestString.Length()))) { + fail("TestText: SetText 2"); + continueTest = succeeded = false; + } + + if (continueTest) { + acpCurrent = 0; + while (acpCurrent < LONG(mTestString.Length())) { + if (!mMgr->GetFocusedStore()) { + fail("TestText: GetFocusedStore returns null #6"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetText(acpCurrent, -1, &buffer[acpCurrent], + BUFFER_SIZE, &bufferRet, runInfo, + RUNINFO_SIZE, &runInfoRet, &acpRet); + if (!(SUCCEEDED(hr) && + acpRet > acpCurrent && + bufferRet <= mTestString.Length() && + runInfoRet > 0)) { + fail("TestText: GetText failed after SetText 2"); + continueTest = succeeded = false; + break; + } + acpCurrent = acpRet; + } + } + + if (continueTest) { + if (!(acpCurrent == LONG(mTestString.Length()) && + !wcsncmp(buffer, mTestString.get(), mTestString.Length()))) { + fail("TestText: unexpected GetText result after SetText 2"); + succeeded = false; + } + } + return succeeded; +} + +bool +TestApp::TestExtents(void) +{ + if (!mMgr->GetFocusedStore()) { + fail("TestExtents: GetFocusedStore returns null #1"); + return false; + } + + TS_SELECTION_ACP sel; + sel.acpStart = 0; + sel.acpEnd = 0; + sel.style.ase = TS_AE_END; + sel.style.fInterimChar = FALSE; + mMgr->GetFocusedStore()->SetSelection(1, &sel); + + nsCOMPtr selCon; + if (!(NS_SUCCEEDED(GetSelCon(getter_AddRefs(selCon))) && selCon)) { + fail("TestExtents: get nsISelectionController"); + return false; + } + selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_FOCUS_REGION, true); + + nsCOMPtr window(do_GetInterface(mWindow)); + if (!window) { + fail("TestExtents: get nsIDOMWindow"); + return false; + } + RECT windowRect, screenRect, textRect1, textRect2; + BOOL clipped; + int32_t val; + TsViewCookie view; + HRESULT hr; + + nsresult nsr = window->GetScreenX(&val); + windowRect.left = val; + nsr |= window->GetScreenY(&val); + windowRect.top = val; + nsr |= window->GetOuterWidth(&val); + windowRect.right = windowRect.left + val; + nsr |= window->GetOuterHeight(&val); + windowRect.bottom = windowRect.top + val; + if (!(NS_SUCCEEDED(nsr))) { + fail("TestExtents: get window rect failed"); + return false; + } + + if (!mMgr->GetFocusedStore()) { + fail("TestExtents: GetFocusedStore returns null #2"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetActiveView(&view); + if (!(SUCCEEDED(hr))) { + fail("TestExtents: GetActiveView"); + return false; + } + + if (!mMgr->GetFocusedStore()) { + fail("TestExtents: GetFocusedStore returns null #3"); + return false; + } + + bool succeeded = true; + HWND hwnd; + hr = mMgr->GetFocusedStore()->GetWnd(view, &hwnd); + if (!(SUCCEEDED(hr) && + ::IsWindow(hwnd))) { + fail("TestExtents: GetWnd"); + succeeded = false; + } + + ::SetRectEmpty(&screenRect); + + if (!mMgr->GetFocusedStore()) { + fail("TestExtents: GetFocusedStore returns null #4"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetScreenExt(view, &screenRect); + if (!(SUCCEEDED(hr) && + screenRect.left > windowRect.left && + screenRect.top > windowRect.top && + screenRect.right > screenRect.left && + screenRect.bottom > screenRect.top && + screenRect.right < windowRect.right && + screenRect.bottom < windowRect.bottom)) { + fail("TestExtents: GetScreenExt"); + succeeded = false; + } + + const LONG GETTEXTEXT1_START = 0; + const LONG GETTEXTEXT1_END = 0; + + ::SetRectEmpty(&textRect1); + + if (!mMgr->GetFocusedStore()) { + fail("TestExtents: GetFocusedStore returns null #5"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetTextExt(view, GETTEXTEXT1_START, + GETTEXTEXT1_END, &textRect1, + &clipped); + if (!(SUCCEEDED(hr) && + textRect1.left >= screenRect.left && + textRect1.top >= screenRect.top && + textRect1.right < screenRect.right && + textRect1.bottom <= screenRect.bottom && + textRect1.right >= textRect1.left && + textRect1.bottom > textRect1.top)) { + fail("TestExtents: GetTextExt (offset %ld to %ld)", + GETTEXTEXT1_START, GETTEXTEXT1_END); + succeeded = false; + } + + const LONG GETTEXTEXT2_START = 10; + const LONG GETTEXTEXT2_END = 25; + + ::SetRectEmpty(&textRect2); + + if (!mMgr->GetFocusedStore()) { + fail("TestExtents: GetFocusedStore returns null #6"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetTextExt(view, GETTEXTEXT2_START, + GETTEXTEXT2_END, &textRect2, + &clipped); + if (!(SUCCEEDED(hr) && + textRect2.left >= screenRect.left && + textRect2.top >= screenRect.top && + textRect2.right <= screenRect.right && + textRect2.bottom <= screenRect.bottom && + textRect2.right > textRect2.left && + textRect2.bottom > textRect2.top)) { + fail("TestExtents: GetTextExt (offset %ld to %ld)", + GETTEXTEXT2_START, GETTEXTEXT2_END); + succeeded = false; + } + + // Offsets must be between GETTEXTEXT2_START and GETTEXTEXT2_END + const LONG GETTEXTEXT3_START = 23; + const LONG GETTEXTEXT3_END = 23; + + ::SetRectEmpty(&textRect1); + + if (!mMgr->GetFocusedStore()) { + fail("TestExtents: GetFocusedStore returns null #7"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetTextExt(view, GETTEXTEXT3_START, + GETTEXTEXT3_END, &textRect1, + &clipped); + // Rectangle must be entirely inside the previous rectangle, + // since GETTEXTEXT3_START and GETTEXTEXT3_END are between + // GETTEXTEXT2_START and GETTEXTEXT2_START + if (!(SUCCEEDED(hr) && ::IsRectEmpty(&textRect1) || + (textRect1.left >= textRect2.left && + textRect1.top >= textRect2.top && + textRect1.right <= textRect2.right && + textRect1.bottom <= textRect2.bottom && + textRect1.right >= textRect1.left && + textRect1.bottom > textRect1.top))) { + fail("TestExtents: GetTextExt (offset %ld to %ld)", + GETTEXTEXT3_START, GETTEXTEXT3_END); + succeeded = false; + } + return succeeded; +} + +bool +TestApp::TestCompositionSelectionAndText(char* aTestName, + LONG aExpectedSelStart, + LONG aExpectedSelEnd, + nsString& aReferenceString) +{ + if (!mMgr->GetFocusedStore()) { + fail("TestCompositionSelectionAndText: GetFocusedStore returns null #1"); + return false; + } + + TS_SELECTION_ACP currentSel; + ULONG selFetched = 0; + HRESULT hr = mMgr->GetFocusedStore()->GetSelection(TF_DEFAULT_SELECTION, 1, + ¤tSel, &selFetched); + if (!(SUCCEEDED(hr) && + 1 == selFetched && + currentSel.acpStart == aExpectedSelStart && + currentSel.acpEnd == aExpectedSelEnd)) { + fail("TestComposition: GetSelection (%s)", aTestName); + return false; + } + + const uint32_t bufferSize = 0x100, runInfoSize = 0x10; + char16_t buffer[bufferSize]; + TS_RUNINFO runInfo[runInfoSize]; + ULONG bufferRet, runInfoRet; + LONG acpRet, acpCurrent = 0; + while (acpCurrent < LONG(aReferenceString.Length())) { + if (!mMgr->GetFocusedStore()) { + fail("TestCompositionSelectionAndText: GetFocusedStore returns null #2"); + return false; + } + + hr = mMgr->GetFocusedStore()->GetText(acpCurrent, aReferenceString.Length(), + &buffer[acpCurrent], bufferSize, + &bufferRet, runInfo, runInfoSize, + &runInfoRet, &acpRet); + if (!(SUCCEEDED(hr) && + acpRet > acpCurrent && + bufferRet <= aReferenceString.Length() && + runInfoRet > 0)) { + fail("TestComposition: GetText (%s)", aTestName); + return false; + } + acpCurrent = acpRet; + } + if (!(acpCurrent == aReferenceString.Length() && + !wcsncmp(buffer, aReferenceString.get(), aReferenceString.Length()))) { + fail("TestComposition: unexpected GetText result (%s)", aTestName); + return false; + } + return true; +} + +bool +TestApp::TestComposition(void) +{ + if (!mMgr->GetFocusedStore()) { + fail("TestComposition: GetFocusedStore returns null #1"); + return false; + } + + nsRefPtr sink; + HRESULT hr = + mMgr->GetFocusedStore()->QueryInterface(IID_ITfContextOwnerCompositionSink, + getter_AddRefs(sink)); + if (!(SUCCEEDED(hr))) { + fail("TestComposition: QueryInterface"); + return false; + } + + const LONG PRECOMPOSITION_SEL_START = 2; + const LONG PRECOMPOSITION_SEL_END = PRECOMPOSITION_SEL_START; + const TsActiveSelEnd PRECOMPOSITION_SEL_SELEND = TS_AE_END; + + TS_SELECTION_ACP sel; + sel.acpStart = PRECOMPOSITION_SEL_START; + sel.acpEnd = PRECOMPOSITION_SEL_END; + sel.style.ase = PRECOMPOSITION_SEL_SELEND; + sel.style.fInterimChar = FALSE; + hr = mMgr->GetFocusedStore()->SetSelection(1, &sel); + if (!(SUCCEEDED(hr))) { + fail("TestComposition: SetSelection (pre-composition)"); + return false; + } + + if (!mMgr->GetFocusedStore()) { + fail("TestComposition: GetFocusedStore returns null #2"); + return false; + } + + TS_TEXTCHANGE textChange; + NS_NAMED_LITERAL_STRING(insertString1, "Compo1"); + hr = mMgr->GetFocusedStore()->InsertTextAtSelection(TF_IAS_NOQUERY, + insertString1.get(), + insertString1.Length(), + nullptr, nullptr, + &textChange); + if (!(SUCCEEDED(hr) && + sel.acpEnd == textChange.acpStart && + sel.acpEnd == textChange.acpOldEnd && + sel.acpEnd + insertString1.Length() == textChange.acpNewEnd)) { + fail("TestComposition: InsertTextAtSelection"); + return false; + } + sel.acpEnd = textChange.acpNewEnd; + + if (!mMgr->GetFocusedAttrProp()) { + fail("TestComposition: GetFocusedAttrProp returns null #1"); + return false; + } + mMgr->GetFocusedAttrProp()->mRanges.Clear(); + nsRefPtr range = + new TSFRangeImpl(textChange.acpStart, + textChange.acpNewEnd - textChange.acpOldEnd); + if (!mMgr->GetFocusedAttrProp()) { + fail("TestComposition: GetFocusedAttrProp returns null #2"); + return false; + } + mMgr->GetFocusedAttrProp()->mRanges.AppendElement(range); + + BOOL okay = FALSE; + hr = sink->OnStartComposition(mMgr->GetFocusedContext(), &okay); + if (!(SUCCEEDED(hr) && + okay)) { + fail("TestComposition: OnStartComposition"); + return false; + } + + + if (!mMgr->GetFocusedStore()) { + fail("TestComposition: GetFocusedStore returns null #3"); + return false; + } + + NS_NAMED_LITERAL_STRING(insertString2, "Composition2"); + hr = mMgr->GetFocusedStore()->SetText(0, range->mStart + range->mLength, + range->mStart + range->mLength, + insertString2.get(), + insertString2.Length(), + &textChange); + if (!(SUCCEEDED(hr) && + sel.acpEnd == textChange.acpStart && + sel.acpEnd == textChange.acpOldEnd && + sel.acpEnd + insertString2.Length() == textChange.acpNewEnd)) { + fail("TestComposition: SetText 1"); + return false; + } + sel.acpEnd = textChange.acpNewEnd; + range->mLength += textChange.acpNewEnd - textChange.acpOldEnd; + + + if (!mMgr->GetFocusedStore()) { + fail("TestComposition: GetFocusedStore returns null #4"); + return false; + } + + const LONG COMPOSITION3_TEXT_START_OFFSET = -8; // offset 8 from the end + const LONG COMPOSITION3_TEXT_END_OFFSET = 4; + + const LONG COMPOSITION3_TEXT_START = range->mStart + range->mLength + + COMPOSITION3_TEXT_START_OFFSET; + const LONG COMPOSITION3_TEXT_END = COMPOSITION3_TEXT_START + + COMPOSITION3_TEXT_END_OFFSET; + + NS_NAMED_LITERAL_STRING(insertString3, "Compo3"); + hr = mMgr->GetFocusedStore()->SetText(0, COMPOSITION3_TEXT_START, + COMPOSITION3_TEXT_END, + insertString3.get(), + insertString3.Length(), + &textChange); + if (!(SUCCEEDED(hr) && + sel.acpEnd + COMPOSITION3_TEXT_START_OFFSET == textChange.acpStart && + sel.acpEnd + COMPOSITION3_TEXT_START_OFFSET + + COMPOSITION3_TEXT_END_OFFSET == textChange.acpOldEnd && + sel.acpEnd + insertString3.Length() + COMPOSITION3_TEXT_START_OFFSET == + textChange.acpNewEnd)) { + fail("TestComposition: SetText 2"); + return false; + } + sel.acpEnd = textChange.acpNewEnd; + range->mLength += textChange.acpNewEnd - textChange.acpOldEnd; + + + nsString referenceString; + referenceString.Append(mTestString.get(), sel.acpStart); + referenceString.Append(insertString1); + referenceString.Append(insertString2.get(), + insertString2.Length() + COMPOSITION3_TEXT_START_OFFSET); + referenceString.Append(insertString3); + referenceString.Append(insertString2.get() + insertString2.Length() - + COMPOSITION3_TEXT_END_OFFSET, COMPOSITION3_TEXT_END_OFFSET); + referenceString.Append(mTestString.get() + sel.acpStart, + COMPOSITION3_TEXT_END_OFFSET); + + if (!TestCompositionSelectionAndText("composition", + sel.acpEnd, sel.acpEnd, + referenceString)) + return false; + + + if (!mMgr->GetFocusedStore()) { + fail("TestComposition: GetFocusedStore returns null #5"); + return false; + } + + const LONG POSTCOMPOSITION_SEL_START = sel.acpEnd - 8; + const LONG POSTCOMPOSITION_SEL_END = POSTCOMPOSITION_SEL_START + 2; + + sel.acpStart = POSTCOMPOSITION_SEL_START; + sel.acpEnd = POSTCOMPOSITION_SEL_END; + hr = mMgr->GetFocusedStore()->SetSelection(1, &sel); + if (!(SUCCEEDED(hr))) { + fail("TestComposition: SetSelection (composition)"); + return false; + } + + if (!mMgr->GetFocusedAttrProp()) { + fail("TestComposition: GetFocusedAttrProp returns null #3"); + return false; + } + mMgr->GetFocusedAttrProp()->mRanges.Clear(); + + hr = sink->OnEndComposition(mMgr->GetFocusedContext()); + if (!(SUCCEEDED(hr))) { + fail("TestComposition: OnEndComposition"); + return false; + } + + if (!TestCompositionSelectionAndText("post-composition", + sel.acpStart, sel.acpEnd, + referenceString)) + return false; + + const LONG EMPTYCOMPOSITION_START = range->mStart + 2; + const LONG EMPTYCOMPOSITION_LENGTH = range->mLength - 4; + + range->mStart = EMPTYCOMPOSITION_START; + range->mLength = EMPTYCOMPOSITION_LENGTH; + if (!mMgr->GetFocusedAttrProp()) { + fail("TestComposition: GetFocusedAttrProp returns null #4"); + return false; + } + mMgr->GetFocusedAttrProp()->mRanges.AppendElement(range); + + okay = FALSE; + hr = sink->OnStartComposition(mMgr->GetFocusedContext(), &okay); + if (!(SUCCEEDED(hr) && + okay)) { + fail("TestComposition: OnStartComposition (empty composition)"); + return false; + } + + if (!mMgr->GetFocusedAttrProp()) { + fail("TestComposition: GetFocusedAttrProp returns null #5"); + return false; + } + mMgr->GetFocusedAttrProp()->mRanges.Clear(); + + hr = sink->OnEndComposition(mMgr->GetFocusedContext()); + if (!(SUCCEEDED(hr))) { + fail("TestComposition: OnEndComposition (empty composition)"); + return false; + } + + if (!TestCompositionSelectionAndText("empty composition", + range->mStart, range->mStart + range->mLength, + referenceString)) + return false; + + return true; +} + +bool +TestApp::TestNotificationTextChange(nsIWidget* aWidget, + uint32_t aCode, + const nsAString& aCharacter, + LONG aStart, + LONG aOldEnd, + LONG aNewEnd) +{ + MSG msg; + if (::PeekMessageW(&msg, nullptr, WM_USER_TSF_TEXTCHANGE, + WM_USER_TSF_TEXTCHANGE, PM_REMOVE)) + ::DispatchMessageW(&msg); + if (!mMgr->GetFocusedContext()) { + fail("TestNotificationTextChange: GetFocusedContext returns null"); + return false; + } + mMgr->GetFocusedContext()->mTextChanged = false; + nsresult nsr = aWidget->SynthesizeNativeKeyEvent(0, aCode, 0, + aCharacter, aCharacter); + if (::PeekMessageW(&msg, nullptr, WM_USER_TSF_TEXTCHANGE, + WM_USER_TSF_TEXTCHANGE, PM_REMOVE)) + ::DispatchMessageW(&msg); + return NS_SUCCEEDED(nsr) && + mMgr->GetFocusedContext()->mTextChanged && + aStart == mMgr->GetFocusedContext()->mTextChangeData.acpStart && + aOldEnd == mMgr->GetFocusedContext()->mTextChangeData.acpOldEnd && + aNewEnd == mMgr->GetFocusedContext()->mTextChangeData.acpNewEnd; +} + +bool +TestApp::TestNotification(void) +{ + nsresult nsr; + // get selection to test notification support + nsCOMPtr selCon; + if (!(NS_SUCCEEDED(GetSelCon(getter_AddRefs(selCon))) && selCon)) { + fail("TestNotification: get nsISelectionController"); + return false; + } + + nsr = selCon->CompleteMove(false, false); + if (!(NS_SUCCEEDED(nsr))) { + fail("TestNotification: CompleteMove"); + return false; + } + + if (!mMgr->GetFocusedContext()) { + fail("TestNotification: GetFocusedContext returns null #1"); + return false; + } + + mMgr->GetFocusedContext()->mSelChanged = false; + nsr = selCon->CharacterMove(true, false); + if (!(NS_SUCCEEDED(nsr) && + mMgr->GetFocusedContext()->mSelChanged)) { + fail("TestNotification: CharacterMove"); + return false; + } + + if (!mMgr->GetFocusedContext()) { + fail("TestNotification: GetFocusedContext returns null #2"); + return false; + } + + mMgr->GetFocusedContext()->mSelChanged = false; + nsr = selCon->CharacterMove(true, true); + if (!(NS_SUCCEEDED(nsr) && + mMgr->GetFocusedContext()->mSelChanged)) { + fail("TestNotification: CharacterMove (extend)"); + return false; + } + + nsCOMPtr widget; + if (!GetWidget(getter_AddRefs(widget))) { + fail("TestNotification: get nsIWidget"); + return false; + } + + NS_NAMED_LITERAL_STRING(character, ""); + NS_NAMED_LITERAL_STRING(characterA, "A"); + + // The selection test code above placed the selection at offset 1 to 2 + const LONG TEXTCHANGE1_START = 1; + const LONG TEXTCHANGE1_OLDEND = 2; + const LONG TEXTCHANGE1_NEWEND = 2; + + // replace single selected character with 'A' + if (!TestNotificationTextChange(widget, 'A', characterA, + TEXTCHANGE1_START, TEXTCHANGE1_OLDEND, TEXTCHANGE1_NEWEND)) { + fail("TestNotification: text change 1"); + return false; + } + + const LONG TEXTCHANGE2_START = TEXTCHANGE1_NEWEND; + const LONG TEXTCHANGE2_OLDEND = TEXTCHANGE1_NEWEND; + const LONG TEXTCHANGE2_NEWEND = TEXTCHANGE1_NEWEND + 1; + + // insert 'A' + if (!TestNotificationTextChange(widget, 'A', characterA, + TEXTCHANGE2_START, TEXTCHANGE2_OLDEND, TEXTCHANGE2_NEWEND)) { + fail("TestNotification: text change 2"); + return false; + } + + const LONG TEXTCHANGE3_START = TEXTCHANGE2_NEWEND - 1; + const LONG TEXTCHANGE3_OLDEND = TEXTCHANGE2_NEWEND; + const LONG TEXTCHANGE3_NEWEND = TEXTCHANGE2_NEWEND - 1; + + // backspace + if (!TestNotificationTextChange(widget, '\b', character, + TEXTCHANGE3_START, TEXTCHANGE3_OLDEND, TEXTCHANGE3_NEWEND)) { + fail("TestNotification: text change 3"); + return false; + } + return true; +} + +bool +TestApp::TestEditMessages(void) +{ + mTestString = NS_LITERAL_STRING( + "This is a test of\nthe native editing command messages"); + // 0123456789012345678901 2345678901234567890123456789012 + // 0 1 2 3 4 5 + + // The native text string is increased by converting \n to \r\n. + uint32_t testStringLength = mTestString.Length() + 1; + + mTextArea->SetValue(mTestString); + mTextArea->Focus(); + + nsCOMPtr widget; + if (!GetWidget(getter_AddRefs(widget))) { + fail("TestEditMessages: get nsIWidget"); + return false; + } + + HWND wnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW); + bool result = true; + + if (!::SendMessage(wnd, EM_CANUNDO, 0, 0)) { + fail("TestEditMessages: EM_CANUNDO"); + return false; + } + + if (::SendMessage(wnd, EM_CANREDO, 0, 0)) { + fail("TestEditMessages: EM_CANREDO #1"); + return false; + } + + + if (!::SendMessage(wnd, EM_UNDO, 0, 0)) { + fail("TestEditMessages: EM_UNDO #1"); + return false; + } + + nsAutoString str; + mTextArea->GetValue(str); + if (str == mTestString) { + fail("TestEditMessage: EM_UNDO #1, failed to execute"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + if (!::SendMessage(wnd, EM_CANREDO, 0, 0)) { + fail("TestEditMessages: EM_CANREDO #2"); + return false; + } + + if (!::SendMessage(wnd, EM_REDO, 0, 0)) { + fail("TestEditMessages: EM_REDO #1"); + return false; + } + + mTextArea->GetValue(str); + if (str != mTestString) { + fail("TestEditMessage: EM_REDO #1, failed to execute"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + TS_SELECTION_ACP sel; + HRESULT hr; + + sel.acpStart = 0; + sel.acpEnd = testStringLength; + sel.style.ase = TS_AE_END; + sel.style.fInterimChar = FALSE; + hr = mMgr->GetFocusedStore()->SetSelection(1, &sel); + if (!(SUCCEEDED(hr))) { + fail("TestEditMessages: SetSelection #1"); + return false; + } + + ::SendMessage(wnd, WM_CUT, 0, 0); + mTextArea->GetValue(str); + if (!str.IsEmpty()) { + fail("TestEditMessages: WM_CUT"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + ::SendMessage(wnd, WM_PASTE, 0, 0); + mTextArea->GetValue(str); + if (str != mTestString) { + fail("TestEditMessages: WM_PASTE #1"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + ::SendMessage(wnd, WM_PASTE, 0, 0); + mTextArea->GetValue(str); + nsAutoString expectedStr(mTestString); + expectedStr += mTestString; + if (str != expectedStr) { + fail("TestEditMessages: WM_PASTE #2"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + sel.acpStart = 0; + sel.acpEnd = testStringLength; + hr = mMgr->GetFocusedStore()->SetSelection(1, &sel); + if (!(SUCCEEDED(hr))) { + fail("TestEditMessages: SetSelection #2"); + return false; + } + + ::SendMessage(wnd, WM_CLEAR, 0, 0); + mTextArea->GetValue(str); + if (str != mTestString) { + fail("TestEditMessages: WM_CLEAR #1"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + sel.acpStart = 4; + sel.acpEnd = testStringLength; + hr = mMgr->GetFocusedStore()->SetSelection(1, &sel); + if (!(SUCCEEDED(hr))) { + fail("TestEditMessages: SetSelection #3"); + return false; + } + + ::SendMessage(wnd, WM_COPY, 0, 0); + mTextArea->GetValue(str); + if (str != mTestString) { + fail("TestEditMessages: WM_COPY"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + if (!::SendMessage(wnd, EM_CANPASTE, 0, 0)) { + fail("TestEditMessages: EM_CANPASTE #1"); + return false; + } + + if (!::SendMessage(wnd, EM_CANPASTE, CF_TEXT, 0)) { + fail("TestEditMessages: EM_CANPASTE #2"); + return false; + } + + if (!::SendMessage(wnd, EM_CANPASTE, CF_UNICODETEXT, 0)) { + fail("TestEditMessages: EM_CANPASTE #3"); + return false; + } + + ::SendMessage(wnd, WM_PASTE, 0, 0); + mTextArea->GetValue(str); + if (str != mTestString) { + fail("TestEditMessages: WM_PASTE #3"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + sel.acpStart = 4; + sel.acpEnd = testStringLength; + hr = mMgr->GetFocusedStore()->SetSelection(1, &sel); + if (!(SUCCEEDED(hr))) { + fail("TestEditMessages: SetSelection #3"); + return false; + } + + ::SendMessage(wnd, WM_CLEAR, 0, 0); + mTextArea->GetValue(str); + if (str != NS_LITERAL_STRING("This")) { + fail("TestEditMessages: WM_CLEAR #2"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + ::SendMessage(wnd, WM_PASTE, 0, 0); + mTextArea->GetValue(str); + if (str != mTestString) { + fail("TestEditMessages: WM_PASTE #4"); + printf("Current Str: \"%s\"\n", NS_ConvertUTF16toUTF8(str).get()); + return false; + } + + return true; +} + +bool +TestApp::TestScrollMessages(void) +{ + NS_NAMED_LITERAL_STRING(kLine, "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss\n"); + mTestString.Truncate(); + for (uint32_t i = 0; i < 30; i++) { + mTestString.Append(kLine); + } + + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), + NS_LITERAL_STRING("width:3em;height:3em;word-wrap:normal;")); + mTextArea->SetValue(mTestString); + mTextArea->Focus(); + + nsCOMPtr widget; + if (!GetWidget(getter_AddRefs(widget))) { + fail("TestScrollMessages: get nsIWidget"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + nsCOMPtr textArea = do_QueryInterface(mTextArea); + if (!textArea) { + fail("TestScrollMessages: get nsIDOMElement"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + +#define DO_CHECK(aFailureCondition, aDescription) \ + if (aFailureCondition) { \ + nsAutoCString str(aDescription); \ + str.Append(": "); \ + str.Append(#aFailureCondition); \ + fail(str.get()); \ + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); \ + return false; \ + } + + HWND wnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW); + + textArea->SetScrollTop(0); + textArea->SetScrollLeft(0); + + if (::SendMessage(wnd, WM_VSCROLL, SB_LINEDOWN, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_VSCROLL #1"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + int32_t x, y, prevX, prevY; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_VSCROLL #1"); + DO_CHECK(y == 0, "TestScrollMessages: SendMessage WM_VSCROLL #1"); + + if (::SendMessage(wnd, WM_HSCROLL, SB_LINERIGHT, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_HSCROLL #1"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + const int32_t kLineWidth = x; + const int32_t kLineHeight = y; + + DO_CHECK(x == 0, "TestScrollMessages: SendMessage WM_HSCROLL #1"); + DO_CHECK(y != prevY, "TestScrollMessages: SendMessage WM_HSCROLL #1"); + + if (::SendMessage(wnd, WM_VSCROLL, SB_LINEUP, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_VSCROLL #2"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != prevX, "TestScrollMessages: SendMessage WM_VSCROLL #2"); + DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_VSCROLL #2"); + + if (::SendMessage(wnd, WM_HSCROLL, SB_LINELEFT, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_HSCROLL #2"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_HSCROLL #2"); + DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_HSCROLL #2"); + + if (::SendMessage(wnd, WM_VSCROLL, SB_PAGEDOWN, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_VSCROLL #3"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_VSCROLL #3"); + DO_CHECK(y <= kLineHeight, "TestScrollMessages: SendMessage WM_VSCROLL #3"); + + if (::SendMessage(wnd, WM_HSCROLL, SB_PAGERIGHT, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_HSCROLL #3"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x <= kLineWidth, "TestScrollMessages: SendMessage WM_HSCROLL #3"); + DO_CHECK(y != prevY, "TestScrollMessages: SendMessage WM_HSCROLL #3"); + + const int32_t kPageWidth = x; + const int32_t kPageHeight = y; + + ::SendMessage(wnd, WM_VSCROLL, SB_LINEDOWN, 0); + ::SendMessage(wnd, WM_VSCROLL, SB_LINEUP, 0); + ::SendMessage(wnd, WM_HSCROLL, SB_LINERIGHT, 0); + ::SendMessage(wnd, WM_HSCROLL, SB_LINELEFT, 0); + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != prevX, "TestScrollMessages: SB_LINELEFT scrolled wrong amount"); + DO_CHECK(y != prevY, "TestScrollMessages: SB_LINEUP scrolled wrong amount"); + + if (::SendMessage(wnd, WM_VSCROLL, SB_PAGEUP, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_VSCROLL #4"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != prevX, "TestScrollMessages: SendMessage WM_VSCROLL #4"); + DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_VSCROLL #4"); + + if (::SendMessage(wnd, WM_HSCROLL, SB_PAGELEFT, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_HSCROLL #4"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_HSCROLL #4"); + DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_HSCROLL #4"); + + if (::SendMessage(wnd, WM_VSCROLL, SB_BOTTOM, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_VSCROLL #5"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_VSCROLL #5"); + DO_CHECK(y <= kPageHeight, "TestScrollMessages: SendMessage WM_VSCROLL #5"); + + if (::SendMessage(wnd, WM_HSCROLL, SB_RIGHT, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_HSCROLL #6"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x <= kPageWidth, "TestScrollMessages: SendMessage WM_HSCROLL #5"); + DO_CHECK(y != prevY, "TestScrollMessages: SendMessage WM_HSCROLL #5"); + + ::SendMessage(wnd, WM_VSCROLL, SB_LINEDOWN, 0); + ::SendMessage(wnd, WM_HSCROLL, SB_LINERIGHT, 0); + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != prevX, "SB_RIGHT didn't scroll to right most"); + DO_CHECK(y != prevY, "SB_BOTTOM didn't scroll to bottom most"); + + ::SendMessage(wnd, WM_VSCROLL, SB_PAGEUP, 0); + ::SendMessage(wnd, WM_VSCROLL, SB_PAGEDOWN, 0); + ::SendMessage(wnd, WM_HSCROLL, SB_PAGELEFT, 0); + ::SendMessage(wnd, WM_HSCROLL, SB_PAGERIGHT, 0); + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != prevX, "TestScrollMessages: SB_PAGELEFT scrolled wrong amount"); + DO_CHECK(y != prevY, "TestScrollMessages: SB_PAGEUP scrolled wrong amount"); + + if (::SendMessage(wnd, WM_VSCROLL, SB_TOP, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_VSCROLL #6"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != prevX, "TestScrollMessages: SendMessage WM_VSCROLL #6"); + DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_VSCROLL #6"); + + if (::SendMessage(wnd, WM_HSCROLL, SB_LEFT, 0) != 0) { + fail("TestScrollMessages: SendMessage WM_HSCROLL #4"); + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return false; + } + + prevX = x; + prevY = y; + textArea->GetScrollTop(&y); + textArea->GetScrollLeft(&x); + + DO_CHECK(x != 0, "TestScrollMessages: SendMessage WM_HSCROLL #6"); + DO_CHECK(y != 0, "TestScrollMessages: SendMessage WM_HSCROLL #6"); +#undef DO_CHECK + + mTextArea->SetAttribute(NS_LITERAL_STRING("style"), EmptyString()); + return true; +} + +bool +TestApp::GetWidget(nsIWidget** aWidget) +{ + nsCOMPtr docShell; + nsresult rv = mWindow->GetDocShell(getter_AddRefs(docShell)); + if (NS_FAILED(rv) || !docShell) { + return false; + } + + nsCOMPtr presShell; + rv = docShell->GetPresShell(getter_AddRefs(presShell)); + if (NS_FAILED(rv) || !presShell) { + return false; + } + + nsRefPtr viewManager = presShell->GetViewManager(); + if (!viewManager) { + return false; + } + + rv = viewManager->GetRootWidget(aWidget); + return (NS_SUCCEEDED(rv) && aWidget); +} + +nsresult +TestApp::GetSelCon(nsISelectionController** aSelCon) +{ + nsCOMPtr docShell; + nsresult nsr = mWindow->GetDocShell(getter_AddRefs(docShell)); + if (NS_SUCCEEDED(nsr) && docShell) { + nsCOMPtr presShell; + nsr = docShell->GetPresShell(getter_AddRefs(presShell)); + if (NS_SUCCEEDED(nsr) && presShell) { + nsIFrame* frame = + nsCOMPtr(do_QueryInterface(mCurrentNode))->GetPrimaryFrame(); + if (frame) { + nsPresContext* presContext = presShell->GetPresContext(); + if (presContext) { + nsr = frame->GetSelectionController(presContext, aSelCon); + } + } + } + } + return nsr; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("TestWinTSF (bug #88831)"); + if (xpcom.failed()) + return 1; + + nsRefPtr tests = new TestApp(); + if (!tests) + return 1; + + if (NS_FAILED(tests->Run())) { + fail("run failed"); + return 1; + } + return int(tests->CheckFailed()); +}