--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1818,17 +1818,17 @@ InitEvent(WidgetGUIEvent& aEvent, Layout
if (aPt) {
aEvent.mRefPoint = *aPt;
}
aEvent.mTime = PR_IntervalNow();
}
NS_IMETHODIMP
nsDOMWindowUtils::SendQueryContentEvent(uint32_t aType,
- uint32_t aOffset, uint32_t aLength,
+ int64_t aOffset, uint32_t aLength,
int32_t aX, int32_t aY,
uint32_t aAdditionalFlags,
nsIQueryContentEventResult **aResult)
{
*aResult = nullptr;
nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
@@ -1920,23 +1920,39 @@ nsDOMWindowUtils::SendQueryContentEvent(
if (selectionType != SelectionType::eNormal &&
message != eQuerySelectedText) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIWidget> targetWidget = widget;
LayoutDeviceIntPoint pt(aX, aY);
- bool useNativeLineBreak =
+ WidgetQueryContentEvent::Options options;
+ options.mUseNativeLineBreak =
!(aAdditionalFlags & QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
+ options.mRelativeToInsertionPoint =
+ (aAdditionalFlags &
+ QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT) != 0;
+ if (options.mRelativeToInsertionPoint) {
+ switch (message) {
+ case eQueryTextContent:
+ case eQueryCaretRect:
+ case eQueryTextRect:
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+ } else if (aOffset < 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
if (message == eQueryCharacterAtPoint) {
// Looking for the widget at the point.
WidgetQueryContentEvent dummyEvent(true, eQueryContentState, widget);
- dummyEvent.mUseNativeLineBreak = useNativeLineBreak;
+ dummyEvent.Init(options);
InitEvent(dummyEvent, &pt);
nsIFrame* popupFrame =
nsLayoutUtils::GetPopupFrameForEventCoordinates(presContext->GetRootPresContext(), &dummyEvent);
LayoutDeviceIntRect widgetBounds;
nsresult rv = widget->GetClientBounds(widgetBounds);
NS_ENSURE_SUCCESS(rv, rv);
widgetBounds.MoveTo(0, 0);
@@ -1953,29 +1969,29 @@ nsDOMWindowUtils::SendQueryContentEvent(
pt += widget->WidgetToScreenOffset() - targetWidget->WidgetToScreenOffset();
WidgetQueryContentEvent queryEvent(true, message, targetWidget);
InitEvent(queryEvent, &pt);
switch (message) {
case eQueryTextContent:
- queryEvent.InitForQueryTextContent(aOffset, aLength, useNativeLineBreak);
+ queryEvent.InitForQueryTextContent(aOffset, aLength, options);
break;
case eQueryCaretRect:
- queryEvent.InitForQueryCaretRect(aOffset, useNativeLineBreak);
+ queryEvent.InitForQueryCaretRect(aOffset, options);
break;
case eQueryTextRect:
- queryEvent.InitForQueryTextRect(aOffset, aLength, useNativeLineBreak);
+ queryEvent.InitForQueryTextRect(aOffset, aLength, options);
break;
case eQuerySelectedText:
- queryEvent.InitForQuerySelectedText(selectionType, useNativeLineBreak);
+ queryEvent.InitForQuerySelectedText(selectionType, options);
break;
default:
- queryEvent.mUseNativeLineBreak = useNativeLineBreak;
+ queryEvent.Init(options);
break;
}
nsEventStatus status;
nsresult rv = targetWidget->DispatchEvent(&queryEvent, status);
NS_ENSURE_SUCCESS(rv, rv);
nsQueryContentEventResult* result = new nsQueryContentEventResult();
--- a/dom/base/nsQueryContentEventResult.cpp
+++ b/dom/base/nsQueryContentEventResult.cpp
@@ -6,16 +6,47 @@
#include "nsQueryContentEventResult.h"
#include "nsIWidget.h"
#include "nsPoint.h"
#include "mozilla/TextEvents.h"
using namespace mozilla;
+/******************************************************************************
+ * Is*PropertyAvailable() methods which check if the property is available
+ * (valid) with the event message.
+ ******************************************************************************/
+
+static bool IsNotFoundPropertyAvailable(EventMessage aEventMessage)
+{
+ return aEventMessage == eQuerySelectedText ||
+ aEventMessage == eQueryCharacterAtPoint;
+}
+
+static bool IsOffsetPropertyAvailable(EventMessage aEventMessage)
+{
+ return aEventMessage == eQueryTextContent ||
+ aEventMessage == eQueryTextRect ||
+ aEventMessage == eQueryCaretRect ||
+ IsNotFoundPropertyAvailable(aEventMessage);
+}
+
+static bool IsRectRelatedPropertyAvailable(EventMessage aEventMessage)
+{
+ return aEventMessage == eQueryCaretRect ||
+ aEventMessage == eQueryTextRect ||
+ aEventMessage == eQueryEditorRect ||
+ aEventMessage == eQueryCharacterAtPoint;
+}
+
+/******************************************************************************
+ * nsQueryContentEventResult
+ ******************************************************************************/
+
NS_INTERFACE_MAP_BEGIN(nsQueryContentEventResult)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIQueryContentEventResult)
NS_INTERFACE_MAP_ENTRY(nsIQueryContentEventResult)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsQueryContentEventResult)
NS_IMPL_RELEASE(nsQueryContentEventResult)
@@ -25,22 +56,43 @@ nsQueryContentEventResult::nsQueryConten
{
}
nsQueryContentEventResult::~nsQueryContentEventResult()
{
}
NS_IMETHODIMP
-nsQueryContentEventResult::GetOffset(uint32_t *aOffset)
+nsQueryContentEventResult::GetOffset(uint32_t* aOffset)
{
- bool notFound;
- nsresult rv = GetNotFound(¬Found);
- NS_ENSURE_SUCCESS(rv, rv);
- NS_ENSURE_TRUE(!notFound, NS_ERROR_NOT_AVAILABLE);
+ if (NS_WARN_IF(!mSucceeded)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_WARN_IF(!IsOffsetPropertyAvailable(mEventMessage))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // With some event message, both offset and notFound properties are available.
+ // In that case, offset value may mean "not found". If so, this method
+ // shouldn't return mOffset as the result because it's a special value for
+ // "not found".
+ if (IsNotFoundPropertyAvailable(mEventMessage)) {
+ bool notFound;
+ nsresult rv = GetNotFound(¬Found);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv; // Just an unexpected case...
+ }
+ // As said above, if mOffset means "not found", offset property shouldn't
+ // return its value without any errors.
+ if (NS_WARN_IF(notFound)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
*aOffset = mOffset;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetTentativeCaretOffset(uint32_t* aOffset)
{
bool notFound;
@@ -50,68 +102,60 @@ nsQueryContentEventResult::GetTentativeC
}
if (NS_WARN_IF(notFound)) {
return NS_ERROR_NOT_AVAILABLE;
}
*aOffset = mTentativeCaretOffset;
return NS_OK;
}
-static bool IsRectEnabled(EventMessage aEventMessage)
-{
- return aEventMessage == eQueryCaretRect ||
- aEventMessage == eQueryTextRect ||
- aEventMessage == eQueryEditorRect ||
- aEventMessage == eQueryCharacterAtPoint;
-}
-
NS_IMETHODIMP
nsQueryContentEventResult::GetReversed(bool *aReversed)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(mEventMessage == eQuerySelectedText, NS_ERROR_NOT_AVAILABLE);
*aReversed = mReversed;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetLeft(int32_t *aLeft)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
- NS_ENSURE_TRUE(IsRectEnabled(mEventMessage),
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
NS_ERROR_NOT_AVAILABLE);
*aLeft = mRect.x;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetWidth(int32_t *aWidth)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
- NS_ENSURE_TRUE(IsRectEnabled(mEventMessage),
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
NS_ERROR_NOT_AVAILABLE);
*aWidth = mRect.width;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetTop(int32_t *aTop)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
- NS_ENSURE_TRUE(IsRectEnabled(mEventMessage),
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
NS_ERROR_NOT_AVAILABLE);
*aTop = mRect.y;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetHeight(int32_t *aHeight)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
- NS_ENSURE_TRUE(IsRectEnabled(mEventMessage),
+ NS_ENSURE_TRUE(IsRectRelatedPropertyAvailable(mEventMessage),
NS_ERROR_NOT_AVAILABLE);
*aHeight = mRect.height;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetText(nsAString &aText)
{
@@ -127,22 +171,22 @@ NS_IMETHODIMP
nsQueryContentEventResult::GetSucceeded(bool *aSucceeded)
{
NS_ENSURE_TRUE(mEventMessage != eVoidEvent, NS_ERROR_NOT_INITIALIZED);
*aSucceeded = mSucceeded;
return NS_OK;
}
NS_IMETHODIMP
-nsQueryContentEventResult::GetNotFound(bool *aNotFound)
+nsQueryContentEventResult::GetNotFound(bool* aNotFound)
{
- NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
- NS_ENSURE_TRUE(mEventMessage == eQuerySelectedText ||
- mEventMessage == eQueryCharacterAtPoint,
- NS_ERROR_NOT_AVAILABLE);
+ if (NS_WARN_IF(!mSucceeded) ||
+ NS_WARN_IF(!IsNotFoundPropertyAvailable(mEventMessage))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
*aNotFound = (mOffset == WidgetQueryContentEvent::NOT_FOUND);
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetTentativeCaretOffsetNotFound(bool* aNotFound)
{
if (NS_WARN_IF(!mSucceeded)) {
@@ -162,17 +206,18 @@ nsQueryContentEventResult::SetEventResul
mEventMessage = aEvent.mMessage;
mSucceeded = aEvent.mSucceeded;
mReversed = aEvent.mReply.mReversed;
mRect = aEvent.mReply.mRect;
mOffset = aEvent.mReply.mOffset;
mTentativeCaretOffset = aEvent.mReply.mTentativeCaretOffset;
mString = aEvent.mReply.mString;
- if (!IsRectEnabled(mEventMessage) || !aWidget || !mSucceeded) {
+ if (!IsRectRelatedPropertyAvailable(mEventMessage) ||
+ !aWidget || !mSucceeded) {
return;
}
nsIWidget* topWidget = aWidget->GetTopLevelWidget();
if (!topWidget || topWidget == aWidget) {
return;
}
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1,16 +1,17 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ContentEventHandler.h"
#include "mozilla/IMEStateManager.h"
+#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLUnknownElement.h"
#include "mozilla/dom/Selection.h"
#include "nsCaret.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsCopySupport.h"
@@ -264,28 +265,60 @@ ContentEventHandler::InitCommon(Selectio
return NS_OK;
}
nsresult
ContentEventHandler::Init(WidgetQueryContentEvent* aEvent)
{
NS_ASSERTION(aEvent, "aEvent must not be null");
+ if (NS_WARN_IF(!aEvent->mInput.IsValidOffset()) ||
+ NS_WARN_IF(!aEvent->mInput.IsValidEventMessage(aEvent->mMessage))) {
+ return NS_ERROR_FAILURE;
+ }
+
// Note that we should ignore WidgetQueryContentEvent::Input::mSelectionType
// if the event isn't eQuerySelectedText.
SelectionType selectionType =
aEvent->mMessage == eQuerySelectedText ? aEvent->mInput.mSelectionType :
SelectionType::eNormal;
if (NS_WARN_IF(selectionType == SelectionType::eNone)) {
return NS_ERROR_FAILURE;
}
nsresult rv = InitCommon(selectionType);
NS_ENSURE_SUCCESS(rv, rv);
+ // Be aware, WidgetQueryContentEvent::mInput::mOffset should be made absolute
+ // offset before sending it to ContentEventHandler because querying selection
+ // every time may be expensive. So, if the caller caches selection, it
+ // should initialize the event with the cached value.
+ if (aEvent->mInput.mRelativeToInsertionPoint) {
+ MOZ_ASSERT(selectionType == SelectionType::eNormal);
+ RefPtr<TextComposition> composition =
+ IMEStateManager::GetTextCompositionFor(aEvent->mWidget);
+ if (composition) {
+ uint32_t compositionStart = composition->NativeOffsetOfStartComposition();
+ if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ LineBreakType lineBreakType = GetLineBreakType(aEvent);
+ uint32_t selectionStart = 0;
+ rv = GetFlatTextLengthBefore(mFirstSelectedRange,
+ &selectionStart, lineBreakType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
aEvent->mSucceeded = false;
aEvent->mReply.mContentsRoot = mRootContent.get();
aEvent->mReply.mHasSelection = !mSelection->IsCollapsed();
nsRect r;
nsIFrame* frame = nsCaret::GetGeometry(mSelection, &r);
@@ -1692,17 +1725,18 @@ ContentEventHandler::OnQueryCharacterAtP
NodePosition(contentOffsets),
mRootContent, &offset,
GetLineBreakType(aEvent));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
WidgetQueryContentEvent textRect(true, eQueryTextRect, aEvent->mWidget);
- textRect.InitForQueryTextRect(offset, 1, aEvent->mUseNativeLineBreak);
+ WidgetQueryContentEvent::Options options(*aEvent);
+ textRect.InitForQueryTextRect(offset, 1, options);
rv = OnQueryTextRect(&textRect);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(textRect.mSucceeded, NS_ERROR_FAILURE);
// currently, we don't need to get the actual text.
aEvent->mReply.mOffset = offset;
aEvent->mReply.mRect = textRect.mReply.mRect;
aEvent->mSucceeded = true;
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1098,26 +1098,35 @@ interface nsIDOMWindowUtils : nsISupport
const unsigned long QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT =
0x0020;
const unsigned long QUERY_CONTENT_FLAG_SELECTION_ACCESSIBILITY = 0x0040;
const unsigned long QUERY_CONTENT_FLAG_SELECTION_FIND = 0x0080;
const unsigned long QUERY_CONTENT_FLAG_SELECTION_URLSECONDARY = 0x0100;
const unsigned long QUERY_CONTENT_FLAG_SELECTION_URLSTRIKEOUT = 0x0200;
/**
+ * One of sendQueryContentEvent()'s aAdditionalFlags. If this is specified,
+ * aOffset is relative to start of selection or composition.
+ * Note that this is supported only when QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK
+ * is not specified for now.
+ */
+ const unsigned long QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT =
+ 0x0400;
+
+ /**
* Synthesize a query content event. Note that the result value returned here
* is in LayoutDevice pixels rather than CSS pixels.
*
* @param aType One of the following const values. And see also each comment
* for the other parameters and the result.
* @param aAdditionalFlags See the description of QUERY_CONTENT_FLAG_*.
*/
nsIQueryContentEventResult sendQueryContentEvent(
in unsigned long aType,
- in unsigned long aOffset,
+ in long long aOffset,
in unsigned long aLength,
in long aX,
in long aY,
[optional] in unsigned long aAdditionalFlags);
/**
* QUERY_SELECTED_TEXT queries the first selection range's information.
*
--- a/testing/mochitest/tests/SimpleTest/ChromeUtils.js
+++ b/testing/mochitest/tests/SimpleTest/ChromeUtils.js
@@ -8,38 +8,16 @@
*/
const EventUtils = {};
const scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].
getService(Components.interfaces.mozIJSSubScriptLoader);
scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
/**
- * Synthesize a query text content event.
- *
- * @param aOffset The character offset. 0 means the first character in the
- * selection root.
- * @param aLength The length of getting text. If the length is too long,
- * the extra length is ignored.
- * @param aWindow Optional (If null, current |window| will be used)
- * @return An nsIQueryContentEventResult object. If this failed,
- * the result might be null.
- */
-function synthesizeQueryTextContent(aOffset, aLength, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return nullptr;
- }
- return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT,
- aOffset, aLength, 0, 0,
- QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
-}
-
-/**
* Synthesize a query text rect event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of the text. If the length is too long,
* the extra length is ignored.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -1564,21 +1564,50 @@ const QUERY_CONTENT_FLAG_SELECTION_IME_R
const QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT = 0x0008;
const QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT = 0x0010;
const QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT = 0x0020;
const QUERY_CONTENT_FLAG_SELECTION_ACCESSIBILITY = 0x0040;
const QUERY_CONTENT_FLAG_SELECTION_FIND = 0x0080;
const QUERY_CONTENT_FLAG_SELECTION_URLSECONDARY = 0x0100;
const QUERY_CONTENT_FLAG_SELECTION_URLSTRIKEOUT = 0x0200;
+const QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT = 0x0400;
+
const SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK = 0x0000;
const SELECTION_SET_FLAG_USE_XP_LINE_BREAK = 0x0001;
const SELECTION_SET_FLAG_REVERSE = 0x0002;
/**
+ * Synthesize a query text content event.
+ *
+ * @param aOffset The character offset. 0 means the first character in the
+ * selection root.
+ * @param aLength The length of getting text. If the length is too long,
+ * the extra length is ignored.
+ * @param aIsRelative Optional (If true, aOffset is relative to start of
+ * composition if there is, or start of selection.)
+ * @param aWindow Optional (If null, current |window| will be used)
+ * @return An nsIQueryContentEventResult object. If this failed,
+ * the result might be null.
+ */
+function synthesizeQueryTextContent(aOffset, aLength, aIsRelative, aWindow)
+{
+ var utils = _getDOMWindowUtils(aWindow);
+ if (!utils) {
+ return nullptr;
+ }
+ var flags = QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK;
+ if (aIsRelative === true) {
+ flags |= QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT;
+ }
+ return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT,
+ aOffset, aLength, 0, 0, flags);
+}
+
+/**
* Synthesize a query selected text event.
*
* @param aSelectionType Optional, one of QUERY_CONTENT_FLAG_SELECTION_*.
* If null, QUERY_CONTENT_FLAG_SELECTION_NORMAL will
* be used.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -634,52 +634,80 @@ public:
virtual WidgetEvent* Duplicate() const override
{
// This event isn't an internal event of any DOM event.
NS_ASSERTION(!IsAllowedToDispatchDOMEvent(),
"WidgetQueryContentEvent needs to support Duplicate()");
MOZ_CRASH("WidgetQueryContentEvent doesn't support Duplicate()");
}
- void InitForQueryTextContent(uint32_t aOffset, uint32_t aLength,
- bool aUseNativeLineBreak = true)
+ struct Options final
+ {
+ bool mUseNativeLineBreak;
+ bool mRelativeToInsertionPoint;
+
+ explicit Options()
+ : mUseNativeLineBreak(true)
+ , mRelativeToInsertionPoint(false)
+ {
+ }
+
+ explicit Options(const WidgetQueryContentEvent& aEvent)
+ : mUseNativeLineBreak(aEvent.mUseNativeLineBreak)
+ , mRelativeToInsertionPoint(aEvent.mInput.mRelativeToInsertionPoint)
+ {
+ }
+ };
+
+ void Init(const Options& aOptions)
+ {
+ mUseNativeLineBreak = aOptions.mUseNativeLineBreak;
+ mInput.mRelativeToInsertionPoint = aOptions.mRelativeToInsertionPoint;
+ MOZ_ASSERT(mInput.IsValidEventMessage(mMessage));
+ }
+
+ void InitForQueryTextContent(int64_t aOffset, uint32_t aLength,
+ const Options& aOptions = Options())
{
NS_ASSERTION(mMessage == eQueryTextContent,
"wrong initializer is called");
mInput.mOffset = aOffset;
mInput.mLength = aLength;
- mUseNativeLineBreak = aUseNativeLineBreak;
+ Init(aOptions);
+ MOZ_ASSERT(mInput.IsValidOffset());
}
- void InitForQueryCaretRect(uint32_t aOffset,
- bool aUseNativeLineBreak = true)
+ void InitForQueryCaretRect(int64_t aOffset,
+ const Options& aOptions = Options())
{
NS_ASSERTION(mMessage == eQueryCaretRect,
"wrong initializer is called");
mInput.mOffset = aOffset;
- mUseNativeLineBreak = aUseNativeLineBreak;
+ Init(aOptions);
+ MOZ_ASSERT(mInput.IsValidOffset());
}
- void InitForQueryTextRect(uint32_t aOffset, uint32_t aLength,
- bool aUseNativeLineBreak = true)
+ void InitForQueryTextRect(int64_t aOffset, uint32_t aLength,
+ const Options& aOptions = Options())
{
NS_ASSERTION(mMessage == eQueryTextRect,
"wrong initializer is called");
mInput.mOffset = aOffset;
mInput.mLength = aLength;
- mUseNativeLineBreak = aUseNativeLineBreak;
+ Init(aOptions);
+ MOZ_ASSERT(mInput.IsValidOffset());
}
void InitForQuerySelectedText(SelectionType aSelectionType,
- bool aUseNativeLineBreak = true)
+ const Options& aOptions = Options())
{
MOZ_ASSERT(mMessage == eQuerySelectedText);
MOZ_ASSERT(aSelectionType != SelectionType::eNone);
mInput.mSelectionType = aSelectionType;
- mUseNativeLineBreak = aUseNativeLineBreak;
+ Init(aOptions);
}
void InitForQueryDOMWidgetHittest(const mozilla::LayoutDeviceIntPoint& aPoint)
{
NS_ASSERTION(mMessage == eQueryDOMWidgetHittest,
"wrong initializer is called");
mRefPoint = aPoint;
}
@@ -721,26 +749,71 @@ public:
{
uint32_t EndOffset() const
{
CheckedInt<uint32_t> endOffset =
CheckedInt<uint32_t>(mOffset) + mLength;
return NS_WARN_IF(!endOffset.isValid()) ? UINT32_MAX : endOffset.value();
}
- uint32_t mOffset;
+ int64_t mOffset;
uint32_t mLength;
SelectionType mSelectionType;
+ // If mOffset is true, mOffset is relative to the start offset of
+ // composition if there is, otherwise, the start of the first selection
+ // range.
+ bool mRelativeToInsertionPoint;
Input()
: mOffset(0)
, mLength(0)
, mSelectionType(SelectionType::eNormal)
+ , mRelativeToInsertionPoint(false)
{
}
+
+ bool IsValidOffset() const
+ {
+ return mRelativeToInsertionPoint || mOffset >= 0;
+ }
+ bool IsValidEventMessage(EventMessage aEventMessage) const
+ {
+ if (!mRelativeToInsertionPoint) {
+ return true;
+ }
+ switch (aEventMessage) {
+ case eQueryTextContent:
+ case eQueryCaretRect:
+ case eQueryTextRect:
+ return true;
+ default:
+ return false;
+ }
+ }
+ bool MakeOffsetAbsolute(uint32_t aInsertionPointOffset)
+ {
+ if (NS_WARN_IF(!mRelativeToInsertionPoint)) {
+ return true;
+ }
+ mRelativeToInsertionPoint = false;
+ // If mOffset + aInsertionPointOffset becomes negative value,
+ // we should assume the absolute offset is 0.
+ if (mOffset < 0 && -mOffset > aInsertionPointOffset) {
+ mOffset = 0;
+ return true;
+ }
+ // Otherwise, we don't allow too large offset.
+ CheckedInt<uint32_t> absOffset = mOffset + aInsertionPointOffset;
+ if (NS_WARN_IF(!absOffset.isValid())) {
+ mOffset = UINT32_MAX;
+ return false;
+ }
+ mOffset = absOffset.value();
+ return true;
+ }
} mInput;
struct Reply final
{
void* mContentsRoot;
uint32_t mOffset;
// mTentativeCaretOffset is used by only eQueryCharacterAtPoint.
// This is the offset where caret would be if user clicked at the mRefPoint.
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -143,16 +143,35 @@ function checkContent(aExpectedText, aMe
": synthesizeQueryTextContent " + aID)) {
return false;
}
is(textContent.text, aExpectedText,
aMessage + ": composition string is wrong " + aID);
return textContent.text == aExpectedText;
}
+function checkContentRelativeToSelection(aRelativeOffset, aLength, aExpectedOffset, aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ aMessage += " (aRelativeOffset=" + aRelativeOffset + "): "
+ var textContent = synthesizeQueryTextContent(aRelativeOffset, aLength, true);
+ if (!checkQueryContentResult(textContent, aMessage +
+ "synthesizeQueryTextContent " + aID)) {
+ return false;
+ }
+ is(textContent.offset, aExpectedOffset,
+ aMessage + "offset is wrong " + aID);
+ is(textContent.text, aExpectedText,
+ aMessage + "text is wrong " + aID);
+ return textContent.offset == aExpectedOffset &&
+ textContent.text == aExpectedText;
+}
+
function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
{
if (!aID) {
aID = "";
}
var selectedText = synthesizeQuerySelectedText();
if (!checkQueryContentResult(selectedText, aMessage +
": synthesizeQuerySelectedText " + aID)) {
@@ -3580,16 +3599,104 @@ function runQueryIMESelectionTest()
!checkIMESelection("SelectedClause", true, startoffset + 3, "d", "runQueryIMESelectionTest: unrealistic testcase")) {
synthesizeComposition({ type: "compositioncommitasis" });
return;
}
synthesizeComposition({ type: "compositioncommitasis" });
}
+function runQueryContentEventRelativeToInsertionPoint()
+{
+ textarea.focus();
+ textarea.value = "0123456789";
+
+ var startoffset = textarea.selectionStart = textarea.selectionEnd = 0;
+
+ if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#5")) {
+ return;
+ }
+
+ textarea.selectionEnd = 5;
+
+ if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-5]"), "#5") {
+ return;
+ }
+
+ startoffset = textarea.selectionStart = textarea.selectionEnd = 4;
+
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "4", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "5", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "9", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#5")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "4", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "89", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#4") ||
+ !checkContentRelativeToSelection(11, 1, 11, "", "runQueryContentEventRelativeToInsertionPoint[composition at 4]")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ // Move start of composition at first compositionupdate event.
+ function onCompositionUpdate(aEvent)
+ {
+ startoffset = textarea.selectionStart = textarea.selectionEnd = textarea.selectionStart - 1;
+ textarea.removeEventListener("compositionupdate", onCompositionUpdate);
+ }
+ textarea.addEventListener("compositionupdate", onCompositionUpdate);
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "b", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "789", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#4") ||
+ !checkContentRelativeToSelection(12, 1, 12, "", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#5")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+}
+
function runCSSTransformTest()
{
textarea.focus();
textarea.value = "some text";
textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
var editorRect = synthesizeQueryEditorRect();
if (!checkQueryContentResult(editorRect,
"runCSSTransformTest: editorRect")) {
@@ -5924,16 +6031,17 @@ function runTest()
runCompositionCommitTest();
runCompositionTest();
runCompositionEventTest();
runCharAtPointTest(textarea, "textarea in the document");
runCharAtPointAtOutsideTest();
runSetSelectionEventTest();
runQueryTextContentEventTest();
runQueryIMESelectionTest();
+ runQueryContentEventRelativeToInsertionPoint();
runCSSTransformTest();
runBug722639Test();
runForceCommitTest();
runNestedSettingValue();
runBug811755Test();
runIsComposingTest();
runRedundantChangeTest();
runNotRedundantChangeTest();