The Problem
The Embed and Entity Embed modules present a use case where fully rendered entities may need to be embedded in CKEditor instances. Sometimes these entities will need to send additional out-of-band assets (CSS and JavaScript) to the client in an AJAX response. The problem is that CSS is never applied to iframe CKEditor instances, because the add_css AJAX command is hard-coded to prepend the incoming styles to (effectively) window.document.head.
Proposed Resolution
Provide a new AJAX command specifically for adding styles to a CKEditor instance using CKEditor's own API.
API Changes
The CKEditor module will expose a new AJAX command.
Backwards Compatibility
Unaffected.
Remaining Tasks
JS system maintainer and CKEditor maintainers should OK this change.The security team should OK this change (?)Done.Iterate over the patch.Write tests if possible.- Commit and partAY!
Comment | File | Size | Author |
---|---|---|---|
#50 | interdiff-2765525-30-50.txt | 1.37 KB | phenaproxima |
#50 | 2765525-50.patch | 10.99 KB | phenaproxima |
#30 | interdiff-2765525-28-30.txt | 7.77 KB | phenaproxima |
#30 | 2765525-30.patch | 10.77 KB | phenaproxima |
#28 | interdiff-2765525-23-28.txt | 7.21 KB | phenaproxima |
Comments
Comment #2
phenaproximaComment #3
phenaproximaMade a few fixes requested by @nod_.
Comment #4
Wim LeersInteresting! More detailed feedback to follow. But this definitely needs functional JS tests.
Comment #5
Wim LeersI don't understand why we'd modify the existing
add_css
AJAX command. That seems unnecessarily risky.That'd make much more sense to me.
… but it looks like you'd need to duplicate quite a bit from
add_css
in this newadd_css_ckeditor
command. So if @nod_ and other AJAX framework maintainers are okay with it, then I am too.I'd rename
add_css_ckeditor
toadd_css_ckeditor_iframe
.Comment #6
droplet CreditAttribution: droplet commented@import hack for IEs in CORE maybe outdated. In this case, we could consider using CKeditor's API.
http://docs.ckeditor.com/#!/api/CKEDITOR.dom.document-method-appendStyle...
Quickedit using `contenteditable`
Comment #8
Wim LeersNW for tests.
Comment #9
phenaproximaThis is definitely not RTBC-quality yet, but it adds a rough, functional JS test that proves it can work :) Hey, you gotta walk before you can run...
Comment #10
phenaproximaNew approach! I really love what @droplet said in #6. I think using CKEditor's existing API makes an enormous amount of sense, and increases the flexibility of the AJAX command (i.e., supporting inline editors rather than just iframes). That's what this patch does, which means core's ajax.js can be left untouched.
Comment #11
phenaproximaD'oh! Forgot to remove the AddCssCommand subclass. The interdiff in #10 should suffice for review purposes; just ignore the existence of AddCssCommand.
Comment #12
Wim LeersThis patch is looking great! :) However, one key question: what would call this AJAX command and when?
We shouldn't support this. Drupal core doesn't support inline styles. This should not either.
(Inline styles also come with significant security risks.)
This does not need further security review; loading stylesheets in CKEditor is not something new, Drupal already does this. The difference is that here it needs to happen dynamically.
Can you add a
@see
to CKEditor's documentation?Incomplete docs: class docs, property docs, method docs.
Nice :)
You're modifying
JavascriptTestBase
: you're expanding its API. This will need to be documented.Finally, can you also upload a test-only patch? That should fail, and will hence prove that the test is testing what it should be testing.
Comment #13
phenaproximaOkay. Here it is again, with the requested changes, and a fail patch. I had to leave most of the existing infrastructure in place for the fail patch, since this cannot be functionally tested without it. Because I needed to reroll, there is no interdiff.
An entirely fair question!
The use case arose in Lightning when I tried to fix #2729377: Size video previews sanely. The problem is that when videos are embedded into CKEditor (using Entity Embed as implemented in Lightning), they are supposed to have a bit of CSS attached that prevents them from taking up the entire width of their container (the editor window, in this case). The relevant CSS is loaded into the browser by the AJAX framework, but it's not loaded into CKEditor itself because the add_css AJAX command is hard-coded to append incoming CSS to the main <head> element. So the video would still be taking up the full width of the CKEditor instance, because the styles were not being added there. This is going to be an ongoing problem as Entity Embed improves and people embed "live" entities into CKEditor, especially ones that involve rich media. Supporting that stuff is the impetus, and main use case, for this patch.
When would it be called? Hard to say off the top of my head, since this is still in its infancy. I think, once this is in core, Lightning will use this command by altering Entity Embed's AJAX responses, converting any add_css commands so that the styles will be attached in CKEditor rather than the main browser window.
Comment #14
phenaproximaWhoops, sorry! I screwed up the patch in #13 by forgetting a pair of double quotes. They'll both fail. These should behave as expected.
Comment #15
phenaproximaComment #16
phenaproximaComment #21
phenaproximaFail patch failed exactly as expected, but it looks like I forgot a single character in the real patch that prevented it from passing. Fixed here.
Comment #22
droplet CreditAttribution: droplet commentedlittle space problems, 2 instead 4 spaces
This isn't related to current issue scope. Can you open a new issue?
Also it's a `assertWaitOnAjaxRequest()` in JSWebAssert class
Comment #23
phenaproximaNice catches. I didn't know about assertWaitOnAjaxRequest, thanks for that! :) Both fixed in this patch.
Comment #24
Wim LeersYou'd need it in the "main window" too, because it's possible CKEditor is being used in-place, in which case it lives in the "main window" rather than in an
<iframe>
.Can you now post a failing and passing patch at the same time? :)
iframe instance
I'm somewhat concerned that the test isn't an actual integration test. In reality, it's a bit more difficult to let the server know the DOM ID of the CKEditor instance. In this test, it's just hardcoded.
Finally, I'd like to see test coverage of an inline CKEditor instance too, and test the behavior there. Looking at http://docs.ckeditor.com/#!/api/CKEDITOR.dom.document, I don't know if this will work for both inline and iframe instances.
Comment #25
phenaproximaThe whole point of using CKEditor's API to add the style sheet is so that it will work with both inline and iframe -- presumably, anyway. When I opened this issue, my goal was just to make it work with iframes, but that was before I knew that CKEditor exposed an API function to add a style sheet.
I can change the test so that the client sends the editor instance ID to the server, but I'm not sure what that will prove. In reality, the server must be given the instance ID, presumably sent by the client during the AJAX request. How the client would go about doing that is up to the client-side code, which is (as far as I know) outside the scope of this issue.
I'll see what I can do about making it work with an inline editor instance, assuming core supports that. But if the CKEditor module always instantiates its editors as iframes, I'm not sure what benefit it would confer to check that it works with inlines as well.
Comment #26
Wim LeersThat's what Quick Edit uses.
Because it impacts what the server side must do: whether sending only this new AJAX command is sufficient (regardless of CKEditor instance type), or whether the server must be know when to also send the already existing CSS AJAX command.
i.e. I want us to understand the need + use cases + how to use it before adding new code. I'm the one who has to maintain it, after all.
Comment #27
Wim LeersNote that you don't have to test with Quick Edit. That'd tie the test to too many modules. I just meant that you'll find examples of how to create an inline CKE instance there.
I'd do:
JavascriptTestBase
test add A.css to the iframe instance, and check that it doesn't exist in the main pageJavascriptTestBase
test add B.css to the inline instance, and check that it exists on the main page, but not in the iframe instanceSomething like that.
Comment #28
phenaproximaThat is nice and clear. I have rewritten the test to do exactly what you described in #27.
Comment #29
Wim LeersSuch awesome test coverage! Thank you :)
Add a second line here:
Indentation error here (on the last quoted line).
4-space indentation.
Run this through
eslint
, and you'll get all complaints that a committer will push back against as well.Either rename these to
(inline|iframe)_cke_instance
, or add a comment such as:)
I'd rename these to
s/an editor/a CKEditor Text Editor/
s/editor/Text Editor/
Needs FQCN.
Use
file_url_transform_relative(file_create_url(drupal_get_path('module', 'ckeditor_test') . '/css/test.css'))
.SUPERNIT: I've never seen "iframe" written this way before. (But apparently there's a few occurrences in Drupal core.)
Can you change it to just "iframe"? That's consistent with the majority of Drupal core, and the rest of the CKEditor module.
Please no! This will install everything under the sun. This makes this test super slow.
It's not even necessary anymore with your updated test I think :)
You will need to add
ckeditor
here if you removestandard
per the previous point.Why user 1? This should not be necessary.
WOAH MAD SCIENTIST LEVEL 9000
I had to read this five times to make sense of it. It's effectively just evaluating this JS:
Which makes sense. Except the first part. I first thought it was using http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-getComputedS..., but it's not. It's not using CKE's API, but the DOM API: https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle.
It's using the CKE instance to get to the window… but that's not necessary. You can simplify
%s.window.$.getComputedStyle
to justwindow.getComputedStyle
.And then you might as well simplify this entire thing to just:
Once the nits and the simplification are done, this is RTBC.
Comment #30
phenaproximaAll set. This'll be a big interdiff.
Regarding #29.15: Ultimately I realized I can use CKEditor's API to get the computed style. So I'm doing this instead:
CKEDITOR.instances['foo'].document.getBody().getComputedStyle()
. Seems to work great!Comment #31
Wim LeersSuperb! Thank you :)
Nit: please combine these on one line. Makes for better greppability if we need to change that pattern in the future (it was introduced in #1494670: References to CSS, JS, and similar files should be root-relative URLs: avoids mixed content warnings & fewer bytes to send.
Does not block commit.
Perfect :)
Comment #32
Wim LeersThe one thing this still needs is a CR to inform people of this new API, this new capability. Does not need to block commit though: I vouch I will create the CR if @phenaproxima does not find the time.
Comment #33
phenaproximaChange record written. https://www.drupal.org/node/2822566
Comment #34
phenaproximaComment #36
phenaproximaNo, it didn't. Random CI fail on the cusp of commit...
Comment #38
phenaproximaNo, that is an unrelated failure.
Comment #40
Wim LeersNo point in re-RTBC'ing until #2828045: Tests failing with unrelated „Translation: The French translation is not available” is fixed.
Comment #41
MixologicComment #43
Wim Leers#2828438: Exception when adding tab to a node managed by content moderation broke HEAD, and caused these failures. It's currently being re-tested.
Comment #45
phenaproximaRandom fail.
Comment #46
alexpottI don't think we need to add the extra argument AjaxCommands. We already pass in Drupal so instead of
AjaxCommands.prototype.ckeditor_add_stylesheet
we can just doDrupal.AjaxCommands.prototype.ckeditor_add_stylesheet
. Then there's noif
and we're more BC and forward compatible. Plusckeditor_add_stylesheet
should be camel case to be consistent with the rest of ajax commands.This should say what is being tested.
Comment #47
Wim Leers#46.1: there will still be an
if
, because theDrupal
object alone doesn't guarantee thatDrupal.AjaxCommands
will exist. I'm fine with what you proposed, but the current patch is actually consistent with the rest of that JS file:Drupal.displace
is also explicitly injected.#46.5: I'd like that too, but after 1.5 month of waiting I don't think it's reasonable to block this on their review.
Keeping at NW for the nits in 2+3+4.
Comment #48
alexpottRe #46.1 then an awful lot of existing JS has this weakness. I agree that the approach of passing Drupal.ajaxCommands in the most recent patch is consistent with this file. That said there is no if around code using displace and debounce. I thought that the recommendation in #46.1 might make it easier for anyone swapping out this JS but after thinking about it I'm not sure.
Comment #49
Wim Leers#48: the difference is that most JS that adds an AJAX command depends on the
drupal/drupal.ajax
library. That's not the case here! Thedrupal/drupal.ckeditor
library does NOT depend ondrupal/drupal.ajax
, because it does not need it. But it aims to support this when necessary. Hence the condition.This is also exactly why there is no "if" around
displace
anddebounce
: because those are dependencies.Comment #50
phenaproximaFixed #2, 3, and 4 from #46. Since these were only comments, setting back to RTBC.
Comment #52
MixologicComment #53
alexpottCommitted 6fa5085 and pushed to 8.3.x. Thanks!
Thanks for explaining @Wim Leers in #49.