function handleChild(node, childNode, mOptions) {
				var oAggregation = mOptions.aggregation,
					mAggregations = mOptions.allAggregations,
					pRequireContext = mOptions.chain,
					oClosestBinding = mOptions.closestBinding,
					oConfig = mOptions.config,
					oNamedAggregation,
					fnCreateStashedControl;

				// inspect only element nodes
				if (childNode.nodeType === 1 /* ELEMENT_NODE */) {

					if (childNode.namespaceURI === XML_COMPOSITE_NAMESPACE) {
						mSettings[localName(childNode)] = childNode.querySelector("*");
						return undefined;
					}
					// check for a named aggregation (must have the same namespace as the parent and an aggregation with the same name must exist)
					oNamedAggregation = childNode.namespaceURI === ns && mAggregations && mAggregations[localName(childNode)];

					if (oNamedAggregation) {
						// the children of the current childNode are aggregated controls (or HTML) below the named aggregation
						return handleChildren(childNode, {
							aggregation: oNamedAggregation,
							allAggregations: null,
							chain: pRequireContext,
							closestBinding: oClosestBinding,
							config: oConfig
						});
					} else if (oAggregation) {
						// TODO consider moving this to a place where HTML and SVG nodes can be handled properly
						// create a StashedControl for inactive controls, which is not placed in an aggregation
						if (childNode.getAttribute("stashed") === "true" && !bEnrichFullIds) {
							var oStashedNode = childNode;
							// clone the node and remove all descendants
							childNode = childNode.cloneNode();
							// remove stashed attribute as it is an unknown property.
							oStashedNode.removeAttribute("stashed");
							fnCreateStashedControl = function() {
								var sControlId = getId(oView, childNode);

								StashedControlSupport.createStashedControl({
									wrapperId: sControlId,
									fnCreate: function(bSync) {
										var bPrevAsync = bAsync;

										/**
										 * To stay compatible with legacy factories in 1.x, if the view was originally created sync,
										 * we don't switch to async processing here, even if the unstash() is called async.
										 * @deprecated
										 */
										if (bAsync === false) {
											bSync = true;
										}

										// temporarily switch the stashed subtree to async=false in case the unstash() operation is triggered sync.
										// the scoped var bAsync applies to everything contained in this view, the original value is restored after the unstash operation
										bAsync = !bSync;

										try {
											setUI5Attribute(oStashedNode, "unstash");
											let vUnstashedControl = handleChild(node, oStashedNode, {
												aggregation: oAggregation,
												allAggregations: mAggregations,
												chain: SyncPromise.resolve(oRequireContext),
												closestBinding: oClosestBinding
											});
											/**
											 * @deprecated
											 */
											if (bSync) {
												vUnstashedControl = vUnstashedControl.unwrap();
											}
											return vUnstashedControl;
										} finally {
											bAsync = bPrevAsync;
										}
									}
								});
							};

							scopedRunWithOwner(fnCreateStashedControl);

							// ...and mark the stashed node as invisible.
							// The original visibility value is still scoped in the clone (visible could be bound, yet stashed controls are never visible)
							childNode.removeAttribute("visible");
							setUI5Attribute(childNode, "invisible");
						}

						if ( mSettings[oAggregation.name] &&
							// whether the created controls will be the template for a list binding
							typeof mSettings[oAggregation.name].path === "string") {
							oClosestBinding = {
								aggregation: oAggregation.name,
								id: mSettings.id
							};

							// mark that the content aggregation of the View node is bound
							if (bViewRootNode && oAggregation.name === "content") {
								oConfig = oConfig || {};
								oConfig.contentBound = true;
							}
						}

						// child node name does not equal an aggregation name,
						// so this child must be a control (or HTML) which is aggregated below the DEFAULT aggregation
						return createControls(childNode, pRequireContext, oClosestBinding, oAggregation, oConfig).then(function(aControls) {
							for (var j = 0; j < aControls.length; j++) {
								var oControl = aControls[j];
								// append the child to the aggregation
								var name = oAggregation.name;

								// oControl is an ExtensionPoint placeholder
								// only in Flexibility scenario if an ExtensionProvider is given!
								if (oControl._isExtensionPoint) {
									if (!mSettings[name]) {
										mSettings[name] = [];
									}

									var aExtensionPointList = mAggregationsWithExtensionPoints[name];
									if (!aExtensionPointList) {
										aExtensionPointList = mAggregationsWithExtensionPoints[name] = [];
									}
									// if the aggregation already exists we get the
									oControl.index = mSettings[name].length;
									oControl.aggregationName = name;
									oControl.closestAggregationBindingCarrier = oClosestBinding && oClosestBinding.id; // TODO can we safely assume that this has an "id"?
									oControl.closestAggregationBinding = oClosestBinding && oClosestBinding.aggregation;

									// connect extension points
									var oLast = aExtensionPointList[aExtensionPointList.length - 1];
									if (oLast) {
										oLast._nextSibling = oControl;
									}

									aExtensionPointList.push(oControl);
								} else if (oAggregation.multiple) {
									// 1..n AGGREGATION
									if (!mSettings[name]) {
										mSettings[name] = [];
									}
									if (typeof mSettings[name].path === "string") {
										assert(!mSettings[name].template, "list bindings support only a single template object");
										mSettings[name].template = oControl;
									} else {
										mSettings[name].push(oControl);
									}
								} else {
									// 0..1 AGGREGATION
									assert(!mSettings[name], "multiple aggregates defined for aggregation " + name + " with cardinality 0..1");
									mSettings[name] = oControl;
								}
							}
							return aControls;
						});
					} else {
						throw new Error(createErrorInfo(childNode, "Cannot add direct child without default aggregation defined for control " + oMetadata.getElementName()));
					}
				} else if (childNode.nodeType === 3 /* TEXT_NODE */) {
					if (oConfig && oConfig.rootArea) {
						createControls(childNode, pRequireContext, oClosestBinding, oAggregation, oConfig);
					} else {
						var sTextContent = childNode.textContent || childNode.text;
						if (sTextContent && sTextContent.trim()) { // whitespace would be okay
							throw new Error(createErrorInfo(childNode, "Cannot add text nodes as direct child of an aggregation. For adding text to an aggregation, a surrounding html tag is needed."));
						}
					}
				} // other nodes types are silently ignored

			}
@return {Promise} resolving to an array with 0..n controls created from a node
@private