mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-29 11:57:32 +09:00
Issue #2388 - Part 2: move focus at every selection change when it's called by JS
This commit is contained in:
parent
a83e4b6a34
commit
681193a256
4 changed files with 202 additions and 16 deletions
|
|
@ -998,7 +998,16 @@ nsRange::DoSetRange(nsINode* aStartN, uint32_t aStartOffset,
|
|||
// Notify any selection listeners. This has to occur last because otherwise the world
|
||||
// could be observed by a selection listener while the range was in an invalid state.
|
||||
if (mSelection) {
|
||||
mSelection->NotifySelectionListeners(mCalledByJS);
|
||||
// Our internal code should not move focus with using this instance while
|
||||
// it's calling Selection::NotifySelectionListeners() which may move focus
|
||||
// or calls selection listeners. So, let's set mCalledByJS to false here
|
||||
// since non-*JS() methods don't set it to false.
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = false;
|
||||
// Be aware, this range may be modified or stop being a range for selection
|
||||
// after this call. Additionally, the selection instance may have gone.
|
||||
RefPtr<Selection> selection = mSelection;
|
||||
selection->NotifySelectionListeners(calledByJSRestorer.SavedValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1228,7 +1237,8 @@ nsRange::IsValidBoundary(nsINode* aNode)
|
|||
void
|
||||
nsRange::SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetStart(aNode, aOffset, aErr);
|
||||
}
|
||||
|
||||
|
|
@ -1290,7 +1300,8 @@ nsRange::SetStart(nsINode* aParent, uint32_t aOffset)
|
|||
void
|
||||
nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetStartBefore(aNode, aErr);
|
||||
}
|
||||
|
||||
|
|
@ -1328,7 +1339,8 @@ nsRange::SetStartBefore(nsIDOMNode* aSibling)
|
|||
void
|
||||
nsRange::SetStartAfterJS(nsINode& aNode, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetStartAfter(aNode, aErr);
|
||||
}
|
||||
|
||||
|
|
@ -1366,7 +1378,8 @@ nsRange::SetStartAfter(nsIDOMNode* aSibling)
|
|||
void
|
||||
nsRange::SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetEnd(aNode, aOffset, aErr);
|
||||
}
|
||||
|
||||
|
|
@ -1487,7 +1500,8 @@ nsRange::SetStartAndEnd(nsINode* aStartParent, uint32_t aStartOffset,
|
|||
void
|
||||
nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetEndBefore(aNode, aErr);
|
||||
}
|
||||
|
||||
|
|
@ -1525,7 +1539,8 @@ nsRange::SetEndBefore(nsIDOMNode* aSibling)
|
|||
void
|
||||
nsRange::SetEndAfterJS(nsINode& aNode, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetEndAfter(aNode, aErr);
|
||||
}
|
||||
|
||||
|
|
@ -1578,7 +1593,8 @@ nsRange::Collapse(bool aToStart)
|
|||
void
|
||||
nsRange::CollapseJS(bool aToStart)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
Unused << Collapse(aToStart);
|
||||
}
|
||||
|
||||
|
|
@ -1596,7 +1612,8 @@ nsRange::SelectNode(nsIDOMNode* aN)
|
|||
void
|
||||
nsRange::SelectNodeJS(nsINode& aNode, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SelectNode(aNode, aErr);
|
||||
}
|
||||
|
||||
|
|
@ -1642,7 +1659,8 @@ nsRange::SelectNodeContents(nsIDOMNode* aN)
|
|||
void
|
||||
nsRange::SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr)
|
||||
{
|
||||
AutoCalledByJSSetter markAsCalledByJS(*this);
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SelectNodeContents(aNode, aErr);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ protected:
|
|||
// Assume that this is guaranteed that this is held by the caller when
|
||||
// this is used. (Note that we cannot use AutoRestore for mCalledByJS
|
||||
// due to a bit field.)
|
||||
class MOZ_RAII AutoCalledByJSSetter final
|
||||
class MOZ_RAII AutoCalledByJSRestore final
|
||||
{
|
||||
private:
|
||||
nsRange& mRange;
|
||||
|
|
@ -421,18 +421,18 @@ protected:
|
|||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
public:
|
||||
explicit AutoCalledByJSSetter(nsRange& aRange
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
explicit AutoCalledByJSRestore(nsRange& aRange
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: mRange(aRange)
|
||||
, mOldValue(aRange.mCalledByJS)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
mRange.mCalledByJS = true;
|
||||
}
|
||||
~AutoCalledByJSSetter()
|
||||
~AutoCalledByJSRestore()
|
||||
{
|
||||
mRange.mCalledByJS = mOldValue;
|
||||
}
|
||||
bool SavedValue() const { return mOldValue; }
|
||||
};
|
||||
|
||||
struct MOZ_STACK_CLASS AutoInvalidateSelection
|
||||
|
|
|
|||
|
|
@ -21,8 +21,12 @@
|
|||
struct CachedOffsetForFrame;
|
||||
class nsAutoScrollTimer;
|
||||
class nsIContentIterator;
|
||||
class nsIDocument;
|
||||
class nsIEditor;
|
||||
class nsIFrame;
|
||||
class nsIHTMLEditor;
|
||||
class nsFrameSelection;
|
||||
class nsPIDOMWindowOuter;
|
||||
struct SelectionDetails;
|
||||
class nsCopySupport;
|
||||
class nsHTMLCopyEncoder;
|
||||
|
|
@ -359,6 +363,43 @@ private:
|
|||
*/
|
||||
nsresult AddItemInternal(nsRange* aRange, int32_t* aOutIndex);
|
||||
|
||||
nsIDocument* GetDocument() const;
|
||||
nsPIDOMWindowOuter* GetWindow() const;
|
||||
nsIEditor* GetEditor() const;
|
||||
|
||||
/**
|
||||
* GetCommonEditingHostForAllRanges() returns common editing host of all
|
||||
* ranges if there is. If at least one of the ranges is in non-editable
|
||||
* element, returns nullptr. See following examples for the detail:
|
||||
*
|
||||
* <div id="a" contenteditable>
|
||||
* an[cestor
|
||||
* <div id="b" contenteditable="false">
|
||||
* non-editable
|
||||
* <div id="c" contenteditable>
|
||||
* desc]endant
|
||||
* in this case, this returns div#a because div#c is also in div#a.
|
||||
*
|
||||
* <div id="a" contenteditable>
|
||||
* an[ce]stor
|
||||
* <div id="b" contenteditable="false">
|
||||
* non-editable
|
||||
* <div id="c" contenteditable>
|
||||
* de[sc]endant
|
||||
* in this case, this returns div#a because second range is also in div#a
|
||||
* and common ancestor of the range (i.e., div#c) is editable.
|
||||
*
|
||||
* <div id="a" contenteditable>
|
||||
* an[ce]stor
|
||||
* <div id="b" contenteditable="false">
|
||||
* [non]-editable
|
||||
* <div id="c" contenteditable>
|
||||
* de[sc]endant
|
||||
* in this case, this returns nullptr because the second range is in
|
||||
* non-editable area.
|
||||
*/
|
||||
Element* GetCommonEditingHostForAllRanges();
|
||||
|
||||
// These are the ranges inside this selection. They are kept sorted in order
|
||||
// of DOM start position.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
|
|||
#include "nsIEditor.h"
|
||||
#include "nsIHTMLEditor.h"
|
||||
#include "nsFocusManager.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
|
@ -1877,6 +1878,7 @@ printf(" * TakeFocus - moving into new cell\n");
|
|||
// Don't notify selection listeners if batching is on:
|
||||
if (GetBatching())
|
||||
return NS_OK;
|
||||
// Be aware, the Selection instance may be destroyed after this call.
|
||||
return NotifySelectionListeners(SelectionType::eNormal);
|
||||
}
|
||||
|
||||
|
|
@ -1917,6 +1919,7 @@ nsFrameSelection::SetDragState(bool aState)
|
|||
mDragSelectingCells = false;
|
||||
// Notify that reason is mouse up.
|
||||
PostReason(nsISelectionListener::MOUSEUP_REASON);
|
||||
// Be aware, the Selection instance may be destroyed after this call.
|
||||
NotifySelectionListeners(SelectionType::eNormal);
|
||||
}
|
||||
}
|
||||
|
|
@ -2411,6 +2414,7 @@ nsFrameSelection::EndBatchChanges(int16_t aReason)
|
|||
int16_t postReason = PopReason() | aReason;
|
||||
PostReason(postReason);
|
||||
mChangesDuringBatching = false;
|
||||
// Be aware, the Selection instance may be destroyed after this call.
|
||||
NotifySelectionListeners(SelectionType::eNormal);
|
||||
}
|
||||
}
|
||||
|
|
@ -2422,7 +2426,8 @@ nsFrameSelection::NotifySelectionListeners(SelectionType aSelectionType)
|
|||
int8_t index = GetIndexFromSelectionType(aSelectionType);
|
||||
if (index >=0 && mDomSelections[index])
|
||||
{
|
||||
return mDomSelections[index]->NotifySelectionListeners();
|
||||
RefPtr<Selection> selection = mDomSelections[index];
|
||||
return selection->NotifySelectionListeners();
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
|
@ -4946,7 +4951,10 @@ Selection::RemoveAllRanges(ErrorResult& aRv)
|
|||
RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
|
||||
frameSelection->ClearTableCellSelection();
|
||||
|
||||
// Be aware, this instance may be destroyed after this call.
|
||||
// XXX Why doesn't this call Selection::NotifySelectionListener() directly?
|
||||
result = frameSelection->NotifySelectionListeners(GetType());
|
||||
|
||||
// Also need to notify the frames!
|
||||
// PresShell::CharacterDataChanged should do that on DocumentChanged
|
||||
if (NS_FAILED(result)) {
|
||||
|
|
@ -5031,6 +5039,8 @@ Selection::AddRangeInternal(nsRange& aRange, nsIDocument* aDocument,
|
|||
if (!mFrameSelection)
|
||||
return;//nothing to do
|
||||
|
||||
// Be aware, this instance may be destroyed after this call.
|
||||
// XXX Why doesn't this call Selection::NotifySelectionListener() directly?
|
||||
RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
|
||||
result = frameSelection->NotifySelectionListeners(GetType());
|
||||
if (NS_FAILED(result)) {
|
||||
|
|
@ -5126,6 +5136,9 @@ Selection::RemoveRange(nsRange& aRange, ErrorResult& aRv)
|
|||
|
||||
if (!mFrameSelection)
|
||||
return;//nothing to do
|
||||
|
||||
// Be aware, this instance may be destroyed after this call.
|
||||
// XXX Why doesn't this call Selection::NotifySelectionListener() directly?
|
||||
RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
|
||||
rv = frameSelection->NotifySelectionListeners(GetType());
|
||||
if (NS_FAILED(rv)) {
|
||||
|
|
@ -5241,6 +5254,9 @@ Selection::Collapse(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
|
|||
}
|
||||
setAnchorFocusRange(0);
|
||||
selectFrames(presContext, range, true);
|
||||
|
||||
// Be aware, this instance may be destroyed after this call.
|
||||
// XXX Why doesn't this call Selection::NotifySelectionListener() directly?
|
||||
result = frameSelection->NotifySelectionListeners(GetType());
|
||||
if (NS_FAILED(result)) {
|
||||
aRv.Throw(result);
|
||||
|
|
@ -5816,6 +5832,9 @@ Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
|
|||
printf ("Sel. Extend to %p %s %d\n", content.get(),
|
||||
nsAtomCString(content->NodeInfo()->NameAtom()).get(), aOffset);
|
||||
#endif
|
||||
|
||||
// Be aware, this instance may be destroyed after this call.
|
||||
// XXX Why doesn't this call Selection::NotifySelectionListener() directly?
|
||||
RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
|
||||
res = frameSelection->NotifySelectionListeners(GetType());
|
||||
if (NS_FAILED(res)) {
|
||||
|
|
@ -5989,6 +6008,30 @@ Selection::GetPresShell() const
|
|||
return mFrameSelection->GetShell();
|
||||
}
|
||||
|
||||
nsIDocument*
|
||||
Selection::GetDocument() const
|
||||
{
|
||||
nsIPresShell* presShell = GetPresShell();
|
||||
return presShell ? presShell->GetDocument() : nullptr;
|
||||
}
|
||||
|
||||
nsPIDOMWindowOuter*
|
||||
Selection::GetWindow() const
|
||||
{
|
||||
nsIDocument* document = GetDocument();
|
||||
return document ? document->GetWindow() : nullptr;
|
||||
}
|
||||
|
||||
nsIEditor*
|
||||
Selection::GetEditor() const
|
||||
{
|
||||
nsPresContext* presContext = GetPresContext();
|
||||
if (!presContext) {
|
||||
return nullptr;
|
||||
}
|
||||
return nsContentUtils::GetHTMLEditor(presContext);
|
||||
}
|
||||
|
||||
nsIFrame *
|
||||
Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect)
|
||||
{
|
||||
|
|
@ -6290,6 +6333,46 @@ Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove,
|
|||
}
|
||||
}
|
||||
|
||||
Element*
|
||||
Selection::GetCommonEditingHostForAllRanges()
|
||||
{
|
||||
Element* editingHost = nullptr;
|
||||
for (RangeData& rangeData : mRanges) {
|
||||
nsRange* range = rangeData.mRange;
|
||||
MOZ_ASSERT(range);
|
||||
nsINode* commonAncestorNode = range->GetCommonAncestor();
|
||||
if (!commonAncestorNode || !commonAncestorNode->IsContent()) {
|
||||
return nullptr;
|
||||
}
|
||||
nsIContent* commonAncestor = commonAncestorNode->AsContent();
|
||||
Element* foundEditingHost = commonAncestor->GetEditingHost();
|
||||
// Even when common ancestor is a non-editable element in a contenteditable
|
||||
// element, we don't need to move focus to the contenteditable element
|
||||
// because Chromium doesn't set focus to it.
|
||||
if (!foundEditingHost) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!editingHost) {
|
||||
editingHost = foundEditingHost;
|
||||
continue;
|
||||
}
|
||||
if (editingHost == foundEditingHost) {
|
||||
continue;
|
||||
}
|
||||
if (nsContentUtils::ContentIsDescendantOf(foundEditingHost, editingHost)) {
|
||||
continue;
|
||||
}
|
||||
if (nsContentUtils::ContentIsDescendantOf(editingHost, foundEditingHost)) {
|
||||
editingHost = foundEditingHost;
|
||||
continue;
|
||||
}
|
||||
// editingHost and foundEditingHost are not a descendant of the other.
|
||||
// So, there is no common editing host.
|
||||
return nullptr;
|
||||
}
|
||||
return editingHost;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Selection::NotifySelectionListeners(bool aCalledByJS)
|
||||
{
|
||||
|
|
@ -6304,6 +6387,50 @@ Selection::NotifySelectionListeners()
|
|||
if (!mFrameSelection)
|
||||
return NS_OK;//nothing to do
|
||||
|
||||
// Our internal code should not move focus with using this class while
|
||||
// this moves focus nor from selection listeners.
|
||||
AutoRestore<bool> calledByJSRestorer(mCalledByJS);
|
||||
mCalledByJS = false;
|
||||
|
||||
// When normal selection is changed by Selection API, we need to move focus
|
||||
// if common ancestor of all ranges are in an editing host. Note that we
|
||||
// don't need to move focus *to* the other focusable node, because other
|
||||
// browsers don't do this, either.
|
||||
if (mSelectionType == SelectionType::eNormal &&
|
||||
calledByJSRestorer.SavedValue()) {
|
||||
nsPIDOMWindowOuter* window = GetWindow();
|
||||
nsIDocument* document = GetDocument();
|
||||
// If the document is in design mode or doesn't have contenteditable
|
||||
// element, we don't need to move focus.
|
||||
if (window && document && !document->HasFlag(NODE_IS_EDITABLE) &&
|
||||
GetEditor()) {
|
||||
RefPtr<Element> newEditingHost = GetCommonEditingHostForAllRanges();
|
||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
nsIContent* focusedContent =
|
||||
fm->GetFocusedDescendant(window, false, getter_AddRefs(focusedWindow));
|
||||
nsCOMPtr<Element> focusedElement = do_QueryInterface(focusedContent);
|
||||
// When all selected ranges are in an editing host, it should take focus.
|
||||
if (newEditingHost && newEditingHost != focusedElement) {
|
||||
MOZ_ASSERT(!newEditingHost->IsInNativeAnonymousSubtree());
|
||||
nsCOMPtr<nsIDOMElement> domElementToFocus =
|
||||
do_QueryInterface(newEditingHost->AsDOMNode());
|
||||
// Note that don't steal focus from focused window if the window doesn't
|
||||
// have focus.
|
||||
fm->SetFocus(domElementToFocus, nsIFocusManager::FLAG_NOSWITCHFRAME);
|
||||
}
|
||||
// Otherwise, if current focused element is an editing host, it should
|
||||
// be blurred if there is no common editing host of the selected ranges.
|
||||
else if (!newEditingHost && focusedElement &&
|
||||
focusedElement == focusedElement->GetEditingHost()) {
|
||||
IgnoredErrorResult err;
|
||||
focusedElement->Blur(err);
|
||||
NS_WARNING_ASSERTION(!err.Failed(),
|
||||
"Failed to blur focused element");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
|
||||
if (frameSelection->GetBatching()) {
|
||||
frameSelection->SetDirty();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue