mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-08 16:58:38 +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
|
|
@ -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