Преглед изворни кода

Merge pull request #7797 from ckeditor/t/7334

Feature (engine): Added conversion API to upcast and downcast helpers. Closes #7334.

MAJOR BREAKING CHANGE (engine): `config.view` callback of `DowncastHelpers` takes `DowncastConversionApi` instead of `DowncastWriter`. See #7334.

MAJOR BREAKING CHANGE (engine): `config.model` callback of `UpcastHelpers` takes `UpcastConversionApi` instead of `ModelWriter`. See #7334.
Maciej пре 5 година
родитељ
комит
16c9711989
47 измењених фајлова са 343 додато и 319 уклоњено
  1. 2 2
      packages/ckeditor5-autoformat/tests/blockautoformatediting.js
  2. 55 35
      packages/ckeditor5-engine/src/conversion/downcasthelpers.js
  3. 32 26
      packages/ckeditor5-engine/src/conversion/upcasthelpers.js
  4. 3 3
      packages/ckeditor5-engine/src/dev-utils/model.js
  5. 40 64
      packages/ckeditor5-engine/tests/conversion/downcasthelpers.js
  6. 25 6
      packages/ckeditor5-engine/tests/conversion/upcasthelpers.js
  7. 1 1
      packages/ckeditor5-enter/src/shiftenter.js
  8. 1 1
      packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js
  9. 1 1
      packages/ckeditor5-font/src/fontsize/fontsizeediting.js
  10. 1 1
      packages/ckeditor5-font/src/utils.js
  11. 1 1
      packages/ckeditor5-font/tests/utils.js
  12. 9 9
      packages/ckeditor5-horizontal-line/src/horizontallineediting.js
  13. 3 3
      packages/ckeditor5-image/src/image/imageediting.js
  14. 3 3
      packages/ckeditor5-image/tests/image/converters.js
  15. 3 3
      packages/ckeditor5-link/src/linkediting.js
  16. 2 1
      packages/ckeditor5-link/src/utils.js
  17. 4 2
      packages/ckeditor5-link/tests/utils.js
  18. 2 2
      packages/ckeditor5-list/tests/todolistediting.js
  19. 9 9
      packages/ckeditor5-media-embed/src/mediaembedediting.js
  20. 2 2
      packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
  21. 2 2
      packages/ckeditor5-mention/docs/_snippets/features/mention-customization.js
  22. 2 2
      packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
  23. 4 4
      packages/ckeditor5-mention/docs/features/mentions.md
  24. 3 3
      packages/ckeditor5-mention/src/mentionediting.js
  25. 2 2
      packages/ckeditor5-mention/tests/manual/mention-custom-view.js
  26. 7 7
      packages/ckeditor5-mention/tests/manual/mention.js
  27. 4 4
      packages/ckeditor5-mention/tests/mentionediting.js
  28. 1 1
      packages/ckeditor5-mention/tests/mentionui.js
  29. 14 14
      packages/ckeditor5-page-break/src/pagebreakediting.js
  30. 2 2
      packages/ckeditor5-paragraph/src/paragraph.js
  31. 4 4
      packages/ckeditor5-restricted-editing/src/restrictededitingmodeediting.js
  32. 2 2
      packages/ckeditor5-restricted-editing/src/standardeditingmodeediting.js
  33. 1 1
      packages/ckeditor5-typing/tests/utils/inlinehighlight.js
  34. 2 2
      packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js
  35. 4 4
      packages/ckeditor5-widget/src/utils.js
  36. 7 7
      packages/ckeditor5-widget/tests/manual/inline-widget.js
  37. 8 8
      packages/ckeditor5-widget/tests/manual/keyboard.js
  38. 1 1
      packages/ckeditor5-widget/tests/manual/nested-widgets.js
  39. 1 1
      packages/ckeditor5-widget/tests/manual/selection-handle.js
  40. 7 7
      packages/ckeditor5-widget/tests/manual/type-around.js
  41. 4 4
      packages/ckeditor5-widget/tests/manual/widget-with-nestededitable.js
  42. 4 4
      packages/ckeditor5-widget/tests/verticalnavigation.js
  43. 8 8
      packages/ckeditor5-widget/tests/widget-integration.js
  44. 14 14
      packages/ckeditor5-widget/tests/widget.js
  45. 13 13
      packages/ckeditor5-widget/tests/widgetresize.js
  46. 12 12
      packages/ckeditor5-widget/tests/widgettoolbarrepository.js
  47. 11 11
      packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js

+ 2 - 2
packages/ckeditor5-autoformat/tests/blockautoformatediting.js

@@ -167,7 +167,7 @@ describe( 'blockAutoformatEditing', () => {
 			editor.conversion.for( 'downcast' )
 				.elementToElement( {
 					model: 'softBreak',
-					view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+					view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
 				} );
 
 			const spy = testUtils.sinon.spy();
@@ -207,7 +207,7 @@ describe( 'blockAutoformatEditing', () => {
 			editor.conversion.for( 'downcast' )
 				.elementToElement( {
 					model: 'softBreak',
-					view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+					view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
 				} );
 
 			const spy = testUtils.sinon.spy();

+ 55 - 35
packages/ckeditor5-engine/src/conversion/downcasthelpers.js

@@ -52,8 +52,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *		editor.conversion.for( 'downcast' ).elementToElement( {
 	 *			model: 'heading',
-	 *			view: ( modelElement, viewWriter ) => {
-	 *				return viewWriter.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) )
+	 *			view: ( modelElement, conversionApi ) => {
+	 *				const { writer } = conversionApi;
+	 *
+	 *				return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) );
 	 *			}
 	 *		} );
 	 *
@@ -64,7 +66,7 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model element to convert.
 	 * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
-	 * that takes the model element and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}
+	 * that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
 	 * as parameters and returns a view container element.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
@@ -120,8 +122,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *		editor.conversion.for( 'downcast' ).attributeToElement( {
 	 *			model: 'bold',
-	 *			view: ( modelAttributeValue, viewWriter ) => {
-	 *				return viewWriter.createAttributeElement( 'span', {
+	 *			view: ( modelAttributeValue, conversionApi ) => {
+	 *				const { writer } = conversionApi;
+	 *
+	 *				return writer.createAttributeElement( 'span', {
 	 *					style: 'font-weight:' + modelAttributeValue
 	 *				} );
 	 *			}
@@ -132,8 +136,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *				key: 'color',
 	 *				name: '$text'
 	 *			},
-	 *			view: ( modelAttributeValue, viewWriter ) => {
-	 *				return viewWriter.createAttributeElement( 'span', {
+	 *			view: ( modelAttributeValue, conversionApi ) => {
+	 *				const { writer } = conversionApi;
+	 *
+	 *				return writer.createAttributeElement( 'span', {
 	 *					style: 'color:' + modelAttributeValue
 	 *				} );
 	 *			}
@@ -147,9 +153,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
 	 * of `String`s with possible values if the model attribute is an enumerable.
 	 * @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function
-	 * that takes the model attribute value and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}
-	 * as parameters and returns a view attribute element. If `config.model.values` is
-	 * given, `config.view` should be an object assigning values from `config.model.values` to view element definitions or functions.
+	 * that takes the model attribute value and
+	 * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters and returns a view
+	 * attribute element. If `config.model.values` is given, `config.view` should be an object assigning values from `config.model.values`
+	 * to view element definitions or functions.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
@@ -201,7 +208,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *		editor.conversion.for( 'downcast' ).attributeToAttribute( {
 	 *			model: 'styled',
-	 *			view: modelAttributeValue => ( { key: 'class', value: 'styled-' + modelAttributeValue } )
+	 *			view: modelAttributeValue => ( {
+	 *				key: 'class',
+	 *				value: 'styled-' + modelAttributeValue
+	 *			} )
 	 *		} );
 	 *
 	 * **Note**: Downcasting to a style property requires providing `value` as an object:
@@ -225,7 +235,8 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
 	 * the attribute key, possible values and, optionally, an element name to convert from.
 	 * @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes
-	 * the model attribute value and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
+	 * the model attribute value and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+	 * as parameters and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
 	 * array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.
 	 * If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
 	 * `{ key, value }` objects or a functions.
@@ -269,8 +280,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 	 *			model: 'search',
-	 *			view: ( markerData, viewWriter ) => {
-	 *				return viewWriter.createUIElement( 'span', {
+	 *			view: ( markerData, conversionApi ) => {
+	 *				const { writer } = conversionApi;
+	 *
+	 *				return writer.createUIElement( 'span', {
 	 *					'data-marker': 'search',
 	 *					'data-start': markerData.isOpening
 	 *				} );
@@ -278,7 +291,8 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *		} );
 	 *
 	 * If a function is passed as the `config.view` parameter, it will be used to generate both boundary elements. The function
-	 * receives the `data` object as a parameter and should return an instance of the
+	 * receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+	 * as a parameters and should return an instance of the
 	 * {@link module:engine/view/uielement~UIElement view UI element}. The `data` object and
 	 * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi `conversionApi`} are passed from
 	 * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}. Additionally,
@@ -291,8 +305,9 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @method #markerToElement
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model marker (or model marker group) to convert.
-	 * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
-	 * that takes the model marker data as a parameter and returns a view UI element.
+	 * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function that
+	 * takes the model marker data and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+	 * as a parameters and returns a view UI element.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
@@ -329,7 +344,7 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *		editor.conversion.for( 'downcast' ).markerToHighlight( {
 	 *			model: 'comment',
-	 *			view: data => {
+	 *			view: ( data, converstionApi ) => {
 	 *				// Assuming that the marker name is in a form of comment:commentType.
 	 *				const commentType = data.markerName.split( ':' )[ 1 ];
 	 *
@@ -340,7 +355,8 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *		} );
 	 *
 	 * If a function is passed as the `config.view` parameter, it will be used to generate the highlight descriptor. The function
-	 * receives the `data` object as a parameter and should return a
+	 * receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+	 * as a parameters and should return a
 	 * {@link module:engine/conversion/downcasthelpers~HighlightDescriptor highlight descriptor}.
 	 * The `data` object properties are passed from {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}.
 	 *
@@ -351,7 +367,9 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model marker (or model marker group) to convert.
 	 * @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} config.view A highlight descriptor
-	 * that will be used for highlighting or a function that takes the model marker data as a parameter and returns a highlight descriptor.
+	 * that will be used for highlighting or a function that takes the model marker data and
+	 * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as a parameters
+	 * and returns a highlight descriptor.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
@@ -464,8 +482,9 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @method #markerToData
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model marker (or model marker group) to convert.
-	 * @param {Function} [config.view] A function that takes the model marker name as a parameter and returns an object with the `group`
-	 * and `name` properties.
+	 * @param {Function} [config.view] A function that takes the model marker name and
+	 * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as a parameters
+	 * and returns an object with the `group` and `name` properties.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
@@ -703,10 +722,10 @@ export function wrap( elementCreator ) {
 	return ( evt, data, conversionApi ) => {
 		// Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed
 		// or the attribute was removed.
-		const oldViewElement = elementCreator( data.attributeOldValue, conversionApi.writer );
+		const oldViewElement = elementCreator( data.attributeOldValue, conversionApi );
 
 		// Create node to wrap with.
-		const newViewElement = elementCreator( data.attributeNewValue, conversionApi.writer );
+		const newViewElement = elementCreator( data.attributeNewValue, conversionApi );
 
 		if ( !oldViewElement && !newViewElement ) {
 			return;
@@ -766,7 +785,7 @@ export function wrap( elementCreator ) {
  */
 export function insertElement( elementCreator ) {
 	return ( evt, data, conversionApi ) => {
-		const viewElement = elementCreator( data.item, conversionApi.writer );
+		const viewElement = elementCreator( data.item, conversionApi );
 
 		if ( !viewElement ) {
 			return;
@@ -803,10 +822,10 @@ export function insertUIElement( elementCreator ) {
 		// Create two view elements. One will be inserted at the beginning of marker, one at the end.
 		// If marker is collapsed, only "opening" element will be inserted.
 		data.isOpening = true;
-		const viewStartElement = elementCreator( data, conversionApi.writer );
+		const viewStartElement = elementCreator( data, conversionApi );
 
 		data.isOpening = false;
-		const viewEndElement = elementCreator( data, conversionApi.writer );
+		const viewEndElement = elementCreator( data, conversionApi );
 
 		if ( !viewStartElement || !viewEndElement ) {
 			return;
@@ -880,7 +899,7 @@ function removeUIElement() {
 // @returns {Function} Add marker converter.
 function insertMarkerData( viewCreator ) {
 	return ( evt, data, conversionApi ) => {
-		const viewMarkerData = viewCreator( data.markerName );
+		const viewMarkerData = viewCreator( data.markerName, conversionApi );
 
 		if ( !viewMarkerData ) {
 			return;
@@ -961,7 +980,7 @@ function insertMarkerAsElement( position, isStart, conversionApi, data, viewMark
 // @returns {Function} Remove marker converter.
 function removeMarkerData( viewCreator ) {
 	return ( evt, data, conversionApi ) => {
-		const viewData = viewCreator( data.markerName );
+		const viewData = viewCreator( data.markerName, conversionApi );
 
 		if ( !viewData ) {
 			return;
@@ -1036,8 +1055,8 @@ function removeMarkerData( viewCreator ) {
 // @returns {Function} Set/change attribute converter.
 function changeAttribute( attributeCreator ) {
 	return ( evt, data, conversionApi ) => {
-		const oldAttribute = attributeCreator( data.attributeOldValue, data );
-		const newAttribute = attributeCreator( data.attributeNewValue, data );
+		const oldAttribute = attributeCreator( data.attributeOldValue, conversionApi );
+		const newAttribute = attributeCreator( data.attributeNewValue, conversionApi );
 
 		if ( !oldAttribute && !newAttribute ) {
 			return;
@@ -1487,7 +1506,7 @@ function normalizeToElementConfig( view, viewElementType ) {
 		return view;
 	}
 
-	return ( modelData, viewWriter ) => createViewElementFromDefinition( view, viewWriter, viewElementType );
+	return ( modelData, conversionApi ) => createViewElementFromDefinition( view, conversionApi, viewElementType );
 }
 
 // Creates a view element instance from the provided {@link module:engine/view/elementdefinition~ElementDefinition} and class.
@@ -1496,13 +1515,14 @@ function normalizeToElementConfig( view, viewElementType ) {
 // @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
 // @param {'container'|'attribute'|'ui'} viewElementType
 // @returns {module:engine/view/element~Element}
-function createViewElementFromDefinition( viewElementDefinition, viewWriter, viewElementType ) {
+function createViewElementFromDefinition( viewElementDefinition, conversionApi, viewElementType ) {
 	if ( typeof viewElementDefinition == 'string' ) {
 		// If `viewElementDefinition` is given as a `String`, normalize it to an object with `name` property.
 		viewElementDefinition = { name: viewElementDefinition };
 	}
 
 	let element;
+	const viewWriter = conversionApi.writer;
 	const attributes = Object.assign( {}, viewElementDefinition.attributes );
 
 	if ( viewElementType == 'container' ) {
@@ -1543,11 +1563,11 @@ function createViewElementFromDefinition( viewElementDefinition, viewWriter, vie
 
 function getFromAttributeCreator( config ) {
 	if ( config.model.values ) {
-		return ( modelAttributeValue, viewWriter ) => {
+		return ( modelAttributeValue, conversionApi ) => {
 			const view = config.view[ modelAttributeValue ];
 
 			if ( view ) {
-				return view( modelAttributeValue, viewWriter );
+				return view( modelAttributeValue, conversionApi );
 			}
 
 			return null;

+ 32 - 26
packages/ckeditor5-engine/src/conversion/upcasthelpers.js

@@ -59,7 +59,9 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *				name: 'p',
 	 *				classes: 'heading'
 	 * 			},
-	 * 			model: ( viewElement, modelWriter ) => {
+	 * 			model: ( viewElement, conversionApi ) => {
+	 * 				const modelWriter = conversionApi.writer;
+	 *
 	 * 				return modelWriter.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
 	 * 			}
 	 * 		} );
@@ -71,8 +73,9 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * @param {Object} config Conversion configuration.
 	 * @param {module:engine/view/matcher~MatcherPattern} [config.view] Pattern matching all view elements which should be converted. If not
 	 * set, the converter will fire for every view element.
-	 * @param {String|module:engine/model/element~Element|Function} config.model Name of the model element, a model element
-	 * instance or a function that takes a view element and returns a model element. The model element will be inserted in the model.
+	 * @param {String|module:engine/model/element~Element|Function} config.model Name of the model element, a model element instance or a
+	 * function that takes a view element and {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API}
+	 * and returns a model element. The model element will be inserted in the model.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 */
@@ -135,7 +138,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *			},
 	 *			model: {
 	 *				key: 'fontSize',
-	 *				value: viewElement => {
+	 *				value: ( viewElement, conversionApi ) => {
 	 *					const fontSize = viewElement.getStyle( 'font-size' );
 	 *					const value = fontSize.substr( 0, fontSize.length - 2 );
 	 *
@@ -157,7 +160,8 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * @param {Object} config Conversion configuration.
 	 * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
 	 * @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
-	 * the model attribute. `value` property may be set as a function that takes a view element and returns the value.
+	 * the model attribute. `value` property may be set as a function that takes a view element and
+	 * {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the value.
 	 * If `String` is given, the model attribute value will be set to `true`.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
@@ -231,7 +235,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *			},
 	 *			model: {
 	 *				key: 'styled'
-	 *				value: viewElement => {
+	 *				value: ( viewElement, conversionApi ) => {
 	 *					const regexp = /styled-([\S]+)/;
 	 *					const match = viewElement.getAttribute( 'class' ).match( regexp );
 	 *
@@ -263,7 +267,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *			},
 	 *			model: {
 	 *				key: 'lineHeight',
-	 *				value: viewElement => viewElement.getStyle( 'line-height' )
+	 *				value: ( viewElement, conversionApi ) => viewElement.getStyle( 'line-height' )
 	 *			}
 	 *		} );
 	 *
@@ -278,7 +282,8 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * property specifying a view element name from/on which the attribute should be converted. `value` can be given as a `String`,
 	 * a `RegExp` or a function callback, that takes view attribute value as the only parameter and returns `Boolean`.
 	 * @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
-	 * the model attribute. `value` property may be set as a function that takes a view element and returns the value.
+	 * the model attribute. `value` property may be set as a function that takes a view element and
+	 * {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the value.
 	 * If `String` is given, the model attribute value will be same as view attribute value.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
@@ -310,7 +315,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *
 	 *		editor.conversion.for( 'upcast' ).elementToMarker( {
 	 *			view: 'marker-search',
-	 *			model: viewElement => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
+	 *			model: ( viewElement, conversionApi ) => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
 	 *		} );
 	 *
 	 *		editor.conversion.for( 'upcast' ).elementToMarker( {
@@ -400,13 +405,13 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *		// Using a custom function which is the same as the default conversion:
 	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
 	 *			view: 'comment',
-	 *			model: name => 'comment:' + name,
+	 *			model: ( name, conversionApi ) => 'comment:' + name,
 	 *		} );
 	 *
 	 *		// Using the converter priority:
 	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
 	 *			view: 'comment',
-	 *			model: name => 'comment:' + name,
+	 *			model: ( name, conversionApi ) => 'comment:' + name,
 	 *			converterPriority: 'high'
 	 *		} );
 	 *
@@ -416,8 +421,8 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * @method #dataToMarker
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.view The marker group name to convert.
-	 * @param {Function} [config.model] A function that takes the `name` part from the view element or attribute and returns the marker
-	 * name.
+	 * @param {Function} [config.model] A function that takes the `name` part from the view element or attribute and
+	 * {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the marker name.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 */
@@ -697,7 +702,7 @@ function upcastAttributeToMarker( config ) {
 
 		function addMarkerElements( position, markerViewNames ) {
 			for ( const markerViewName of markerViewNames ) {
-				const markerName = config.model( markerViewName );
+				const markerName = config.model( markerViewName, conversionApi );
 				const element = conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
 
 				conversionApi.writer.insert( element, position );
@@ -754,7 +759,7 @@ function prepareToElementConverter( config ) {
 			return;
 		}
 
-		const modelElement = getModelElement( config.model, data.viewItem, conversionApi.writer );
+		const modelElement = getModelElement( config.model, data.viewItem, conversionApi );
 
 		if ( !modelElement ) {
 			return;
@@ -775,12 +780,12 @@ function prepareToElementConverter( config ) {
 //
 // @param {String|Function|module:engine/model/element~Element} model Model conversion configuration.
 // @param {module:engine/view/node~Node} input The converted view node.
-// @param {module:engine/model/writer~Writer} writer A writer instance to use to create the model element.
-function getModelElement( model, input, writer ) {
+// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi The upcast conversion API.
+function getModelElement( model, input, conversionApi ) {
 	if ( model instanceof Function ) {
-		return model( input, writer );
+		return model( input, conversionApi );
 	} else {
-		return writer.createElement( model );
+		return conversionApi.writer.createElement( model );
 	}
 }
 
@@ -858,7 +863,8 @@ function prepareToAttributeConverter( config, shallow ) {
 		}
 
 		const modelKey = config.model.key;
-		const modelValue = typeof config.model.value == 'function' ? config.model.value( data.viewItem ) : config.model.value;
+		const modelValue = typeof config.model.value == 'function' ?
+			config.model.value( data.viewItem, conversionApi ) : config.model.value;
 
 		// Do not convert if attribute building function returned falsy value.
 		if ( modelValue === null ) {
@@ -939,10 +945,10 @@ function setAttributeOn( modelRange, modelAttribute, shallow, conversionApi ) {
 function normalizeElementToMarkerConfig( config ) {
 	const oldModel = config.model;
 
-	config.model = ( viewElement, modelWriter ) => {
-		const markerName = typeof oldModel == 'string' ? oldModel : oldModel( viewElement );
+	config.model = ( viewElement, conversionApi ) => {
+		const markerName = typeof oldModel == 'string' ? oldModel : oldModel( viewElement, conversionApi );
 
-		return modelWriter.createElement( '$marker', { 'data-name': markerName } );
+		return conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
 	};
 }
 
@@ -956,11 +962,11 @@ function normalizeDataToMarkerConfig( config, type ) {
 	// Upcast <markerGroup-start> and <markerGroup-end> elements.
 	configForElements.view = config.view + '-' + type;
 
-	configForElements.model = ( viewElement, modelWriter ) => {
+	configForElements.model = ( viewElement, conversionApi ) => {
 		const viewName = viewElement.getAttribute( 'name' );
-		const markerName = config.model( viewName );
+		const markerName = config.model( viewName, conversionApi );
 
-		return modelWriter.createElement( '$marker', { 'data-name': markerName } );
+		return conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
 	};
 
 	return configForElements;

+ 3 - 3
packages/ckeditor5-engine/src/dev-utils/model.js

@@ -229,8 +229,8 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
 	downcastDispatcher.on( 'insert:$text', insertText() );
 	downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
 		if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) {
-			const converter = wrap( ( modelAttributeValue, viewWriter ) => {
-				return viewWriter.createAttributeElement(
+			const converter = wrap( ( modelAttributeValue, { writer } ) => {
+				return writer.createAttributeElement(
 					'model-text-with-attributes',
 					{ [ data.attributeKey ]: stringifyAttributeValue( modelAttributeValue ) }
 				);
@@ -248,7 +248,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
 
 	downcastDispatcher.on( 'selection', convertRangeSelection() );
 	downcastDispatcher.on( 'selection', convertCollapsedSelection() );
-	downcastDispatcher.on( 'addMarker', insertUIElement( ( data, writer ) => {
+	downcastDispatcher.on( 'addMarker', insertUIElement( ( data, { writer } ) => {
 		const name = data.markerName + ':' + ( data.isOpening ? 'start' : 'end' );
 
 		return writer.createUIElement( name );

+ 40 - 64
packages/ckeditor5-engine/tests/conversion/downcasthelpers.js

@@ -104,7 +104,7 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.elementToElement( {
 				model: 'heading',
-				view: ( modelElement, viewWriter ) => viewWriter.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) )
+				view: ( modelElement, { writer } ) => writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) )
 			} );
 
 			model.change( writer => {
@@ -225,8 +225,8 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => {
-					return viewWriter.createAttributeElement( 'span', { style: 'font-weight:' + modelAttributeValue } );
+				view: ( modelAttributeValue, { writer } ) => {
+					return writer.createAttributeElement( 'span', { style: 'font-weight:' + modelAttributeValue } );
 				}
 			} );
 
@@ -243,15 +243,15 @@ describe( 'DowncastHelpers', () => {
 					key: 'color',
 					name: '$text'
 				},
-				view: ( modelAttributeValue, viewWriter ) => {
-					return viewWriter.createAttributeElement( 'span', { style: 'color:' + modelAttributeValue } );
+				view: ( modelAttributeValue, { writer } ) => {
+					return writer.createAttributeElement( 'span', { style: 'color:' + modelAttributeValue } );
 				}
 			} );
 
 			downcastHelpers.elementToElement( {
 				model: 'smiley',
-				view: ( modelElement, viewWriter ) => {
-					return viewWriter.createEmptyElement( 'img', {
+				view: ( modelElement, { writer } ) => {
+					return writer.createEmptyElement( 'img', {
 						src: 'smile.jpg',
 						class: 'smiley'
 					} );
@@ -283,7 +283,7 @@ describe( 'DowncastHelpers', () => {
 
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'b' )
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'b' )
 			} );
 
 			model.change( writer => {
@@ -304,9 +304,9 @@ describe( 'DowncastHelpers', () => {
 
 			downcastHelpers.attributeToElement( {
 				model: 'style',
-				view: ( modelAttributeValue, viewWriter ) => {
+				view: ( modelAttributeValue, { writer } ) => {
 					if ( modelAttributeValue == 'bold' ) {
-						return viewWriter.createAttributeElement( 'b' );
+						return writer.createAttributeElement( 'b' );
 					}
 				}
 			} );
@@ -333,8 +333,8 @@ describe( 'DowncastHelpers', () => {
 
 			downcastHelpers.attributeToElement( {
 				model: 'link',
-				view: ( modelAttributeValue, viewWriter ) => {
-					return viewWriter.createAttributeElement( 'a', { href: modelAttributeValue } );
+				view: ( modelAttributeValue, { writer } ) => {
+					return writer.createAttributeElement( 'a', { href: modelAttributeValue } );
 				}
 			} );
 
@@ -357,7 +357,7 @@ describe( 'DowncastHelpers', () => {
 
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'b' )
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'b' )
 			} );
 
 			model.change( writer => {
@@ -378,11 +378,11 @@ describe( 'DowncastHelpers', () => {
 
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'b' )
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'b' )
 			} );
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'strong' ),
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'strong' ),
 				converterPriority: 'high'
 			} );
 
@@ -423,7 +423,7 @@ describe( 'DowncastHelpers', () => {
 			downcastHelpers.elementToElement( { model: 'image', view: 'img' } );
 			downcastHelpers.elementToElement( {
 				model: 'paragraph',
-				view: ( modelItem, viewWriter ) => viewWriter.createContainerElement( 'p' )
+				view: ( modelItem, { writer } ) => writer.createContainerElement( 'p' )
 			} );
 
 			downcastHelpers.attributeToAttribute( {
@@ -605,7 +605,12 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.attributeToAttribute( {
 				model: 'styled',
-				view: attributeValue => ( { key: 'class', value: 'styled-' + attributeValue } )
+				view: ( attributeValue, conversionApi ) => {
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( DowncastWriter );
+
+					return { key: 'class', value: 'styled-' + attributeValue };
+				}
 			} );
 
 			model.change( writer => {
@@ -677,41 +682,6 @@ describe( 'DowncastHelpers', () => {
 			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
 		} );
 
-		it( 'should convert insert/change/remove with attribute generating function as a parameter', () => {
-			downcastHelpers.elementToElement( { model: 'div', view: 'div' } );
-			downcastHelpers.attributeToAttribute( {
-				model: 'theme',
-				view: ( value, data ) => {
-					if ( data.item instanceof ModelElement && data.item.childCount > 0 ) {
-						value += ' fix-content';
-					}
-
-					return { key: 'class', value };
-				}
-			} );
-
-			const modelParagraph = new ModelElement( 'paragraph', { theme: 'nice' }, new ModelText( 'foobar' ) );
-			const modelDiv = new ModelElement( 'div', { theme: 'nice' } );
-
-			model.change( writer => {
-				writer.insert( [ modelParagraph, modelDiv ], modelRootStart );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="nice fix-content">foobar</p><div class="nice"></div></div>' );
-
-			model.change( writer => {
-				writer.setAttribute( 'theme', 'awesome', modelParagraph );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="awesome fix-content">foobar</p><div class="nice"></div></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'theme', modelParagraph );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p><div class="nice"></div></div>' );
-		} );
-
 		it( 'should be possible to override setAttribute', () => {
 			downcastHelpers.attributeToAttribute( {
 				model: 'class',
@@ -807,8 +777,8 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.markerToElement( {
 				model: 'search',
-				view: ( data, viewWriter ) => {
-					return viewWriter.createUIElement( 'span', { 'data-marker': 'search', 'data-start': data.isOpening } );
+				view: ( data, { writer } ) => {
+					return writer.createUIElement( 'span', { 'data-marker': 'search', 'data-start': data.isOpening } );
 				}
 			} );
 
@@ -838,7 +808,7 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove ui element', () => {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 
 				model.change( writer => {
@@ -859,7 +829,7 @@ describe( 'DowncastHelpers', () => {
 
 				downcastHelpers.markerToElement( {
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 
 				controller.downcastDispatcher.on( 'addMarker:marker', ( evt, data, conversionApi ) => {
@@ -911,7 +881,7 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove ui element - element as a creator', () => {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 
 				model.change( writer => {
@@ -931,7 +901,7 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove ui element - function as a creator', () => {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': data.markerName } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': data.markerName } )
 				} );
 
 				model.change( writer => {
@@ -951,12 +921,12 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove different opening and ending element', () => {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
-					view: ( data, viewWriter ) => {
+					view: ( data, { writer } ) => {
 						if ( data.isOpening ) {
-							return viewWriter.createUIElement( 'span', { 'class': data.markerName, 'data-start': true } );
+							return writer.createUIElement( 'span', { 'class': data.markerName, 'data-start': true } );
 						}
 
-						return viewWriter.createUIElement( 'span', { 'class': data.markerName, 'data-end': true } );
+						return writer.createUIElement( 'span', { 'class': data.markerName, 'data-end': true } );
 					}
 				} );
 
@@ -980,7 +950,7 @@ describe( 'DowncastHelpers', () => {
 
 				downcastHelpers.markerToElement( {
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 				controller.downcastDispatcher.on( 'addMarker:marker', ( evt, data, conversionApi ) => {
 					conversionApi.consumable.consume( data.item, 'addMarker:marker' );
@@ -1316,9 +1286,12 @@ describe( 'DowncastHelpers', () => {
 
 			downcastHelpers.markerToData( {
 				model: 'group',
-				view: markerName => {
+				view: ( markerName, conversionApi ) => {
 					const namePart = markerName.split( ':' )[ 1 ];
 
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( DowncastWriter );
+
 					return {
 						group: 'g',
 						name: namePart + '_' + customData[ namePart ]
@@ -1510,9 +1483,12 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.markerToHighlight( {
 				model: 'comment',
-				view: data => {
+				view: ( data, conversionApi ) => {
 					const commentType = data.markerName.split( ':' )[ 1 ];
 
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( DowncastWriter );
+
 					return {
 						classes: [ 'comment', 'comment-' + commentType ]
 					};

+ 25 - 6
packages/ckeditor5-engine/tests/conversion/upcasthelpers.js

@@ -29,6 +29,7 @@ import Mapper from '../../src/conversion/mapper';
 import ViewSelection from '../../src/view/selection';
 import ViewRange from '../../src/view/range';
 import { StylesProcessor } from '../../src/view/stylesmap';
+import Writer from '../../src/model/writer';
 
 /* globals console */
 
@@ -108,8 +109,8 @@ describe( 'UpcastHelpers', () => {
 					name: 'p',
 					classes: 'heading'
 				},
-				model: ( viewElement, modelWriter ) => {
-					return modelWriter.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
+				model: ( viewElement, { writer } ) => {
+					return writer.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
 				}
 			} );
 
@@ -250,10 +251,13 @@ describe( 'UpcastHelpers', () => {
 				},
 				model: {
 					key: 'fontSize',
-					value: viewElement => {
+					value: ( viewElement, conversionApi ) => {
 						const fontSize = viewElement.getStyle( 'font-size' );
 						const value = fontSize.substr( 0, fontSize.length - 2 );
 
+						// To ensure conversion API is provided.
+						expect( conversionApi.writer ).to.instanceof( Writer );
+
 						if ( value <= 10 ) {
 							return 'small';
 						} else if ( value > 12 ) {
@@ -504,10 +508,13 @@ describe( 'UpcastHelpers', () => {
 				},
 				model: {
 					key: 'styled',
-					value: viewElement => {
+					value: ( viewElement, conversionApi ) => {
 						const regexp = /styled-([\S]+)/;
 						const match = viewElement.getAttribute( 'class' ).match( regexp );
 
+						// To ensure conversion API is provided.
+						expect( conversionApi.writer ).to.instanceof( Writer );
+
 						return match[ 1 ];
 					}
 				}
@@ -660,7 +667,12 @@ describe( 'UpcastHelpers', () => {
 		it( 'config.model is a function', () => {
 			upcastHelpers.elementToMarker( {
 				view: 'comment',
-				model: viewElement => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
+				model: ( viewElement, conversionApi ) => {
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( Writer );
+
+					return 'comment:' + viewElement.getAttribute( 'data-comment-id' );
+				}
 			} );
 
 			const frag = new ViewDocumentFragment( viewDocument, [
@@ -837,7 +849,14 @@ describe( 'UpcastHelpers', () => {
 		} );
 
 		it( 'conversion callback, mixed, multiple markers, name', () => {
-			upcastHelpers.dataToMarker( { view: 'g', model: name => 'group:' + name.split( '_' )[ 0 ] } );
+			upcastHelpers.dataToMarker( {
+				view: 'g',
+				model: ( name, conversionApi ) => {
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( Writer );
+
+					return 'group:' + name.split( '_' )[ 0 ];
+				} } );
 
 			expectResult(
 				viewParse(

+ 1 - 1
packages/ckeditor5-enter/src/shiftenter.js

@@ -51,7 +51,7 @@ export default class ShiftEnter extends Plugin {
 		conversion.for( 'downcast' )
 			.elementToElement( {
 				model: 'softBreak',
-				view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+				view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
 			} );
 
 		view.addObserver( EnterObserver );

+ 1 - 1
packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js

@@ -92,7 +92,7 @@ export default class FontFamilyEditing extends Plugin {
 
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 			model: FONT_FAMILY,
-			view: ( attributeValue, writer ) => {
+			view: ( attributeValue, { writer } ) => {
 				return writer.createAttributeElement( 'span', { style: 'font-family:' + attributeValue }, { priority: 7 } );
 			}
 		} );

+ 1 - 1
packages/ckeditor5-font/src/fontsize/fontsizeediting.js

@@ -117,7 +117,7 @@ export default class FontSizeEditing extends Plugin {
 
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 			model: FONT_SIZE,
-			view: ( attributeValue, writer ) => {
+			view: ( attributeValue, { writer } ) => {
 				if ( !attributeValue ) {
 					return;
 				}

+ 1 - 1
packages/ckeditor5-font/src/utils.js

@@ -82,7 +82,7 @@ export function renderUpcastAttribute( styleAttr ) {
  * @param {String} styleAttr
  */
 export function renderDowncastElement( styleAttr ) {
-	return ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'span', {
+	return ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'span', {
 		style: `${ styleAttr }:${ modelAttributeValue }`
 	}, { priority: 7 } );
 }

+ 1 - 1
packages/ckeditor5-font/tests/utils.js

@@ -60,7 +60,7 @@ describe( 'utils', () => {
 			const fake = testUtils.sinon.fake();
 			const fakeViewWriter = { createAttributeElement: fake };
 
-			downcastViewConverterFn( 'blue', fakeViewWriter );
+			downcastViewConverterFn( 'blue', { writer: fakeViewWriter } );
 
 			sinon.assert.calledWithExactly( fake, 'span', { style: 'color:blue' }, { priority: 7 } );
 		} );

+ 9 - 9
packages/ckeditor5-horizontal-line/src/horizontallineediting.js

@@ -42,24 +42,24 @@ export default class HorizontalLineEditing extends Plugin {
 
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'horizontalLine',
-			view: ( modelElement, viewWriter ) => {
-				return viewWriter.createEmptyElement( 'hr' );
+			view: ( modelElement, { writer } ) => {
+				return writer.createEmptyElement( 'hr' );
 			}
 		} );
 
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'horizontalLine',
-			view: ( modelElement, viewWriter ) => {
+			view: ( modelElement, { writer } ) => {
 				const label = t( 'Horizontal line' );
-				const viewWrapper = viewWriter.createContainerElement( 'div' );
-				const viewHrElement = viewWriter.createEmptyElement( 'hr' );
+				const viewWrapper = writer.createContainerElement( 'div' );
+				const viewHrElement = writer.createEmptyElement( 'hr' );
 
-				viewWriter.addClass( 'ck-horizontal-line', viewWrapper );
-				viewWriter.setCustomProperty( 'hr', true, viewWrapper );
+				writer.addClass( 'ck-horizontal-line', viewWrapper );
+				writer.setCustomProperty( 'hr', true, viewWrapper );
 
-				viewWriter.insert( viewWriter.createPositionAt( viewWrapper, 0 ), viewHrElement );
+				writer.insert( writer.createPositionAt( viewWrapper, 0 ), viewHrElement );
 
-				return toHorizontalLineWidget( viewWrapper, viewWriter, label );
+				return toHorizontalLineWidget( viewWrapper, writer, label );
 			}
 		} );
 

+ 3 - 3
packages/ckeditor5-image/src/image/imageediting.js

@@ -61,12 +61,12 @@ export default class ImageEditing extends Plugin {
 
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'image',
-			view: ( modelElement, viewWriter ) => createImageViewElement( viewWriter )
+			view: ( modelElement, { writer } ) => createImageViewElement( writer )
 		} );
 
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'image',
-			view: ( modelElement, viewWriter ) => toImageWidget( createImageViewElement( viewWriter ), viewWriter, t( 'image widget' ) )
+			view: ( modelElement, { writer } ) => toImageWidget( createImageViewElement( writer ), writer, t( 'image widget' ) )
 		} );
 
 		conversion.for( 'downcast' )
@@ -82,7 +82,7 @@ export default class ImageEditing extends Plugin {
 						src: true
 					}
 				},
-				model: ( viewImage, modelWriter ) => modelWriter.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } )
+				model: ( viewImage, { writer } ) => writer.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } )
 			} )
 			.attributeToAttribute( {
 				view: {

+ 3 - 3
packages/ckeditor5-image/tests/image/converters.js

@@ -37,8 +37,8 @@ describe( 'Image converters', () => {
 					isBlock: true
 				} );
 
-				const editingElementCreator = ( modelElement, viewWriter ) =>
-					toImageWidget( createImageViewElement( viewWriter ), viewWriter, '' );
+				const editingElementCreator = ( modelElement, { writer } ) =>
+					toImageWidget( createImageViewElement( writer ), writer, '' );
 
 				editor.conversion.for( 'editingDowncast' ).elementToElement( {
 					model: 'image',
@@ -75,7 +75,7 @@ describe( 'Image converters', () => {
 							src: true
 						}
 					},
-					model: ( viewImage, writer ) => {
+					model: ( viewImage, { writer } ) => {
 						imgConverterCalled = true;
 
 						return writer.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } );

+ 3 - 3
packages/ckeditor5-link/src/linkediting.js

@@ -75,8 +75,8 @@ export default class LinkEditing extends Plugin {
 			.attributeToElement( { model: 'linkHref', view: createLinkElement } );
 
 		editor.conversion.for( 'editingDowncast' )
-			.attributeToElement( { model: 'linkHref', view: ( href, writer ) => {
-				return createLinkElement( ensureSafeUrl( href ), writer );
+			.attributeToElement( { model: 'linkHref', view: ( href, conversionApi ) => {
+				return createLinkElement( ensureSafeUrl( href ), conversionApi );
 			} } );
 
 		editor.conversion.for( 'upcast' )
@@ -190,7 +190,7 @@ export default class LinkEditing extends Plugin {
 
 			editor.conversion.for( 'downcast' ).attributeToElement( {
 				model: decorator.id,
-				view: ( manualDecoratorName, writer ) => {
+				view: ( manualDecoratorName, { writer } ) => {
 					if ( manualDecoratorName ) {
 						const attributes = manualDecorators.get( decorator.id ).attributes;
 						const element = writer.createAttributeElement( 'a', attributes, { priority: 5 } );

+ 2 - 1
packages/ckeditor5-link/src/utils.js

@@ -31,9 +31,10 @@ export function isLinkElement( node ) {
  * Creates link {@link module:engine/view/attributeelement~AttributeElement} with the provided `href` attribute.
  *
  * @param {String} href
+ * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
  * @returns {module:engine/view/attributeelement~AttributeElement}
  */
-export function createLinkElement( href, writer ) {
+export function createLinkElement( href, { writer } ) {
 	// Priority 5 - https://github.com/ckeditor/ckeditor5-link/issues/121.
 	const linkElement = writer.createAttributeElement( 'a', { href }, { priority: 5 } );
 	writer.setCustomProperty( 'link', true, linkElement );

+ 4 - 2
packages/ckeditor5-link/tests/utils.js

@@ -15,7 +15,8 @@ import { createLinkElement, isLinkElement, ensureSafeUrl, normalizeDecorators, i
 describe( 'utils', () => {
 	describe( 'isLinkElement()', () => {
 		it( 'should return true for elements created by createLinkElement', () => {
-			const element = createLinkElement( 'http://ckeditor.com', new ViewDowncastWriter( new ViewDocument() ) );
+			const writer = new ViewDowncastWriter( new ViewDocument() );
+			const element = createLinkElement( 'http://ckeditor.com', { writer } );
 
 			expect( isLinkElement( element ) ).to.be.true;
 		} );
@@ -35,7 +36,8 @@ describe( 'utils', () => {
 
 	describe( 'createLinkElement()', () => {
 		it( 'should create link AttributeElement', () => {
-			const element = createLinkElement( 'http://cksource.com', new ViewDowncastWriter( new ViewDocument() ) );
+			const writer = new ViewDowncastWriter( new ViewDocument() );
+			const element = createLinkElement( 'http://cksource.com', { writer } );
 
 			expect( isLinkElement( element ) ).to.be.true;
 			expect( element.priority ).to.equal( 5 );

+ 2 - 2
packages/ckeditor5-list/tests/todolistediting.js

@@ -559,12 +559,12 @@ describe( 'TodoListEditing', () => {
 
 			editor.conversion.for( 'downcast' ).markerToElement( {
 				model: 'element1',
-				view: ( data, writer ) => writer.createUIElement( 'element1' )
+				view: ( data, { writer } ) => writer.createUIElement( 'element1' )
 			} );
 
 			editor.conversion.for( 'downcast' ).markerToElement( {
 				model: 'element2',
-				view: ( data, writer ) => writer.createUIElement( 'element2' )
+				view: ( data, { writer } ) => writer.createUIElement( 'element2' )
 			} );
 
 			editor.conversion.for( 'downcast' ).markerToHighlight( {

+ 9 - 9
packages/ckeditor5-media-embed/src/mediaembedediting.js

@@ -177,10 +177,10 @@ export default class MediaEmbedEditing extends Plugin {
 		// Model -> Data
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'media',
-			view: ( modelElement, viewWriter ) => {
+			view: ( modelElement, { writer } ) => {
 				const url = modelElement.getAttribute( 'url' );
 
-				return createMediaFigureElement( viewWriter, registry, url, {
+				return createMediaFigureElement( writer, registry, url, {
 					renderMediaPreview: url && renderMediaPreview
 				} );
 			}
@@ -195,13 +195,13 @@ export default class MediaEmbedEditing extends Plugin {
 		// Model -> View (element)
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'media',
-			view: ( modelElement, viewWriter ) => {
+			view: ( modelElement, { writer } ) => {
 				const url = modelElement.getAttribute( 'url' );
-				const figure = createMediaFigureElement( viewWriter, registry, url, {
+				const figure = createMediaFigureElement( writer, registry, url, {
 					renderForEditingView: true
 				} );
 
-				return toMediaWidget( figure, viewWriter, t( 'media widget' ) );
+				return toMediaWidget( figure, writer, t( 'media widget' ) );
 			}
 		} );
 
@@ -221,11 +221,11 @@ export default class MediaEmbedEditing extends Plugin {
 						url: true
 					}
 				},
-				model: ( viewMedia, modelWriter ) => {
+				model: ( viewMedia, { writer } ) => {
 					const url = viewMedia.getAttribute( 'url' );
 
 					if ( registry.hasMedia( url ) ) {
-						return modelWriter.createElement( 'media', { url } );
+						return writer.createElement( 'media', { url } );
 					}
 				}
 			} )
@@ -237,11 +237,11 @@ export default class MediaEmbedEditing extends Plugin {
 						'data-oembed-url': true
 					}
 				},
-				model: ( viewMedia, modelWriter ) => {
+				model: ( viewMedia, { writer } ) => {
 					const url = viewMedia.getAttribute( 'data-oembed-url' );
 
 					if ( registry.hasMedia( url ) ) {
-						return modelWriter.createElement( 'media', { url } );
+						return writer.createElement( 'media', { url } );
 					}
 				}
 			} );

+ 2 - 2
packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js

@@ -127,7 +127,7 @@ function MentionLinks( editor ) {
 	// element.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: 'mention',
-		view: ( modelAttributeValue, viewWriter ) => {
+		view: ( modelAttributeValue, { writer } ) => {
 			// Do not convert empty attributes (lack of value means no mention).
 			if ( !modelAttributeValue ) {
 				return;
@@ -142,7 +142,7 @@ function MentionLinks( editor ) {
 				href = `https://example.com/social/${ modelAttributeValue.id.slice( 1 ) }`;
 			}
 
-			return viewWriter.createAttributeElement( 'a', {
+			return writer.createAttributeElement( 'a', {
 				class: 'mention',
 				'data-mention': modelAttributeValue.id,
 				href

+ 2 - 2
packages/ckeditor5-mention/docs/_snippets/features/mention-customization.js

@@ -68,13 +68,13 @@ function MentionCustomization( editor ) {
 	// Downcast the model 'mention' text attribute to a view <a> element.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: 'mention',
-		view: ( modelAttributeValue, viewWriter ) => {
+		view: ( modelAttributeValue, { writer } ) => {
 			// Do not convert empty attributes (lack of value means no mention).
 			if ( !modelAttributeValue ) {
 				return;
 			}
 
-			return viewWriter.createAttributeElement( 'a', {
+			return writer.createAttributeElement( 'a', {
 				class: 'mention',
 				'data-mention': modelAttributeValue.id,
 				'data-user-id': modelAttributeValue.userId,

+ 2 - 2
packages/ckeditor5-mention/docs/examples/chat-with-mentions.md

@@ -308,7 +308,7 @@ function MentionLinks( editor ) {
 	// element.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: 'mention',
-		view: ( modelAttributeValue, viewWriter ) => {
+		view: ( modelAttributeValue, { writer } ) => {
 			// Do not convert empty attributes (lack of value means no mention).
 			if ( !modelAttributeValue ) {
 				return;
@@ -323,7 +323,7 @@ function MentionLinks( editor ) {
 				href = `https://example.com/social/${ modelAttributeValue.id.slice( 1 ) }`;
 			}
 
-			return viewWriter.createAttributeElement( 'a', {
+			return writer.createAttributeElement( 'a', {
 				class: 'mention',
 				'data-mention': modelAttributeValue.id,
 				href

+ 4 - 4
packages/ckeditor5-mention/docs/features/mentions.md

@@ -247,13 +247,13 @@ function MentionCustomization( editor ) {
 	// Downcast the model 'mention' text attribute to a view <a> element.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: 'mention',
-		view: ( modelAttributeValue, viewWriter ) => {
+		view: ( modelAttributeValue, { writer } ) => {
 			// Do not convert empty attributes (lack of value means no mention).
 			if ( !modelAttributeValue ) {
 				return;
 			}
 
-			return viewWriter.createAttributeElement( 'a', {
+			return writer.createAttributeElement( 'a', {
 				class: 'mention',
 				'data-mention': modelAttributeValue.id,
 				'data-user-id': modelAttributeValue.userId,
@@ -339,13 +339,13 @@ function MentionCustomization( editor ) {
 	// Downcast the model 'mention' text attribute to a view <a> element.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: 'mention',
-		view: ( modelAttributeValue, viewWriter ) => {
+		view: ( modelAttributeValue, { writer } ) => {
 			// Do not convert empty attributes (lack of value means no mention).
 			if ( !modelAttributeValue ) {
 				return;
 			}
 
-			return viewWriter.createAttributeElement( 'a', {
+			return writer.createAttributeElement( 'a', {
 				class: 'mention',
 				'data-mention': modelAttributeValue.id,
 				'data-user-id': modelAttributeValue.userId,

+ 3 - 3
packages/ckeditor5-mention/src/mentionediting.js

@@ -129,9 +129,9 @@ function preventPartialMentionDowncast( dispatcher ) {
 // Creates a mention element from the mention data.
 //
 // @param {Object} mention
-// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
+// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
 // @returns {module:engine/view/attributeelement~AttributeElement}
-function createViewMentionElement( mention, viewWriter ) {
+function createViewMentionElement( mention, { writer } ) {
 	if ( !mention ) {
 		return;
 	}
@@ -146,7 +146,7 @@ function createViewMentionElement( mention, viewWriter ) {
 		priority: 20
 	};
 
-	return viewWriter.createAttributeElement( 'span', attributes, options );
+	return writer.createAttributeElement( 'span', attributes, options );
 }
 
 // Model post-fixer that disallows typing with selection when the selection is placed after the text node with the mention attribute or

+ 2 - 2
packages/ckeditor5-mention/tests/manual/mention-custom-view.js

@@ -41,12 +41,12 @@ class CustomMentionAttributeView extends Plugin {
 
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 			model: 'mention',
-			view: ( modelAttributeValue, viewWriter ) => {
+			view: ( modelAttributeValue, { writer } ) => {
 				if ( !modelAttributeValue ) {
 					return;
 				}
 
-				return viewWriter.createAttributeElement( 'a', {
+				return writer.createAttributeElement( 'a', {
 					class: 'mention',
 					'data-mention': modelAttributeValue.id,
 					'href': modelAttributeValue.link

+ 7 - 7
packages/ckeditor5-mention/tests/manual/mention.js

@@ -30,10 +30,10 @@ class InlineWidget extends Plugin {
 
 		editor.conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'placeholder',
-			view: ( modelItem, viewWriter ) => {
-				const widgetElement = createPlaceholderView( modelItem, viewWriter );
+			view: ( modelItem, conversionApi ) => {
+				const widgetElement = createPlaceholderView( modelItem, conversionApi );
 
-				return toWidget( widgetElement, viewWriter );
+				return toWidget( widgetElement, conversionApi.writer );
 			}
 		} );
 
@@ -66,11 +66,11 @@ class InlineWidget extends Plugin {
 
 		this._createToolbarButton();
 
-		function createPlaceholderView( modelItem, viewWriter ) {
-			const widgetElement = viewWriter.createContainerElement( 'placeholder' );
-			const viewText = viewWriter.createText( '{' + modelItem.getAttribute( 'type' ) + '}' );
+		function createPlaceholderView( modelItem, { writer } ) {
+			const widgetElement = writer.createContainerElement( 'placeholder' );
+			const viewText = writer.createText( '{' + modelItem.getAttribute( 'type' ) + '}' );
 
-			viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), viewText );
+			writer.insert( writer.createPositionAt( widgetElement, 0 ), viewText );
 
 			return widgetElement;
 		}

+ 4 - 4
packages/ckeditor5-mention/tests/mentionediting.js

@@ -188,12 +188,12 @@ describe( 'MentionEditing', () => {
 
 			editor.conversion.for( 'downcast' ).attributeToElement( {
 				model: 'mention',
-				view: ( modelAttributeValue, viewWriter ) => {
+				view: ( modelAttributeValue, { writer } ) => {
 					if ( !modelAttributeValue ) {
 						return;
 					}
 
-					return viewWriter.createAttributeElement( 'a', {
+					return writer.createAttributeElement( 'a', {
 						class: 'mention',
 						'data-mention': modelAttributeValue.id,
 						'href': modelAttributeValue.link
@@ -672,12 +672,12 @@ function addCustomMentionConverters( editor ) {
 
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: 'mention',
-		view: ( modelAttributeValue, viewWriter ) => {
+		view: ( modelAttributeValue, { writer } ) => {
 			if ( !modelAttributeValue ) {
 				return;
 			}
 
-			return viewWriter.createAttributeElement( 'b', {
+			return writer.createAttributeElement( 'b', {
 				class: 'mention',
 				'data-mention': modelAttributeValue.id
 			}, {

+ 1 - 1
packages/ckeditor5-mention/tests/mentionui.js

@@ -566,7 +566,7 @@ describe( 'MentionUI', () => {
 				editor.conversion.for( 'downcast' )
 					.elementToElement( {
 						model: 'softBreak',
-						view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+						view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
 					} );
 
 				setData( model, '<paragraph>abc<softBreak></softBreak>[] foo</paragraph>' );

+ 14 - 14
packages/ckeditor5-page-break/src/pagebreakediting.js

@@ -42,8 +42,8 @@ export default class PageBreakEditing extends Plugin {
 
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'pageBreak',
-			view: ( modelElement, viewWriter ) => {
-				const divElement = viewWriter.createContainerElement( 'div', {
+			view: ( modelElement, { writer } ) => {
+				const divElement = writer.createContainerElement( 'div', {
 					class: 'page-break',
 					// If user has no `.ck-content` styles, it should always break a page during print.
 					style: 'page-break-after: always'
@@ -51,11 +51,11 @@ export default class PageBreakEditing extends Plugin {
 
 				// For a rationale of using span inside a div see:
 				// https://github.com/ckeditor/ckeditor5-page-break/pull/1#discussion_r328934062.
-				const spanElement = viewWriter.createContainerElement( 'span', {
+				const spanElement = writer.createContainerElement( 'span', {
 					style: 'display: none'
 				} );
 
-				viewWriter.insert( viewWriter.createPositionAt( divElement, 0 ), spanElement );
+				writer.insert( writer.createPositionAt( divElement, 0 ), spanElement );
 
 				return divElement;
 			}
@@ -63,21 +63,21 @@ export default class PageBreakEditing extends Plugin {
 
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'pageBreak',
-			view: ( modelElement, viewWriter ) => {
+			view: ( modelElement, { writer } ) => {
 				const label = t( 'Page break' );
-				const viewWrapper = viewWriter.createContainerElement( 'div' );
-				const viewLabelElement = viewWriter.createContainerElement( 'span' );
-				const innerText = viewWriter.createText( t( 'Page break' ) );
+				const viewWrapper = writer.createContainerElement( 'div' );
+				const viewLabelElement = writer.createContainerElement( 'span' );
+				const innerText = writer.createText( t( 'Page break' ) );
 
-				viewWriter.addClass( 'page-break', viewWrapper );
-				viewWriter.setCustomProperty( 'pageBreak', true, viewWrapper );
+				writer.addClass( 'page-break', viewWrapper );
+				writer.setCustomProperty( 'pageBreak', true, viewWrapper );
 
-				viewWriter.addClass( 'page-break__label', viewLabelElement );
+				writer.addClass( 'page-break__label', viewLabelElement );
 
-				viewWriter.insert( viewWriter.createPositionAt( viewWrapper, 0 ), viewLabelElement );
-				viewWriter.insert( viewWriter.createPositionAt( viewLabelElement, 0 ), innerText );
+				writer.insert( writer.createPositionAt( viewWrapper, 0 ), viewLabelElement );
+				writer.insert( writer.createPositionAt( viewLabelElement, 0 ), innerText );
 
-				return toPageBreakWidget( viewWrapper, viewWriter, label );
+				return toPageBreakWidget( viewWrapper, writer, label );
 			}
 		} );
 

+ 2 - 2
packages/ckeditor5-paragraph/src/paragraph.js

@@ -55,7 +55,7 @@ export default class Paragraph extends Plugin {
 		// Handles element which has not been converted by any plugin and checks if it would be converted if
 		// we wrap it in a paragraph or change it to a paragraph.
 		editor.conversion.for( 'upcast' ).elementToElement( {
-			model: ( viewElement, modelWriter ) => {
+			model: ( viewElement, { writer } ) => {
 				if ( !Paragraph.paragraphLikeElements.has( viewElement.name ) ) {
 					return null;
 				}
@@ -65,7 +65,7 @@ export default class Paragraph extends Plugin {
 					return null;
 				}
 
-				return modelWriter.createElement( 'paragraph' );
+				return writer.createElement( 'paragraph' );
 			},
 			view: /.+/,
 			converterPriority: 'low'

+ 4 - 4
packages/ckeditor5-restricted-editing/src/restrictededitingmodeediting.js

@@ -161,8 +161,8 @@ export default class RestrictedEditingModeEditing extends Plugin {
 		// Additionally the editing pipeline should always display a collapsed markers.
 		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 			model: 'restrictedEditingException',
-			view: ( markerData, viewWriter ) => {
-				return viewWriter.createUIElement( 'span', {
+			view: ( markerData, { writer } ) => {
+				return writer.createUIElement( 'span', {
 					class: 'restricted-editing-exception restricted-editing-exception_collapsed'
 				} );
 			}
@@ -170,8 +170,8 @@ export default class RestrictedEditingModeEditing extends Plugin {
 
 		editor.conversion.for( 'dataDowncast' ).markerToElement( {
 			model: 'restrictedEditingException',
-			view: ( markerData, viewWriter ) => {
-				return viewWriter.createEmptyElement( 'span', {
+			view: ( markerData, { writer } ) => {
+				return writer.createEmptyElement( 'span', {
 					class: 'restricted-editing-exception'
 				} );
 			}

+ 2 - 2
packages/ckeditor5-restricted-editing/src/standardeditingmodeediting.js

@@ -45,10 +45,10 @@ export default class StandardEditingModeEditing extends Plugin {
 
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 			model: 'restrictedEditingException',
-			view: ( modelAttributeValue, viewWriter ) => {
+			view: ( modelAttributeValue, { writer } ) => {
 				if ( modelAttributeValue ) {
 					// Make the restricted editing <span> outer-most in the view.
-					return viewWriter.createAttributeElement( 'span', { class: 'restricted-editing-exception' }, { priority: -10 } );
+					return writer.createAttributeElement( 'span', { class: 'restricted-editing-exception' }, { priority: -10 } );
 				}
 			}
 		} );

+ 1 - 1
packages/ckeditor5-typing/tests/utils/inlinehighlight.js

@@ -30,7 +30,7 @@ describe( 'inlineHighlight', () => {
 		model.schema.extend( '$text', { allowAttributes: 'linkHref' } );
 
 		editor.conversion.for( 'editingDowncast' )
-			.attributeToElement( { model: 'linkHref', view: ( href, writer ) => {
+			.attributeToElement( { model: 'linkHref', view: ( href, { writer } ) => {
 				return writer.createAttributeElement( 'a', { href } );
 			} } );
 

+ 2 - 2
packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js

@@ -127,12 +127,12 @@ describe( 'ContextualBalloon', () => {
 
 				editor.conversion.for( 'downcast' ).elementToElement( {
 					model: 'widget',
-					view: ( modelElement, viewWriter ) => viewWriter.createContainerElement( 'figure', { contenteditable: 'false' } )
+					view: ( modelElement, { writer } ) => writer.createContainerElement( 'figure', { contenteditable: 'false' } )
 				} );
 
 				editor.conversion.for( 'downcast' ).elementToElement( {
 					model: 'nestedEditable',
-					view: ( modelElement, viewWriter ) => viewWriter.createContainerElement( 'figcaption', { contenteditable: 'true' } )
+					view: ( modelElement, { writer } ) => writer.createContainerElement( 'figcaption', { contenteditable: 'true' } )
 				} );
 
 				setModelData( model, '<widget><nestedEditable>[]foo</nestedEditable></widget>' );

+ 4 - 4
packages/ckeditor5-widget/src/utils.js

@@ -65,7 +65,7 @@ export function isWidget( node ) {
  *		editor.conversion.for( 'editingDowncast' )
  *			.elementToElement( {
  *				model: 'widget',
- *				view: ( modelItem, writer ) => {
+ *				view: ( modelItem, { writer } ) => {
  *					const div = writer.createContainerElement( 'div', { class: 'widget' } );
  *
  *					return toWidget( div, writer, { label: 'some widget' } );
@@ -75,7 +75,7 @@ export function isWidget( node ) {
  *		editor.conversion.for( 'dataDowncast' )
  *			.elementToElement( {
  *				model: 'widget',
- *				view: ( modelItem, writer ) => {
+ *				view: ( modelItem, { writer } ) => {
  *					return writer.createContainerElement( 'div', { class: 'widget' } );
  *				}
  *			} );
@@ -208,7 +208,7 @@ export function getLabel( element ) {
  *		editor.conversion.for( 'editingDowncast' )
  *			.elementToElement( {
  *				model: 'nested',
- *				view: ( modelItem, writer ) => {
+ *				view: ( modelItem, { writer } ) => {
  *					const div = writer.createEditableElement( 'div', { class: 'nested' } );
  *
  *					return toWidgetEditable( nested, writer );
@@ -218,7 +218,7 @@ export function getLabel( element ) {
  *		editor.conversion.for( 'dataDowncast' )
  *			.elementToElement( {
  *				model: 'nested',
- *				view: ( modelItem, writer ) => {
+ *				view: ( modelItem, { writer } ) => {
  *					return writer.createContainerElement( 'div', { class: 'nested' } );
  *				}
  *			} );

+ 7 - 7
packages/ckeditor5-widget/tests/manual/inline-widget.js

@@ -36,10 +36,10 @@ class InlineWidget extends Plugin {
 
 		editor.conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'placeholder',
-			view: ( modelItem, viewWriter ) => {
-				const widgetElement = createPlaceholderView( modelItem, viewWriter );
+			view: ( modelItem, conversionApi ) => {
+				const widgetElement = createPlaceholderView( modelItem, conversionApi );
 
-				return toWidget( widgetElement, viewWriter );
+				return toWidget( widgetElement, conversionApi.writer );
 			}
 		} );
 
@@ -72,11 +72,11 @@ class InlineWidget extends Plugin {
 
 		this._createToolbarButton();
 
-		function createPlaceholderView( modelItem, viewWriter ) {
-			const widgetElement = viewWriter.createContainerElement( 'placeholder' );
-			const viewText = viewWriter.createText( '{' + modelItem.getAttribute( 'type' ) + '}' );
+		function createPlaceholderView( modelItem, { writer } ) {
+			const widgetElement = writer.createContainerElement( 'placeholder' );
+			const viewText = writer.createText( '{' + modelItem.getAttribute( 'type' ) + '}' );
 
-			viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), viewText );
+			writer.insert( writer.createPositionAt( widgetElement, 0 ), viewText );
 
 			return widgetElement;
 		}

+ 8 - 8
packages/ckeditor5-widget/tests/manual/keyboard.js

@@ -19,7 +19,7 @@ function BlockWidget( editor ) {
 
 	editor.conversion.for( 'downcast' ).elementToElement( {
 		model: 'div',
-		view: ( modelElement, writer ) => {
+		view: ( modelElement, { writer } ) => {
 			return toWidget(
 				writer.createContainerElement( 'div', {
 					class: 'widget'
@@ -108,10 +108,10 @@ class InlineWidget extends Plugin {
 
 		editor.conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'placeholder',
-			view: ( modelItem, viewWriter ) => {
-				const widgetElement = createPlaceholderView( modelItem, viewWriter );
+			view: ( modelItem, conversionApi ) => {
+				const widgetElement = createPlaceholderView( modelItem, conversionApi );
 
-				return toWidget( widgetElement, viewWriter );
+				return toWidget( widgetElement, conversionApi.writer );
 			}
 		} );
 
@@ -130,11 +130,11 @@ class InlineWidget extends Plugin {
 			viewToModelPositionOutsideModelElement( editor.model, viewElement => viewElement.name == 'placeholder' )
 		);
 
-		function createPlaceholderView( modelItem, viewWriter ) {
-			const widgetElement = viewWriter.createContainerElement( 'placeholder' );
-			const viewText = viewWriter.createText( '{placeholder}' );
+		function createPlaceholderView( modelItem, { writer } ) {
+			const widgetElement = writer.createContainerElement( 'placeholder' );
+			const viewText = writer.createText( '{placeholder}' );
 
-			viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), viewText );
+			writer.insert( writer.createPositionAt( widgetElement, 0 ), viewText );
 
 			return widgetElement;
 		}

+ 1 - 1
packages/ckeditor5-widget/tests/manual/nested-widgets.js

@@ -24,7 +24,7 @@ function MyPlugin( editor ) {
 
 	editor.conversion.for( 'downcast' ).elementToElement( {
 		model: 'div',
-		view: ( modelElement, writer ) => {
+		view: ( modelElement, { writer } ) => {
 			return toWidget( writer.createContainerElement( 'div', { class: 'widget' } ), writer );
 		}
 	} );

+ 1 - 1
packages/ckeditor5-widget/tests/manual/selection-handle.js

@@ -18,7 +18,7 @@ function MyPlugin( editor ) {
 
 	editor.conversion.for( 'downcast' ).elementToElement( {
 		model: 'div',
-		view: ( modelElement, writer ) => {
+		view: ( modelElement, { writer } ) => {
 			return toWidget( writer.createContainerElement( 'div', {
 				class: 'widget'
 			} ),

+ 7 - 7
packages/ckeditor5-widget/tests/manual/type-around.js

@@ -29,10 +29,10 @@ class InlineWidget extends Plugin {
 
 		editor.conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'placeholder',
-			view: ( modelItem, viewWriter ) => {
-				const widgetElement = createPlaceholderView( modelItem, viewWriter );
+			view: ( modelItem, conversionApi ) => {
+				const widgetElement = createPlaceholderView( modelItem, conversionApi );
 
-				return toWidget( widgetElement, viewWriter );
+				return toWidget( widgetElement, conversionApi.writer );
 			}
 		} );
 
@@ -53,11 +53,11 @@ class InlineWidget extends Plugin {
 
 		this._createToolbarButton();
 
-		function createPlaceholderView( modelItem, viewWriter ) {
-			const widgetElement = viewWriter.createContainerElement( 'placeholder' );
-			const viewText = viewWriter.createText( '{inline-widget}' );
+		function createPlaceholderView( modelItem, { writer } ) {
+			const widgetElement = writer.createContainerElement( 'placeholder' );
+			const viewText = writer.createText( '{inline-widget}' );
 
-			viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), viewText );
+			writer.insert( writer.createPositionAt( widgetElement, 0 ), viewText );
 
 			return widgetElement;
 		}

+ 4 - 4
packages/ckeditor5-widget/tests/manual/widget-with-nestededitable.js

@@ -40,13 +40,13 @@ ClassicEditor
 		editor.conversion.for( 'dataDowncast' )
 			.elementToElement( {
 				model: 'widget',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					return writer.createContainerElement( 'div', { class: 'widget' } );
 				}
 			} )
 			.elementToElement( {
 				model: 'nested',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					return writer.createContainerElement( 'div', { class: 'nested' } );
 				}
 			} );
@@ -54,7 +54,7 @@ ClassicEditor
 		editor.conversion.for( 'editingDowncast' )
 			.elementToElement( {
 				model: 'widget',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					const div = writer.createContainerElement( 'div', { class: 'widget' } );
 
 					return toWidget( div, writer, { label: 'widget label' } );
@@ -62,7 +62,7 @@ ClassicEditor
 			} )
 			.elementToElement( {
 				model: 'nested',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					const nested = writer.createEditableElement( 'div', { class: 'nested' } );
 
 					return toWidgetEditable( nested, writer );

+ 4 - 4
packages/ckeditor5-widget/tests/verticalnavigation.js

@@ -1258,13 +1258,13 @@ describe( 'Widget - vertical keyboard navigation near widgets', () => {
 		editor.conversion.for( 'dataDowncast' )
 			.elementToElement( {
 				model: 'widget',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					return writer.createContainerElement( 'figure' );
 				}
 			} )
 			.elementToElement( {
 				model: 'nested',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					return writer.createContainerElement( 'figcaption' );
 				}
 			} );
@@ -1272,7 +1272,7 @@ describe( 'Widget - vertical keyboard navigation near widgets', () => {
 		editor.conversion.for( 'editingDowncast' )
 			.elementToElement( {
 				model: 'widget',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					const div = writer.createContainerElement( 'figure' );
 
 					return toWidget( div, writer, { label: 'widget label' } );
@@ -1280,7 +1280,7 @@ describe( 'Widget - vertical keyboard navigation near widgets', () => {
 			} )
 			.elementToElement( {
 				model: 'nested',
-				view: ( modelItem, writer ) => {
+				view: ( modelItem, { writer } ) => {
 					const nested = writer.createEditableElement( 'figcaption' );
 
 					return toWidgetEditable( nested, writer );

+ 8 - 8
packages/ckeditor5-widget/tests/widget-integration.js

@@ -68,27 +68,27 @@ describe( 'Widget - integration', () => {
 					.elementToElement( { model: 'image', view: 'img' } )
 					.elementToElement( {
 						model: 'widget',
-						view: ( modelItem, viewWriter ) => {
-							const div = viewWriter.createContainerElement( 'div' );
+						view: ( modelItem, { writer } ) => {
+							const div = writer.createContainerElement( 'div' );
 
-							return toWidget( div, viewWriter, { label: 'element label' } );
+							return toWidget( div, writer, { label: 'element label' } );
 						}
 					} )
 					.elementToElement( {
 						model: 'inline-widget',
-						view: ( modelItem, viewWriter ) => {
-							const span = viewWriter.createContainerElement( 'span' );
+						view: ( modelItem, { writer } ) => {
+							const span = writer.createContainerElement( 'span' );
 
-							return toWidget( span, viewWriter );
+							return toWidget( span, writer );
 						}
 					} )
 					.elementToElement( {
 						model: 'nested',
-						view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
+						view: ( modelItem, { writer } ) => writer.createEditableElement( 'figcaption', { contenteditable: true } )
 					} )
 					.elementToElement( {
 						model: 'editable',
-						view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
+						view: ( modelItem, { writer } ) => writer.createEditableElement( 'figcaption', { contenteditable: true } )
 					} );
 			} );
 	} );

+ 14 - 14
packages/ckeditor5-widget/tests/widget.js

@@ -90,29 +90,29 @@ describe( 'Widget', () => {
 					.elementToElement( { model: 'div', view: 'div' } )
 					.elementToElement( {
 						model: 'widget',
-						view: ( modelItem, viewWriter ) => {
-							const b = viewWriter.createAttributeElement( 'b' );
-							const div = viewWriter.createContainerElement( 'div' );
-							viewWriter.insert( viewWriter.createPositionAt( div, 0 ), b );
+						view: ( modelItem, { writer } ) => {
+							const b = writer.createAttributeElement( 'b' );
+							const div = writer.createContainerElement( 'div' );
+							writer.insert( writer.createPositionAt( div, 0 ), b );
 
-							return toWidget( div, viewWriter, { label: 'element label' } );
+							return toWidget( div, writer, { label: 'element label' } );
 						}
 					} )
 					.elementToElement( {
 						model: 'inline-widget',
-						view: ( modelItem, viewWriter ) => {
-							const span = viewWriter.createContainerElement( 'span' );
+						view: ( modelItem, { writer } ) => {
+							const span = writer.createContainerElement( 'span' );
 
-							return toWidget( span, viewWriter );
+							return toWidget( span, writer );
 						}
 					} )
 					.elementToElement( {
 						model: 'nested',
-						view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
+						view: ( modelItem, { writer } ) => writer.createEditableElement( 'figcaption', { contenteditable: true } )
 					} )
 					.elementToElement( {
 						model: 'editable',
-						view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
+						view: ( modelItem, { writer } ) => writer.createEditableElement( 'figcaption', { contenteditable: true } )
 					} );
 			} );
 	} );
@@ -1288,15 +1288,15 @@ describe( 'Widget', () => {
 						.elementToElement( { model: 'paragraph', view: 'p' } )
 						.elementToElement( {
 							model: 'widget',
-							view: ( modelItem, viewWriter ) => {
-								const widget = viewWriter.createContainerElement( 'div' );
+							view: ( modelItem, { writer } ) => {
+								const widget = writer.createContainerElement( 'div' );
 
-								return toWidget( widget, viewWriter, { hasSelectionHandle: true } );
+								return toWidget( widget, writer, { hasSelectionHandle: true } );
 							}
 						} )
 						.elementToElement( {
 							model: 'nested',
-							view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
+							view: ( modelItem, { writer } ) => writer.createEditableElement( 'figcaption', { contenteditable: true } )
 						} );
 				} );
 		} );

+ 13 - 13
packages/ckeditor5-widget/tests/widgetresize.js

@@ -598,19 +598,19 @@ describe( 'WidgetResize', () => {
 		editor.conversion.for( 'downcast' )
 			.elementToElement( {
 				model: 'widget',
-				view: ( modelItem, viewWriter ) => {
-					const parentDiv = viewWriter.createContainerElement( 'div' );
-					viewWriter.setStyle( 'height', '50px', parentDiv );
-					viewWriter.setStyle( 'width', '25%', parentDiv ); // It evaluates to 100px.
-
-					const subDiv = viewWriter.createContainerElement( 'div' );
-					viewWriter.insert( viewWriter.createPositionAt( subDiv, 'start' ), viewWriter.createText( 'foo' ) );
-					viewWriter.addClass( 'sub-div', subDiv );
-					viewWriter.setStyle( 'height', '20px', subDiv );
-					viewWriter.setStyle( 'width', '50px', subDiv );
-					viewWriter.insert( viewWriter.createPositionAt( parentDiv, 'start' ), subDiv );
-
-					return toWidget( parentDiv, viewWriter, {
+				view: ( modelItem, { writer } ) => {
+					const parentDiv = writer.createContainerElement( 'div' );
+					writer.setStyle( 'height', '50px', parentDiv );
+					writer.setStyle( 'width', '25%', parentDiv ); // It evaluates to 100px.
+
+					const subDiv = writer.createContainerElement( 'div' );
+					writer.insert( writer.createPositionAt( subDiv, 'start' ), writer.createText( 'foo' ) );
+					writer.addClass( 'sub-div', subDiv );
+					writer.setStyle( 'height', '20px', subDiv );
+					writer.setStyle( 'width', '50px', subDiv );
+					writer.insert( writer.createPositionAt( parentDiv, 'start' ), subDiv );
+
+					return toWidget( parentDiv, writer, {
 						label: 'element label'
 					} );
 				}

+ 12 - 12
packages/ckeditor5-widget/tests/widgettoolbarrepository.js

@@ -742,18 +742,18 @@ class FakeWidget extends Plugin {
 
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'fake-widget',
-			view: ( modelElement, viewWriter ) => {
-				return viewWriter.createContainerElement( 'div' );
+			view: ( modelElement, { writer } ) => {
+				return writer.createContainerElement( 'div' );
 			}
 		} );
 
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'fake-widget',
-			view: ( modelElement, viewWriter ) => {
-				const fakeWidget = viewWriter.createContainerElement( 'div' );
-				viewWriter.setCustomProperty( 'fakeWidget', true, fakeWidget );
+			view: ( modelElement, { writer } ) => {
+				const fakeWidget = writer.createContainerElement( 'div' );
+				writer.setCustomProperty( 'fakeWidget', true, fakeWidget );
 
-				return toWidget( fakeWidget, viewWriter, { label: 'fake-widget' } );
+				return toWidget( fakeWidget, writer, { label: 'fake-widget' } );
 			}
 		} );
 
@@ -794,18 +794,18 @@ class FakeChildWidget extends Plugin {
 
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'fake-child-widget',
-			view: ( modelElement, viewWriter ) => {
-				return viewWriter.createContainerElement( 'div' );
+			view: ( modelElement, { writer } ) => {
+				return writer.createContainerElement( 'div' );
 			}
 		} );
 
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'fake-child-widget',
-			view: ( modelElement, viewWriter ) => {
-				const fakeWidget = viewWriter.createContainerElement( 'div' );
-				viewWriter.setCustomProperty( 'fakeChildWidget', true, fakeWidget );
+			view: ( modelElement, { writer } ) => {
+				const fakeWidget = writer.createContainerElement( 'div' );
+				writer.setCustomProperty( 'fakeChildWidget', true, fakeWidget );
 
-				return toWidget( fakeWidget, viewWriter, { label: 'fake-child-widget' } );
+				return toWidget( fakeWidget, writer, { label: 'fake-child-widget' } );
 			}
 		} );
 

+ 11 - 11
packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js

@@ -1600,20 +1600,20 @@ describe( 'WidgetTypeAround', () => {
 		editor.conversion.for( 'downcast' )
 			.elementToElement( {
 				model: 'blockWidget',
-				view: ( modelItem, viewWriter ) => {
-					const container = viewWriter.createContainerElement( 'div' );
-					const viewText = viewWriter.createText( 'block-widget' );
+				view: ( modelItem, { writer } ) => {
+					const container = writer.createContainerElement( 'div' );
+					const viewText = writer.createText( 'block-widget' );
 
-					viewWriter.insert( viewWriter.createPositionAt( container, 0 ), viewText );
+					writer.insert( writer.createPositionAt( container, 0 ), viewText );
 
-					return toWidget( container, viewWriter, {
+					return toWidget( container, writer, {
 						label: 'block widget'
 					} );
 				}
 			} )
 			.elementToElement( {
 				model: 'nested',
-				view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'nested', { contenteditable: true } )
+				view: ( modelItem, { writer } ) => writer.createEditableElement( 'nested', { contenteditable: true } )
 			} );
 	}
 
@@ -1627,13 +1627,13 @@ describe( 'WidgetTypeAround', () => {
 		editor.conversion.for( 'downcast' )
 			.elementToElement( {
 				model: 'inlineWidget',
-				view: ( modelItem, viewWriter ) => {
-					const container = viewWriter.createContainerElement( 'inlineWidget' );
-					const viewText = viewWriter.createText( 'inline-widget' );
+				view: ( modelItem, { writer } ) => {
+					const container = writer.createContainerElement( 'inlineWidget' );
+					const viewText = writer.createText( 'inline-widget' );
 
-					viewWriter.insert( viewWriter.createPositionAt( container, 0 ), viewText );
+					writer.insert( writer.createPositionAt( container, 0 ), viewText );
 
-					return toWidget( container, viewWriter, {
+					return toWidget( container, writer, {
 						label: 'inline widget'
 					} );
 				}