зеркало из https://github.com/mozilla/gecko-dev.git
408 строки
12 KiB
C++
408 строки
12 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* vim: sw=2 ts=8 et :
|
|
*/
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/ContentCache.h"
|
|
#include "mozilla/IMEStateManager.h"
|
|
#include "mozilla/TextComposition.h"
|
|
#include "mozilla/TextEvents.h"
|
|
#include "nsIWidget.h"
|
|
#include "nsRefPtr.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace widget;
|
|
|
|
/*****************************************************************************
|
|
* mozilla::ContentCache
|
|
*****************************************************************************/
|
|
|
|
ContentCache::ContentCache()
|
|
: mCompositionStart(UINT32_MAX)
|
|
, mCompositionEventsDuringRequest(0)
|
|
, mIsComposing(false)
|
|
, mRequestedToCommitOrCancelComposition(false)
|
|
{
|
|
}
|
|
|
|
void
|
|
ContentCache::Clear()
|
|
{
|
|
mText.Truncate();
|
|
}
|
|
|
|
bool
|
|
ContentCache::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
|
|
nsIWidget* aWidget) const
|
|
{
|
|
MOZ_ASSERT(aWidget);
|
|
|
|
aEvent.mSucceeded = false;
|
|
aEvent.mWasAsync = false;
|
|
aEvent.mReply.mFocusedWidget = aWidget;
|
|
|
|
switch (aEvent.message) {
|
|
case NS_QUERY_SELECTED_TEXT:
|
|
aEvent.mReply.mOffset = mSelection.StartOffset();
|
|
if (mSelection.Collapsed()) {
|
|
aEvent.mReply.mString.Truncate(0);
|
|
} else {
|
|
if (NS_WARN_IF(SelectionEndIsGraterThanTextLength())) {
|
|
return false;
|
|
}
|
|
aEvent.mReply.mString =
|
|
Substring(mText, aEvent.mReply.mOffset, mSelection.Length());
|
|
}
|
|
aEvent.mReply.mReversed = mSelection.Reversed();
|
|
aEvent.mReply.mHasSelection = true;
|
|
aEvent.mReply.mWritingMode = mSelection.mWritingMode;
|
|
break;
|
|
case NS_QUERY_TEXT_CONTENT: {
|
|
uint32_t inputOffset = aEvent.mInput.mOffset;
|
|
uint32_t inputEndOffset =
|
|
std::min(aEvent.mInput.EndOffset(), mText.Length());
|
|
if (NS_WARN_IF(inputEndOffset < inputOffset)) {
|
|
return false;
|
|
}
|
|
aEvent.mReply.mOffset = inputOffset;
|
|
aEvent.mReply.mString =
|
|
Substring(mText, inputOffset, inputEndOffset - inputOffset);
|
|
break;
|
|
}
|
|
case NS_QUERY_TEXT_RECT:
|
|
if (NS_WARN_IF(!GetUnionTextRects(aEvent.mInput.mOffset,
|
|
aEvent.mInput.mLength,
|
|
aEvent.mReply.mRect))) {
|
|
// XXX We don't have cache for this request.
|
|
return false;
|
|
}
|
|
if (aEvent.mInput.mOffset < mText.Length()) {
|
|
aEvent.mReply.mString =
|
|
Substring(mText, aEvent.mInput.mOffset,
|
|
mText.Length() >= aEvent.mInput.EndOffset() ?
|
|
aEvent.mInput.mLength : UINT32_MAX);
|
|
} else {
|
|
aEvent.mReply.mString.Truncate(0);
|
|
}
|
|
aEvent.mReply.mOffset = aEvent.mInput.mOffset;
|
|
// XXX This may be wrong if storing range isn't in the selection range.
|
|
aEvent.mReply.mWritingMode = mSelection.mWritingMode;
|
|
break;
|
|
case NS_QUERY_CARET_RECT:
|
|
if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
|
|
aEvent.mReply.mRect))) {
|
|
// XXX If the input offset is in the range of cached text rects,
|
|
// we can guess the caret rect.
|
|
return false;
|
|
}
|
|
aEvent.mReply.mOffset = aEvent.mInput.mOffset;
|
|
break;
|
|
case NS_QUERY_EDITOR_RECT:
|
|
aEvent.mReply.mRect = mEditorRect;
|
|
break;
|
|
}
|
|
aEvent.mSucceeded = true;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::CacheAll(nsIWidget* aWidget)
|
|
{
|
|
if (NS_WARN_IF(!CacheText(aWidget)) ||
|
|
NS_WARN_IF(!CacheSelection(aWidget)) ||
|
|
NS_WARN_IF(!CacheTextRects(aWidget)) ||
|
|
NS_WARN_IF(!CacheEditorRect(aWidget))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::CacheSelection(nsIWidget* aWidget)
|
|
{
|
|
mCaret.Clear();
|
|
mSelection.Clear();
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWidget);
|
|
aWidget->DispatchEvent(&selection, status);
|
|
if (NS_WARN_IF(!selection.mSucceeded)) {
|
|
return false;
|
|
}
|
|
if (selection.mReply.mReversed) {
|
|
mSelection.mAnchor =
|
|
selection.mReply.mOffset + selection.mReply.mString.Length();
|
|
mSelection.mFocus = selection.mReply.mOffset;
|
|
} else {
|
|
mSelection.mAnchor = selection.mReply.mOffset;
|
|
mSelection.mFocus =
|
|
selection.mReply.mOffset + selection.mReply.mString.Length();
|
|
}
|
|
mSelection.mWritingMode = selection.GetWritingMode();
|
|
|
|
nsRefPtr<TextComposition> textComposition =
|
|
IMEStateManager::GetTextCompositionFor(aWidget);
|
|
if (textComposition) {
|
|
mCaret.mOffset = textComposition->OffsetOfTargetClause();
|
|
} else {
|
|
mCaret.mOffset = selection.mReply.mOffset;
|
|
}
|
|
|
|
status = nsEventStatus_eIgnore;
|
|
WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, aWidget);
|
|
caretRect.InitForQueryCaretRect(mCaret.mOffset);
|
|
aWidget->DispatchEvent(&caretRect, status);
|
|
if (NS_WARN_IF(!caretRect.mSucceeded)) {
|
|
mCaret.Clear();
|
|
return false;
|
|
}
|
|
mCaret.mRect = caretRect.mReply.mRect;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::CacheEditorRect(nsIWidget* aWidget)
|
|
{
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
WidgetQueryContentEvent editorRectEvent(true, NS_QUERY_EDITOR_RECT, aWidget);
|
|
aWidget->DispatchEvent(&editorRectEvent, status);
|
|
if (NS_WARN_IF(!editorRectEvent.mSucceeded)) {
|
|
return false;
|
|
}
|
|
mEditorRect = editorRectEvent.mReply.mRect;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::CacheText(nsIWidget* aWidget)
|
|
{
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
WidgetQueryContentEvent queryText(true, NS_QUERY_TEXT_CONTENT, aWidget);
|
|
queryText.InitForQueryTextContent(0, UINT32_MAX);
|
|
aWidget->DispatchEvent(&queryText, status);
|
|
if (NS_WARN_IF(!queryText.mSucceeded)) {
|
|
SetText(EmptyString());
|
|
return false;
|
|
}
|
|
SetText(queryText.mReply.mString);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::CacheTextRects(nsIWidget* aWidget)
|
|
{
|
|
mTextRectArray.Clear();
|
|
|
|
nsRefPtr<TextComposition> textComposition =
|
|
IMEStateManager::GetTextCompositionFor(aWidget);
|
|
if (NS_WARN_IF(!textComposition)) {
|
|
return true;
|
|
}
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
mTextRectArray.mRects.SetCapacity(textComposition->String().Length());
|
|
mTextRectArray.mStart = textComposition->NativeOffsetOfStartComposition();
|
|
uint32_t endOffset =
|
|
mTextRectArray.mStart + textComposition->String().Length();
|
|
for (uint32_t i = mTextRectArray.mStart; i < endOffset; i++) {
|
|
WidgetQueryContentEvent textRect(true, NS_QUERY_TEXT_RECT, aWidget);
|
|
textRect.InitForQueryTextRect(i, 1);
|
|
aWidget->DispatchEvent(&textRect, status);
|
|
if (NS_WARN_IF(!textRect.mSucceeded)) {
|
|
mTextRectArray.Clear();
|
|
return false;
|
|
}
|
|
mTextRectArray.mRects.AppendElement(textRect.mReply.mRect);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ContentCache::SetText(const nsAString& aText)
|
|
{
|
|
mText = aText;
|
|
}
|
|
|
|
void
|
|
ContentCache::SetSelection(uint32_t aStartOffset,
|
|
uint32_t aLength,
|
|
bool aReversed,
|
|
const WritingMode& aWritingMode)
|
|
{
|
|
if (!aReversed) {
|
|
mSelection.mAnchor = aStartOffset;
|
|
mSelection.mFocus = aStartOffset + aLength;
|
|
} else {
|
|
mSelection.mAnchor = aStartOffset + aLength;
|
|
mSelection.mFocus = aStartOffset;
|
|
}
|
|
mSelection.mWritingMode = aWritingMode;
|
|
}
|
|
|
|
void
|
|
ContentCache::SetSelection(uint32_t aAnchorOffset,
|
|
uint32_t aFocusOffset,
|
|
const WritingMode& aWritingMode)
|
|
{
|
|
mSelection.mAnchor = aAnchorOffset;
|
|
mSelection.mFocus = aFocusOffset;
|
|
mSelection.mWritingMode = aWritingMode;
|
|
}
|
|
|
|
bool
|
|
ContentCache::InitTextRectArray(uint32_t aOffset,
|
|
const RectArray& aTextRectArray)
|
|
{
|
|
if (NS_WARN_IF(aOffset >= TextLength()) ||
|
|
NS_WARN_IF(aOffset + aTextRectArray.Length() > TextLength())) {
|
|
return false;
|
|
}
|
|
mTextRectArray.mStart = aOffset;
|
|
mTextRectArray.mRects = aTextRectArray;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::GetTextRect(uint32_t aOffset,
|
|
LayoutDeviceIntRect& aTextRect) const
|
|
{
|
|
if (NS_WARN_IF(!mTextRectArray.InRange(aOffset))) {
|
|
aTextRect.SetEmpty();
|
|
return false;
|
|
}
|
|
aTextRect = mTextRectArray.GetRect(aOffset);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::GetUnionTextRects(uint32_t aOffset,
|
|
uint32_t aLength,
|
|
LayoutDeviceIntRect& aUnionTextRect) const
|
|
{
|
|
if (NS_WARN_IF(!mTextRectArray.InRange(aOffset, aLength))) {
|
|
aUnionTextRect.SetEmpty();
|
|
return false;
|
|
}
|
|
aUnionTextRect = mTextRectArray.GetUnionRect(aOffset, aLength);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::InitCaretRect(uint32_t aOffset,
|
|
const LayoutDeviceIntRect& aCaretRect)
|
|
{
|
|
if (NS_WARN_IF(aOffset > TextLength())) {
|
|
return false;
|
|
}
|
|
mCaret.mOffset = aOffset;
|
|
mCaret.mRect = aCaretRect;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::GetCaretRect(uint32_t aOffset,
|
|
LayoutDeviceIntRect& aCaretRect) const
|
|
{
|
|
if (mCaret.mOffset != aOffset) {
|
|
aCaretRect.SetEmpty();
|
|
return false;
|
|
}
|
|
aCaretRect = mCaret.mRect;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ContentCache::OnCompositionEvent(const WidgetCompositionEvent& aEvent)
|
|
{
|
|
if (!aEvent.CausesDOMTextEvent()) {
|
|
MOZ_ASSERT(aEvent.message == NS_COMPOSITION_START);
|
|
mIsComposing = !aEvent.CausesDOMCompositionEndEvent();
|
|
mCompositionStart = SelectionStart();
|
|
// XXX What's this case??
|
|
if (mRequestedToCommitOrCancelComposition) {
|
|
mCommitStringByRequest = aEvent.mData;
|
|
mCompositionEventsDuringRequest++;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// XXX Why do we ignore following composition events here?
|
|
// TextComposition must handle following events correctly!
|
|
|
|
// During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
|
|
// widget usually sends a NS_COMPOSITION_CHANGE event to finalize or
|
|
// clear the composition, respectively.
|
|
// Because the event will not reach content in time, we intercept it
|
|
// here and pass the text as the DidRequestToCommitOrCancelComposition()
|
|
// return value.
|
|
if (mRequestedToCommitOrCancelComposition) {
|
|
mCommitStringByRequest = aEvent.mData;
|
|
mCompositionEventsDuringRequest++;
|
|
return false;
|
|
}
|
|
|
|
// We must be able to simulate the selection because
|
|
// we might not receive selection updates in time
|
|
if (!mIsComposing) {
|
|
mCompositionStart = SelectionStart();
|
|
}
|
|
// XXX This causes different behavior from non-e10s mode.
|
|
// Selection range should represent caret position in the composition
|
|
// string but this means selection range is all of the composition string.
|
|
SetSelection(mCompositionStart + aEvent.mData.Length(),
|
|
mSelection.mWritingMode);
|
|
mIsComposing = !aEvent.CausesDOMCompositionEndEvent();
|
|
return true;
|
|
}
|
|
|
|
uint32_t
|
|
ContentCache::RequestToCommitComposition(nsIWidget* aWidget,
|
|
bool aCancel,
|
|
nsAString& aLastString)
|
|
{
|
|
mRequestedToCommitOrCancelComposition = true;
|
|
mCompositionEventsDuringRequest = 0;
|
|
|
|
aWidget->NotifyIME(IMENotification(aCancel ? REQUEST_TO_CANCEL_COMPOSITION :
|
|
REQUEST_TO_COMMIT_COMPOSITION));
|
|
|
|
mRequestedToCommitOrCancelComposition = false;
|
|
aLastString = mCommitStringByRequest;
|
|
mCommitStringByRequest.Truncate(0);
|
|
return mCompositionEventsDuringRequest;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* mozilla::ContentCache::TextRectArray
|
|
*****************************************************************************/
|
|
|
|
LayoutDeviceIntRect
|
|
ContentCache::TextRectArray::GetRect(uint32_t aOffset) const
|
|
{
|
|
LayoutDeviceIntRect rect;
|
|
if (InRange(aOffset)) {
|
|
rect = mRects[aOffset - mStart];
|
|
}
|
|
return rect;
|
|
}
|
|
|
|
LayoutDeviceIntRect
|
|
ContentCache::TextRectArray::GetUnionRect(uint32_t aOffset,
|
|
uint32_t aLength) const
|
|
{
|
|
LayoutDeviceIntRect rect;
|
|
if (!InRange(aOffset, aLength)) {
|
|
return rect;
|
|
}
|
|
for (uint32_t i = 0; i < aLength; i++) {
|
|
rect = rect.Union(mRects[aOffset - mStart + i]);
|
|
}
|
|
return rect;
|
|
}
|
|
|
|
} // namespace mozilla
|