Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for: editor.insertHtml and editor.insertElement pollutes editable with empty spans #2987

Merged
merged 42 commits into from Jun 18, 2019

Conversation

engineering-this
Copy link
Contributor

@engineering-this engineering-this commented Mar 27, 2019

What is the purpose of this pull request?

Bug fix

Does your PR contain necessary tests?

All patches which change the editor code must include tests. You can always read more
on PR testing,
how to set the testing environment and
how to create tests
in the official CKEditor documentation.

This PR contains

  • Unit tests
  • Manual tests

What changes did you make?

Inside prepareRangeToDataInsertion I've added check that will remove created nodes for which isEmptyInlineRemoveable is true.

Closes #2813

Copy link
Member

@jacekbogdanski jacekbogdanski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still able to reproduce the issue using editor.insertElement function. IMO it doesn't make sense to fix only part of the issue if different insertion functions are still bugged.

I'm wondering if we can safely remove spans from content in such manner. They may be a part of some custom feature like bookmarks, although this issue: #2484 says otherwise and even our selection.createBookmars function creates spans with   character 🤔

These changes are pretty wide in the context of the whole editor. Although, I ran all unit tests on Chrome with no fails.

I'm also wondering if this PR shouldn't be a part of major release, because it may cause some breaking changes if someone gets around spans removal and using them as a part of some feature. WDYT?

core/editable.js Outdated
@@ -1717,14 +1718,24 @@
// Split inline elements so HTML will be inserted with its own styles.
path = range.startPath();
if ( ( node = path.contains( isInline, false, 1 ) ) ) {
range.splitElement( node );
clone = range.splitElement( node );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit misleading thus this element is not a clone but the late element after the split. Maybe rename it into endNode?

core/editable.js Outdated
that.inlineStylesRoot = node;
that.inlineStylesPeak = path.lastElement;
}

// Record inline merging candidates for later cleanup in place.
bm = range.createBookmark();

// When called by insertHtml remove empty element created after splitting (#2813).
if ( isHtml && clone && clone.isEmptyInlineRemoveable() ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to extract both span node removals into a separate function to avoid duplication.

@@ -0,0 +1,33 @@
/* bender-tags: editor,insertion */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need a separate file for these tests? It looks like they could be placed inside tests/core/editable/insertion.js. WDYT?


bender.tools.selection.setWithHtml( editor, initial );

editor.insertHtml( '<div>div</div>' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing tests for editor.insertHtmlIntoRange which also uses prepareRangeToDataInsertion private function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added such tests. Small note, it's a method of editable 😉

@engineering-this
Copy link
Contributor Author

I'm also wondering if this PR shouldn't be a part of major release, because it may cause some breaking changes if someone gets around spans removal and using them as a part of some feature. WDYT?

It is possible that someone is somehow preserving empty spans (#2484). However this fix affects elements that were created or altered by removing selected content from selection containers thus it shouldn't affect empty spans that were in editor before insertion.

Also after looking at element.isEmptyInlineRemovable it looks like changing CKEDITOR.dtd.$removeEmpty[ elementName ] to false should be sufficient to make desired element safe from being removed when empty.

@engineering-this engineering-this changed the title Fix for: editor.insertHtml pollutes editable with empty spans Fix for: editor.insertHtml and editor.insertElement pollutes editable with empty spans Apr 3, 2019
@engineering-this engineering-this changed the base branch from master to major April 3, 2019 08:24
@jacekbogdanski jacekbogdanski self-assigned this Apr 5, 2019
Copy link
Member

@jacekbogdanski jacekbogdanski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like editor.insertHtml doesn't work correctly with block elements and your tests exploit this issue:

  1. Open tests/core/editable/manual/insert manual test.
  2. Follow test steps for insert html button.

Expected

Div element is inserted under paragraph like it's done for insert element button.

Actual

Div element is inlined inside paragraph.

I see that there is no other easy way around this issue, so it would be nice to create separate ticket for it and reference the issue inside manual test. Also, don't forget to add information about workaround for manual test inside newly opened issue ticket, so we can get back to it when the issue is resolved.

I'm finding unit tests inside tests/core/editable/insert.js hightly unreadable. I see that you tried to save some LOC but it shouldn't be done in expense of readibility. As an example, the first test checking editor.insertHtml could be as simple as:

var editor = this.editor;

bender.tools.selection.setWithHtml( editor, '<span>{foo}bar</span>' );
editor.insertHtml( '<div>div</div>' );

assert.areEqual( '<div>div</div><span>bar</span>', editor.editable().getHtml() );

It also would be great to add tests verifying that empty edge spans are preserved for both inserted html and editor content. E.g. you could check if div html into <span>foo{baz} </span> preserves the space. We will be sure that your changes don't touch user content.

I'm also missing a manual test for insertHtmlIntoRange and insertElementIntoRange.

core/editable.js Outdated
@@ -2286,6 +2301,10 @@
return insert;
} )();

function removeEmptyInline( element ) {
element && element.isEmptyInlineRemoveable() && element.remove();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use if statement if a return value of the expression is irrelevant.

@engineering-this
Copy link
Contributor Author

engineering-this commented Apr 9, 2019

I'm finding unit tests inside tests/core/editable/insert.js hightly unreadable. I see that you tried to save some LOC but it shouldn't be done in expense of readibility. As an example, the first test checking editor.insertHtml could be as simple as:

I can rework test, so each test case is explicitly declared, however I'd prefer to use one general test callback which will test each case based on options param like we do in many test files. WDYT @jacekbogdanski

@engineering-this
Copy link
Contributor Author

It looks like editor.insertHtml doesn't work correctly with block elements and your tests exploit this issue:

Extracted to #3042

@engineering-this
Copy link
Contributor Author

It also would be great to add tests verifying that empty edge spans are preserved for both inserted html and editor content. E.g. you could check if div html into foo{baz} preserves the space. We will be sure that your changes don't touch user content.

I've added such test case, but I need to mention that when you press space in editor, when selection is at the end of any element, than &nbsp; will be inserted, because otherwise space won't be rendered by browser, so &nbsp; entity is used in tc.

Copy link
Member

@jacekbogdanski jacekbogdanski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One test is failing on IE10, IE9:
image

var methods = [ 'insertHtml', 'insertHtmlIntoRange', 'insertElement', 'insertElementIntoRange', 'insertText' ];

CKEDITOR.tools.array.forEach( methods, function( methodName ) {
tests[ options.name.replace( 'insert', methodName ) ] = function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small suggestion to compose a test method name rather than replace insert string:

tests[ 'test ' + methodName + ' ' + options.name ] = ...

seems less hacky for me and more resistant for incorrect usage e.g. if we decide to add new test without insert prefix.

}

function assertInsertionMethod( editor, expected, methodName, html ) {
var useRange = methodName.indexOf( 'Range' ) >= 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could make it even more readable by using a switch statement instead of string sniffing directly from addInsertionTests?

bender.test( tests );

function addInsertionTests( tests ) {
CKEDITOR.tools.array.forEach( tests, addInsertionTest );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests variable already exists in this file. I know that function is very small, nevertheless, maybe you could rename it to avoid using the same name? Hoisting makes name covering a bit unreadable.

};

// Add tests for (#2813).
addInsertionTests( [

This comment was marked as resolved.

CHANGES.md Outdated Show resolved Hide resolved
@engineering-this
Copy link
Contributor Author

Apparently space character ' ' should be used instead of &nbsp; on IE. This change result in multiple tests failing in IEs. I've extracted it to separate ticket #3061, as it seems to be unrelated with the issue we are fixing here. Failing test are not ignored on IE.

Copy link
Member

@jacekbogdanski jacekbogdanski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just some minor issues.

Failing test are not ignored on IE.

But the tests are ignored on IE 🤔

It would be nice to add a test case when you are inserting an empty element.

@@ -4,7 +4,8 @@
'use strict';

var doc = CKEDITOR.document,
tools = bender.tools;
tools = bender.tools,
space = CKEDITOR.env.ie ? ' ' : '&nbsp;';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are declaring space here for IE issue, but at the same time IE tests are ignored and the test is working on Edge without this modification.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this value should be here, as this is used for the IE, however because of other bug test is ignored. Once #3061 is resolved test can be just unignored.

name: 'when selection is in the middle of span',
initial: '<span>foo{bar}' + space + '</span>',
data: '<div>div</div>',
expected: '<span>foo</span><div>div</div><span>' + space + '</span>',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add test covering the case when selection is preceded with space (<span> {foo}</span>) because you are removing it at https://github.com/ckeditor/ckeditor-dev/blob/2669504728055ff89038f8bf87828cb6925868ae/core/editable.js#L468-L469

@f1ames
Copy link
Contributor

f1ames commented Jun 17, 2019

Rebased on the latest major.

Copy link
Contributor

@f1ames f1ames left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recent tests fix looks good 👍

However, I have encountered a group of cases which I'm not sure how to treat them. It happens when selection spans over multiple block elements:

chrome issue

It still leaves empty elements, but it seems the cause is different. Not sure if we should treat it as separate issue 🤔 Also the empty spans are not focusable so caret cannot be placed inside and are removed on getData(). It kind of looks like behaviour related to this issue, WDYT? Alternatively we can treat it as a follow-up and fix if necessary.

@engineering-this
Copy link
Contributor Author

Although it looks similar to the original bug it's caused by something else, so I'm for extracting it as a follow-up issue.

@f1ames
Copy link
Contributor

f1ames commented Jun 18, 2019

Although it looks similar to the original bug it's caused by something else, so I'm for extracting it as a follow-up issue.

I have extracted it to #3177. Since it is present from 4.5 (or even older), there is no need to fix it unless really necessary and we should be careful to make sure nothing breaks since it touches multiple block elements.

@f1ames f1ames self-requested a review June 18, 2019 08:54
Copy link
Contributor

@f1ames f1ames left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, looks good 👍

@f1ames f1ames merged commit 639a4b0 into major Jun 18, 2019
@f1ames f1ames deleted the t/2813 branch June 18, 2019 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

editor.insertHtml pollutes editable with empty spans
3 participants