Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

T/86: Plugin#destroy() should remove listeners #87

Merged
merged 9 commits into from
Jun 20, 2017
Merged

T/86: Plugin#destroy() should remove listeners #87

merged 9 commits into from
Jun 20, 2017

Conversation

pomek
Copy link
Member

@pomek pomek commented May 29, 2017

Suggested merge commit message (convention)

Feature: Added default implementation for Plugin.destroy(). Introduced PluginCollection.destroy() method which calls Plugin.destroy() for each loaded plugin. Editor.destroy() calls PluginCollection.destroy() method too. Closes ckeditor/ckeditor5#2916.

…ails.

Added default implementation for `Plugin.destroy()`.

Introduced `PluginCollection.destroy()` method which calls `Plugin.destroy()` for each loaded plugin.

`Editor.destroy()` calls `PluginCollection.destroy()` method too.
@pomek pomek requested a review from Reinmar May 29, 2017 08:50
@@ -149,6 +149,7 @@ export default class Editor {

return Promise.resolve()
.then( () => {
this.plugins.destroy();
Copy link
Member

Choose a reason for hiding this comment

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

You made it return a promise, but you don't handle this promise. Which means there's no good test for it.

@@ -208,6 +208,21 @@ export default class PluginCollection {
}

/**
* Destroys each loaded plugin.
Copy link
Member

Choose a reason for hiding this comment

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

Destroys all loaded plugins.

@pomek
Copy link
Member Author

pomek commented May 29, 2017

@Reinmar, could you review once again? I am not sure whether I did what you wanted.

@Reinmar
Copy link
Member

Reinmar commented May 29, 2017

There's still no test what PluginCollection does when any of its plugins has an async destroy().

destroy() {
const promises = [];

for ( const [ , pluginInstance ] of this ) {
Copy link
Member

Choose a reason for hiding this comment

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

First of all: this could be simplified as: Array.from( this._plugins.values() ).map( plugin => plugin.destroy() )

Second, destroy() is optional in PluginInterface.

Copy link
Member Author

Choose a reason for hiding this comment

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

We cannot use Array.from( this._plugins.values() ) because we will call plugin.destroy() twice for plugins that have a defined name.

Copy link
Member

Choose a reason for hiding this comment

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

But we can still do Array.from( this ).map( ... ).

@pomek
Copy link
Member Author

pomek commented May 29, 2017

@Reinmar, I added tests async plugins and plugin which does not have a destroy() method. Could you review once again?

@Reinmar
Copy link
Member

Reinmar commented May 29, 2017

All fine now, except the fact that tests started failing. Run all of them and you'll see at least couple failed.

@pomek
Copy link
Member Author

pomek commented May 31, 2017

Thanks to @szymonkups we figured out what happens with the failing tests.

UI of the editor was removed too early and the plugins cannot destroy themselves.

Plugins must be destroyed before destroying the UI.

But unfortunately, one test still fails.

Chrome 58.0.3029 (Mac OS X 10.12.4) ContextualBalloon "after each" hook for "should remove balloon panel view from editor body collection and clear stack" FAILED
        CKEditorError: collection-remove-404: Item not found.
            at ViewCollection.remove (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:39391:10)
            at ContextualBalloon.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:47009:28)
            at Array.from.map.filter.map.pluginInstance (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:43932:43)
            at Array.map (native)
            at PluginCollection.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:43932:5)
            at ClassicTestEditor.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:42065:23)
            at ClassicTestEditor.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:44072:17)
            at ClassicTestEditor.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:45275:16)
            at Context.afterEach (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:47428:10)

In order to reproduce the bug, you need to check out ckeditor5-editor-classic on branch t/ckeditor5-core/86 and ckeditor5-core on branch t/86. Then you can call npm t from the CKE5 repository.

Anyone could help me?

CC: @Reinmar, @oleq.

@szymonkups
Copy link
Contributor

szymonkups commented May 31, 2017

ClassicEditor.destroy() does this:

return this.ui.destroy()
   .then( () => super.destroy() );
  1. In the first step UI is destroyed along with it's view - so all view collections are destroyed (with things added there by plugins).
  2. Then super.destroy() is called, which destroys editor and plugins. If any of the plugins added something to editor's UI - it's already destroyed. Problem occurs when plugin calls destroy() on view which was destroyed in the first step.

@Reinmar
Copy link
Member

Reinmar commented May 31, 2017

UI of the editor was removed too early and the plugins cannot destroy themselves.

I'm not sure that I understand – I think you missed explaining the crucial bit. Why plugins can't destroy themselves if the UI is removed?

Based on @szymonkups's comment I'm guessing that in that particular test UI isn't destroyed before being removed and that's causing some kind of issue. Is that true?

@pomek
Copy link
Member Author

pomek commented Jun 5, 2017

Why plugins can't destroy themselves if the UI is removed?

You know that I cannot know the answer to this question… It looks like two different things use the same object (let's call it as common part). And if the first one wants to destroy itself, the common part is also destroyed. Then, the second one cannot destroy the common part because it is already done.

But I figured out another an interesting thing.

The failing tests (link/link.js, image/image.js, build-classic/ckeditor.js) will pass if we stop clearing the reference to viewCollections in ckeditor5-ui/src/view.js#L322.

So at this moment, I found two solutions which will resolve the issue with failing tests:

  1. Change order of destroying the editor components. Inherited classes must call super.destroy() method before destroying any other component. Finally, it allows removing plugins at the very beginning. Unfortunately, another test will fail (ContextualBalloon "after each" hook for "should remove balloon panel view from editor body collection and clear stack").
  2. Do not change an order of destroying the editor components and do not clear a reference to viewCollections in ui/view~View#destroy.

I don't know which solution is proper…

@oleq
Copy link
Member

oleq commented Jun 5, 2017

I'm not sure if that helps, but we faced similar issues in the past, like https://github.com/ckeditor/ckeditor5-ui/issues/203. ATM, the async destruction chain when View#destroy() is called looks as follows:

  1. View calls VC#destroy() and waits for all #_viewCollections to be destroyed.
  2. Each VC waits for all promises related to VC#add() to resolve...
  3. ...then it calls View#destroy() on each of the views (going recursively to 1.), then waits for destruction to finish.
  4. View clears reference to #_viewCollections.

It's all recursive and Promise–controlled, so the code destroying the root view must respect that. It's enough to destroy the root view to kill the entire tree so you got to make sure things aren't called twice or sth.

TBH, it all boils down to https://github.com/ckeditor/ckeditor5-ui/issues/225, which will be such a relief for us and I'm really looking forward to seeing it happen.

@Reinmar
Copy link
Member

Reinmar commented Jun 5, 2017

Hm... the very first thing which is confusing here is which tests fail exactly.

E.g. I started investigating this from the failing ui/panel/balloon/contextualballoon.js test and it turned out it's obvious. The test looks like this:

it.only( 'should remove balloon panel view from editor body collection and clear stack', () => {
			balloon.destroy();

			expect( editor.ui.view.body.getIndex( balloon.view ) ).to.equal( -1 );
			expect( balloon.visibleView ).to.null;
		} );

And the ContextualBalloon#destroy() looks like this:

	destroy() {
		this.editor.ui.view.body.remove( this.view );
		this.view.destroy();
		this._stack.clear();
		super.destroy();
	}

How do you expect this test to work if the body.remove() method is called twice with the same view? The test is wrong and that's it.

@Reinmar
Copy link
Member

Reinmar commented Jun 5, 2017

Are there any other tests failing?

@pomek
Copy link
Member Author

pomek commented Jun 6, 2017

Are there any other tests failing?

No. Everything except this one (should remove balloon panel view from editor body collection and clear stack) works fine.

@Reinmar
Copy link
Member

Reinmar commented Jun 6, 2017

Are you sure? I've just run the tests and got this:

-AAAA with id 20088140
................................................................................
............................................................06 06 2017 09:28:52.829:WARN [web-server]: 404: /foo.png
..
Chrome 58.0.3029 (Mac OS X 10.12.4) ClassicEditor create() "after each" hook for "creates an instance which inherits from the ClassicEditor" FAILED
	TypeError: Cannot read property 'map' of null
	    at BalloonPanelView.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:25191:44)
	    at ContextualBalloon.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:57178:13)
	    at Array.from.map.filter.map.pluginInstance (packages/ckeditor5-build-classic/tests/ckeditor.js:44088:43)
	    at Array.map (native)
	    at PluginCollection.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:44088:5)
	    at ClassicEditor.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:42221:23)
	    at ClassicEditor.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:45346:17)
	    at ui.destroy.then (packages/ckeditor5-build-classic/tests/ckeditor.js:52194:23)
	    at <anonymous>
Chrome 58.0.3029 (Mac OS X 10.12.4) ClassicEditor destroy() sets the data back to the editor element FAILED
	TypeError: Cannot read property 'map' of null
	    at BalloonPanelView.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:25191:44)
	    at ContextualBalloon.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:57178:13)
	    at Array.from.map.filter.map.pluginInstance (packages/ckeditor5-build-classic/tests/ckeditor.js:44088:43)
	    at Array.map (native)
	    at PluginCollection.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:44088:5)
	    at ClassicEditor.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:42221:23)
	    at ClassicEditor.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:45346:17)
	    at ui.destroy.then (packages/ckeditor5-build-classic/tests/ckeditor.js:52194:23)
	    at <anonymous>
Chrome 58.0.3029 (Mac OS X 10.12.4) ClassicEditor destroy() restores the editor element FAILED
	TypeError: Cannot read property 'map' of null
	    at BalloonPanelView.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:25191:44)
	    at ContextualBalloon.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:57178:13)
	    at Array.from.map.filter.map.pluginInstance (packages/ckeditor5-build-classic/tests/ckeditor.js:44088:43)
	    at Array.map (native)
	    at PluginCollection.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:44088:5)
	    at ClassicEditor.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:42221:23)
	    at ClassicEditor.destroy (packages/ckeditor5-build-classic/tests/ckeditor.js:45346:17)
	    at ui.destroy.then (packages/ckeditor5-build-classic/tests/ckeditor.js:52194:23)
	    at <anonymous>
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
.........................................................................06 06 2017 09:29:25.955:WARN [web-server]: 404: /foo.png
....06 06 2017 09:29:26.225:WARN [web-server]: 404: /foo.png
..06 06 2017 09:29:26.397:WARN [web-server]: 404: /foo.png
.
06 06 2017 09:29:26.448:WARN [web-server]: 404: /foo.png
.06 06 2017 09:29:26.508:WARN [web-server]: 404: /foo.png
...............................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
...................................................06 06 2017 09:29:51.211:WARN [web-server]: 404: /foo
.............................
................................................................................
................................................................................
................................................................................
................................................................................
..............................................................
Chrome 58.0.3029 (Mac OS X 10.12.4) ContextualBalloon "after each" hook for "should remove balloon panel view from editor body collection and clear stack" FAILED
	CKEditorError: collection-remove-404: Item not found.
	    at ViewCollection.remove (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:39391:10)
	    at ContextualBalloon.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:47159:28)
	    at Array.from.map.filter.map.pluginInstance (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:43932:43)
	    at Array.map (native)
	    at PluginCollection.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:43932:5)
	    at ClassicTestEditor.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:42065:23)
	    at ClassicTestEditor.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:44221:17)
	    at ClassicTestEditor.destroy (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:45275:16)
	    at Context.afterEach (packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js:47578:10)
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
...................................
Chrome 58.0.3029 (Mac OS X 10.12.4) DomEmitterMixin listenTo should work for DOM Nodes belonging to another window FAILED
	Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

@pomek
Copy link
Member Author

pomek commented Jun 6, 2017

I said you need to check out one more branch.

In order to reproduce the bug, you need to check out ckeditor5-editor-classic on branch t/ckeditor5-core/86

@Reinmar
Copy link
Member

Reinmar commented Jun 6, 2017

OK, sorry. I haven't noticed that part of your comment.

@Reinmar
Copy link
Member

Reinmar commented Jun 6, 2017

I've looked into this and I think that we're facing here a more general issue. I'm 99% sure that the change proposed in ckeditor5-editor-classic is incorrect.

I wrote more about this topic in ckeditor/ckeditor5#114 (comment). Without figuring this out we won't be able to close this PR.

@Reinmar
Copy link
Member

Reinmar commented Jun 12, 2017

@oleq
Copy link
Member

oleq commented Jun 20, 2017

This PR is blocked by ckeditor/ckeditor5-ui#248.

Please check if ckeditor/ckeditor5-ui#254 helps.

@Reinmar
Copy link
Member

Reinmar commented Jun 20, 2017

All tests pass with ckeditor/ckeditor5-ui#254.

@Reinmar Reinmar merged commit 77e5217 into master Jun 20, 2017
@Reinmar Reinmar deleted the t/86 branch June 20, 2017 18:41
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Plugin#destroy() should remove listeners
4 participants