mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 739396, part 1 - Split a range (that are to be added to the Selection as a result of a user action) into multiple ranges that excludes any non-selectable (-moz-user-select:none) sub-trees.
This commit is contained in:
parent
d0973c7c50
commit
501e985685
@ -3039,3 +3039,95 @@ nsRange::Constructor(const GlobalObject& aGlobal,
|
|||||||
|
|
||||||
return window->GetDoc()->CreateRange(aRv);
|
return window->GetDoc()->CreateRange(aRv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsRange::ExcludeNonSelectableNodes(nsTArray<nsRefPtr<nsRange>>* aOutRanges)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(mIsPositioned);
|
||||||
|
MOZ_ASSERT(mEndParent);
|
||||||
|
MOZ_ASSERT(mStartParent);
|
||||||
|
|
||||||
|
nsRange* range = this;
|
||||||
|
nsRefPtr<nsRange> newRange;
|
||||||
|
while (range) {
|
||||||
|
nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
|
||||||
|
nsresult rv = iter->Init(range);
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool added = false;
|
||||||
|
bool seenSelectable = false;
|
||||||
|
nsIContent* firstNonSelectableContent = nullptr;
|
||||||
|
while (true) {
|
||||||
|
ErrorResult err;
|
||||||
|
nsINode* node = iter->GetCurrentNode();
|
||||||
|
iter->Next();
|
||||||
|
bool selectable = true;
|
||||||
|
nsIContent* content =
|
||||||
|
node && node->IsContent() ? node->AsContent() : nullptr;
|
||||||
|
if (content) {
|
||||||
|
nsIFrame* frame = content->GetPrimaryFrame();
|
||||||
|
for (nsIContent* p = content; !frame && (p = p->GetParent()); ) {
|
||||||
|
frame = p->GetPrimaryFrame();
|
||||||
|
}
|
||||||
|
if (frame) {
|
||||||
|
frame->IsSelectable(&selectable, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectable) {
|
||||||
|
if (!firstNonSelectableContent) {
|
||||||
|
firstNonSelectableContent = content;
|
||||||
|
}
|
||||||
|
if (iter->IsDone() && seenSelectable) {
|
||||||
|
// The tail end of the initial range is non-selectable - truncate the
|
||||||
|
// current range before the first non-selectable node.
|
||||||
|
range->SetEndBefore(*firstNonSelectableContent, err);
|
||||||
|
}
|
||||||
|
} else if (firstNonSelectableContent) {
|
||||||
|
if (range == this && !seenSelectable) {
|
||||||
|
// This is the initial range and all its nodes until now are
|
||||||
|
// non-selectable so just trim them from the start.
|
||||||
|
range->SetStartBefore(*node, err);
|
||||||
|
if (err.Failed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break; // restart the same range with a new iterator
|
||||||
|
} else {
|
||||||
|
// Save the end point before truncating the range.
|
||||||
|
nsINode* endParent = range->mEndParent;
|
||||||
|
int32_t endOffset = range->mEndOffset;
|
||||||
|
|
||||||
|
// Truncate the current range before the first non-selectable node.
|
||||||
|
range->SetEndBefore(*firstNonSelectableContent, err);
|
||||||
|
|
||||||
|
// Store it in the result (strong ref) - do this before creating
|
||||||
|
// a new range in |newRange| below so we don't drop the last ref
|
||||||
|
// to the range created in the previous iteration.
|
||||||
|
if (!added && !err.Failed()) {
|
||||||
|
aOutRanges->AppendElement(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new range for the remainder.
|
||||||
|
rv = CreateRange(node, 0, endParent, endOffset,
|
||||||
|
getter_AddRefs(newRange));
|
||||||
|
if (NS_FAILED(rv) || newRange->Collapsed()) {
|
||||||
|
newRange = nullptr;
|
||||||
|
}
|
||||||
|
range = newRange;
|
||||||
|
break; // create a new iterator for the new range, if any
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
seenSelectable = true;
|
||||||
|
if (!added) {
|
||||||
|
added = true;
|
||||||
|
aOutRanges->AppendElement(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (iter->IsDone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -264,6 +264,19 @@ public:
|
|||||||
nsINode* aEndParent, int32_t aEndOffset,
|
nsINode* aEndParent, int32_t aEndOffset,
|
||||||
bool aClampToEdge, bool aFlushLayout);
|
bool aClampToEdge, bool aFlushLayout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan this range for -moz-user-select:none nodes and split it up into
|
||||||
|
* multiple ranges to exclude those nodes. The resulting ranges are put
|
||||||
|
* in aOutRanges. If no -moz-user-select:none node is found in the range
|
||||||
|
* then |this| is unmodified and is the only range in aOutRanges.
|
||||||
|
* Otherwise, |this| will be modified so that it ends before the first
|
||||||
|
* -moz-user-select:none node and additional ranges may also be created.
|
||||||
|
* If all nodes in the range are -moz-user-select:none then aOutRanges
|
||||||
|
* will be empty.
|
||||||
|
* @param aOutRanges the resulting set of ranges
|
||||||
|
*/
|
||||||
|
void ExcludeNonSelectableNodes(nsTArray<nsRefPtr<nsRange>>* aOutRanges);
|
||||||
|
|
||||||
typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable;
|
typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable;
|
||||||
protected:
|
protected:
|
||||||
void RegisterCommonAncestor(nsINode* aNode);
|
void RegisterCommonAncestor(nsINode* aNode);
|
||||||
|
@ -2620,6 +2620,7 @@ NS_IMETHODIMP nsDocumentViewer::SelectAll()
|
|||||||
rv = selection->RemoveAllRanges();
|
rv = selection->RemoveAllRanges();
|
||||||
if (NS_FAILED(rv)) return rv;
|
if (NS_FAILED(rv)) return rv;
|
||||||
|
|
||||||
|
mozilla::dom::Selection::AutoApplyUserSelectStyle userSelection(selection);
|
||||||
rv = selection->SelectAllChildren(bodyNode);
|
rv = selection->SelectAllChildren(bodyNode);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,13 @@
|
|||||||
|
|
||||||
#include "nsIWeakReference.h"
|
#include "nsIWeakReference.h"
|
||||||
|
|
||||||
|
#include "mozilla/AutoRestore.h"
|
||||||
|
#include "mozilla/TextRange.h"
|
||||||
#include "nsISelection.h"
|
#include "nsISelection.h"
|
||||||
#include "nsISelectionController.h"
|
#include "nsISelectionController.h"
|
||||||
#include "nsISelectionPrivate.h"
|
#include "nsISelectionPrivate.h"
|
||||||
#include "nsRange.h"
|
#include "nsRange.h"
|
||||||
#include "nsThreadUtils.h"
|
#include "nsThreadUtils.h"
|
||||||
#include "mozilla/TextRange.h"
|
|
||||||
#include "nsWrapperCache.h"
|
#include "nsWrapperCache.h"
|
||||||
|
|
||||||
struct CachedOffsetForFrame;
|
struct CachedOffsetForFrame;
|
||||||
@ -100,8 +101,14 @@ public:
|
|||||||
int32_t aFlags = 0);
|
int32_t aFlags = 0);
|
||||||
nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
|
nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
|
||||||
nsTArray<RangeData>* aOutput);
|
nsTArray<RangeData>* aOutput);
|
||||||
nsresult AddItem(nsRange *aRange, int32_t* aOutIndex);
|
/**
|
||||||
nsresult RemoveItem(nsRange *aRange);
|
* AddItem adds aRange to this Selection. If mApplyUserSelectStyle is true,
|
||||||
|
* then aRange is first scanned for -moz-user-select:none nodes and split up
|
||||||
|
* into multiple ranges to exclude those before adding the resulting ranges
|
||||||
|
* to this Selection.
|
||||||
|
*/
|
||||||
|
nsresult AddItem(nsRange* aRange, int32_t* aOutIndex);
|
||||||
|
nsresult RemoveItem(nsRange* aRange);
|
||||||
nsresult RemoveCollapsedRanges();
|
nsresult RemoveCollapsedRanges();
|
||||||
nsresult Clear(nsPresContext* aPresContext);
|
nsresult Clear(nsPresContext* aPresContext);
|
||||||
nsresult Collapse(nsINode* aParentNode, int32_t aOffset);
|
nsresult Collapse(nsINode* aParentNode, int32_t aOffset);
|
||||||
@ -205,6 +212,19 @@ public:
|
|||||||
|
|
||||||
nsresult NotifySelectionListeners();
|
nsresult NotifySelectionListeners();
|
||||||
|
|
||||||
|
friend struct AutoApplyUserSelectStyle;
|
||||||
|
struct MOZ_STACK_CLASS AutoApplyUserSelectStyle
|
||||||
|
{
|
||||||
|
AutoApplyUserSelectStyle(Selection* aSelection
|
||||||
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||||
|
: mSavedValue(aSelection->mApplyUserSelectStyle)
|
||||||
|
{
|
||||||
|
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||||
|
aSelection->mApplyUserSelectStyle = true;
|
||||||
|
}
|
||||||
|
AutoRestore<bool> mSavedValue;
|
||||||
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||||
|
};
|
||||||
private:
|
private:
|
||||||
|
|
||||||
class ScrollSelectionIntoViewEvent;
|
class ScrollSelectionIntoViewEvent;
|
||||||
@ -258,6 +278,11 @@ private:
|
|||||||
int32_t* aStartIndex, int32_t* aEndIndex);
|
int32_t* aStartIndex, int32_t* aEndIndex);
|
||||||
RangeData* FindRangeData(nsIDOMRange* aRange);
|
RangeData* FindRangeData(nsIDOMRange* aRange);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for AddItem.
|
||||||
|
*/
|
||||||
|
nsresult AddItemInternal(nsRange* aRange, int32_t* aOutIndex);
|
||||||
|
|
||||||
// These are the ranges inside this selection. They are kept sorted in order
|
// These are the ranges inside this selection. They are kept sorted in order
|
||||||
// of DOM start position.
|
// of DOM start position.
|
||||||
//
|
//
|
||||||
@ -281,6 +306,11 @@ private:
|
|||||||
CachedOffsetForFrame *mCachedOffsetForFrame;
|
CachedOffsetForFrame *mCachedOffsetForFrame;
|
||||||
nsDirection mDirection;
|
nsDirection mDirection;
|
||||||
SelectionType mType;
|
SelectionType mType;
|
||||||
|
/**
|
||||||
|
* True if the current selection operation was initiated by user action.
|
||||||
|
* It determines whether we exclude -moz-user-select:none nodes or not.
|
||||||
|
*/
|
||||||
|
bool mApplyUserSelectStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "nsIFrame.h"
|
#include "nsIFrame.h"
|
||||||
#include "nsIContent.h"
|
#include "nsIContent.h"
|
||||||
#include "nsISelectionController.h"
|
#include "nsISelectionController.h"
|
||||||
|
#include "nsISelectionListener.h"
|
||||||
#include "nsITableCellLayout.h"
|
#include "nsITableCellLayout.h"
|
||||||
#include "nsIDOMElement.h"
|
#include "nsIDOMElement.h"
|
||||||
#include "WordMovementType.h"
|
#include "WordMovementType.h"
|
||||||
@ -613,9 +614,18 @@ private:
|
|||||||
int16_t PopReason()
|
int16_t PopReason()
|
||||||
{
|
{
|
||||||
int16_t retval = mSelectionChangeReason;
|
int16_t retval = mSelectionChangeReason;
|
||||||
mSelectionChangeReason = 0;
|
mSelectionChangeReason = nsISelectionListener::NO_REASON;
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
bool IsUserSelectionReason() const
|
||||||
|
{
|
||||||
|
return (mSelectionChangeReason &
|
||||||
|
(nsISelectionListener::DRAG_REASON |
|
||||||
|
nsISelectionListener::MOUSEDOWN_REASON |
|
||||||
|
nsISelectionListener::MOUSEUP_REASON |
|
||||||
|
nsISelectionListener::KEYPRESS_REASON)) !=
|
||||||
|
nsISelectionListener::NO_REASON;
|
||||||
|
}
|
||||||
|
|
||||||
friend class mozilla::dom::Selection;
|
friend class mozilla::dom::Selection;
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
@ -1548,6 +1548,11 @@ nsFrameSelection::TakeFocus(nsIContent* aNewFocus,
|
|||||||
if (!mDomSelections[index])
|
if (!mDomSelections[index])
|
||||||
return NS_ERROR_NULL_POINTER;
|
return NS_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
Maybe<Selection::AutoApplyUserSelectStyle> userSelect;
|
||||||
|
if (IsUserSelectionReason()) {
|
||||||
|
userSelect.emplace(mDomSelections[index]);
|
||||||
|
}
|
||||||
|
|
||||||
//traverse through document and unselect crap here
|
//traverse through document and unselect crap here
|
||||||
if (!aContinueSelection) {//single click? setting cursor down
|
if (!aContinueSelection) {//single click? setting cursor down
|
||||||
uint32_t batching = mBatching;//hack to use the collapse code.
|
uint32_t batching = mBatching;//hack to use the collapse code.
|
||||||
@ -3103,6 +3108,7 @@ Selection::Selection()
|
|||||||
: mCachedOffsetForFrame(nullptr)
|
: mCachedOffsetForFrame(nullptr)
|
||||||
, mDirection(eDirNext)
|
, mDirection(eDirNext)
|
||||||
, mType(nsISelectionController::SELECTION_NORMAL)
|
, mType(nsISelectionController::SELECTION_NORMAL)
|
||||||
|
, mApplyUserSelectStyle(false)
|
||||||
{
|
{
|
||||||
SetIsDOMBinding();
|
SetIsDOMBinding();
|
||||||
}
|
}
|
||||||
@ -3112,6 +3118,7 @@ Selection::Selection(nsFrameSelection* aList)
|
|||||||
, mCachedOffsetForFrame(nullptr)
|
, mCachedOffsetForFrame(nullptr)
|
||||||
, mDirection(eDirNext)
|
, mDirection(eDirNext)
|
||||||
, mType(nsISelectionController::SELECTION_NORMAL)
|
, mType(nsISelectionController::SELECTION_NORMAL)
|
||||||
|
, mApplyUserSelectStyle(false)
|
||||||
{
|
{
|
||||||
SetIsDOMBinding();
|
SetIsDOMBinding();
|
||||||
}
|
}
|
||||||
@ -3454,6 +3461,25 @@ Selection::AddItem(nsRange* aItem, int32_t* aOutIndex)
|
|||||||
|
|
||||||
NS_ASSERTION(aOutIndex, "aOutIndex can't be null");
|
NS_ASSERTION(aOutIndex, "aOutIndex can't be null");
|
||||||
|
|
||||||
|
if (mApplyUserSelectStyle) {
|
||||||
|
nsAutoTArray<nsRefPtr<nsRange>, 4> rangesToAdd;
|
||||||
|
aItem->ExcludeNonSelectableNodes(&rangesToAdd);
|
||||||
|
for (size_t i = 0; i < rangesToAdd.Length(); ++i) {
|
||||||
|
nsresult rv = AddItemInternal(rangesToAdd[i], aOutIndex);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
}
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
return AddItemInternal(aItem, aOutIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
Selection::AddItemInternal(nsRange* aItem, int32_t* aOutIndex)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aItem);
|
||||||
|
MOZ_ASSERT(aItem->IsPositioned());
|
||||||
|
MOZ_ASSERT(aOutIndex);
|
||||||
|
|
||||||
*aOutIndex = -1;
|
*aOutIndex = -1;
|
||||||
|
|
||||||
// a common case is that we have no ranges yet
|
// a common case is that we have no ranges yet
|
||||||
@ -4443,8 +4469,7 @@ Selection::AddRange(nsRange& aRange, ErrorResult& aRv)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didAddRange)
|
if (!didAddRange) {
|
||||||
{
|
|
||||||
result = AddItem(&aRange, &rangeIndex);
|
result = AddItem(&aRange, &rangeIndex);
|
||||||
if (NS_FAILED(result)) {
|
if (NS_FAILED(result)) {
|
||||||
aRv.Throw(result);
|
aRv.Throw(result);
|
||||||
@ -4452,7 +4477,10 @@ Selection::AddRange(nsRange& aRange, ErrorResult& aRv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_ASSERTION(rangeIndex >= 0, "Range index not returned");
|
if (rangeIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setAnchorFocusRange(rangeIndex);
|
setAnchorFocusRange(rangeIndex);
|
||||||
|
|
||||||
// Make sure the caret appears on the next line, if at a newline
|
// Make sure the caret appears on the next line, if at a newline
|
||||||
@ -4921,7 +4949,48 @@ Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//mFrameSelection->InvalidateDesiredX();
|
nsDirection dir = GetDirection();
|
||||||
|
|
||||||
|
// If aParentNode is inside a range in a multi-range selection we need
|
||||||
|
// to remove the ranges that follows in the selection direction and
|
||||||
|
// make that range the mAnchorFocusRange.
|
||||||
|
if (mRanges.Length() > 1) {
|
||||||
|
for (size_t i = 0; i < mRanges.Length(); ++i) {
|
||||||
|
nsRange* range = mRanges[i].mRange;
|
||||||
|
bool disconnected1 = false;
|
||||||
|
bool disconnected2 = false;
|
||||||
|
const bool isBeforeStart =
|
||||||
|
nsContentUtils::ComparePoints(range->GetStartParent(),
|
||||||
|
range->StartOffset(),
|
||||||
|
&aParentNode, aOffset,
|
||||||
|
&disconnected1) > 0;
|
||||||
|
const bool isAfterEnd =
|
||||||
|
nsContentUtils::ComparePoints(range->GetEndParent(),
|
||||||
|
range->EndOffset(),
|
||||||
|
&aParentNode, aOffset,
|
||||||
|
&disconnected2) < 0;
|
||||||
|
if (!isBeforeStart && !isAfterEnd && !disconnected1 && !disconnected2) {
|
||||||
|
// aParentNode/aOffset is inside 'range'.
|
||||||
|
mAnchorFocusRange = range;
|
||||||
|
if (dir == eDirNext) {
|
||||||
|
for (size_t j = i + 1; j < mRanges.Length(); ++j) {
|
||||||
|
nsRange* r = mRanges[j].mRange;
|
||||||
|
r->SetInSelection(false);
|
||||||
|
selectFrames(presContext, r, false);
|
||||||
|
}
|
||||||
|
mRanges.TruncateLength(i + 1);
|
||||||
|
} else {
|
||||||
|
for (size_t j = 0; j < i; ++j) {
|
||||||
|
nsRange* r = mRanges[j].mRange;
|
||||||
|
r->SetInSelection(false);
|
||||||
|
selectFrames(presContext, r, false);
|
||||||
|
}
|
||||||
|
mRanges.RemoveElementsAt(0, i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nsINode* anchorNode = GetAnchorNode();
|
nsINode* anchorNode = GetAnchorNode();
|
||||||
nsINode* focusNode = GetFocusNode();
|
nsINode* focusNode = GetFocusNode();
|
||||||
@ -4935,8 +5004,6 @@ Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
|
|||||||
int32_t startOffset = range->StartOffset();
|
int32_t startOffset = range->StartOffset();
|
||||||
int32_t endOffset = range->EndOffset();
|
int32_t endOffset = range->EndOffset();
|
||||||
|
|
||||||
nsDirection dir = GetDirection();
|
|
||||||
|
|
||||||
//compare anchor to old cursor.
|
//compare anchor to old cursor.
|
||||||
|
|
||||||
// We pass |disconnected| to the following ComparePoints calls in order
|
// We pass |disconnected| to the following ComparePoints calls in order
|
||||||
@ -5162,6 +5229,14 @@ Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mRanges.Length() > 1) {
|
||||||
|
for (size_t i = 0; i < mRanges.Length(); ++i) {
|
||||||
|
nsRange* range = mRanges[i].mRange;
|
||||||
|
MOZ_ASSERT(range->IsInSelection());
|
||||||
|
selectFrames(presContext, range, range->IsInSelection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_OUT_RANGE(range);
|
DEBUG_OUT_RANGE(range);
|
||||||
#ifdef DEBUG_SELECTION
|
#ifdef DEBUG_SELECTION
|
||||||
if (eDirNext == mDirection)
|
if (eDirNext == mDirection)
|
||||||
|
Loading…
Reference in New Issue
Block a user