зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1406726 - HTMLEditRules::WillInsertBreak() should reset mNewNode with caret position r=m_kato
HTMLEditRules::WillInsertBreak() started to use HTMLEditRules::MakeBasicBlock() to wrap existing inline elements with default paragraph separator if inline elements are children of editing host. However, HTMLEditRules::ApplyBlockStyle() called by HTMLEditRules::MakeBasicBlock() sets mNewNode to last new block node. So, if it wraps inline elements after caret position, mNewNode becomes after expected caret position and HTMLEditRules::AfterEditInner() will move caret into it. This patch make HTMLEditRules::WillInsertBreak() reset mNewNode with caret position after calling HTMLEditRules::MakeBasicBlock(). Additionally, this patch fixes a bug of HTMLEditor::IsVisibleBRElement(). That is, it uses only editable nodes to check if given <br> element is visible. However, editable state is not related to this check. If <br> element is followed by non-editable inline node (except invisible data nodes), it always visible. This bug caused getting wrong nodes with HTMLEditRules::GetNodesFromSelection() which is used by HTMLEditRules::MakeBasicBlock(). Therefore, this patch also adds lots of EditorBase::Get(Next|Previous)ElementOrText*() and HTMLEditor::Get(Next|Previous)HTMLElementOrText*() to ignore only invisible data nodes. Note that even with this fix, the range of nodes computed by HTMLEditRules::GetNodesFromSelection() is still odd if only non-editable elements follow a <br> node which is first <br> element after the caret position. However, what is expected by the execCommand spec is unclear. So, automated test added by this patch checks current inconsistent behavior for now. MozReview-Commit-ID: 2m52StwoEEH --HG-- extra : rebase_source : 6b9b2338e35c4d2e89a405fd8e1ffc7b0873ca1e
This commit is contained in:
Родитель
0a16fae4da
Коммит
18912959bd
|
@ -3571,17 +3571,20 @@ EditorBase::GetNodeLocation(nsINode* aChild,
|
|||
nsIContent*
|
||||
EditorBase::GetPreviousNodeInternal(nsINode& aNode,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
if (!IsDescendantOfEditorRoot(&aNode)) {
|
||||
return nullptr;
|
||||
}
|
||||
return FindNode(&aNode, false, aFindEditableNode, aNoBlockCrossing);
|
||||
return FindNode(&aNode, false,
|
||||
aFindEditableNode, aFindAnyDataNode, aNoBlockCrossing);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
EditorBase::GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
MOZ_ASSERT(aPoint.IsSetAndValid());
|
||||
|
@ -3597,13 +3600,15 @@ EditorBase::GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
|
|||
return nullptr;
|
||||
}
|
||||
return GetPreviousNodeInternal(*aPoint.GetContainer(),
|
||||
aFindEditableNode, aNoBlockCrossing);
|
||||
aFindEditableNode, aFindAnyDataNode,
|
||||
aNoBlockCrossing);
|
||||
}
|
||||
|
||||
// else look before the child at 'aOffset'
|
||||
if (aPoint.GetChild()) {
|
||||
return GetPreviousNodeInternal(*aPoint.GetChild(),
|
||||
aFindEditableNode, aNoBlockCrossing);
|
||||
aFindEditableNode, aFindAnyDataNode,
|
||||
aNoBlockCrossing);
|
||||
}
|
||||
|
||||
// unless there isn't one, in which case we are at the end of the node
|
||||
|
@ -3614,29 +3619,34 @@ EditorBase::GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!aFindEditableNode || IsEditable(rightMostNode)) {
|
||||
if ((!aFindEditableNode || IsEditable(rightMostNode)) &&
|
||||
(aFindAnyDataNode || IsElementOrText(*rightMostNode))) {
|
||||
return rightMostNode;
|
||||
}
|
||||
|
||||
// restart the search from the non-editable node we just found
|
||||
return GetPreviousNodeInternal(*rightMostNode,
|
||||
aFindEditableNode, aNoBlockCrossing);
|
||||
aFindEditableNode, aFindAnyDataNode,
|
||||
aNoBlockCrossing);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
EditorBase::GetNextNodeInternal(nsINode& aNode,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
if (!IsDescendantOfEditorRoot(&aNode)) {
|
||||
return nullptr;
|
||||
}
|
||||
return FindNode(&aNode, true, aFindEditableNode, aNoBlockCrossing);
|
||||
return FindNode(&aNode, true,
|
||||
aFindEditableNode, aFindAnyDataNode, aNoBlockCrossing);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
EditorBase::GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
MOZ_ASSERT(aPoint.IsSetAndValid());
|
||||
|
@ -3670,13 +3680,15 @@ EditorBase::GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!aFindEditableNode || IsEditable(leftMostNode)) {
|
||||
if ((!aFindEditableNode || IsEditable(leftMostNode)) &&
|
||||
(aFindAnyDataNode || IsElementOrText(*leftMostNode))) {
|
||||
return leftMostNode;
|
||||
}
|
||||
|
||||
// restart the search from the non-editable node we just found
|
||||
return GetNextNodeInternal(*leftMostNode,
|
||||
aFindEditableNode, aNoBlockCrossing);
|
||||
aFindEditableNode, aFindAnyDataNode,
|
||||
aNoBlockCrossing);
|
||||
}
|
||||
|
||||
// unless there isn't one, in which case we are at the end of the node
|
||||
|
@ -3687,7 +3699,8 @@ EditorBase::GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
|
|||
}
|
||||
|
||||
return GetNextNodeInternal(*point.GetContainer(),
|
||||
aFindEditableNode, aNoBlockCrossing);
|
||||
aFindEditableNode, aFindAnyDataNode,
|
||||
aNoBlockCrossing);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
|
@ -3746,6 +3759,7 @@ nsIContent*
|
|||
EditorBase::FindNode(nsINode* aCurrentNode,
|
||||
bool aGoForward,
|
||||
bool aEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool bNoBlockCrossing)
|
||||
{
|
||||
if (IsEditorRoot(aCurrentNode)) {
|
||||
|
@ -3763,11 +3777,13 @@ EditorBase::FindNode(nsINode* aCurrentNode,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!aEditableNode || IsEditable(candidate)) {
|
||||
if ((!aEditableNode || IsEditable(candidate)) &&
|
||||
(aFindAnyDataNode || IsElementOrText(*candidate))) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return FindNode(candidate, aGoForward, aEditableNode, bNoBlockCrossing);
|
||||
return FindNode(candidate, aGoForward,
|
||||
aEditableNode, aFindAnyDataNode, bNoBlockCrossing);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
|
|
|
@ -632,12 +632,15 @@ protected:
|
|||
nsIContent* FindNode(nsINode* aCurrentNode,
|
||||
bool aGoForward,
|
||||
bool aEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool bNoBlockCrossing);
|
||||
|
||||
/**
|
||||
* Get the node immediately previous node of aNode.
|
||||
* @param atNode The node from which we start the search.
|
||||
* @param aFindEditableNode If true, only return an editable node.
|
||||
* @param aFindAnyDataNode If true, may return invisible data node
|
||||
* like Comment.
|
||||
* @param aNoBlockCrossing If true, don't move across "block" nodes,
|
||||
* whatever that means.
|
||||
* @return The node that occurs before aNode in
|
||||
|
@ -647,6 +650,7 @@ protected:
|
|||
*/
|
||||
nsIContent* GetPreviousNodeInternal(nsINode& aNode,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool aNoBlockCrossing);
|
||||
|
||||
/**
|
||||
|
@ -654,12 +658,15 @@ protected:
|
|||
*/
|
||||
nsIContent* GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool aNoBlockCrossing);
|
||||
|
||||
/**
|
||||
* Get the node immediately next node of aNode.
|
||||
* @param aNode The node from which we start the search.
|
||||
* @param aFindEditableNode If true, only return an editable node.
|
||||
* @param aFindAnyDataNode If true, may return invisible data node
|
||||
* like Comment.
|
||||
* @param aNoBlockCrossing If true, don't move across "block" nodes,
|
||||
* whatever that means.
|
||||
* @return The node that occurs after aNode in the
|
||||
|
@ -669,6 +676,7 @@ protected:
|
|||
*/
|
||||
nsIContent* GetNextNodeInternal(nsINode& aNode,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool bNoBlockCrossing);
|
||||
|
||||
/**
|
||||
|
@ -676,6 +684,7 @@ protected:
|
|||
*/
|
||||
nsIContent* GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
|
||||
bool aFindEditableNode,
|
||||
bool aFindAnyDataNode,
|
||||
bool aNoBlockCrossing);
|
||||
|
||||
|
||||
|
@ -807,36 +816,52 @@ public:
|
|||
*/
|
||||
nsIContent* GetPreviousNode(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousNodeInternal(aPoint, false, false);
|
||||
return GetPreviousNodeInternal(aPoint, false, true, false);
|
||||
}
|
||||
nsIContent* GetPreviousElementOrText(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousNodeInternal(aPoint, false, false, false);
|
||||
}
|
||||
nsIContent* GetPreviousEditableNode(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousNodeInternal(aPoint, true, false);
|
||||
return GetPreviousNodeInternal(aPoint, true, true, false);
|
||||
}
|
||||
nsIContent* GetPreviousNodeInBlock(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousNodeInternal(aPoint, false, true);
|
||||
return GetPreviousNodeInternal(aPoint, false, true, true);
|
||||
}
|
||||
nsIContent* GetPreviousElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousNodeInternal(aPoint, false, false, true);
|
||||
}
|
||||
nsIContent* GetPreviousEditableNodeInBlock(
|
||||
const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousNodeInternal(aPoint, true, true);
|
||||
return GetPreviousNodeInternal(aPoint, true, true, true);
|
||||
}
|
||||
nsIContent* GetPreviousNode(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousNodeInternal(aNode, false, false);
|
||||
return GetPreviousNodeInternal(aNode, false, true, false);
|
||||
}
|
||||
nsIContent* GetPreviousElementOrText(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousNodeInternal(aNode, false, false, false);
|
||||
}
|
||||
nsIContent* GetPreviousEditableNode(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousNodeInternal(aNode, true, false);
|
||||
return GetPreviousNodeInternal(aNode, true, true, false);
|
||||
}
|
||||
nsIContent* GetPreviousNodeInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousNodeInternal(aNode, false, true);
|
||||
return GetPreviousNodeInternal(aNode, false, true, true);
|
||||
}
|
||||
nsIContent* GetPreviousElementOrTextInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousNodeInternal(aNode, false, false, true);
|
||||
}
|
||||
nsIContent* GetPreviousEditableNodeInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousNodeInternal(aNode, true, true);
|
||||
return GetPreviousNodeInternal(aNode, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -867,36 +892,52 @@ public:
|
|||
*/
|
||||
nsIContent* GetNextNode(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextNodeInternal(aPoint, false, false);
|
||||
return GetNextNodeInternal(aPoint, false, true, false);
|
||||
}
|
||||
nsIContent* GetNextElementOrText(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextNodeInternal(aPoint, false, false, false);
|
||||
}
|
||||
nsIContent* GetNextEditableNode(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextNodeInternal(aPoint, true, false);
|
||||
return GetNextNodeInternal(aPoint, true, true, false);
|
||||
}
|
||||
nsIContent* GetNextNodeInBlock(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextNodeInternal(aPoint, false, true);
|
||||
return GetNextNodeInternal(aPoint, false, true, true);
|
||||
}
|
||||
nsIContent* GetNextElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextNodeInternal(aPoint, false, false, true);
|
||||
}
|
||||
nsIContent* GetNextEditableNodeInBlock(
|
||||
const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextNodeInternal(aPoint, true, true);
|
||||
return GetNextNodeInternal(aPoint, true, true, true);
|
||||
}
|
||||
nsIContent* GetNextNode(nsINode& aNode)
|
||||
{
|
||||
return GetNextNodeInternal(aNode, false, false);
|
||||
return GetNextNodeInternal(aNode, false, true, false);
|
||||
}
|
||||
nsIContent* GetNextElementOrText(nsINode& aNode)
|
||||
{
|
||||
return GetNextNodeInternal(aNode, false, false, false);
|
||||
}
|
||||
nsIContent* GetNextEditableNode(nsINode& aNode)
|
||||
{
|
||||
return GetNextNodeInternal(aNode, true, false);
|
||||
return GetNextNodeInternal(aNode, true, true, false);
|
||||
}
|
||||
nsIContent* GetNextNodeInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetNextNodeInternal(aNode, false, true);
|
||||
return GetNextNodeInternal(aNode, false, true, true);
|
||||
}
|
||||
nsIContent* GetNextElementOrTextInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetNextNodeInternal(aNode, false, false, true);
|
||||
}
|
||||
nsIContent* GetNextEditableNodeInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetNextNodeInternal(aNode, true, true);
|
||||
return GetNextNodeInternal(aNode, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -974,6 +1015,20 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if aNode is a usual element node (not bogus node) or
|
||||
* a text node. In other words, returns true if aNode is a usual element
|
||||
* node or visible data node.
|
||||
*/
|
||||
bool IsElementOrText(const nsINode& aNode) const
|
||||
{
|
||||
if (!aNode.IsContent() || IsMozEditorBogusNode(&aNode)) {
|
||||
return false;
|
||||
}
|
||||
return aNode.NodeType() == nsINode::ELEMENT_NODE ||
|
||||
aNode.NodeType() == nsINode::TEXT_NODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if selection is in an editable element and both the range
|
||||
* start and the range end are editable. E.g., even if the selection range
|
||||
|
@ -985,7 +1040,7 @@ public:
|
|||
/**
|
||||
* Returns true if aNode is a MozEditorBogus node.
|
||||
*/
|
||||
bool IsMozEditorBogusNode(nsINode* aNode)
|
||||
bool IsMozEditorBogusNode(const nsINode* aNode) const
|
||||
{
|
||||
return aNode && aNode->IsElement() &&
|
||||
aNode->AsElement()->AttrValueIs(kNameSpaceID_None,
|
||||
|
|
|
@ -1752,6 +1752,14 @@ HTMLEditRules::WillInsertBreak(Selection& aSelection,
|
|||
*aHandled = true;
|
||||
return NS_OK;
|
||||
}
|
||||
// Now, mNewBlock is last created block element for wrapping inline
|
||||
// elements around the caret position and AfterEditInner() will move
|
||||
// caret into it. However, it may be different from block parent of
|
||||
// the caret position. E.g., MakeBasicBlock() may wrap following
|
||||
// inline elements of a <br> element which is next sibling of container
|
||||
// of the caret. So, we need to adjust mNewBlock here for avoiding
|
||||
// jumping caret to odd position.
|
||||
mNewBlock = blockParent;
|
||||
}
|
||||
|
||||
// If block is empty, populate with br. (For example, imagine a div that
|
||||
|
@ -5925,6 +5933,18 @@ HTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere,
|
|||
|
||||
// look ahead through any further inline nodes that aren't across a <br> from
|
||||
// us, and that are enclosed in the same block.
|
||||
// XXX Currently, we stop block-extending when finding visible <br> element.
|
||||
// This might be different from "block-extend" of execCommand spec.
|
||||
// However, the spec is really unclear.
|
||||
// XXX Probably, scanning only editable nodes is wrong for
|
||||
// EditAction::makeBasicBlock because it might be better to wrap existing
|
||||
// inline elements even if it's non-editable. For example, following
|
||||
// examples with insertParagraph causes different result:
|
||||
// * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
|
||||
// * <div contenteditable>foo[]<b>bar</b></div>
|
||||
// * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
|
||||
// Only in the first case, after the caret position isn't wrapped with
|
||||
// new <div> element.
|
||||
nsCOMPtr<nsIContent> nextNode =
|
||||
htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
|
||||
|
||||
|
|
|
@ -420,8 +420,26 @@ protected:
|
|||
void MakeTransitionList(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
|
||||
nsTArray<bool>& aTransitionArray);
|
||||
nsresult RemoveBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray);
|
||||
|
||||
/**
|
||||
* ApplyBlockStyle() formats all nodes in aNodeArray with block elements
|
||||
* whose name is aBlockTag.
|
||||
* If aNodeArray has an inline element, a block element is created and the
|
||||
* inline element and following inline elements are moved into the new block
|
||||
* element.
|
||||
* If aNodeArray has <br> elements, they'll be removed from the DOM tree and
|
||||
* new block element will be created when there are some remaining inline
|
||||
* elements.
|
||||
* If aNodeArray has a block element, this calls itself with children of
|
||||
* the block element. Then, new block element will be created when there
|
||||
* are some remaining inline elements.
|
||||
*
|
||||
* @param aNodeArray Must be descendants of a node.
|
||||
* @param aBlockTag The element name of new block elements.
|
||||
*/
|
||||
nsresult ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
|
||||
nsAtom& aBlockTag);
|
||||
|
||||
nsresult MakeBlockquote(nsTArray<OwningNonNull<nsINode>>& aNodeArray);
|
||||
|
||||
/**
|
||||
|
|
|
@ -870,8 +870,13 @@ HTMLEditor::IsVisibleBRElement(nsINode* aNode)
|
|||
if (!TextEditUtils::IsBreak(aNode)) {
|
||||
return false;
|
||||
}
|
||||
// Check if there is a later node in block after br
|
||||
nsCOMPtr<nsINode> nextNode = GetNextEditableHTMLNodeInBlock(*aNode);
|
||||
// Check if there is another element or text node in block after current
|
||||
// <br> element.
|
||||
// Note that even if following node is non-editable, it may make the
|
||||
// <br> element visible if it just exists.
|
||||
// E.g., foo<br><button contenteditable="false">button</button>
|
||||
// However, we need to ignore invisible data nodes like comment node.
|
||||
nsCOMPtr<nsINode> nextNode = GetNextHTMLElementOrTextInBlock(*aNode);
|
||||
if (nextNode && TextEditUtils::IsBreak(nextNode)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -890,7 +895,11 @@ HTMLEditor::IsVisibleBRElement(nsINode* aNode)
|
|||
|
||||
// If there's an inline node after this one that's not a break, and also a
|
||||
// prior break, this break must be visible.
|
||||
nsCOMPtr<nsINode> priorNode = GetPreviousEditableHTMLNodeInBlock(*aNode);
|
||||
// Note that even if previous node is non-editable, it may make the
|
||||
// <br> element visible if it just exists.
|
||||
// E.g., <button contenteditable="false"><br>foo
|
||||
// However, we need to ignore invisible data nodes like comment node.
|
||||
nsCOMPtr<nsINode> priorNode = GetPreviousHTMLElementOrTextInBlock(*aNode);
|
||||
if (priorNode && TextEditUtils::IsBreak(priorNode)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -3703,6 +3712,29 @@ HTMLEditor::GetNextHTMLSibling(nsINode* aNode)
|
|||
return node;
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
HTMLEditor::GetPreviousHTMLElementOrTextInternal(nsINode& aNode,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
if (!GetActiveEditingHost()) {
|
||||
return nullptr;
|
||||
}
|
||||
return aNoBlockCrossing ? GetPreviousElementOrTextInBlock(aNode) :
|
||||
GetPreviousElementOrText(aNode);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
HTMLEditor::GetPreviousHTMLElementOrTextInternal(
|
||||
const EditorRawDOMPoint& aPoint,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
if (!GetActiveEditingHost()) {
|
||||
return nullptr;
|
||||
}
|
||||
return aNoBlockCrossing ? GetPreviousElementOrTextInBlock(aPoint) :
|
||||
GetPreviousElementOrText(aPoint);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
HTMLEditor::GetPreviousEditableHTMLNodeInternal(nsINode& aNode,
|
||||
bool aNoBlockCrossing)
|
||||
|
@ -3725,6 +3757,28 @@ HTMLEditor::GetPreviousEditableHTMLNodeInternal(const EditorRawDOMPoint& aPoint,
|
|||
GetPreviousEditableNode(aPoint);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
HTMLEditor::GetNextHTMLElementOrTextInternal(nsINode& aNode,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
if (!GetActiveEditingHost()) {
|
||||
return nullptr;
|
||||
}
|
||||
return aNoBlockCrossing ? GetNextElementOrTextInBlock(aNode) :
|
||||
GetNextElementOrText(aNode);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
HTMLEditor::GetNextHTMLElementOrTextInternal(const EditorRawDOMPoint& aPoint,
|
||||
bool aNoBlockCrossing)
|
||||
{
|
||||
if (!GetActiveEditingHost()) {
|
||||
return nullptr;
|
||||
}
|
||||
return aNoBlockCrossing ? GetNextElementOrTextInBlock(aPoint) :
|
||||
GetNextElementOrText(aPoint);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
HTMLEditor::GetNextEditableHTMLNodeInternal(nsINode& aNode,
|
||||
bool aNoBlockCrossing)
|
||||
|
|
|
@ -891,6 +891,39 @@ protected:
|
|||
|
||||
nsIContent* GetNextHTMLSibling(nsINode* aNode);
|
||||
|
||||
/**
|
||||
* GetPreviousHTMLElementOrText*() methods are similar to
|
||||
* EditorBase::GetPreviousElementOrText*() but this won't return nodes
|
||||
* outside active editing host.
|
||||
*/
|
||||
nsIContent* GetPreviousHTMLElementOrText(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousHTMLElementOrTextInternal(aNode, false);
|
||||
}
|
||||
nsIContent* GetPreviousHTMLElementOrTextInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetPreviousHTMLElementOrTextInternal(aNode, true);
|
||||
}
|
||||
nsIContent* GetPreviousHTMLElementOrText(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousHTMLElementOrTextInternal(aPoint, false);
|
||||
}
|
||||
nsIContent*
|
||||
GetPreviousHTMLElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetPreviousHTMLElementOrTextInternal(aPoint, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetPreviousHTMLElementOrTextInternal() methods are common implementation
|
||||
* of above methods. Please don't use this method directly.
|
||||
*/
|
||||
nsIContent* GetPreviousHTMLElementOrTextInternal(nsINode& aNode,
|
||||
bool aNoBlockCrossing);
|
||||
nsIContent*
|
||||
GetPreviousHTMLElementOrTextInternal(const EditorRawDOMPoint& aPoint,
|
||||
bool aNoBlockCrossing);
|
||||
|
||||
/**
|
||||
* GetPreviousEditableHTMLNode*() methods are similar to
|
||||
* EditorBase::GetPreviousEditableNode() but this won't return nodes outside
|
||||
|
@ -924,12 +957,48 @@ protected:
|
|||
const EditorRawDOMPoint& aPoint,
|
||||
bool aNoBlockCrossing);
|
||||
|
||||
/**
|
||||
* GetNextHTMLElementOrText*() methods are similar to
|
||||
* EditorBase::GetNextElementOrText*() but this won't return nodes outside
|
||||
* active editing host.
|
||||
*
|
||||
* Note that same as EditorBase::GetTextEditableNode(), methods which take
|
||||
* |const EditorRawDOMPoint&| start to search from the node pointed by it.
|
||||
* On the other hand, methods which take |nsINode&| start to search from
|
||||
* next node of aNode.
|
||||
*/
|
||||
nsIContent* GetNextHTMLElementOrText(nsINode& aNode)
|
||||
{
|
||||
return GetNextHTMLElementOrTextInternal(aNode, false);
|
||||
}
|
||||
nsIContent* GetNextHTMLElementOrTextInBlock(nsINode& aNode)
|
||||
{
|
||||
return GetNextHTMLElementOrTextInternal(aNode, true);
|
||||
}
|
||||
nsIContent* GetNextHTMLElementOrText(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextHTMLElementOrTextInternal(aPoint, false);
|
||||
}
|
||||
nsIContent* GetNextHTMLElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
|
||||
{
|
||||
return GetNextHTMLElementOrTextInternal(aPoint, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetNextHTMLNodeInternal() methods are common implementation
|
||||
* of above methods. Please don't use this method directly.
|
||||
*/
|
||||
nsIContent* GetNextHTMLElementOrTextInternal(nsINode& aNode,
|
||||
bool aNoBlockCrossing);
|
||||
nsIContent* GetNextHTMLElementOrTextInternal(const EditorRawDOMPoint& aPoint,
|
||||
bool aNoBlockCrossing);
|
||||
|
||||
/**
|
||||
* GetNextEditableHTMLNode*() methods are similar to
|
||||
* EditorBase::GetNextEditableNode() but this won't return nodes outside
|
||||
* active editing host.
|
||||
*
|
||||
* Note that same as EditorBaseGetTextEditableNode(), methods which take
|
||||
* Note that same as EditorBase::GetTextEditableNode(), methods which take
|
||||
* |const EditorRawDOMPoint&| start to search from the node pointed by it.
|
||||
* On the other hand, methods which take |nsINode&| start to search from
|
||||
* next node of aNode.
|
||||
|
|
|
@ -255,6 +255,7 @@ skip-if = toolkit == 'android' # bug 1315898
|
|||
[test_bug1390562.html]
|
||||
[test_bug1394758.html]
|
||||
[test_bug1399722.html]
|
||||
[test_bug1406726.html]
|
||||
[test_bug1409520.html]
|
||||
[test_bug1425997.html]
|
||||
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1406726
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1406726</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406726">Mozilla Bug 1406726</a>
|
||||
<p id="display"></p>
|
||||
<div id="editor" contenteditable></div>
|
||||
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 1406726 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(() => {
|
||||
let editor = document.getElementById("editor");
|
||||
let selection = window.getSelection();
|
||||
|
||||
editor.focus();
|
||||
for (let paragraphSeparator of ["div", "p"]) {
|
||||
document.execCommand("defaultParagraphSeparator", false, paragraphSeparator);
|
||||
|
||||
// The result of editor.innerHTML may be wrong in this tests.
|
||||
// Currently, editor wraps following elements of <br> element with default
|
||||
// paragraph separator only when there is only non-editable elements.
|
||||
// This behavior should be standardized by execCommand spec.
|
||||
|
||||
editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>";
|
||||
selection.collapse(editor.childNodes.item(2), "bar".length);
|
||||
document.execCommand("insertParagraph", false);
|
||||
is(editor.innerHTML, "foo<br>" +
|
||||
"<" + paragraphSeparator + ">bar</" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + "><br></" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + "><span contenteditable=\"false\">baz</span></" + paragraphSeparator + ">",
|
||||
"All inline nodes including non-editable <span> element should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
|
||||
ok(selection.isCollapsed, "Selection should be collapsed");
|
||||
is(selection.anchorNode, editor.childNodes.item(3),
|
||||
"Caret should be in the third line");
|
||||
is(selection.anchorOffset, 0,
|
||||
"Caret should be at start of the third line");
|
||||
|
||||
editor.innerHTML = "foo<br>bar<br><span>baz</span>";
|
||||
selection.collapse(editor.childNodes.item(2), "bar".length);
|
||||
document.execCommand("insertParagraph", false);
|
||||
is(editor.innerHTML, "foo<br>" +
|
||||
"<" + paragraphSeparator + ">bar</" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + "><br></" + paragraphSeparator + ">" +
|
||||
"<span>baz</span>",
|
||||
"All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
|
||||
ok(selection.isCollapsed, "Selection should be collapsed");
|
||||
is(selection.anchorNode, editor.childNodes.item(3),
|
||||
"Caret should be in the third line");
|
||||
is(selection.anchorOffset, 0,
|
||||
"Caret should be at start of the third line");
|
||||
|
||||
editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>qux";
|
||||
selection.collapse(editor.childNodes.item(2), "bar".length);
|
||||
document.execCommand("insertParagraph", false);
|
||||
is(editor.innerHTML, "foo<br>" +
|
||||
"<" + paragraphSeparator + ">bar</" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + "><br></" + paragraphSeparator + ">" +
|
||||
"<span contenteditable=\"false\">baz</span>qux",
|
||||
"All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
|
||||
ok(selection.isCollapsed, "Selection should be collapsed");
|
||||
is(selection.anchorNode, editor.childNodes.item(3),
|
||||
"Caret should be in the third line");
|
||||
is(selection.anchorOffset, 0,
|
||||
"Caret should be at start of the third line");
|
||||
|
||||
editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>";
|
||||
selection.collapse(editor.childNodes.item(2), "ba".length);
|
||||
document.execCommand("insertParagraph", false);
|
||||
is(editor.innerHTML, "foo<br>" +
|
||||
"<" + paragraphSeparator + ">ba</" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + ">r</" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + "><span contenteditable=\"false\">baz</span></" + paragraphSeparator + ">",
|
||||
"All inline nodes including non-editable <span> element should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
|
||||
ok(selection.isCollapsed, "Selection should be collapsed");
|
||||
is(selection.anchorNode, editor.childNodes.item(3).firstChild,
|
||||
"Caret should be in the text node in the third line");
|
||||
is(selection.anchorOffset, 0,
|
||||
"Caret should be at start of the text node in the third line");
|
||||
|
||||
editor.innerHTML = "foo<br>bar<br><span>baz</span>";
|
||||
selection.collapse(editor.childNodes.item(2), "ba".length);
|
||||
document.execCommand("insertParagraph", false);
|
||||
is(editor.innerHTML, "foo<br>" +
|
||||
"<" + paragraphSeparator + ">ba</" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + ">r</" + paragraphSeparator + ">" +
|
||||
"<span>baz</span>",
|
||||
"All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
|
||||
ok(selection.isCollapsed, "Selection should be collapsed");
|
||||
is(selection.anchorNode, editor.childNodes.item(3).firstChild,
|
||||
"Caret should be in the text node in the third line");
|
||||
is(selection.anchorOffset, 0,
|
||||
"Caret should be at start of the text node in the third line");
|
||||
|
||||
editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>qux";
|
||||
selection.collapse(editor.childNodes.item(2), "ba".length);
|
||||
document.execCommand("insertParagraph", false);
|
||||
is(editor.innerHTML, "foo<br>" +
|
||||
"<" + paragraphSeparator + ">ba</" + paragraphSeparator + ">" +
|
||||
"<" + paragraphSeparator + ">r</" + paragraphSeparator + ">" +
|
||||
"<span contenteditable=\"false\">baz</span>qux",
|
||||
"All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
|
||||
ok(selection.isCollapsed, "Selection should be collapsed");
|
||||
is(selection.anchorNode, editor.childNodes.item(3).firstChild,
|
||||
"Caret should be in the text node in the third line");
|
||||
is(selection.anchorOffset, 0,
|
||||
"Caret should be at start of the text node in the third line");
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче