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

Singleton for styles processor was a bad idea #6091

Closed
3 tasks
Reinmar opened this issue Jan 20, 2020 · 6 comments · Fixed by ckeditor/ckeditor5-engine#1826
Closed
3 tasks

Singleton for styles processor was a bad idea #6091

Reinmar opened this issue Jan 20, 2020 · 6 comments · Fixed by ckeditor/ckeditor5-engine#1826
Assignees
Labels
package:engine status:discussion type:improvement This issue reports a possible enhancement of an existing feature.

Comments

@Reinmar
Copy link
Member

Reinmar commented Jan 20, 2020

📝 Provide a description of the improvement

So, when making a decision that we can go with a styles processor singleton I was only thinking how one editor can affect another. That was a bit fishy, but acceptable.

However, then, thank god, I realized that test files will affect one another and that's just too ugly. There's no reasonable way to clean this up other than just stop using singleton.

Why didn't we use a singleton in the first place? Because we need an access to a view document from all elements. Through that document we ca access a styles processor of that particular view document.

Interestingly, I've just checked that view nodes have the #document property but it's going to work only for elements that are in the document already. This is not going to work for document fragments and detached nodes unfortunately.

I can see two options here:

  • Make the current #document return the document based on a writer which created it. Both writers (upcast and downcast) will have to have the #document property too. The #document could only be changed when a node is inserted to an element/docfrag from a different #document.
    • This way of tracking the document may actually be faster than the current recursive root lookup.
    • It's also quite clear in terms of when the parent document may change.
    • We could also throw in case of documents mismatch.
  • Introduce a new property which will work like the #document that I proposed in the former point.

cc @scofalik @jodator

Steps

  • Add real #document property to view.Node and view.DocumentFragment. This property will be passed to those nodes/dfs through the first param of their constructors. This requires passing document to UpcastWriter (DowncastWriter already knows about the document).
    • Do not forget about a specific case when a node is create in document A and then it's added to document B. At this moment, the document property needs to be updated. However, it would be useful to add a debug console log (CK_DEBUG_ENGINE?) so we can find out about this weird scenario when one node is being moved from one document to another.
  • Change implementation of the style processor rules registration so it's bound with a specific document instance. Use those rules in a view.Element#_styles map.

If you'd like to see this improvement implemented, add a 👍 reaction to this post.

@Reinmar Reinmar added type:improvement This issue reports a possible enhancement of an existing feature. package:engine status:discussion labels Jan 20, 2020
@Reinmar Reinmar added this to the next milestone Jan 20, 2020
@Reinmar
Copy link
Member Author

Reinmar commented Jan 21, 2020

We've got 👍 from @scofalik for the first option, so let's do this :)

@jodator
Copy link
Contributor

jodator commented Jan 21, 2020

Yes, singletons are always bad :D The other idea was to not allow extend the style processing rules. This is not feasible as we want to have this style processing rules added only when needed.

Taken this into consideration I don't see any other "clean" way of doing this. The style processing rules should be added per document context so the #document option seems fine for me.

@pomek
Copy link
Member

pomek commented Feb 17, 2020

After adding the #document property to view.Node and view.DocumentFragment as the first argument of their constructors, I spotted some problems:

  • A lot of tests started failing because view.Document is now the first parameter in a constructor for the above classes. After spending some time on adjusting API, those tests are green again.
  • Getter view.Node#document should be removed but there is at least one piece of the code where we base on the #document value (needsPlaceholder() function). The question is: how to check whether an element was removed? As for know, I decided to check whether the passed element is an instance of view.DocumentFragment. After debugging this test case I noticed that the element is a child of DF after being removed from the document. However, I am not sure about the solution I did.
  • view.Document is a heavy object and we should avoid creating it. It means that DomConverter.domToView() function and DomConverter.domChildrenToView() generator require an instance of view.Document as the first parameter.
  • Since the above, XmlDataProcessort.toView() and HtmlDataProcessor.toView() must create an own instance of the document which will be passed to DomConverter.
  • Also, the view.Renderer class needs the view.Document instance but it can be extracted from the view.View class where rendered is created. I decided to simplify the Renderer's constructor. Instead of passing two/three parameters, it accepts the view.View instance and we extract required classes from it.
  • At the beginning, we thought that adding nodes from one document to nodes in another document shouldn't be possible. It's not true because we must support it because of things like XmlDataProcessor or HtmlDataProcessor which have no access to the target document.

I am still trying to understand one test that fails:

FAILED TESTS:
  downcast selection converters
    clean-up
      clearAttributes
        ✖ should do nothing if the attribute element had been already removed
          Chrome 80.0.3987 (Mac OS X 10.14.6)
        TypeError: Cannot read property '_removeChildren' of null
            at AttributeElement._remove (webpack:///./packages/ckeditor5-engine/src/view/node.js?:278:15)
            at DowncastWriter.mergeAttributes (webpack:///./packages/ckeditor5-engine/src/view/downcastwriter.js?:553:19)
            at DowncastDispatcher.eval (webpack:///./packages/ckeditor5-engine/src/conversion/downcasthelpers.js?:567:27)
            at DowncastDispatcher.fire (webpack:///./packages/ckeditor5-utils/src/emittermixin.js?:218:30)
            at DowncastDispatcher.convertSelection (webpack:///./packages/ckeditor5-engine/src/conversion/downcastdispatcher.js?:279:8)
            at eval (webpack:///./packages/ckeditor5-engine/tests/conversion/downcasthelpers.js?:2245:17)
            at View.change (webpack:///./packages/ckeditor5-engine/src/view/view.js?:491:27)
            at Context.eval (webpack:///./packages/ckeditor5-engine/tests/conversion/downcasthelpers.js?:2236:10)

After fixing that, I'll be ready for closing the first part of the issue.

@pomek
Copy link
Member

pomek commented Feb 17, 2020

Ok, I found it. https://github.com/ckeditor/ckeditor5-engine/blob/7ab19cff179b375e3bd1fb7e5413529423a473c6/src/conversion/downcasthelpers.js#L546 will be always true.
I replaced the line with the snippet below:

if ( range.end.parent.parent ) {

and all the tests are green.

pomek added a commit that referenced this issue Feb 17, 2020
@pomek
Copy link
Member

pomek commented Feb 18, 2020

Change implementation of the style processor rules registration so it's bound with a specific document instance. Use those rules in a view.Element#_styles map.

During this point in the issue, we decided to create a single instance of the StyleProcessor in the core Editor class.

Why? The result of the downcast and the upcast conversions must be the same. It's also safer and easier to add processing rules in a single place instead of creating a few instances of SP.

It means, we need to pass the instance of SP to:

  • view.Document (through the view.View and EditingController classes),
  • (Html|Xml)DataProcessor through constructors (because #toView() creates an instance of view.Document),
  • DataController - the same reason as above.

All I need to do now is fix all failing tests because (again) we changed APIs of many classes. But I guess, we achieved the goal.

cc: @jodator

@jodator
Copy link
Contributor

jodator commented Feb 18, 2020

Yes. Looking at where it is passed we need to have a single instance injected as a dependency on all of those classes so the behavior is consistent in all of the pipelines.

Another thing is that to allow extensibility of this system we have editor.eding.view.document.addStyleProcessorRules() which after these changes will be set in the wrong place. However setting this on editor.addStyleProcessorRules() looks also not so nice.

The other way around this I see is to have editor.stylesProcessor.addRules() or something similar. (BREAKING CHANGE 😞. Buuuut as we didn't release the editor yet we can:

  • make current addStyleProcessorRules() private/protected or
  • move it as proposed to editor.stylesProcesor.addRules() [name might be better - I'm concerned about its location).

Reinmar added a commit to ckeditor/ckeditor5-typing that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-ui that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-utils that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-watchdog that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-widget that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-paste-from-office that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-mention that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-markdown-gfm that referenced this issue Mar 3, 2020
Other: Adjusted the data processor implementation to changes in `ckeditor5-engine`. See ckeditor/ckeditor5#6091.

MAJOR BREAKING CHANGES: The `GFMDataProcessor()` requires the view document instance as its first parameter.
Reinmar added a commit to ckeditor/ckeditor5-image that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-editor-inline that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-editor-decoupled that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-editor-classic that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-editor-balloon that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-core that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-clipboard that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-engine that referenced this issue Mar 3, 2020
Other: `StylesProcessor` rules will not be stored in a singleton, which made them shared between editor instances. In order to allow binding a styles processor instance to a specific view document, we had to replace a dynamic `#document` property in view nodes with a static one, set upon node creation. Closes ckeditor/ckeditor5#6091.

MAJOR BREAKING CHANGES: `EditingController` requires an instance of StylesProcessor in its constructor.

MAJOR BREAKING CHANGES: `DataController` requires an instance of StylesProcessor in its constructor.

MAJOR BREAKING CHANGES: `DomConverter`, `HtmlDataProcessor` and `XmlDataProcessor` require an instance of the view document in their constructors.

MAJOR BREAKING CHANGES: The `View` class requires an instance of `StylesProcessor` as its first argument.

MAJOR BREAKING CHANGES: The `createViewElementFromHighlightDescriptor()` function that is exported by `src/conversion/downcasthelpers.js` file requires an instance of the view document as its first argument.

MAJOR BREAKING CHANGES: Method `view.Document#addStyleProcessorRules()` has been moved to DataController class.

MINOR BREAKING CHANGES: `DataController` does not accept the data processor instance any more.

MAJOR BREAKING CHANGES: The `#document` getter was removed from model nodes. Only the root element holds the reference to the model document. For attached nodes, use `node.root.document` to access it.
Reinmar added a commit to ckeditor/ckeditor5-table that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Reinmar added a commit to ckeditor/ckeditor5-indent that referenced this issue Mar 3, 2020
Internal: Adjusted the code to changes required for replacing the `StylesProcessor` singleton with an instance of the class. See ckeditor/ckeditor5#6091.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package:engine status:discussion type:improvement This issue reports a possible enhancement of an existing feature.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants