Problem/Motivation

Inline images have a different set of allowed attributes compared to block images. It seems like there are some cases where an image could accidentally convert into an inline image on upcast, leading into attributes being removed.

Steps to reproduce

  1. Ensure that <div> is not allowed, but images are allowed, including captioning and aligning.
  2. Create following markup: <div><img data-caption data-align></div>
  3. This will be converted into <p><img data-caption data-align></p> (which consecutively converts to
    )
  4. Downcast the model again and see that data-caption and data-align disappear 💥

Proposed resolution

If the image has a caption, the image must be created as a block image to ensure the caption is not lost on conversion (This is based on the assumption that preserving the image caption is more important to the content creator than preserving the wrapping element that doesn't allow block images.) and make it block/inline agnostic to fix the alignment.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

lauriii created an issue. See original summary.

lauriii’s picture

Issue summary: View changes
lauriii’s picture

Status: Active » Needs review
StatusFileSize
new1.88 KB
new95.17 KB

This seems to be something we could fix on our side.

lauriii’s picture

StatusFileSize
new1.88 KB
new24.85 KB

Oopsie, glad to see DrupalCI is doing what it's supposed to, and checks that CKEditor was built correctly! 👏

lauriii’s picture

StatusFileSize
new24.85 KB
lauriii’s picture

StatusFileSize
new24.85 KB

The last submitted patch, 4: 3269868-4-test-only.patch, failed testing. View results

wim leers’s picture

Status: Needs review » Needs work
Issue tags: +data loss, +stable blocker
  1. +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/drupalimageediting.js
    @@ -158,19 +173,12 @@ function modelImageStyleToDataAttribute() {
    -      alignBlockRight: 'right',
    -      alignBlockLeft: 'left',
    

    🤔 There is no longer any mention of alignBlock* model attribute values … how is that possible?

  2. +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/drupalimageediting.js
    @@ -268,8 +276,26 @@ function viewImageToModelImage(editor) {
    +    // Create image that's allowed in the given context. If the image has
    +    // caption or alignment, the image must be created as a block image to
    +    // ensure attributes are not lost on conversion. This is based on the
    +    // assumption that preserving the image attributes is more important to the
    +    // editor than preserving the wrapping element that doesn't allow block
    +    // images.
    +    if (
    +      schema.checkChild(data.modelCursor, 'imageInline') &&
    +      !hasDataAlign &&
    +      !hasDataCaption
    +    ) {
    

    +1 — this makes sense! This avoids data loss.

  3. +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
    @@ -140,6 +140,43 @@ function (ConstraintViolation $v) {
    +  /**
    +   * Ensures that attributes are retained on conversion.
    +   */
    +  public function testConversion() {
    

    🤓 Nit: I think that the name is a bit off. It's not just conversion. It's ensuring that data-align and data-caption are not lost, not even for inline images.

  4. +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
    @@ -140,6 +140,43 @@ function (ConstraintViolation $v) {
    +    // Run test cases in a single test to make the test run faster.
    +    $test_attributes = [
    +      'data-caption="test caption 🦙"',
    +      'data-align="left"',
    +    ];
    +
    +    foreach ($test_attributes as $test_attribute) {
    +      $img_tag = '<img ' . $test_attribute . ' alt="drupalimage test image" data-entity-type="file" data-entity-uuid="' . $this->file->uuid() . '" src="' . $this->file->createFileUrl() . '" />';
    +      $test_cases = [
    +        // Plain image tag for a baseline.
    +        $img_tag,
    +        // Image tag wrapped with <p>.
    +        "<p>$img_tag</p>",
    +        // Image tag wrapped with a paragraph like element.
    +        "<div>$img_tag</div>",
    +      ];
    

    👍 Ran locally with and without the JS changes.

    Failed without the JS changes.

    Passed with the JS changes.

Needs work only for the third point.

wim leers’s picture

Issue tags: +JavaScript
lauriii’s picture

Issue tags: +Needs tests

#8.1: We don't have buttons for the image styles that force conversion to block images so I don't think we should be using them in the upcast either. That points out that the logic I applied here might be still incorrect since data-align could be applied to inline image 🤔 I think we need to test that specific scenario and make a slight change to the logic.
#8.3 Do you have recommendation on what a better name for the test could be?

lauriii’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new25.85 KB
new23.56 KB

Here's a patch that should address #10.1.

This also adds additional test coverage which extends the test beyond the attributes.

wim leers’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/drupalimageediting.js
    @@ -276,24 +276,18 @@ function viewImageToModelImage(editor) {
    +    // preserving the image caption is more important to the editor than
    

    Übernit: "editor" here could be misinterpreted as the text editor, not the person doing the editing. "content creator" would clarify that.

  2. +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/drupalimageediting.js
    @@ -279,39 +296,33 @@ function viewImageToModelImage(editor) {
    -      consumable.test(viewItem, { name: true, attributes: 'data-align' })
    +      consumable.test(viewItem, {
    +        name: true,
    +        attributes: 'data-align',
    +      })
    

    Nit: No-op change, let's revert this.

  3. +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
    @@ -146,33 +147,53 @@ function (ConstraintViolation $v) {
    +    foreach ($test_attributes as $test_attribute => $mode) {
    

    🤔 AFAICT $mode here really means "expected upcasting by CKEditor 5 if the starting point is an inline image".

    So I think that $expected_upcast_behavior_when_wrapped_in_block_element would be a better name?

    (This took by FAR the longest amount of time to figure out!)

  4. RE #8.3: what about testDynamicUpcastBehavior() or testInlineVsBlockImageUpcasting() or testAttributeRetentionDuringUpcasting()?
ravi.shankar’s picture

Status: Needs work » Needs review
StatusFileSize
new25.93 KB
new3.23 KB

Here I have tried to address the points of comment #12, please review.

Status: Needs review » Needs work

The last submitted patch, 13: 3269868-13.patch, failed testing. View results

lauriii’s picture

Status: Needs work » Needs review

Thank you @ravi.shankar!

#12.4 The test is also ensuring correct behavior for downcast

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

#15 But this only adding a fix for upcasting, in viewImageToModelImage()? If you can think of a better name, that'd be awesome, but I do think this is a net improvement. While imperfect, it better conveys why we have this test coverage at least? 😊 So … feel free to rename again and keep the "RTBC" status, but that really was my only remaining point of confusion.

Given that you have not raised concerns about the change I proposed in #12.3 nor in @ravi.shankar's implementation of it, I think that means you're +1 to that too.

So: RTBC! :)

bnjmnm’s picture

Adding "Needs issue summary update", the solution should be summarized at least briefly.

bnjmnm’s picture

Status: Reviewed & tested by the community » Needs work

Basically doc & naming nits. Also note the "Needs issue summary update" from 👆. The caption part can probably be lifted directly from the comments in drupalimageediting, and the align solution could use a sentence or two about how it's effectively Block/Inline agnostic now.

  1. +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
    @@ -140,6 +141,63 @@ function (ConstraintViolation $v) {
    +    $test_attributes = [
    +      '-none-' => 'inline',
    +      'data-caption="test caption 🦙"' => 'block',
    +      'data-align="left"' => 'inline',
    +    ];
    +
    +    foreach ($test_attributes as $test_attribute => $expected_upcast_behavior_when_wrapped_in_block_element) {
    

    What about s/$test_attributes/$attributes_to_retain

    and

    s/$test_attribute/$attribute_to_retain

    Just a little thing that could make the test easier to follow. It's nicely put together so I think naming may help more than additional commments that may add noise.

  2. +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
    @@ -140,6 +141,63 @@ function (ConstraintViolation $v) {
    +        // Image tag wrapped with a paragraph like element.
    

    It's worth specifying (perhaps more concisely than me) that this paragraph-like element is not an allowed tag, so the

    wrapping is expected when inline is the expected upcast behavior. This would mitigate any impressions that this test is inadvertently masking a bug.

andregp’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update
StatusFileSize
new2.5 KB
new26.06 KB

Here I tried to address the points on #18

lauriii’s picture

Status: Needs review » Reviewed & tested by the community

Thank you @andregp!

wim leers’s picture

RTBC++

  • bnjmnm committed 4ad6f75 on 10.0.x
    Issue #3269868 by lauriii, ravi.shankar, andregp, Wim Leers: [...

  • bnjmnm committed d4cec9f on 9.4.x
    Issue #3269868 by lauriii, ravi.shankar, andregp, Wim Leers: [...
bnjmnm’s picture

Version: 9.4.x-dev » 9.3.x-dev

Thanks all!

Committed to 9.4.x and 10.0.x, leaving at RTBC for 9.3.x in case it can be added after the @ckeditor5 version difference is reconciled.

wim leers’s picture

bnjmnm’s picture

Status: Reviewed & tested by the community » Fixed

Thanks @Wim Leers! I somehow missed that #3269651: Update Drupal 9.3.x to CKEditor 5 v34.0.0 along with other un-backported issues was keeping track of the queued issues.

Fixed!

xjm’s picture

Status: Fixed » Postponed

Postponing for backport after the v33 and v34 update.

catch’s picture

Status: Postponed » Needs work
Issue tags: +Needs reroll

v34 is in, so this is unblocked.

lauriii’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new26.06 KB
lauriii’s picture

Status: Needs review » Reviewed & tested by the community

Moving to RTBC since the backport was very straight forward and tests are passing

  • bnjmnm committed 11f0c51 on 9.3.x
    Issue #3269868 by lauriii, andregp, ravi.shankar, Wim Leers: [...
bnjmnm’s picture

Status: Reviewed & tested by the community » Fixed

Yep, the backport is a pretty safe change, it's 99% the recompiled build (which tests would 100% spot problems with) and an encoding difference of the Llama emoji in a test, which can safely be different as its encoding has no impact on the test.

So, it's backported to 9.3! Glad CKEditor 5 v34 is with us!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.