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);
|
||||
}
|
||||
|
||||
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,
|
||||
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;
|
||||
protected:
|
||||
void RegisterCommonAncestor(nsINode* aNode);
|
||||
|
@ -2620,6 +2620,7 @@ NS_IMETHODIMP nsDocumentViewer::SelectAll()
|
||||
rv = selection->RemoveAllRanges();
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
mozilla::dom::Selection::AutoApplyUserSelectStyle userSelection(selection);
|
||||
rv = selection->SelectAllChildren(bodyNode);
|
||||
return rv;
|
||||
}
|
||||
|
@ -9,12 +9,13 @@
|
||||
|
||||
#include "nsIWeakReference.h"
|
||||
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/TextRange.h"
|
||||
#include "nsISelection.h"
|
||||
#include "nsISelectionController.h"
|
||||
#include "nsISelectionPrivate.h"
|
||||
#include "nsRange.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/TextRange.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
struct CachedOffsetForFrame;
|
||||
@ -100,8 +101,14 @@ public:
|
||||
int32_t aFlags = 0);
|
||||
nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
|
||||
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 Clear(nsPresContext* aPresContext);
|
||||
nsresult Collapse(nsINode* aParentNode, int32_t aOffset);
|
||||
@ -205,6 +212,19 @@ public:
|
||||
|
||||
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:
|
||||
|
||||
class ScrollSelectionIntoViewEvent;
|
||||
@ -258,6 +278,11 @@ private:
|
||||
int32_t* aStartIndex, int32_t* aEndIndex);
|
||||
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
|
||||
// of DOM start position.
|
||||
//
|
||||
@ -281,6 +306,11 @@ private:
|
||||
CachedOffsetForFrame *mCachedOffsetForFrame;
|
||||
nsDirection mDirection;
|
||||
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
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "nsIFrame.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsISelectionController.h"
|
||||
#include "nsISelectionListener.h"
|
||||
#include "nsITableCellLayout.h"
|
||||
#include "nsIDOMElement.h"
|
||||
#include "WordMovementType.h"
|
||||
@ -613,9 +614,18 @@ private:
|
||||
int16_t PopReason()
|
||||
{
|
||||
int16_t retval = mSelectionChangeReason;
|
||||
mSelectionChangeReason = 0;
|
||||
mSelectionChangeReason = nsISelectionListener::NO_REASON;
|
||||
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;
|
||||
#ifdef DEBUG
|
||||
|
@ -1548,6 +1548,11 @@ nsFrameSelection::TakeFocus(nsIContent* aNewFocus,
|
||||
if (!mDomSelections[index])
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
|
||||
Maybe<Selection::AutoApplyUserSelectStyle> userSelect;
|
||||
if (IsUserSelectionReason()) {
|
||||
userSelect.emplace(mDomSelections[index]);
|
||||
}
|
||||
|
||||
//traverse through document and unselect crap here
|
||||
if (!aContinueSelection) {//single click? setting cursor down
|
||||
uint32_t batching = mBatching;//hack to use the collapse code.
|
||||
@ -3103,6 +3108,7 @@ Selection::Selection()
|
||||
: mCachedOffsetForFrame(nullptr)
|
||||
, mDirection(eDirNext)
|
||||
, mType(nsISelectionController::SELECTION_NORMAL)
|
||||
, mApplyUserSelectStyle(false)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
@ -3112,6 +3118,7 @@ Selection::Selection(nsFrameSelection* aList)
|
||||
, mCachedOffsetForFrame(nullptr)
|
||||
, mDirection(eDirNext)
|
||||
, mType(nsISelectionController::SELECTION_NORMAL)
|
||||
, mApplyUserSelectStyle(false)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
@ -3454,6 +3461,25 @@ Selection::AddItem(nsRange* aItem, int32_t* aOutIndex)
|
||||
|
||||
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;
|
||||
|
||||
// a common case is that we have no ranges yet
|
||||
@ -4443,8 +4469,7 @@ Selection::AddRange(nsRange& aRange, ErrorResult& aRv)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!didAddRange)
|
||||
{
|
||||
if (!didAddRange) {
|
||||
result = AddItem(&aRange, &rangeIndex);
|
||||
if (NS_FAILED(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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
//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* focusNode = GetFocusNode();
|
||||
@ -4935,8 +5004,6 @@ Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
|
||||
int32_t startOffset = range->StartOffset();
|
||||
int32_t endOffset = range->EndOffset();
|
||||
|
||||
nsDirection dir = GetDirection();
|
||||
|
||||
//compare anchor to old cursor.
|
||||
|
||||
// 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);
|
||||
#ifdef DEBUG_SELECTION
|
||||
if (eDirNext == mDirection)
|
||||
|
Loading…
Reference in New Issue
Block a user