# Source: ui/component.js

    /*global Element, console */
    /**
     * @module montage/ui/component
     * @requires montage
     * @requires montage/core/target
     * @requires montage/core/template
     * @requires montage/core/document-resources
     * @requires montage/core/gate
     * @requires montage/core/promise
     * @requires montage/core/logger
     * @requires montage/core/event/event-manager
     * @requires montage/core/serialization/alias
     * @requires collections/set
     */
    var Montage = require("../core/core").Montage,
        Target = require("../core/target").Target,
        Template = require("../core/template").Template,
        DocumentResources = require("../core/document-resources").DocumentResources,
        Gate = require("../core/gate").Gate,
        Promise = require("../core/promise").Promise,
        defaultEventManager = require("../core/event/event-manager").defaultEventManager,
        Alias = require("../core/serialization/alias").Alias,

        logger = require("../core/logger").logger("component"),
        drawPerformanceLogger = require("../core/logger").logger("Drawing performance").color.green(),
        drawListLogger = require("../core/logger").logger("drawing list").color.blue(),
        needsDrawLogger = require("../core/logger").logger("drawing needsDraw").color.violet(),
        drawLogger = require("../core/logger").logger("drawing").color.blue(),
        WeakMap = require("collections/weak-map"),
        Map = require("collections/map"),
        Set = require("collections/set");

    /**
     * @const
     * @default
     * @type {string}
     */
    var ATTR_LE_COMPONENT = "data-montage-le-component",
        ATTR_LE_ARG = "data-montage-le-arg",
        ATTR_LE_ARG_BEGIN = "data-montage-le-arg-begin",
        ATTR_LE_ARG_END = "data-montage-le-arg-end";

    function loggerToString (object) {
        if (!object) {
            return "NIL";
        }

        return object._montage_metadata.objectName + ":" + Object.hash(object) + " id: " + object.identifier;
    }

    var CssBasedAnimation = Montage.specialize({

        component: {
            value: null
        },

        fromCssClass: {
            value: null
        },

        cssClass: {
            value: null
        },

        toCssClass: {
            value: null
        },

        hasOneFrameDelay: {
            value: false
        },

        _animationAndTransitionProperties: {
            value: [
                "-webkit-animation",
                "-moz-animation",
                "-ms-animation",
                "animation",
                "-webkit-transition",
                "-moz-transition",
                "-ms-transition",
                "transition"
            ]
        },

        _emptyArray: {
            value: []
        },

        /**
         * Parses the computed style value for a css time property and
         * returns an array of numbers representing seconds.
         * For example, the time value string "1s, 2s" will return [1, 2]
         */
        _parseComputedStyleTimeValue: {
            value: function (timeValue) {
                var result,
                    i;

                if (typeof timeValue !== "string" || timeValue === "") {
                    return this._emptyArray;
                }
                result = timeValue.replace(/s| /g, "").split(",");
                for (i = 0; i < result.length; i++) {
                    result[i] = +result[i];
                }
                return result;
            }
        },

        /**
         * Returns an boundary estimate of the maximum time an element would
         * take to complete its css animations and/or transitions.
         */
        _getMaxAnimationTime: {
            value: function () {
                var computedStyle,
                    durations,
                    delays,
                    maxTime = 0,
                    time,
                    length,
                    i, j;

                if (this.component && this.component.element) {
                    computedStyle = global.getComputedStyle(this.component.element);
                    for (i = 0; i < this._animationAndTransitionProperties.length; i++) {
                        durations = this._parseComputedStyleTimeValue(
                            computedStyle.getPropertyValue(this._animationAndTransitionProperties[i] + "-duration")
                        );
                        delays = this._parseComputedStyleTimeValue(
                            computedStyle.getPropertyValue(this._animationAndTransitionProperties[i] + "-delay")
                        );
                        length = Math.max(durations.length, delays.length);
                        for (j = 0; j < length; j++) {
                            if (typeof durations[j] === "undefined") {
                                time = durations[0] || 0;
                            } else {
                                time = durations[j];
                            }
                            if (typeof delays[j] === "undefined") {
                                time += delays[0] || 0;
                            } else {
                                time += delays[j];
                            }
                            if (time > maxTime) {
                                maxTime = time;
                            }
                        }
                    }
                    if (maxTime > 0) {
                        // Browsers take several miliseconds since you add the css animation
                        // or transition property and it really starts. It can range from
                        // very few miliseconds in desktop to a couple of hundreds in mobile
                        // devices, so we are adding 300 miliseconds as a safety value that
                        // should cover the most of the cases.
                        maxTime += 0.3;
                    }
                }
                return maxTime;
            }
        },

        _onAnimationsCompletedTimeout: {
            value: null
        },

        _cancelOnAnimationsCompletedEvent: {
            value: function () {
                clearTimeout(this._onAnimationsCompletedTimeout);
            }
        },

        _onAnimationsCompleted: {
            value: function (callback) {
                var maxTime = this._getMaxAnimationTime(),
                    self;

                if (!maxTime) {
                    callback.call(this);
                    return;
                }
                self = this;
                this._cancelOnAnimationsCompletedEvent();
                this._onAnimationsCompletedTimeout = setTimeout(function () {
                    callback.call(self);
                }, maxTime * 1000);
             }
        },

        _needsToMeasureAnimationTimeOnNextDraw: {
            value: false
        },

        _needsToMeasureAnimationTime: {
            value: false
        },

        handleDidDraw: {
            value: function (event) {
                if (this._needsToMeasureAnimationTimeOnNextDraw) {
                    if (this.fromCssClass) {
                        this.component.classList.remove(this.fromCssClass);
                    }
                    if (this.cssClass) {
                        this.component.classList.add(this.cssClass);
                    }
                    if (this.toCssClass) {
                        this.component.classList.add(this.toCssClass);
                    }
                    this._needsToMeasureAnimationTimeOnNextDraw = false;
                    this._needsToMeasureAnimationTime = true;
                    this.component.needsDraw = true;
                } else {
                    if (this._needsToMeasureAnimationTime) {
                        this._onAnimationsCompleted(function () {
                            if (this._finishedDeferred) {
                                this._finishedDeferred.resolve();
                                this._finishedDeferred = null;
                            }
                        });
                        this.component.removeEventListener("didDraw", this, false);
                        this._needsToMeasureAnimationTime = false;
                    }
                }
            }
        },

        _finishedDeferred: {
            value: null
        },

        finished: {
            get: function () {
                if (!this._finishedDeferred) {
                    this._finishedDeferred = Promise.pending();
                }
                return this._finishedDeferred.promise;
            }
        },

        play: {
            value: function () {
                if (this.component) {
                    if (!this._finishedDeferred || this._cancelled) {
                        this._cancelled = false;
                        this._finishedDeferred = Promise.pending();
                    }
                    this.component.needsDraw = true;
                    if (this.fromCssClass) {
                        if (this.fromCssClass) {
                            this.component.classList.add(this.fromCssClass);
                        }
                        if (this.cssClass) {
                            this.component.classList.remove(this.cssClass);
                        }
                        if (this.toCssClass) {
                            this.component.classList.remove(this.toCssClass);
                        }
                        this._needsToMeasureAnimationTime = false;
                        this._needsToMeasureAnimationTimeOnNextDraw = true;
                    } else {
                        if (this.hasOneFrameDelay) {
                            this._needsToMeasureAnimationTime = false;
                            this._needsToMeasureAnimationTimeOnNextDraw = true;
                        } else {
                            if (this.cssClass) {
                                this.component.classList.add(this.cssClass);
                            }
                            if (this.toCssClass) {
                                this.component.classList.add(this.toCssClass);
                            }
                            this._needsToMeasureAnimationTime = true;
                            this._needsToMeasureAnimationTimeOnNextDraw = false;
                        }
                    }
                    this.component.addEventListener("didDraw", this, false);
                }
            }
        },

        _cancelled: {
            value: false
        },

        cancel: {
            value: function () {
                this._cancelled = true;
                if (this.fromCssClass) {
                    this.component.classList.remove(this.fromCssClass);
                }
                if (this.cssClass) {
                    this.component.classList.remove(this.cssClass);
                }
                if (this.toCssClass) {
                    this.component.classList.remove(this.toCssClass);
                }
                this.component.removeEventListener("didDraw", this, false);
                if (this._finishedDeferred) {
                    this._finishedDeferred.reject();
                }
            }
        }
    });

    var rootComponent;

    /**
     * @class Component
     * @classdesc Base class for all Montage components.
     * @extends Target
     */
    var Component = exports.Component = Target.specialize(/** @lends Component.prototype */{
        // Virtual Interface

        /**
         * A human-friendly display title for the component.
         * Useful when component is used as a view and a view title is needed, e.g. with a navigation bar.
         * Different than {@link Component.identifier}.
         *
         * @example "User Settings Panel"
         *
         * @name Component#title
         * @property {String}
         */

        /**
         * An identifier label used to refer to the component in code.
         * For example, event handler will call `handleIdentifierAction` which uses this identifier.
         * If not defined, will be the same as serialization object's key.
         *
         * Not to be confused with the human-friendly {@link Component.title}.
         *
         * @example "userSettingsPanel"
         *
         * @name Component#identifier
         * @property {String}
         */

        /**
         * Lifecycle hook for when Component's domContent changes.
         *
         * @name Component#contentWillChange
         * @function
         * @param {Element} value - The incoming element.
         */

        DOM_ARG_ATTRIBUTE: {value: "data-arg"},

        drawListLogger: {
            value: drawListLogger
        },
        /**
         * A delegate is an object that has helper methods specific to particular
         * components.
         * For example, a TextField may consult its `deletate`'s
         * `shouldBeginEditing()` method, or inform its `delegate` that it
         * `didBeginEditing()`.
         * Look for details on the documentation of individual components'
         * `delegate` properties.
         *
         * @type {?Object}
         * @default null
        */
        delegate: {
            value: null
        },

        /**
         * This property is populated by the template. It is a map of all the
         * instances present in the template's serialization keyed by their label.
         *
         * @property {boolean} serializable
         * @default false
         * @property {object} value
         * @default null
         */
         _templateObjects: {
             serializable: false,
             value: null
         },
         templateObjects: {
             serializable: false,
             get: function() {
                 if(!this._templateObjects) {
                     this._templateObjects = Object.create(null);
                 }
                 if(!this._setupTemplateObjectsCompleted && this._templateDocumentPart) {
                      this._setupTemplateObjects(this._templateDocumentPart.objects);
                 }
                 return this._templateObjects;
             },
             set: function(value) {
                 this._templateObjects = value;
             }
         },

         getResources: {
             value: function () {
                 if (!this._setupTemplateObjectsCompleted && this._templateDocumentPart) {
                     return this._templateDocumentPart.template.getResources()._resources;
                 }
             }
         },

        /**
         * @private
         * @property {Target} value
         */
        _nextTarget: {
            value: null
        },

        /**
         * The next Target to consider in the event target chain
         *
         * Currently, components themselves do not allow this chain to be broken;
         * setting a component's nextTarget to a falsy value will cause nextTarget
         * to resolve as the parentComponent.
         *
         * To interrupt the propagation path a Target that accepts a falsy
         * nextTarget needs to be set at a component's nextTarget.
         *
         * @param {Target} value
         * @returns {Target}
         */
        nextTarget: {
            get: function () {
                return this._nextTarget || this.parentComponent;
            },
            set: function (value) {
                this._nextTarget = value;
            }
        },

        _ownerDocumentPart: {
            value: null
        },

        _templateDocumentPart: {
            value: null
        },

        _domArguments: {
            value: void 0
        },

        _domArgumentNames: {
            value: void 0
        },

        /**
         * Dispatch the actionEvent this component is configured to emit upon interaction
         * @private
         */
        _dispatchActionEvent: {
            value: function () {
                this.dispatchEvent(this.createActionEvent());
            },
            enumerable: false
        },

        /**
         * Convenience to create a custom event named "action"
         * @function
         * @returns and event to dispatch upon interaction
         */
        createActionEvent: {
            value: function () {
                var actionEvent = document.createEvent("CustomEvent");
                actionEvent.initCustomEvent("action", true, true, null);
                return actionEvent;
            }
        },

        /**
         * The gate controlling the canDraw() response of the component.
         * @type {Gate}
         * @private
         */
        canDrawGate: {
            get: function () {
                if (!this._canDrawGate) {
                    this._canDrawGate = new Gate().initWithDelegate(this);
                    this._canDrawGate.setField("componentTreeLoaded", false);
                }
                return this._canDrawGate;
            },
            enumerable: false
        },

        _blockDrawGate: {
            value: null
        },

        /**
         * The gate controlling whether the component will ask to draw.
         * @type {Gate}
         * @private
         */
        blockDrawGate: {
            enumerable: false,
            get: function () {
                if (!this._blockDrawGate) {
                    this._blockDrawGate = new Gate().initWithDelegate(this);
                    this._blockDrawGate.setField("element", false);
                    this._blockDrawGate.setField("drawRequested", false);
                }
                return this._blockDrawGate;
            }
        },

        _firstDraw: {
            enumerable: false,
            value: true
        },

        _completedFirstDraw: {
            enumerable: false,
            value: false
        },

        originalElement: {
            value: null
        },

        /**
         * @private
         */
        _element: {
            enumerable: false,
            value: null
        },

        /**
         * The element of the component as defined in the template.
         * ```json
         * {
         *    "component": {
         *        "values": {
         *            "element": {"#": "dataMontageId"}
         *        }
         *    }
         * }
         * ```
         * DOM arguments can be passed to the component as direct children of the
         * element. By default the entire content of the element is considered the
         * single DOM argument of the component.
         * Multiple arguments can be given by assigning a `data-arg` attribute to
         * each element that represents an argument.
         *
         * ```html
         * <div data-montage-id="component">
         *     <h1 data-arg="title"></h1>
         *     <div data-arg="content">
         *         <span data-montage-id="text"></span>
         *     <div>
         * </div>
         * ```
         *
         * If the component has a template then this element is replaced by the
         * element that is referenced in its template just before the component
         * enters the document.
         * ```json
         * {
         *    "owner": {
         *        "values": {
         *            "element": {"#": "dataMontageId"}
         *        }
         *    }
         * }
         * ```
         *
         * The component element has a `component` property that points back to the
         * component. This property is specially useful to extrapolate the component
         * tree from the DOM tree. It can also be used for debugging purposes, on
         * the webkit inspector when an element is selected it's possible to find
         * its component by using the `$0.component` command on the console.
         *
         * The element of a component can only be assigned once, it's not possible
         * to change it.
         *
         * @property {DOMElement}
         * @default null
         */
        element: {
            get: function () {
                return this._element;
            },
            set: function (value) {
                if (value === null || value === undefined) {
                    console.warn("Tried to set element of ", this, " to ", value);
                    return;
                }

                if (value.component && value.component !== this) {
                    throw new Error("Element " + value + " is already assigned to another component: " + value.component);
                }

                //jshint -W106
                if (global._montage_le_flag) {
                //jshint +W106
                    value.setAttribute(ATTR_LE_COMPONENT, Montage.getInfoForObject(this).moduleId);
                }

                if (this.isDeserializing) {
                    this.eventManager.registerEventHandlerForElement(this, value);

                    // if this component has a template and has been already instantiated then assume the value is the template.
                    if (this._isTemplateInstantiated) {
                        // this is important for component extension, we don't want to override template element
                        if (!this._templateElement) {
                            this._templateElement = value;
                        }
                    } else {
                        this._element = value;
                        if (!this.blockDrawGate.value && this._element) {
                            this.blockDrawGate.setField("element", true);
                        }
                    }
                } else if (!this._firstDraw) {
                    // If a draw has happened then at some point the element has been set
                    console.error("Cannot change element of ", this, " after it has been set");
                    return;
                } else {
                    this.eventManager.registerEventHandlerForElement(this, value);

                    this._element = value;
                    if (!this.blockDrawGate.value && this._element) {
                        this.blockDrawGate.setField("element", true);
                    }
                }
                this._initializeClassListFromElement(value);
            }
        },

        getElementId: {
            value: function () {
                var element = this._element;

                if (element) {
                    return element.getAttribute("data-montage-id");
                }
            }
        },

        _initDomArguments: {
            value: function () {
                var candidates,
                    domArguments,
                    name,
                    node,
                    element = this.element;

                candidates = element.querySelectorAll("*[" + this.DOM_ARG_ATTRIBUTE + "]");

                // Need to make sure that we filter dom args that are for nested
                // components and not for this component.
                if(candidates.length) {
                    domArguments = {};
                }
                nextCandidate:
                for (var i = 0, candidate; (candidate = candidates[i]); i++) {
                    node = candidate;
                    while ((node = node.parentNode) !== element) {
                        // This candidate is inside another component so skip it.
                        if (node.component) {
                            continue nextCandidate;
                        }
                    }
                    this._findAndDetachComponents(candidate);
                    candidate.parentNode.removeChild(candidate);
                    name = candidate.getAttribute(this.DOM_ARG_ATTRIBUTE);
                    candidate.removeAttribute(this.DOM_ARG_ATTRIBUTE);
                    domArguments[name] = candidate;
                }

                this._domArguments = domArguments;
            }
        },
        _sharedEmptyArray: {
            value: []
        },
        getDomArgumentNames: {
            value: function () {
                if (this._domArgumentNames === void 0) {
                    this._domArgumentNames = this._domArguments ? Object.keys(this._domArguments) : this._sharedEmptyArray;
                }
                return this._domArgumentNames;
            }
        },

        /**
         * This function extracts a DOM argument that was in the element assigned
         * to the component.
         * The star (`*`) argument refers to the entire content of the element when
         * no `data-arg` was given.
         *
         * When a DOM argument is extracted from a Component it is no longer
         * available
         *
         * @function
         * @param {string} name The name of the argument, or `"*"` for the entire
         * content.
         * @returns the element
         */
        extractDomArgument: {
            value: function (name) {
                if(this._domArguments) {
                    var argument;

                    argument = this._domArguments[name];
                    this._domArguments[name] = null;

                    return argument;
                }
                return null;
            }
        },

        /**
         * This function is used to get a Dom Argument out of the origin template
         * (_ownerDocumentPart) of this component.
         * It is not meant to be used with a live DOM, its main purpose it to help
         * the TemplateArgumentProvider implementation.
         *
         * @private
         */
        _getTemplateDomArgument: {
            value: function (name) {
                var candidates,
                    node,
                    element,
                    elementId,
                    serialization,
                    labels,
                    template = this._ownerDocumentPart.template;

                element = template.getElementById(this.getElementId());
                candidates = element.querySelectorAll("*[" + this.DOM_ARG_ATTRIBUTE + "='" + name + "']");

                // Make sure that the argument we find is indeed part of element and
                // not an argument from an inner component.
                nextCandidate:
                for (var i = 0, candidate; (candidate = candidates[i]); i++) {
                    node = candidate;
                    while ((node = node.parentNode) !== element) {
                        elementId = template.getElementId(node);

                        // Check if this node is an element of a component.
                        // TODO: Make this operation faster
                        if (elementId) {
                            serialization = template.getSerialization();
                            labels = serialization.getSerializationLabelsWithElements(
                                elementId);

                            if (labels.length > 0) {
                                // This candidate is inside another component so
                                // skip it.
                                continue nextCandidate;
                            }
                        }
                    }
                    return candidate;
                }
            }
        },

        /**
         * TemplateArgumentProvider implementation
         */

        getTemplateArgumentElement: {
            value: function (argumentName) {
                var ownerModuleId, element, range, argument, label,
                    template = this._ownerDocumentPart.template;

                if (global._montage_le_flag) {
                    ownerModuleId = this.ownerComponent._montage_metadata.moduleId;
                    label = this._montage_metadata.label;
                }

                if (argumentName === "*") {
                    element = template.getElementById(this.getElementId());

                    range = template.document.createRange();
                    range.selectNodeContents(element);
                    argument = range.cloneContents();
                    if (global._montage_le_flag && element.children.length > 0) {
                        this._leTagStarArgument(ownerModuleId, label, argument);
                    }
                } else {
                    argument = this._getTemplateDomArgument(argumentName).cloneNode(true);
                    argument.removeAttribute(this.DOM_ARG_ATTRIBUTE);
                    if (global._montage_le_flag) {
                        this._leTagNamedArgument(ownerModuleId, label, argument,
                            argumentName);
                    }
                }

                return argument;
            }
        },

        getTemplateArgumentSerialization: {
            value: function (elementIds) {
                var template = this._ownerDocumentPart.template;

                return template._createSerializationWithElementIds(elementIds);
            }
        },

        /**
         * @param {string} templatePropertyName "<componentLabel>:<propertyName>"
         * @private
         */
        resolveTemplateArgumentTemplateProperty: {
            value: function (templatePropertyName) {
                var ix = templatePropertyName.indexOf(":"),
                    componentLabel = templatePropertyName.slice(0, ix),
                    propertyName = templatePropertyName.slice(ix),
                    documentPart = this._templateDocumentPart,
                    aliasTemplatePropertyName,
                    aliasComponent,
                    alias,
                    result;

                // Check if the template property is referring to this object at all.
                if (Montage.getInfoForObject(this).label !== componentLabel) {
                    return;
                }

                if (documentPart) {
                    alias = documentPart.objects[propertyName];
                }

                if (alias instanceof Alias) {
                    aliasComponent = documentPart.objects[alias.componentLabel];
                    // Strip the @ prefix
                    aliasTemplatePropertyName = alias.value.slice(1);
                    result = aliasComponent.resolveTemplateArgumentTemplateProperty(aliasTemplatePropertyName);
                    if (!result) {
                        result = aliasTemplatePropertyName;
                    }
                }

                return result;
            }
        },

        setElementWithParentComponent: {
            value: function (element, parent) {
                this._alternateParentComponent = parent;
                if (this.element !== element) {
                    this.element = element;
                }
            }
        },
        _application: {
            value: null
        },
        // access to the Application object
        /**
         * Convenience to access the application object.
         * @type {Application}
        */
        application: {
            enumerable: false,
            get: function () {
                return this._application || (Component.prototype._application = require("../core/application").application);
            }
        },

        /**
         * Convenience to access the defaultEventManager object.
         * @type {EventManager}
         */
        eventManager: {
            enumerable: false,
            get: function () {
                return defaultEventManager;
            }
        },

        /**
         * Convenience to access the rootComponent object.
         * @type {RootComponent}
         */
        rootComponent: {
            enumerable: false,
            get: function () {
                return exports.__root__;
            }
        },

        /**
         * @function
         * @returns targetElementController
         * @private
         */
        elementControllerFromEvent: {
            enumerable: false,
            value: function (event, targetElementController) {
                return targetElementController;
            }
        },

        _alternateParentComponent: {
            value: null
        },

        /**
         * @private
         */
        __parentComponent: {
            value: null
        },

        _parentComponent: {
            set: function (value) {
                this.__parentComponent = value;
                this.dispatchOwnPropertyChange("parentComponent", value);
            },
            get: function () {
                return this.__parentComponent;
            }
        },

        /**
         * The parent component is the component that is found by walking up the
         * DOM tree, starting at the component's `element`. Each component element
         * has a `component` property that points back to the component object, this
         * way it's possible to know which component an element represents.
         *
         * This value is null for the {@link RootComponent}.
         *
         * @type {Component}
         */
        parentComponent: {
            enumerable: false,
            get: function () {
                return this._parentComponent;
            }
        },

        findParentComponent: {
            value: function () {
                var anElement = this.element,
                    aParentNode,
                    eventManager = this.eventManager;
                if (anElement) {
                    while ((aParentNode = anElement.parentNode) && !eventManager.eventHandlerForElement(aParentNode)) {
                        anElement = aParentNode;
                    }
                    return aParentNode ? eventManager.eventHandlerForElement(aParentNode) : this._alternateParentComponent;
                }
            }
        },

        querySelectorComponent: {
            value: function (selector) {
                if (typeof selector !== "string") {
                    throw "querySelectorComponent: Selector needs to be a string.";
                }

                // \s*(?:@([^>\s]+)) leftHandOperand [<label>]
                // \s*(>)?\s* operator [>] (if undefined it's a space)
                // @([^>\s]+) rightHandOperand [<label>]
                // (.*) rest
                var matches = selector.match(/^\s*(?:@([^>\s]+))?(?:\s*(>)?\s*@([^>\s]+)(.*))?$/);
                if (!matches) {
                    throw "querySelectorComponent: Syntax error \"" + selector + "\"";
                }

                var childComponents = this.childComponents,
                    leftHandOperand = matches[1],
                    operator = matches[2] || " ",
                    rightHandOperand = matches[3],
                    rest = matches[4],
                    found,
                    i,
                    childComponent;

                if (leftHandOperand) {
                    rest = rightHandOperand ? "@"+rightHandOperand + rest : "";

                    for (i = 0, childComponent; (childComponent = childComponents[i]); i++) {
                        if (leftHandOperand === Montage.getInfoForObject(childComponent).label) {
                            if (rest) {
                                return childComponent.querySelectorComponent(rest);
                            } else {
                                return childComponent;
                            }
                        } else {
                            found = childComponent.querySelectorComponent(selector);
                            if (found) {
                                return found;
                            }
                        }
                    }
                } else {
                    for (i = 0, childComponent; (childComponent = childComponents[i]); i++) {
                        if (rightHandOperand === Montage.getInfoForObject(childComponent).label) {
                            if (rest) {
                                return childComponent.querySelectorComponent(rest);
                            } else {
                                return childComponent;
                            }
                        }
                    }
                }

                return null;
            }
        },

        querySelectorAllComponent: {
            value: function (selector, owner) {
                if (typeof selector !== "string") {
                    throw "querySelectorComponent: Selector needs to be a string.";
                }

                // (@([^>\s]+)? leftHandOperand [<label>]
                // \s*(>)?\s* operator [>] (if undefined it's a space)
                // @([^>\s]+) rightHandOperand [<label>]
                // (.*) rest
                var matches = selector.match(/^\s*(?:@([^>\s]+))?(?:\s*(>)?\s*@([^>\s]+)(.*))?$/);
                if (!matches) {
                    throw "querySelectorComponent: Syntax error \"" + selector + "\"";
                }

                var childComponents = this.childComponents,
                    leftHandOperand = matches[1],
                    operator = matches[2] || " ",
                    rightHandOperand = matches[3],
                    rest = matches[4],
                    found = [],
                    i,
                    childComponent;

                if (leftHandOperand) {
                    rest = rightHandOperand ? "@"+rightHandOperand + rest : "";
                    for (i = 0, childComponent; (childComponent = childComponents[i]); i++) {
                        if (leftHandOperand === Montage.getInfoForObject(childComponent).label && (!owner || owner === childComponent.ownerComponent)) {
                            if (rest) {
                                found = found.concat(childComponent.querySelectorAllComponent(rest));
                            } else {
                                found.push(childComponent);
                            }
                        } else {
                            found = found.concat(childComponent.querySelectorAllComponent(selector, owner));
                        }
                    }
                } else {
                    for (i = 0, childComponent; (childComponent = childComponents[i]); i++) {
                        if (rightHandOperand === Montage.getInfoForObject(childComponent).label && (!owner || owner === childComponent.ownerComponent)) {
                            if (rest) {
                                found = found.concat(childComponent.querySelectorAllComponent(rest, owner));
                            } else {
                                found.push(childComponent);
                            }
                        }
                    }
                }

                return found;
            }
        },

        /**
         * The template object of the component.
         *
         * @type {Template}
         * @default null
         */
        template: {
            enumerable: false,
            value: null
        },

        /**
         * Specifies whether the component has an HTML template file associated with
         * it.
         * @type {boolean}
         * @default true
         */
        hasTemplate: {
            enumerable: false,
            value: true
        },

        /**
         * @private
         * @type {string}
         */
        _templateModuleId: {
            serializable: false,
            value: null
        },

        _template: {
            value: null
        },

        // Tree level necessary for ordering drawing re: parent-child
        _treeLevel: {
            value: 0
        },

        /**
         * @private
         * @deprecated
         * @function
         * @param {Component} childComponent
         */
        // TODO update all calls to use addChildComponent and remove this method.
        _addChildComponent: {
            value: function (childComponent) {
                return this.addChildComponent(childComponent);
            }
        },

        addChildComponent: {
            value: function (childComponent) {
                if (this.childComponents.indexOf(childComponent) === -1) {
                    this.childComponents.push(childComponent);
                    childComponent._parentComponent = this;
                    childComponent._prepareForEnterDocument();
                    if (childComponent.needsDraw &&
                        !this.rootComponent.isComponentWaitingNeedsDraw(childComponent)) {
                        childComponent._addToParentsDrawList();
                    }
                }
                childComponent._shouldBuildIn = true;
            }
        },

        attachToParentComponent: {
            value: function () {
                this.detachFromParentComponent();
                this._parentComponent = null;

                var parentComponent = this.findParentComponent(),
                    childComponents,
                    childComponent;

                if (parentComponent) {
                    // This component's children may have been attached to the
                    // parent before we were initialized, and so we now need to
                    // check if any of our parent's children should actually be
                    // ours.
                    childComponents = parentComponent.childComponents;
                    for (var i = 0; (childComponent = childComponents[i]); i++) {
                        var newParentComponent = childComponent.findParentComponent();
                        if (newParentComponent === this) {
                            parentComponent.removeChildComponent(childComponent);
                            newParentComponent.addChildComponent(childComponent);
                        }
                    }
                    parentComponent.addChildComponent(this);
                }
            }
        },

        detachFromParentComponent: {
            value: function () {
                var parentComponent = this.parentComponent;

                if (parentComponent) {
                    parentComponent.removeChildComponent(this);
                }
            }
        },

        removeChildComponent: {
            value: function (childComponent) {
                var childComponents = this.childComponents,
                    ix = childComponents.indexOf(childComponent);

                if (ix > -1) {

                    childComponent._exitDocument();

                    childComponents.splice(ix, 1);
                    childComponent._parentComponent = null;
                    childComponent._alternateParentComponent = null;

                    if (childComponent._addedToDrawList) {
                        childComponent._addedToDrawList = false;
                        ix = this._drawList.indexOf(childComponent);
                        this._drawList.splice(ix, 1);
                    }
                    this.rootComponent.removeFromCannotDrawList(childComponent);
                }
            }
        },
        _childComponents: {
            value: null
        },
        /**
         * The child components of the component.
         * This property is readonly and should never be changed.
         *
         * @type {Array.<Component>}
         * @readonly
        */
        childComponents: {
            enumerable: false,
            get: function() {
              return this._childComponents || (this._childComponents = []);
            }
        },

        _needsEnterDocument: {
            value: false
        },

        _inDocument: {
            value: false
        },

        __exitDocument: {
            value: function () {
                if (this._inDocument && typeof this.exitDocument === "function") {
                    this.exitDocument();
                    this._inDocument = false;
                }
            }
        },

        _exitDocument: {
            value: function () {
                var traverse;

                if (this._needsEnterDocument) {
                    this._needsEnterDocument = false;
                } else {
                    traverse = function (component) {
                        var childComponents = component.childComponents,
                            childComponent;

                        for (var i = 0; (childComponent = childComponents[i]); i++) {
                            if (childComponent._isComponentExpanded) {
                                traverse(childComponent);
                            }
                        }

                        if (component._inDocument) {
                            component.__exitDocument();
                        }
                    };

                    traverse(this);
                }
            }
        },

        /**
         * Called when this component is removed from the document's DOM tree.
         * @function
         */
        exitDocument: {
            value: function () {
                if (this.isActiveTarget) {
                    defaultEventManager.activeTarget = this.nextTarget;
                }
            }
        },

        _prepareForEnterDocument: {
            value: function () {
                // On their first draw components will have their needsDraw = true
                // when they loadComponentTree.
                if (this._firstDraw) {
                    this._needsEnterDocument = true;
                } else {
                    this.needsDraw = true;
                    this.traverseComponentTree(function (component) {
                        if (component._needsEnterDocument) {
                            return false;
                        }
                        component._needsEnterDocument = true;
                        component.needsDraw = true;
                    });
                }
            }
        },

        /**
         * The owner component is the owner of the template form which this
         * component was instantiated.
         * @type {Component}
         * @default null
         */
        ownerComponent: {
            enumerable: false,
            value: null
        },

        /**
         * Unused?
         * @private
         */
        components: {
            enumerable: false,
            value: {}
        },

        _isComponentExpanded: {
            enumerable: false,
            value: false
        },

        _isTemplateLoaded: {
            enumerable: false,
            value: false
        },

        _isTemplateInstantiated: {
            enumerable: false,
            value: false
        },

        /**
         * Remove all bindings and starts buffering the needsDraw.
         * @function
         * @private
         */
        cleanupDeletedComponentTree: {
            value: function (cancelBindings) {
                // Deleting bindings in all cases was causing the symptoms expressed in gh-603
                // Until we have a more granular way we shouldn't do this,
                // the cancelBindings parameter is a short term fix.
                if (cancelBindings) {
                    this.cancelBindings();
                }
                this.needsDraw = false;
                this.traverseComponentTree(function (component) {
                    // See above comment
                    if (cancelBindings) {
                        component.cancelBindings();
                    }
                    component.needsDraw = false;
                });
            }
        },

        _newDomContent: {
            enumerable: false,
            value: null
        },

        _elementsToAppend: {
            value: null
        },

        domContent: {
            serializable: false,
            get: function () {
                if (this._element) {
                    return Array.prototype.slice.call(this._element.childNodes, 0);
                } else {
                    return null;
                }
            },
            set: function (value) {
                var components,
                    componentsToAdd = [],
                    i,
                    component;

                if (!this._elementsToAppend) {
                    this._elementsToAppend = [];
                }
                this._newDomContent = value;
                this.needsDraw = true;

                if (this._newDomContent === null) {
                    this._shouldClearDomContentOnNextDraw = true;
                }

                if (typeof this.contentWillChange === "function") {
                    this.contentWillChange(value);
                }

                // cleanup current content
                components = this.childComponents;
                if (value) {
                    if (!this._componentsPendingBuildOut) {
                        this._componentsPendingBuildOut = [];
                    }
                    for (i = components.length - 1; i >= 0; i--) {
                        if (this._componentsPendingBuildOut.indexOf(components[i]) === -1) {
                            this._componentsPendingBuildOut.push(components[i]);
                        }
                    }
                } else {
                    this._componentsPendingBuildOut = [];
                    for (i = components.length - 1; i >= 0; i--) {
                        components[i]._shouldBuildOut = true;
                    }
                }
                if (value instanceof Element) {
                    this._elementsToAppend.push(value);
                    this._findAndDetachComponents(value, componentsToAdd);
                } else if (value && value[0]) {
                    for (i = 0; i < value.length; i++) {
                        this._elementsToAppend.push(value[i]);
                        this._findAndDetachComponents(value[i], componentsToAdd);
                    }
                }

                // not sure if I can rely on _parentComponent to detach the nodes instead of doing one loop for dettach and another to attach...
                for (i = 0; (component = componentsToAdd[i]); i++) {
                    this.addChildComponent(component);
                }
            }
        },

        _shouldClearDomContentOnNextDraw: {
            value: false
        },

        _findAndDetachComponents: {
            value: function (node, components) {
                // TODO: Check if searching the childComponents of the parent
                //       component can make the search faster..
                var component = node.component,
                    children;

                if (!components) {
                    components = [];
                }

                if (component) {
                    component.detachFromParentComponent();
                    components.push(component);
                } else {
                    // DocumentFragments don't have children so we default to
                    // childNodes.
                    children = node.children || node.childNodes;
                    for (var i = 0, child; (child = children[i]); i++) {
                        this._findAndDetachComponents(child, components);
                    }
                }

                return components;
            }
        },

        // Some components, like the repetition, might use their initial set of
        // child components as a template to clone them and instantiate them as the
        // real/effective child components.
        //
        // When this happens the original child components are in a way pointless
        // to the application and should not be used.
        //
        // If other objects get a reference to these child components in the
        // template serialization the way to know that they are going to be
        // cloned is by checking if one of their parent components has
        // its clonesChildComponents set to true.
        clonesChildComponents: {
            writable: false,
            value: false
        },

        _innerTemplate: {value: null},

        innerTemplate: {
            serializable: false,
            get: function () {
                var innerTemplate = this._innerTemplate,
                    ownerDocumentPart,
                    ownerTemplate,
                    elementId,
                    serialization,
                    externalObjectLabels,
                    ownerTemplateObjects,
                    externalObjects;

                if (!innerTemplate) {
                    ownerDocumentPart = this._ownerDocumentPart;

                    if (ownerDocumentPart) {
                        ownerTemplate = ownerDocumentPart.template;

                        elementId = this.getElementId();
                        innerTemplate = ownerTemplate.createTemplateFromElementContents(elementId);

                        serialization = innerTemplate.getSerialization();
                        externalObjectLabels = serialization.getExternalObjectLabels();
                        ownerTemplateObjects = ownerDocumentPart.objects;
                        externalObjects = Object.create(null);

                        for (var i = 0, label; (label = externalObjectLabels[i]); i++) {
                            externalObjects[label] = ownerTemplateObjects[label];
                        }
                        innerTemplate.setInstances(externalObjects);

                        this._innerTemplate = innerTemplate;
                    }
                }

                return innerTemplate;
            },
            set: function (value) {
                this._innerTemplate = value;
            }
        },

        /**
         * This method is called right before draw is called.
         * If ```canDraw()``` returns false, then the component is re-added to
         * the parent's draw list and draw isn't called.
         * This method is evil
         *
         * @function
         * @returns {boolean}
         * @private
         */
        canDraw: {
            value: function () {
                return this._canDraw;
            }
        },

        _canDraw: {
            get: function () {
                return (!this._canDrawGate || this._canDrawGate.value);
            },
            set: function (value) {
                rootComponent.componentCanDraw(this, value);
            },
            enumerable: false
        },

        _prepareCanDraw: {
            enumerable: false,
            value: function _prepareCanDraw() {
                if (!this._isComponentTreeLoaded) {
                    return this.loadComponentTree();
                }
            }
        },

        _blocksOwnerComponentDraw: {
            value: false
        },

        _updateOwnerCanDrawGate: {
            value: function () {
                if (this._blocksOwnerComponentDraw && this.ownerComponent) {
                    this.ownerComponent.canDrawGate.setField(this, this.canDrawGate.value);
                }
            }
        },

        _isComponentTreeLoaded: {
            value: false
        },

        shouldLoadComponentTree: {
            value: true
        },

        _loadComponentTreeDeferred: {value: null},
        loadComponentTree: {
            value: function loadComponentTree() {

                if (!this._loadComponentTreeDeferred) {

                    this.canDrawGate.setField("componentTreeLoaded", false);

                    // only put it in the root component's draw list if the
                    // component has requested to be draw, it's possible to load the
                    // component tree without asking for a draw.
                    // What about the hasTemplate check?
                    if (this.needsDraw || this.hasTemplate) {
                        this._canDraw = false;
                    }

                    var self = this;

                    this._loadComponentTreeDeferred = this.expandComponent()
                        .then(function() {
                            if (self.hasTemplate || self.shouldLoadComponentTree) {
                                var promises,
                                    childComponents = self._childComponents,
                                    childComponent;
                                if (childComponents && childComponents.length) {
                                    promises = [];
                                    for (var i = 0; (childComponent = childComponents[i]); i++) {
                                        promises.push(childComponent.loadComponentTree());
                                    }

                                    return Promise.all(promises);
                                }
                                return Promise.resolve(null);
                            }
                        })
                        .then(function() {
                            self._isComponentTreeLoaded = true;
                            // When the component tree is loaded we need to draw if the
                            // component needs to have its enterDocument() called.
                            // This is because we explicitly avoid drawing when we set
                            // _needsEnterDocument before the first draw because we
                            // don't want to trigger the draw before its component tree
                            // is loaded.
                            if (self._needsEnterDocument) {
                                self.needsDraw = true;
                            }
                            self.canDrawGate.setField("componentTreeLoaded", true);

                        }).catch(function (error) {
                            console.error(error);
                        });
                }
                return this._loadComponentTreeDeferred;
            }
        },

        /**
         *  Whenever traverseComponentTree reaches the end of a subtree Component#expandComponent~callback is called.
         * @function
         * @param {Component#traverseComponentTree~visitor} visitor  visitor
         * @param {Component#traverseComponentTree~callback} callback callback object
         * @private
         */
        traverseComponentTree: {value: function traverseComponentTree(visitor, callback) {
            var self = this;

            function traverse() {
                var childComponents = self.childComponents;
                var childComponent;
                var childLeftCount;

                if (visitor) {
                    // if the visitor returns false stop the traversal for this subtree
                    if (visitor(self) === false) {
                        if (callback) {
                            callback();
                        }
                        return;
                    }
                }

                if ((childLeftCount = childComponents.length) === 0) {
                    if (callback) {
                        callback();
                    }
                    return;
                }

                var visitorFunction = function () {
                    if (--childLeftCount === 0 && callback) {
                        callback();
                    }
                };
                for (var i = 0; (childComponent = childComponents[i]); i++) {
                    childComponent.traverseComponentTree(visitor, visitorFunction);
                }
            }

            if (this._isComponentExpanded) {
                traverse();
            } else if (callback) {
                callback();
            }
        }},
        /**
         * Visitor function for Component#traverseComponentTree. For every component in the tree, the visitor function is
         * called with the current component as an argument.
         * If the function returns false then the traversal is stopped for that subtree.
         * @function Component#traverseComponentTree~visitor
         * @param Component visitedComponent
         */
        /**
         * @function Component#traverseComponentTree~callback
         */

        /**
         * @function
         * @param {Component#expandComponent~callback} callback  TODO
         * @private
         */
        _expandComponentPromise: {value: null},
        expandComponent: {
            value: function expandComponent() {

                if (!this._expandComponentPromise) {
                        if (this.hasTemplate) {
                            var self = this;
                            this._expandComponentPromise = this._instantiateTemplate().then(function() {
                                self._isComponentExpanded = true;
                                self._addTemplateStylesIfNeeded();
                                self.needsDraw = true;
                            }).catch(function (error) {
                                console.error(error);
                            });
                        } else {
                            this._isComponentExpanded = true;
                            this._expandComponentPromise = Promise.resolve();
                        }
                }

                return this._expandComponentPromise;
            }
        },

        _templateObjectDescriptor: {
            value: {
                enumerable: true,
                configurable: true
            }
        },

        _setupTemplateObjects: {
            value: function (objects) {
                this._templateObjects = this._templateObjects || Object.create(null);
                this._addTemplateObjects(objects);
                this._setupTemplateObjectsCompleted = true;
                return this._templateObjects;
            }
        },
        _setupTemplateObjectsCompleted: {
            value: false
        },
        _addTemplateObjects: {
            value: function (objects) {
                var label, object,
                    descriptor = this._templateObjectDescriptor,
                    templateObjects = this._templateObjects;

                /*jshint forin:true */
                // TODO add hasOwnProperty to objects
                for (label in objects) {
                /*jshint forin:false */
                    object = objects[label];
                    if (object !== null && object !== undefined) {
                        if (!Component.prototype.isPrototypeOf(object) || object === this ||
                            object.parentComponent === this) {
                            templateObjects[label] = object;
                        } else {
                            descriptor.get = this._makeTemplateObjectGetter(this, label, object);
                            Object.defineProperty(templateObjects, label, descriptor);
                        }
                    }
                }
            }
        },

        /**
         * @private
         */
        _makeTemplateObjectGetter: {
            value: function (owner, label, object) {
                var querySelectorLabel = "@"+label,
                    isRepeated,
                    components,
                    component;

                return function templateObjectGetter() {
                    if (isRepeated) {
                        return owner.querySelectorAllComponent(querySelectorLabel, owner);
                    } else {
                        components = owner.querySelectorAllComponent(querySelectorLabel, owner);
                        // if there's only one maybe it's not repeated, let's go up
                        // the tree and found out.
                        if (components.length === 1) {
                            component = components[0];
                            while ((component = component.parentComponent)) {
                                if (component === owner) {
                                    // we got to the owner without ever hitting a component
                                    // that repeats its child components, we can
                                    // safely recreate this property with a static value
                                    Object.defineProperty(this, label, {
                                        value: components[0]
                                    });
                                    return components[0];
                                } else if (component.clonesChildComponents) {
                                    break;
                                }
                            }
                        } else if (components.length === 0) {
                            // We didn't find any in the component tree
                            // so it was probably removed in the meanwhile.
                            // We return the one that was in the template
                            // TODO: need to make sure this component hasn't been disposed.
                            return object;
                        }

                        isRepeated = true;
                        return components;
                    }
                };
            }
        },

        _instantiateTemplate: {
            value: function() {
                var self = this;
                return this._loadTemplate().then(function(template) {
                    if (!self._element) {
                        return Promise.reject(new Error("Cannot instantiate template without an element.", self));
                    }

                    var instances = null,
                        _document = self._element.ownerDocument;

                    if (!instances) {
                        instances = Object.create(null);
                    }

                    instances.owner = self;
                    self._isTemplateInstantiated = true;

                    return template.instantiateWithInstances(instances, _document).then(function (documentPart) {
                        documentPart.parentDocumentPart = self._ownerDocumentPart;
                        self._templateDocumentPart = documentPart;
                        documentPart.fragment = null;
                        instances = null;

                    }, function (error) {
                        throw new Error(template.getBaseUrl() + ":" + error.stack || error);
                    });
                });
            }
        },

        _templateDidLoad: {
            value: function (documentPart) {
                //If templateObjects was used in serialization's bindings, this._templateObjects will be created empty in the getter. We use this a signal that it needs to
                //be setup
                //This is call as a delegate by the template before returning the document part from instantiateWithInstances(). Objects in their own templateDidLoad() can
                //call templateObjects, so this._templateDocumentPart is needed here.
                //This is just set, again, later to the same value in the then() of template.instantiateWithInstances() inside _instantiateTemplate()
                this._templateDocumentPart = documentPart;
                if(this._templateObjects) {
                    this._setupTemplateObjects(documentPart.objects);
                }
            }
        },

        _loadTemplatePromise: {value: null},
        _loadTemplate: {
            value: function _loadTemplate() {
                var info;

                if (!this._loadTemplatePromise) {
                    var self = this;
                    info = Montage.getInfoForObject(this);

                    this._loadTemplatePromise = Template.getTemplateWithModuleId(
                        this.templateModuleId, info.require)
                    .then(function(template) {
                        self._template = template;
                        self._isTemplateLoaded = true;

                        return template;
                    });
                }

                return this._loadTemplatePromise;
            }
        },

        /**
         * @private
         * @type {string}
         * @default
         */
        templateModuleId: {
            get: function () {
                return this._templateModuleId || this._getDefaultTemplateModuleId();
            }
        },

        _getDefaultTemplateModuleId: {
            value: function () {
                var templateModuleId,
                    slashIndex,
                    moduleId,
                    info;

                info = Montage.getInfoForObject(this);
                moduleId = info.moduleId;
                slashIndex = moduleId.lastIndexOf("/");
                templateModuleId = moduleId + "/" + moduleId.slice(slashIndex === -1 ? 0 : slashIndex+1, -5 /* ".reel".length */) + ".html";

                return templateModuleId;
            }
        },

        deserializedFromSerialization: {
            value: function () {
                this.attachToParentComponent();
            }
        },

        _deserializedFromTemplate: {
            value: function (owner, label, documentPart) {
                Montage.getInfoForObject(this).label = label;
                this._ownerDocumentPart = documentPart;

                if (! this.hasOwnProperty("identifier")) {
                    this.identifier = label;
                }

                if (!this.ownerComponent) {
                    if (Component.prototype.isPrototypeOf(owner)) {
                        this.ownerComponent = owner;
                    } else {
                        this.ownerComponent = this.rootComponent;
                    }
                    this._updateOwnerCanDrawGate();
                }

                if (this._needsDrawInDeserialization) {
                    this.needsDraw = true;
                }
            }
        },

        blueprintModuleId: {
            serializable: false,
            enumerable: false,
            get: function () {
                return this.objectDescriptorModuleId;
            }
        },

        blueprint: require("../core/core")._objectDescriptorDescriptor,
        objectDescriptor: require("../core/core")._objectDescriptorDescriptor,

        objectDescriptorModuleId: {
            serializable: false,
            enumerable: false,
            get: function () {
                var info = Montage.getInfoForObject(this);
                var self = (info && !info.isInstance) ? this : Object.getPrototypeOf(this);
                if (!Object.getOwnPropertyDescriptor(self, "_objectDescriptorModuleId") || !self._objectDescriptorModuleId) {
                    info = Montage.getInfoForObject(self);
                    var moduleId = info.moduleId,
                        slashIndex = moduleId.lastIndexOf("/"),
                        dotIndex = moduleId.lastIndexOf(".");
                    slashIndex = ( slashIndex === -1 ? 0 : slashIndex + 1 );
                    dotIndex = ( dotIndex === -1 ? moduleId.length : dotIndex );
                    dotIndex = ( dotIndex < slashIndex ? moduleId.length : dotIndex );

                    var objectDescriptorModuleId;
                    if ((dotIndex < moduleId.length) && ( moduleId.slice(dotIndex, moduleId.length) === ".reel")) {
                        // We are in a reel
                        objectDescriptorModuleId = moduleId + "/" + moduleId.slice(slashIndex, dotIndex) + ".meta";
                    } else {
                        // We look for the default
                        objectDescriptorModuleId = moduleId.slice(0, dotIndex) + ".meta";
                    }

                    Montage.defineProperty(self, "_objectDescriptorModuleId", {
                        value: objectDescriptorModuleId
                    });
                }
                return self._objectDescriptorModuleId;
            }
        },

        /**
         * Callback for the ```canDrawGate```.
         * Propagates to the parent and adds the component to the draw list.
         * @function
         * @param {Gate} gate
         * @see Component#canDrawGate
         * @private
         */
        gateDidBecomeTrue: {
            value: function (gate) {
                if (gate === this._canDrawGate) {
                    this._canDraw = true;
                    this._updateOwnerCanDrawGate();
                } else if (gate === this._blockDrawGate) {
                    rootComponent.componentBlockDraw(this);
                    this._prepareCanDraw();
                }
            },
            enumerable: false
        },

        gateDidBecomeFalse: {
            value: function (gate) {
                if (gate === this._canDrawGate) {
                    this._updateOwnerCanDrawGate();
                }
            },
            enumerable: false
        },

        /**
         * Gate that controls the _canDraw property. When it becomes true it sets
         * _canDraw to true.
         * @function
         * @returns Gate
         * @private
         */
        _canDrawGate: {
            enumerable: false,
            value: null
        },

        preparedForActivationEvents: {
            enumerable: false,
            value: false
        },

        _arrayObjectPool: {
            value: {
                pool: null,
                size: 200,
                ix: 0
            }
        },

        _getArray: {
            value: function () {
                if (!this._arrayObjectPool.pool) {
                    this._arrayObjectPool.pool = [];
                    for (var i = 0; i < this._arrayObjectPool.size; i++) {
                        this._arrayObjectPool.pool[i] = [];
                    }
                }

                if (this._arrayObjectPool.ix < this._arrayObjectPool.size) {
                    return this._arrayObjectPool.pool[this._arrayObjectPool.ix++];
                } else {
                    return [];
                }
            }
        },

        _disposeArray: {
            value: function (array) {
                if (this._arrayObjectPool.ix > 0) {
                    array.length = 0;
                    this._arrayObjectPool.pool[--this._arrayObjectPool.ix] = array;
                }
            }
        },

        /**
         * If needsDraw property returns true this call adds the current component
         * instance to the rootComponents draw list.
         * Then it iterates on every child component in the component's drawList.
         * On everyone of them it calls ```canDraw()```.
         * If the result is true, ```_drawIfNeeded()``` is called, otherwise they
         * are ignored.
         * @private
         */
        _drawIfNeeded: {
            enumerable: false,
            value: function _drawIfNeeded(level) {
                var childComponent,
                    oldDrawList, i, childComponentListLength,
                    firstDraw = this._firstDraw;

                this._treeLevel = level;
                if (firstDraw) {
                    this.originalElement = this.element;
                }
                if (this.needsDraw) {
                    this.rootComponent.addToDrawCycle(this);
                }

                if (this._needsEnterDocument) {
                    this._needsEnterDocument = false;
                    this._willEnterDocument();
                    this._enterDocument(firstDraw);
                    if (typeof this.enterDocument === "function") {
                        this.enterDocument(firstDraw);
                    }
                }
                if (firstDraw) {
                    this.originalElement = null;
                }

                if (this._drawList !== null && this._drawList.length > 0) {
                    oldDrawList = this._drawList;
                    this._drawList = this._getArray();
                    childComponentListLength = oldDrawList.length;
                    for (i = 0; i < childComponentListLength; i++) {
                        childComponent = oldDrawList[i];
                        childComponent._addedToDrawList = false;
                        if (childComponent.canDraw()) { // TODO if canDraw is false when does needsDraw get reset?
                            childComponent._drawIfNeeded(level+1);
                        } else if (drawLogger.isDebug) {
                            drawLogger.debug(loggerToString(childComponent) + " can't draw.");
                        }
                    }
                    this._disposeArray(oldDrawList);
                }
            }
        },

        _updateComponentDom: {
            value: function () {
                if (this._firstDraw) {
                    this._prepareForDraw();

                    if (this._composerList) {
                        var composer;

                        // Load any non lazyLoad composers that have been added
                        for (var i = 0, length = this._composerList.length; i < length; i++) {
                            composer = this._composerList[i];

                            if (!composer.lazyLoad) {
                                this.loadComposer(composer);
                            }
                        }
                    }

                    // Will we expose a different property, firstDraw, for components to check
                    this._firstDraw = false;
                }

                if (this._newDomContent !== null || this._shouldClearDomContentOnNextDraw) {
                    if (drawLogger.isDebug) {
                        //jshint -W106
                        logger.debug("Component content changed: component ", this._montage_metadata.objectName, this.identifier, " newDomContent", this._newDomContent);
                        //jshint +W106
                    }
                    this._performDomContentChanges();
                }
            }
        },

        _replaceElementWithTemplate: {
            enumerable: false,
            value: function () {
                var element = this.element,
                    template = this._templateElement,
                    attributes = this.element.attributes,
                    attributeName,
                    value,
                    i,
                    attribute,
                    templateAttributeValue;

                // TODO: get a spec for this, what attributes should we merge?
                for (i = 0; (attribute = attributes[i]); i++) {
                    attributeName = attribute.nodeName;
                    //jshint -W106
                    if (global._montage_le_flag && attributeName === ATTR_LE_COMPONENT) {
                        //jshint +W106
                        value = attribute.nodeValue;
                    } else if (attributeName === "id" || attributeName === "data-montage-id") {
                        value = attribute.nodeValue;
                    } else {
                        templateAttributeValue = template.getAttribute(attributeName) || "";
                        if (templateAttributeValue) {
                            value = templateAttributeValue +
                                (attributeName === "style" ? "; " : " ") +
                                attribute.nodeValue;
                        } else {
                            value = attribute.nodeValue;
                        }
                    }

                    template.setAttribute(attributeName, value);
                }

                this._initializeClassListFromElement(template);

                if (element.parentNode) {
                    element.parentNode.replaceChild(template, element);
                } else if (!this._canDrawOutsideDocument) {
                    console.warn("Warning: Trying to replace element ", element," which has no parentNode");
                }

                this.eventManager.unregisterEventHandlerForElement(element);
                this.eventManager.registerEventHandlerForElement(this, template);
                this._element = template;
                this._templateElement = null;

                // if the DOM content of the component was changed before the
                // template has been drawn then we assume that this change is
                // meant to set the original content of the component and not to
                // replace the entire template with it, that wouldn't make much
                // sense.
                if (this._newDomContent) {
                    this._newDomContent = null;
                    this._shouldClearDomContentOnNextDraw = false;
                }
            }
        },

        _addTemplateStylesIfNeeded: {
            value: function () {
                var part = this._templateDocumentPart;

                if (part) {
                    this.rootComponent.addStyleSheetsFromTemplate(part.template);
                }
            }
        },

        _prepareForDraw: {
            value: function _prepareForDraw() {
                if (logger.isDebug) {
                    logger.debug(this, "_templateElement: " + this._templateElement);
                }

                var leTagArguments;
                //jshint -W106
                if (global._montage_le_flag) {
                    //jshint +W106
                    leTagArguments = this.element.children.length > 0;
                }
                this._initDomArguments();
                if (leTagArguments) {
                    this._leTagArguments();
                }
                if (this._templateElement) {
                    this._bindTemplateParametersToArguments();
                    this._replaceElementWithTemplate();
                }
            },
            enumerable: false
        },

        _leTagArguments: {
            value: function () {
                if (this === this.rootComponent) {
                    return;
                }
                //jshint -W106
                var ownerModuleId = this.ownerComponent._montage_metadata.moduleId;
                var label = this._montage_metadata.label;
                //jshint +W106
                var argumentNames = this.getDomArgumentNames();
                if (!argumentNames || argumentNames.length === 0) {
                    this._leTagStarArgument(ownerModuleId, label, this.element);
                } else {
                    for (var i = 0, name; (name = argumentNames[i]); i++) {
                        this._leTagNamedArgument(ownerModuleId, label,
                            this._domArguments[name], name);
                    }
                }
            }
        },

        _getNodeFirstElement: {
            value: function (node) {
                var element = node.firstElementChild;

                if (!element) {
                    element = node.firstChild;
                    do {
                        if (element.nodeType === Node.ELEMENT_NODE) {
                            break;
                        }
                    } while ((element = element.nextSibling));
                }

                return element;
            }
        },

        _getNodeLastElement: {
            value: function (node) {
                var element = node.lastElementChild;

                if (!element) {
                    element = node.lastChild;
                    do {
                        if (element.nodeType === Node.ELEMENT_NODE) {
                            break;
                        }
                    } while ((element = element.previousSibling));
                }

                return element;
            }
        },

        _leTagStarArgument: {
            value: function (ownerModuleId, label, rootElement) {
                var argumentBegin = this._getNodeFirstElement(rootElement);
                var argumentEnd = this._getNodeLastElement(rootElement);

                argumentBegin.setAttribute(ATTR_LE_ARG_BEGIN,
                    (argumentBegin.getAttribute(ATTR_LE_ARG_BEGIN)||"") + " " +
                        ownerModuleId + "," + label);
                argumentEnd.setAttribute(ATTR_LE_ARG_END,
                    (argumentEnd.getAttribute(ATTR_LE_ARG_END)||"") + " " +
                        ownerModuleId + "," + label);
            }
        },

        _leTagNamedArgument: {
            value: function (ownerModuleId, label, element, name) {
                element.setAttribute(ATTR_LE_ARG,
                    ownerModuleId + "," + label + "," + name);
            }
        },

        _bindTemplateParametersToArguments: {
            value: function () {
                var parameters = this._templateDocumentPart ? this._templateDocumentPart.parameters : null,
                    templateArguments = this._domArguments,
                    parameterElement,
                    argument,
                    validation,
                    contents,
                    components,
                    range,
                    component;

                if ((validation = this._validateTemplateArguments(templateArguments, parameters))) {
                    throw validation;
                }

                for (var key in parameters) {
                    if (parameters.hasOwnProperty(key)) {

                        parameterElement = parameters[key];
                        argument = templateArguments ? templateArguments[key] : void 0;

                        if ((key === "*") || (key === "each")) {
                            if (this._element.childElementCount === 0) {
                             //We're missing an argument, we're going to check if we have a default
                                 if (parameterElement && parameterElement.childElementCount > 0) {
                                     range = this._element.ownerDocument.createRange();
                                     range.selectNodeContents(parameterElement);
                                     parameterElement.parentNode.replaceChild(range.extractContents(), parameterElement);

                                    //Should we re-construct the structure from the default?
                                    //  if(!templateArguments) {
                                    //      templateArguments = this._domArguments = {"*":};
                                    //
                                    //  }
                                 } else {
                                    //  throw new Error('No arguments provided for ' +
                                    //  this.templateModuleId + '. Arguments needed for data-param: ' +
                                    //  key + '.');
                                    //Remove the data-parm="*" element
                                    parameterElement.parentNode.removeChild(parameterElement);
                                 }
                            } else {
                                range = this._element.ownerDocument.createRange();
                                range.selectNodeContents(this._element);
                                contents = range.extractContents();
                            }
                        } else {
                            contents = argument;
                        }

                        if (contents) {
                            var i, length;

                            if (contents instanceof Element) {
                                var classList = parameterElement.classList,
                                    contentsClassList = contents.component ? contents.component.classList : contents.classList;

                                for (i = 0, length = classList.length; i < length; i++) {
                                    contentsClassList.add(classList[i]);
                                }
                            }

                            components = this._findAndDetachComponents(contents);
                            parameterElement.parentNode.replaceChild(contents, parameterElement);

                            for (i = 0; (component = components[i]); i++) {
                                component.attachToParentComponent();
                            }
                        }
                    }
                }
            }
        },

        _validateTemplateArguments: {
            value: function (templateArguments, templateParameters) {
                var parameterNames = templateParameters ? Object.keys(templateParameters) : void 0,
                    param;

                // If the template does not have parameters it is up to the
                // component to use its arguments.
                if (!parameterNames || parameterNames.length === 0) {
                    return;
                }

                // Arguments for non-existant parameters are not allowed.
                // Only the star argument is allowed.
                for (param in templateArguments) {
                    if (param !== "*" && !(param in templateParameters)) {
                        return new Error('"' + param + '" parameter does ' +
                            'not exist in ' + this.templateModuleId);
                    }
                }

                var elementWithStarParameter = templateParameters["*"];

                if (elementWithStarParameter) {
                    for (param in templateParameters) {
                        if (param !== "*" && elementWithStarParameter.contains(templateParameters[param])) {
                            return new Error('"' + param + '" parameter cannot be used within an element with the star parameter');
                        }
                    }
                }
            }
        },

        /**
         * Called by the {@link EventManager} before dispatching a `touchstart` or
         * `mousedown`.
         *
         * The component can implement this method to add event listeners for these
         * events before they are dispatched.
         * @function
         */
        prepareForActivationEvents: {
            enumerable: false,
            value: null
        },

        /**
         * Called to add event listeners on demand
         * @type function
         * @private
         */
        _prepareForActivationEvents: {
            value: function () {
                if (typeof this.prepareForActivationEvents === "function") {
                    this.prepareForActivationEvents();
                }

                if (this._composerList) {
                    var composer;

                    for (var i = 0, length = this._composerList.length; i < length; i++) {
                        composer = this._composerList[i];

                        if (composer.lazyLoad) {
                            this.loadComposer(composer);
                        }
                    }
                }

                this.preparedForActivationEvents = true;
            }
        },

        _performDomContentChanges: {
            value: function () {
                var contents = this._newDomContent,
                    element,
                    elementToAppend,
                    i;

                if (contents || this._shouldClearDomContentOnNextDraw) {
                    element = this._element;

                    // Setting the innerHTML to clear the children will not work on
                    // IE because it modifies the underlying child nodes. Here's the
                    // test case that shows this issue: http://jsfiddle.net/89X6F/
                    for (i = element.childNodes.length - 1; i >= 0; i--) {
                        if (!element.childNodes[i].component) {
                            element.removeChild(element.childNodes[i]);
                        }
                    }

                    if (this._elementsToAppend) {
                        while (this._elementsToAppend.length) {
                            elementToAppend = this._elementsToAppend.shift();
                            if (!element.contains(elementToAppend)) {
                                element.appendChild(elementToAppend);
                            }
                        }
                    }

                    this._newDomContent = null;
                    if (typeof this.contentDidChange === "function") {
                        this.contentDidChange();
                    }
                    this._shouldClearDomContentOnNextDraw = false;
                }
            }
        },

        /**
         * @deprecated
         * @todo remove
         */
        prepareForDraw: {
            enumerable: false,
            value: null
        },

        /**
         * This method is part of the draw cycle and is the prescribed location for
         * components to update its DOM structure or modify its styles.
         *
         * Components should not read the DOM during this phase of the draw cycle
         * as it could force an unwanted reflow from the browser.
         *
         * @function
         * @see http://montagejs.org/docs/Component-draw-cycle.html
         */
        draw: {
            enumerable: false,
            value: Function.noop
        },

        /**
         * This method is part of the draw cycle and it provides the component an
         * opportunity to query the DOM for any necessary calculations before
         * drawing.
         * If the execution of this method sets needsDraw to true on other
         * components, those components will be added to the current draw cycle.
         *
         * Components should not change the DOM during this phase of the draw cycle
         * as it could force an unwanted reflow from the browser.
         *
         * @function
         * @see http://montagejs.org/docs/Component-draw-cycle.html
         */
        willDraw: {
            enumerable: false,
            value: null
        },

        /**
         * This method is part of the draw cycle and it provides the component an
         * opportunity to query the DOM for any necessary calculations after
         * drawing.
         * If the execution of this method sets needsDraw to true on other
         * components, those components will be added to the current draw cycle.
         *
         * Components should not change the DOM during this phase of the draw cycle
         * as it could force an unwanted reflow from the browser.
         *
         * @function
         * @see http://montagejs.org/docs/Component-draw-cycle.html
         */
        didDraw: {
            enumerable: false,
            value: Function.noop
        },

        /**
         * Records whether or not we have been added to the parent's drawList.
         * @private
         */
        _addedToDrawList: {
            value: false
        },

        _addToParentsDrawList: {
            enumerable: false,
            value: function () {
                if (!this._addedToDrawList) {
                    var parentComponent = this._parentComponent;

                    if (parentComponent) {
                        parentComponent._addToDrawList(this);
                        if (this.drawListLogger.isDebug) {
                            //jshint -W106
                            this.drawListLogger.debug(loggerToString(this) + " added to " + loggerToString(parentComponent)  + "'s drawList");
                            //jshint +W106
                        }
                    } else if (this.drawListLogger.isDebug) {
                            this.drawListLogger.debug(this, "parentComponent is null");
                    }
                }
            }
        },

        _needsDraw: {
            value: false
        },

        _needsDrawInDeserialization: {
            value: false
        },

        /**
         * The purpose of this property is to trigger the adding of the component to
         * the draw list. The draw list consists of all the components that will be
         * drawn on the next draw cycle.
         *
         * The draw cycle itself is triggered by the `requestAnimationFrame` API
         * where available, otherwise a shim implemented with `setTimeout` is used.
         *
         * When it happens, the draw cycle will call, in succession, and when they
         * exist, the methods: `willDraw`, `draw`, and `didDraw`.
         *
         * At the end of the draw cycle this property is set back to `false`.
         *
         * @property {boolean}
         * @default false
         */
        needsDraw: {
            enumerable: false,
            get: function () {
                return this._needsDraw;
            },
            set: function (value) {
                if (this.isDeserializing) {
                    // Ignore needsDraw(s) which happen during deserialization
                    this._needsDrawInDeserialization = true;
                    return;
                }
                value = !!value;
                if (this._needsDraw !== value) {
                    if (needsDrawLogger.isDebug) {
                        //jshint -W106
                        needsDrawLogger.debug("needsDraw toggled " + value + " for " + this._montage_metadata.objectName);
                        //jshint +W106
                    }
                    this._needsDraw = value;
                    if (value) {
                        if (this.canDrawGate.value) {
                            this._addToParentsDrawList();
                        } else {
                            this.blockDrawGate.setField("drawRequested", true);
                        }
                    }
                }
            }
        },

        /**
         * Contains the list of childComponents this instance is reponsible for drawing.
         * @private
         */
        _drawList: {
            value: null
        },

        __addToDrawList: {
            enumerable: false,
            value: function (childComponent) {
                if (this._drawList === null) {
                    this._drawList = [childComponent];
                    childComponent._addedToDrawList = true;
                } else {
                    if (this._drawList.indexOf(childComponent) === -1) {
                        this._drawList.push(childComponent);
                        childComponent._addedToDrawList = true;
                    }
                }
            }
        },

        /**
         * Adds the passed in child component to the drawList
         * If the current instance isn't added to the drawList of its parentComponent, then it adds itself.
         * @private
         */
        _addToDrawList: {
            enumerable: false,
            value: function (childComponent) {
                this.__addToDrawList(childComponent);
                this._addToParentsDrawList();
            }
        },

        _templateElement: {
            enumerable: false,
            value: null
        },

        // Pointer Claiming

        /**
         * Ask this component to surrender the specified pointer to the
         * demandingComponent.
         *
         * The component can decide whether or not it should do this given the
         * pointer and demandingComponent involved.
         *
         * Some components may decide not to surrender control ever, while others
         * may do so in certain situations.
         *
         * Returns true if the pointer was surrendered, false otherwise.
         *
         * The demandingComponent is responsible for claiming the surrendered
         * pointer if it desires.
         *
         * @function
         * @param {string} pointer The `pointerIdentifier` that the demanding
         * component is asking this component to surrender
         * @param {Object} demandingComponent The component that is asking this
         * component to surrender the specified pointer
         * @returns {boolean} true
         */
        surrenderPointer: {
            value: function (pointer, demandingComponent) {
                return true;
            }
        },

        // Composers
        /**
         * Variable to track this component's associated composers
         * @private
         */
        _composerList: {
            value: null,
            serializable: false
        },

        composerList: {
            get: function () {
                if (!this._composerList) {
                    this._composerList = [];
                }

                return this._composerList;
            },
            serializable: false
        },

        /**
         * Adds the passed in composer to the component's composer list.
         * @function
         * @param {Composer} composer
         */
        addComposer: {  // What if the same composer instance is added to more than one component?
            value: function (composer) {
                this.addComposerForElement(composer, composer.element);
            }
        },

        /**
         * Adds the passed in composer to the component's composer list and
         * sets the element of the composer to the passed in element.
         * @function
         * @param {Composer} composer
         * @param {Element} element
         */
        addComposerForElement: {
            value: function (composer, element) {
                composer.component = this;
                composer.element = element;
                this.composerList.push(composer);

                if (!this._firstDraw) {  // prepareForDraw has already happened so do the loading here
                    if (!composer.lazyLoad) {
                        this.loadComposer(composer);
                    } else if (this.preparedForActivationEvents) { // even though it's lazyLoad prepareForActivationEvents has already happened
                        this.loadComposer(composer);
                    }
                }
            }
        },

        /**
         * Load a Composer
         * @function
         * @param {Composer} composer
         */
        loadComposer: {
            value: function (composer) {
                if (this._composerList && this._composerList.indexOf(composer) > -1) {
                    Target.prototype.loadComposer.call(this, composer);
                }
            }
        },

        /**
         * Unload a Composer
         * @function
         * @param {Composer} composer
         */
        unloadComposer: {
            value: function (composer) {
                if (this._composerList && this._composerList.indexOf(composer) > -1) {
                    Target.prototype.unloadComposer.call(this, composer);
                }
            }
        },

        /**
         * Adds the passed in composer to the list of composers which will have their
         * frame method called during the next draw cycle.  It causes a draw cycle to be scheduled
         * iff one has not already been scheduled.
         * @function
         * @param {Composer} composer
         */
        scheduleComposer: {
            value: function (composer) {
                this.rootComponent.addToComposerList(composer);
            }
        },

        /**
         * Removes the passed in composer from this component's composer list.  It takes care
         * of calling the composers unload method before removing it from the list.
         * @function
         * @param {Composer} composer
         */
        removeComposer: {
            value: function (composer) {
                if (this._composerList) {
                    for (var i = 0, length = this._composerList.length; i < length; i++) {
                        if (this._composerList[i] === composer) {
                            this.unloadComposer(this._composerList[i]);
                            this._composerList.splice(i, 1);
                            break;
                        }
                    }
                }
            }
        },

        /**
         * A convenience method for removing all composers from a component.  This method
         * is responsible for calling unload on each composer before removing it.
         * @function
         */
        clearAllComposers: {
            value: function () {
                if (this._composerList) {
                    var composerList = this._composerList;

                    for (var i = 0, length = composerList.length; i < length; i++) {
                        this.unloadComposer(composerList[i]);

                    }

                    composerList.length = 0;
                }
            }
        },

        /**
         * The localizer for this component
         * @type {Localizer}
         * @default null
         */
        localizer: {
            value: null
        },

        _waitForLocalizerMessages: {
            value: false
        },

        /**
         * Whether to wait for the localizer to load messages before drawing.
         * Make sure to set the [localizer]{@link Component#localizer} before
         * setting to ```true```.
         *
         * @type {boolean}
         * @default false
         * @example
         * // require localizer
         * var defaultLocalizer = localizer.defaultLocalizer,
         *     _ = defaultLocalizer.localizeSync.bind(defaultLocalizer);
         *
         * exports.Main = Component.specialize( {
         *
         *     constructor: {
         *         value: function () {
         *             this.localizer = defaultLocalizer;
         *             this.waitForLocalizerMessages = true;
         *         }
         *     },
         *
         *     // ...
         *
         *     // no draw happens until the localizer's messages have been loaded
         *     enterDocument: {
         *         value: function (firstTime) {
         *             if (firstTime) {
         *                 this._greeting = _("hello", "Hello {name}!");
         *             }
         *         }
         *     },
         *     draw: {
         *         value: function () {
         *             // this is for illustration only. This example is simple enough that
         *             // you should use a localizations binding
         *             this._element.textContent = this._greeting({name: this.name});
         *         }
         *     }
         * }
         */
        waitForLocalizerMessages: {
            enumerable: false,
            get: function () {
                return this._waitForLocalizerMessages;
            },
            set: function (value) {
                if (this._waitForLocalizerMessages !== value) {
                    if (value === true && !this.localizer.messages) {
                        if (!this.localizer) {
                            throw "Cannot wait for messages on localizer if it is not set";
                        }

                        this._waitForLocalizerMessages = true;

                        logger.debug(this, "waiting for messages from localizer");
                        this.canDrawGate.setField("messages", false);

                        var self = this;
                        this.localizer.messagesPromise.then(function(messages) {
                            if (logger.isDebug) {
                                logger.debug(self, "got messages from localizer");
                            }
                            self.canDrawGate.setField("messages", true);
                        });
                    } else {
                        this._waitForLocalizerMessages = false;
                        this.canDrawGate.setField("messages", true);
                    }
                }
            }
        },

        //
        // Attribute Handling
        //

        /**
         * Stores values that need to be set on the element. Cleared each draw cycle.
         * @private
         */
         __elementAttributeValues: {
             value: null
         },
         _elementAttributeValues: {
             get: function() {
                 return this.__elementAttributeValues || (this.__elementAttributeValues = {});
             }
         },

        /**
         * Stores the descriptors of the properties that can be set on this control
         * @private
         */
        _elementAttributeDescriptors: {
            value: null
        },

        _getElementAttributeDescriptor: {
            value: function (attributeName) {
                var attributeDescriptor, instance = this;
                // walk up the prototype chain from the instance to NativeControl's prototype
                // if _elementAttributeDescriptors is falsy, stop.
                while(instance && instance._elementAttributeDescriptors) {
                    attributeDescriptor = instance._elementAttributeDescriptors[attributeName];
                    if(attributeDescriptor) {
                        break;
                    } else {
                        instance = Object.getPrototypeOf(instance);
                    }
                }
                return attributeDescriptor;
            }
        },

        __shouldBuildIn: {
            value: true
        },

        _shouldBuildIn: {
            get: function () {
                return this.__shouldBuildIn;
            },
            set: function (value) {
                var index;

                value = !!value;
                this.__shouldBuildIn = value;
                if (value) {
                    if (this.parentComponent && this.parentComponent._componentsPendingBuildOut) {
                        index = this.parentComponent._componentsPendingBuildOut.indexOf(this);
                        if (index !== -1) {
                            this.parentComponent._componentsPendingBuildOut.splice(index, 1);
                        }
                    }
                    this._shouldBuildOut = false;
                    if (this._inDocument) {
                        this._buildIn();
                    }
                }
            }
        },

        __shouldBuildOut: {
            value: false
        },

        _shouldBuildOut: {
            get: function () {
                return this.__shouldBuildOut;
            },
            set: function (value) {
                value = !!value;
                this.__shouldBuildOut = value;
                if (value) {
                    this._shouldBuildIn = false;
                    if (this._inDocument) {
                        this._buildOut();
                    }
                }
            }
        },

        buildInInitialAnimation: {
            get: function () {
                var animation = null;

                if (this._activeBuildInAnimation && typeof this._activeBuildInAnimation === "object") {
                    if (typeof this._activeBuildInAnimation.cssClass !== "undefined") {
                        animation = new CssBasedAnimation();
                        animation.component = this;
                        animation.fromCssClass = this._activeBuildInAnimation.fromCssClass;
                        animation.cssClass = this._activeBuildInAnimation.cssClass;
                        animation.toCssClass = this._activeBuildInAnimation.toCssClass;
                    }
                }
                return animation;
            }
        },

        buildInSwitchAnimation: {
            get: function () {
                var animation = null;

                if (this._activeBuildInAnimation && typeof this._activeBuildInAnimation === "object") {
                    if (typeof this._activeBuildInAnimation.cssClass !== "undefined") {
                        animation = new CssBasedAnimation();
                        animation.component = this;
                        animation.cssClass = this._activeBuildInAnimation.cssClass;
                        animation.toCssClass = this._activeBuildInAnimation.toCssClass;
                    }
                }
                return animation;
            }
        },

        buildOutInitialAnimation: {
            get: function () {
                var animation = null;

                if (this._activeBuildOutAnimation && typeof this._activeBuildOutAnimation === "object") {
                    if (typeof this._activeBuildOutAnimation.cssClass !== "undefined") {
                        animation = new CssBasedAnimation();
                        animation.component = this;
                        animation.hasOneFrameDelay = true;
                        animation.fromCssClass = this._activeBuildOutAnimation.fromCssClass;
                        animation.cssClass = this._activeBuildOutAnimation.cssClass;
                        animation.toCssClass = this._activeBuildOutAnimation.toCssClass;
                    }
                }
                return animation;
            }
        },

        buildOutSwitchAnimation: {
            get: function () {
                var animation = null;

                if (this._activeBuildOutAnimation && typeof this._activeBuildOutAnimation === "object") {
                    if (typeof this._activeBuildOutAnimation.cssClass !== "undefined") {
                        animation = new CssBasedAnimation();
                        animation.component = this;
                        animation.cssClass = this._activeBuildOutAnimation.cssClass;
                        animation.toCssClass = this._activeBuildOutAnimation.toCssClass;
                    }
                }
                return animation;
            }
        },

        buildInAnimation: {
            value: null
        },

        buildOutAnimation: {
            value: null
        },

        buildInAnimationOverride: {
            value: null
        },

        buildOutAnimationOverride: {
            value: null
        },

        _activeBuildInAnimation: {
            value: null
        },

        _activeBuildOutAnimation: {
            value: null
        },

        _updateActiveBuildAnimations: {
            value: function () {
                if (this.buildInAnimationOverride) {
                    this._activeBuildInAnimation = this.buildInAnimationOverride;
                } else {
                    this._activeBuildInAnimation = this.buildInAnimation;
                }
                if (this.buildOutAnimationOverride) {
                    this._activeBuildOutAnimation = this.buildOutAnimationOverride;
                } else {
                    this._activeBuildOutAnimation = this.buildOutAnimation;
                }
            }
        },

        _currentBuildAnimation: {
            value: null
        },

        _buildIn: {
            value: function () {
                var self = this;

                if (this._currentBuildAnimation) {
                    this._currentBuildAnimation.cancel();
                }
                if (this._element && this._element.parentNode && this._element.parentNode.component) {
                    if (this._isElementAttachedToParent) {
                        this._currentBuildAnimation = this.buildInSwitchAnimation;
                    } else {
                        this._updateActiveBuildAnimations();
                        this._currentBuildAnimation = this.buildInInitialAnimation;
                    }
                    if (this._currentBuildAnimation) {
                        this._currentBuildAnimation.play();
                        this._currentBuildAnimation.finished.then(function () {
                            self._currentBuildAnimation.cancel();
                            self._currentBuildAnimation = null;
                            self.dispatchEventNamed("buildInEnd", true, true);
                        }, function () {});
                    }
                }
            }
        },

        _buildOut: {
            value: function () {
                var self = this;

                if (this._currentBuildAnimation) {
                    this._currentBuildAnimation.cancel();
                    this._currentBuildAnimation = this.buildOutSwitchAnimation;
                } else {
                    this._updateActiveBuildAnimations();
                    this._currentBuildAnimation = this.buildOutInitialAnimation;
                }
                if (this._element && this._element.parentNode && this._element.parentNode.component) {
                    if (this._currentBuildAnimation) {
                        this._currentBuildAnimation.play();
                        this._currentBuildAnimation.finished.then(function () {
                            var parent = self.parentComponent;
                            self._currentBuildAnimation.cancel();
                            self._currentBuildAnimation = null;
                            self.detachFromParentComponent();
                            self.buildInAnimationOverride = null;
                            self.buildOutAnimationOverride = null;
                            if (self._element.parentNode.component) {
                                self._element.parentNode.removeChild(self._element);
                            }
                            self._isElementAttachedToParent = false;
                            parent.dispatchEventNamed("buildOutEnd", true, true);
                        }, function () {});
                    } else {
                        this.detachFromParentComponent();
                        this.buildInAnimationOverride = null;
                        this.buildOutAnimationOverride = null;
                        if (this._isElementAttachedToParent) {
                            if (this._element.parentNode && this._element.parentNode.component) {
                                this._element.parentNode.removeChild(this._element);
                            }
                            this._isElementAttachedToParent = false;
                        }
                    }
                }
            }
        },
        _willEnterDocument: {

            value: function () {
                var event;

                event = new CustomEvent("willEnterDocument", {
                    details: {
                        component: this
                    },
                    bubbles: false
                });
                this.dispatchEvent(event);
                this._inDocument = true;
                if (this.parentComponent) {
                    this.parentComponent._childWillEnterDocument();
                }
                if (this.__shouldBuildIn) {
                    this._buildIn();
                }
                this._isElementAttachedToParent = true;
                if (this.__shouldBuildOut) {
                    this._buildOut();
                }
            }
        },

        _childWillEnterDocument: {
            value: function () {
                if (this._componentsPendingBuildOut) {
                    while (this._componentsPendingBuildOut.length) {
                        this._componentsPendingBuildOut.pop()._shouldBuildOut = true;
                    }
                }
            }
        },

        /**
         * This function is called when the component element is added to the
         * document's DOM tree.
         *
         * @function Component#enterDocument
         * @param {boolean} firstTime `true` if it's the first time the component
         *                  enters the document.
         */

    // callbacks

        _enterDocument: {
            value: function (firstTime) {
                var originalElement;

                if (firstTime) {
                    // The element is now ready, so we can read the attributes that
                    // have been set on it.
                    originalElement = this.originalElement;

                    var attributes, i, length, name, value, attributeName, descriptor;
                    attributes = originalElement.attributes;
                    if (attributes) {
                        length = attributes.length;
                        for (i=0; i < length; i++) {
                            name = attributes[i].name;
                            value = attributes[i].value;

                            descriptor = this._getElementAttributeDescriptor(name, this);
                            // check if this attribute from the markup is a well-defined attribute of the component
                            if (descriptor || (typeof this[name] !== 'undefined')) {
                                // only set the value if a value has not already been set by binding
                                if (typeof this._elementAttributeValues[name] === 'undefined') {
                                    this._elementAttributeValues[name] = value;
                                    if(this[name] === null || this[name] === undefined) {
                                        this[name] = value;
                                    }
                                }
                            }
                        }
                    }

                    // textContent is a special case since it isn't an attribute
                    descriptor = this._getElementAttributeDescriptor('textContent', this);
                    if(descriptor) {
                        // check if this element has textContent
                        var textContent = originalElement.textContent;
                        if (typeof this._elementAttributeValues.textContent === 'undefined') {
                            this._elementAttributeValues.textContent = textContent;
                            if (this.textContent === null || this.textContent === undefined) {
                                this.textContent = textContent;
                            }
                        }
                    }

                    // Set defaults for any properties that weren't serialised or set
                    // as attributes on the element.
                    //Benoit: This shouldn't be needed on each instance if properly set on the prototype TODO #memory #performance improvement
                    if (this._elementAttributeDescriptors) {
                        for (attributeName in this._elementAttributeDescriptors) {
                            if (this._elementAttributeDescriptors.hasOwnProperty(attributeName)) {
                                descriptor = this._elementAttributeDescriptors[attributeName];
                                var _name = "_"+attributeName;
                                if ((this[_name] === null) && descriptor !== null && "value" in descriptor) {
                                    this[_name] = descriptor.value;
                                }
                            }
                        }
                    }
                }
            }
        },

        /**
         * @private
         * @function
         */
        _draw: {
            value: function () {
                var element = this.element,
                    descriptor;

                //Buffered/deferred element attribute values
                if(this.__elementAttributeValues !== null) {
                    for(var attributeName in this._elementAttributeValues) {
                        if(this._elementAttributeValues.hasOwnProperty(attributeName)) {
                            var value = this[attributeName];
                            descriptor = this._getElementAttributeDescriptor(attributeName, this);
                            if(descriptor) {

                                if(descriptor.dataType === 'boolean') {
                                    if(value === true) {
                                        element[attributeName] = true;
                                        element.setAttribute(attributeName, attributeName.toLowerCase());
                                    } else {
                                        element[attributeName] = false;
                                        element.removeAttribute(attributeName);
                                    }
                                } else {
                                    if(typeof value !== 'undefined') {
                                        if(attributeName === 'textContent') {
                                            element.textContent = value;
                                        } else {
                                            //https://developer.mozilla.org/en/DOM/element.setAttribute
                                            element.setAttribute(attributeName, value);
                                        }

                                    }
                                }

                            }

                            delete this._elementAttributeValues[attributeName];
                        }
                    }
                }

                // classList
                this._drawClassListIntoComponent();
            }
        },

        /**
         * @private
         */
        _classList: {
            value: null
        },

        _classListDirty: {
            value: false
        },

        /**
         * The classList of the component's element, the purpose is to mimic the
         * element's API but to also respects the draw cycle.
         *
         * It can also be bound to by binding each class as a property.
         * example to toggle the complete class:
         *
         * ```json
         * "classList.has('complete')" : { "<-" : "@owner.isComplete"}
         * ```
         *
         * @returns {Set}
         */
        classList: {
            get: function () {
                if (this._classList === null) {
                    this._classList = new Set();
                    this._subscribeToToClassListChanges();
                    this._initializeClassListFromElement(this.element);
                }
                return this._classList;
            }
        },

        /**
         * @private
         */
        _initializeClassListFromElement: {
            value: function (element) {
                if (element && element.classList && element.classList.length > 0) {
                    // important to initializae the classList first, so that the listener doesn't get installed.
                    if (!this._classList) {
                        // we don't want to subscribe then unsubscribe and subscribe again to the ClassList Changes,
                        // So we don't access to the getter of the property classList.
                        this._classList = new Set();

                    } else {
                        if (this._unsubscribeToClassListChanges) {
                            this._unsubscribeToClassListChanges();
                        }
                    }

                    var classList = element.classList;

                    for (var i = 0, length = classList.length; i < length; i++) {
                        this._classList.add(classList[i]);
                    }

                    this._subscribeToToClassListChanges();
                }
            }
        },

        /**
         * @private
         */
        _unsubscribeToClassListChanges: {
            value: null
        },

        /**
         * @private
         */
        _subscribeToToClassListChanges: {
            value: function () {
                this._unsubscribeToClassListChanges = this._classList.addRangeChangeListener(this, "classList");
            }
        },

        handleClassListRangeChange: {
            value: function (plus, minus) {
                this._classListDirty = true;
                this.needsDraw = true;
            }
        },

        _drawClassListIntoComponent: {
            value: function () {
                if (this._classListDirty) {
                    var elementClassList = this.element.classList,
                        classList = this._classList;

                    for (var i = 0, ii = elementClassList.length, className; i < ii; i++) {
                        className = elementClassList.item(i);
                        if (!classList.has(className)) {
                            elementClassList.remove(className);
                            i--;
                            ii--;
                        }
                    }

                    this._classList.forEach(function (cssClass) {
                        elementClassList.add(cssClass);
                    });
                    this._classListDirty = false;
                }
            }
        },

        dispose: {
            value: function () {
                this.cancelBindings();
                this.detachFromParentComponent();
                if (this._element) {
                    defaultEventManager.unregisterEventHandlerForElement(this._element);
                    this._element = null;
                }

                this.childComponents.forEach(function (component) {
                    component.dispose();
                });
            }
        }
    },{
        /**
         * Add the specified properties as properties of this component.
         * @function
         * @param {object} properties An object that contains the properties you want to add.
         * @private
         */
         //TODO, this should be renamed addAttributeProperties
        addAttributes: {
            value: function (properties) {
                var i, descriptor, property, object;
                this.prototype._elementAttributeDescriptors = properties;

                for(property in properties) {
                    if(properties.hasOwnProperty(property)) {
                        object = properties[property];
                        // Make sure that the descriptor is of the correct form.
                        if(object === null || typeof object === "string") {
                            descriptor = {value: object, dataType: "string"};
                            properties[property] = descriptor;
                        } else {
                            descriptor = object;
                        }

                        // Only add the internal property, and getter and setter if
                        // they don't already exist.
                        if(typeof this[property] === 'undefined') {
                            this.defineAttribute(property, descriptor);
                        }
                    }
                }
            }
        },

        //TODO, this should be renamed attributePropertySetter
        defineAttributeSetter: {
            value: function (name, _name, descriptor) {
                return (function (name, attributeName, setter) {
                    return function (value, fromInput) {
                        var descriptor = this._getElementAttributeDescriptor(name, this);

                        // if requested dataType is boolean (eg: checked, readonly etc)
                        // coerce the value to boolean
                        if(descriptor && "boolean" === descriptor.dataType) {
                            value = ( (value || value === "") ? true : false);
                        }

                        // If the set value is different to the current one,
                        // update it here, and set it to be updated on the
                        // element in the next draw cycle.
                        if(typeof value !== 'undefined' && (this[attributeName] !== value)) {

                            if (setter) {
                                setter.call(this, value);
                            } else {
                                this[attributeName] = value;
                            }

                            this._elementAttributeValues[name] = value;
                            if (!fromInput) {
                                this.needsDraw = true;
                            }
                        }
                    };
                }(name, _name, descriptor.set));
            }
        },
        //TODO, this should be renamed attributePropertySetter
        defineAttributeGetter: {
            value: function (_name) {
                return (function (attributeName) {
                    return function () {
                        return this[attributeName];
                    };
                }(_name));
            }
        },
        /**
         * Adds a property to the component with the specified name.
         * This method is used internally by the framework convert a DOM element's
         * standard attributes into bindable properties.
         * It creates an accessor property (getter/setter) with the same name as
         * the specified property, as well as a "backing" data property whose name
         * is prepended with an underscore (_).
         * The backing variable is assigned the value from the property descriptor.
         * For example, if the name "title" is passed as the first parameter, a
         * "title" accessor property is created as well a data property named
         * "_title".
         * @function
         * @param {string} name The property name to add.
         * @param {Object} descriptor An object that specifies the new properties default attributes such as configurable and enumerable.
         * @private
         */
         //https://github.com/kangax/html-minifier/issues/63 for a list of boolean attributes
         //TODO, this should be renamed defineAttributeProperty
        defineAttribute: {
            value: function (name, descriptor) {
                descriptor = descriptor || {};
                var _name = '_' + name;

                var newDescriptor = {
                    configurable: (typeof descriptor.configurable === 'undefined') ? true: descriptor.configurable,
                    enumerable: (typeof descriptor.enumerable === 'undefined') ?  true: descriptor.enumerable,
                    set: this.defineAttributeSetter(name, _name, descriptor),
                    get: descriptor.get || this.defineAttributeGetter(_name)
                };

                // Define _ property
                // TODO this.constructor.defineProperty
                if(!this.prototype.hasOwnProperty(_name)) {
                    Montage.defineProperty(this.prototype, _name, {value: descriptor.value});
                }
                // Define property getter and setter
                Montage.defineProperty(this.prototype, name, newDescriptor);
            }
        }
    });

    /**
     * @class RootComponent
     * @extends Component
     */
    var RootComponent = Component.specialize( /** @lends RootComponent.prototype */{
        constructor: {
            value: function RootComponent() {
                this._drawTree = this._drawTree.bind(this);
                this._readyToDrawListIndex = new Map();
                this._addedStyleSheetsByTemplate = new WeakMap();
            }
        },

        /**
         * @private
         * @function
         * @returns itself
         */
        init: {
            value: function () {
                return this;
            }
        },

        needsDraw: {
            enumerable: true,
            get: function () {
                return !!this._needsDraw;
            },
            set: function (value) {
                if (this._needsDraw !== value) {
                    this._needsDraw = !!value;
                    if (value) {
                        var childComponents = this.childComponents;
                        //jshint -W106
                        for (var i = 0, childComponent; (childComponent = childComponents[i]); i++) {
                            if (needsDrawLogger.isDebug) {
                                needsDrawLogger.debug(this, "needsDraw = true for: " + childComponent._montage_metadata.exportedSymbol);
                            }
                            childComponent.needsDraw = true;
                        }
                        //jshint +W106
                    }
                }
            }
        },

        canDrawGate: {
            get: function () {
                return this._canDrawGate || (this._canDrawGate = new Gate().initWithDelegate(this));
            }
        },

        _clearNeedsDrawTimeOut: {
            value: null
        },

        _needsDrawList: {
            value: []
        },

        _cannotDrawList: {
            value: null
        },

        /**
         * @function
         * @param {Object} component
         */
        componentBlockDraw: {
            value: function (component) {
                this._cannotDrawList = (this._cannotDrawList ? this._cannotDrawList : new Set());
                this._cannotDrawList.add(component);
                if (this._clearNeedsDrawTimeOut) {
                    clearTimeout(this._clearNeedsDrawTimeOut);
                    this._clearNeedsDrawTimeOut = null;
                }
            }
        },

        // TODO: implement this with a flag on the component
        isComponentWaitingNeedsDraw: {
            value: function (component) {
                return this._cannotDrawList.has(component) ||
                    this._needsDrawList.indexOf(component) >= 0;
            }
        },

        /**
         * @function
         * @param {Object} component
         * @param {number} value
         */
        componentCanDraw: {
            value: function (component, value) {
                if (value) {
                    if (!this._cannotDrawList) {
                        return;
                    }
                    this._cannotDrawList.delete(component);
                    this._needsDrawList.push(component);
                    if (this._cannotDrawList.size === 0 && this._needsDrawList.length > 0) {
                        if (!this._clearNeedsDrawTimeOut) {
                            var self = this;
                            // Wait to clear the needsDraw list as components could be loaded synchronously
                            this._clearNeedsDrawTimeOut = setTimeout(function () {
                                self._clearNeedsDrawList();
                            }, 0);
                        }
                    }
                } else {
                    if (this._clearNeedsDrawTimeOut) {
                        clearTimeout(this._clearNeedsDrawTimeOut);
                        this._clearNeedsDrawTimeOut = null;
                    }
                }
            }
        },

        _clearNeedsDrawList: {
            value: function () {
                var component, i, length, needsDrawList = this._needsDrawList;
                length = needsDrawList.length;
                for (i = 0; i < length; i++) {
                    component = needsDrawList[i];
                    if (component.needsDraw ||
                        // Maybe the component doesn't need to draw but has child
                        // components that do.
                        (component._drawList && component._drawList.length > 0)) {
                        component._addToParentsDrawList();
                    }
                }
                this._clearNeedsDrawTimeOut = null;
                needsDrawList.length = 0;
            }
        },

        /**
         * @function
         * @param {Component} componentId
         */
        removeFromCannotDrawList: {
            value: function (component) {
                if (!this._cannotDrawList) {
                    return;
                }

                this._cannotDrawList.delete(component);

                if (this._cannotDrawList.size === 0 && this._needsDrawList.length > 0) {
                    if (!this._clearNeedsDrawTimeOut) {
                        var self = this;
                        this._clearNeedsDrawTimeOut = setTimeout(function () {
                            self._clearNeedsDrawList();
                        }, 0);
                    }
                }
            }
        },

        _cancelDrawIfScheduled: {
            value: function () {
                var requestedAnimationFrame = this.requestedAnimationFrame,
                    cancelAnimationFrame = this.cancelAnimationFrame;
                if (requestedAnimationFrame !== null) {
                    if (!this._frameTime) { // Only cancel it is not already in a drawTree call
                        if (logger.isDebug) {
                            logger.debug(this, "clearing draw");
                        }
                        if (cancelAnimationFrame) {
                            cancelAnimationFrame(requestedAnimationFrame);
                        } else {
                            clearTimeout(requestedAnimationFrame);
                        }
                        this.requestedAnimationFrame = null;
                    }
                }
            }
        },

        /**
         * Adds the passed in child component to the drawList.
         * @private
         */
        _addToDrawList: {
            value: function (childComponent) {
                this.__addToDrawList(childComponent);
                if (this.drawListLogger.isDebug) {
                    this.drawListLogger.debug(this, this.canDrawGate.value, this.requestedAnimationFrame);
                }
                this.drawTree();
            },
            enumerable: false
        },

        /**
         * Adds the passed in composer to the list of composers to be executed
         * in the next draw cycle and requests a draw cycle if one has not been
         * requested yet.
         * @function
         * @param {Composer} composer
         */
        addToComposerList: {
            value: function (composer) {
                this.composerList.push(composer);

                if (drawLogger.isDebug) {
                    drawLogger.debug(this, composer, "Added to composer list");
                }
                // If a draw is already in progress this.drawTree() will not schedule another one, so track
                // that a composer requested a draw in case a new draw does need to be scheduled when the
                // current loop is done
                this._scheduleComposerRequest = true;
                this.drawTree();
            }
        },

        // Create a second composer list so that the lists can be swapped during a draw instead of creating a new array every time
        composerListSwap: {
            get: function () {
                if (!this._composerListSwap) {
                    this._composerListSwap = [];
                }

                return this._composerListSwap;
            }
        },

        _composerListSwap: {
            value: null
        },

        /*
         * Flag to track if a composer is requesting a draw
         * @private
         */
        _scheduleComposerRequest: {
            value: false
        },

        /**
         * The value returned by requestAnimationFrame.
         * If a request has been scheduled but not run yet, else null.
         * @private
         * @type {number}
         * @default null
         */
        requestedAnimationFrame: {
            value: null,
            enumerable: false
        },

        /**
         * @private
         * @function
         */
        requestAnimationFrame: {
            value: (global.requestAnimationFrame || global.webkitRequestAnimationFrame ||
                        global.mozRequestAnimationFrame ||  global.msRequestAnimationFrame || setTimeout),
            enumerable: false
        },

        /**
         * @private
         * @function
         */
        cancelAnimationFrame: {
            value: (global.cancelAnimationFrame ||  global.webkitCancelAnimationFrame ||
                        global.mozCancelAnimationFrame || global.msCancelAnimationFrame || clearTimeout),
            enumerable: false
        },

        /**
         * Set to the current time of the frame while drawing is in progress.
         * The frame time is either supplied by the requestAnimationFrame callback if available in the browser, or by using Date.now if it is a setTimeout.
         * @private
         */
        _frameTime: {
            value: null
        },

        /**
         * oldSource and diff are used to detect DOM modifications outside of the
         * draw loop, but only if drawLogger.isDebug is true.
         * @private
         */
        _oldSource: {
            value: null
        },
        _diff: {
            // Written by John Resig. Used under the Creative Commons Attribution 2.5 License.
            // http://ejohn.org/projects/javascript-diff-algorithm/
            value: function ( o, n ) {
                var ns = {},
                    os = {};

                function isNullOrUndefined(o) {
                    return o === undefined || o === null;
                }

                for (var i = 0; i < n.length; i++ ) {
                    if (isNullOrUndefined(ns[n[i]])) {
                        ns[n[i]] = {
                            rows: [],
                            o: null
                        };
                    }
                    ns[n[i]].rows.push( i );
                }

                for (i = 0; i < o.length; i++ ) {
                    if (isNullOrUndefined(os[o[i]])) {
                        os[o[i]] = {
                            rows: [],
                            n: null
                        };
                    }
                    os[o[i]].rows.push(i);
                }

                for (i in ns ) {
                    if (
                        ns[i].rows.length === 1 &&
                            !isNullOrUndefined(os[i]) &&
                                os[i].rows.length === 1
                    ) {
                        n[ns[i].rows[0]] = {
                            text: n[ns[i].rows[0]],
                            row: os[i].rows[0]
                        };
                        o[ os[i].rows[0] ] = {
                            text: o[ os[i].rows[0] ],
                            row: ns[i].rows[0]
                        };
                    }
                }

                for (i = 0; i < n.length - 1; i++ ) {
                    if (
                        !isNullOrUndefined(n[i].text) && isNullOrUndefined(n[i+1].text) &&
                            n[i].row + 1 < o.length && isNullOrUndefined(o[ n[i].row + 1 ].text) &&
                                n[i+1] === o[ n[i].row + 1 ]
                    ) {
                        n[i+1] = { text: n[i+1], row: n[i].row + 1 };
                        o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };
                    }
                }

                for (i = n.length - 1; i > 0; i-- ) {
                    if (
                        !isNullOrUndefined(n[i].text) && isNullOrUndefined(n[i - 1].text) &&
                            n[i].row > 0 && isNullOrUndefined(o[ n[i].row - 1].text) &&
                                n[i - 1] === o[ n[i].row - 1 ]
                    ) {
                        n[i - 1] = {
                            text: n[i - 1],
                            row: n[i].row - 1
                        };
                        o[n[i].row-1] = {
                            text: o[n[i].row-1],
                            row: i - 1
                        };
                    }
                }

                return {
                    o: o,
                    n: n
                };
            }
        },

        /**
         * @private
         */
        _previousDrawDate: {
            enumerable: false,
            value: 0
        },

        /**
         * @private
         */
        _documentResources: {
            value: null
        },

        /**
         * @private
         */
        _needsStylesheetsDraw: {
            value: false
        },

        /**
         * @private
         */
        _stylesheets: {
            value: []
        },

        /**
         * @function
         */
        addStylesheet: {
            value: function (style) {
                this._stylesheets.push(style);
                this._needsStylesheetsDraw = true;
            }
        },
        _addedStyleSheetsByTemplate: {
            value: null
        },
        addStyleSheetsFromTemplate: {
            value: function(template) {
                if(!this._addedStyleSheetsByTemplate.has(template)) {
                    var resources = template.getResources(),
                        ownerDocument = this.element.ownerDocument,
                        styles = resources.createStylesForDocument(ownerDocument);

                    for (var i = 0, style; (style = styles[i]); i++) {
                        this.addStylesheet(style);
                    }
                    this._addedStyleSheetsByTemplate.set(template,true);
                }
            }
        },
        __bufferDocumentFragment: {
            value: null,
        },
        _bufferDocumentFragment: {
             get: function() {
                 return this.__bufferDocumentFragment || ( this.__bufferDocumentFragment = this._element.ownerDocument.createDocumentFragment());
            }
        },
        /**
         * @private
         */
        drawStylesheets: {
            value: function () {
                var documentResources = this._documentResources,
                    stylesheets = this._stylesheets,
                    stylesheet,
                    documentHead = documentResources._document.head,
                    bufferDocumentFragment = this._bufferDocumentFragment;

                while ((stylesheet = stylesheets.shift())) {
                    documentResources.addStyle(stylesheet,bufferDocumentFragment);
                }

                documentHead.insertBefore(bufferDocumentFragment, documentHead.firstChild);

                this._needsStylesheetsDraw = false;
            }
        },

        /**
         * @private
         */
        drawTree: {
            value: function drawTree() {
                if (this.requestedAnimationFrame === null) { // 0 is a valid requestedAnimationFrame value
                    var requestAnimationFrame = this.requestAnimationFrame;
                    if (requestAnimationFrame) {
                        this.requestedAnimationFrame = requestAnimationFrame.call(window, this._drawTree);
                    } else {
                        // Shim based in Erik Möller's code at
                        // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
                        var currentDate = Date.now(),
                            miliseconds = 17 - currentDate + this._previousDrawDate;

                        if (miliseconds < 0) {
                            miliseconds = 0;
                        }
                        this.requestedAnimationFrame = setTimeout(this._drawTree, miliseconds);
                        this._previousDrawDate = currentDate + miliseconds;
                    }
                    this._scheduleComposerRequest = false;
                }
            },
            enumerable: false
        },

        _drawTree: {
            value: function (timestamp) {
                var drawPerformanceStartTime;

                // Add all stylesheets needed by the components since last
                // draw.
                if (this._needsStylesheetsDraw) {
                    this.drawStylesheets();
                }

                // Wait for all stylesheets to be loaded, do not proceeed
                // with the draw cycle until all needed stylesheets are
                // ready.
                // We need to do this because adding the stylesheets won't
                // make them immediately available for styling even if the
                // file is already loaded.
                if (!this._documentResources.areStylesLoaded) {
                    if (drawPerformanceLogger.isDebug) {
                        console.log("Draw Cycle Waiting Stylesheets: ", this._documentResources._expectedStyles.length);
                    }

                    this.requestedAnimationFrame = null;
                    this.drawTree();
                    return;
                }

                if (drawPerformanceLogger.isDebug) {
                    if (window.performance) {
                        drawPerformanceStartTime = window.performance.now();
                    } else {
                        drawPerformanceStartTime = Date.now();
                    }
                }
                this._frameTime = (timestamp ? timestamp : Date.now());
                if (this._clearNeedsDrawTimeOut) {
                    this._clearNeedsDrawList();
                }
                if (drawLogger.isDebug) {
                    // Detect any DOM modification since the previous draw
                    var newSource = document.body.innerHTML;
                    if (this._oldSource && newSource !== this._oldSource) {
                        var warning = ["DOM modified outside of the draw loop"];
                        var out = this._diff(this._oldSource.split("\n"), newSource.split("\n"));
                        for (var i = 0; i < out.n.length; i++) {
                            if (out.n[i].text === undefined || out.n[i].text === null) {
                                warning.push('+ ' + out.n[i]);
                            } else {
                                for (var n = out.n[i].row + 1; n < out.o.length && (out.o[n].text === undefined || out.o[n].text === null); n++) {
                                    warning.push('- ' + out.o[n]);
                                }
                            }
                        }
                        console.warn(warning.join("\n"));
                    }

                    console.group((timestamp ? drawLogger.toTimeString(new Date(timestamp)) + " " : "") + "Draw Fired");
                }

                this.drawIfNeeded();

                if (drawPerformanceLogger.isDebug) {
                    var drawPerformanceEndTime;
                    if (window.performance) {
                        drawPerformanceEndTime = window.performance.now();
                    } else {
                        drawPerformanceEndTime = Date.now();
                    }

                    console.log("Draw Cycle Time: ",
                        drawPerformanceEndTime - drawPerformanceStartTime,
                        ", Components: ", this._lastDrawComponentsCount);
                }

                if (drawLogger.isDebug) {
                    console.groupEnd();
                    this._oldSource =  document.body.innerHTML;
                }
                this._frameTime = null;
                if (this._scheduleComposerRequest) {
                    this.drawTree();
                }
            }
        },

        /**
         * @private
         */
        _readyToDrawList: {
            enumerable: false,
            value: []
        },

        /**
         * @private
         */
        _readyToDrawListIndex: {
            enumerable: false,
            value: null
        },

        /**
         * @function
         * @param {Component} component Component to add
         */
        addToDrawCycle: {
            value: function (component) {
                var needsDrawListIndex = this._readyToDrawListIndex, length, composer;

                if (needsDrawListIndex.has(component)) {
                    // Requesting a draw of a component that has already been drawn in the current cycle
                    if (drawLogger.isDebug) {
                        if(this !== rootComponent) {
                            drawLogger.debug(loggerToString(this) + " added to the draw cycle twice, this should not happen.");
                        }
                    }
                    return;
                }
                this._readyToDrawList.push(component);
                this._readyToDrawListIndex.set(component, true);

                component._updateComponentDom();
            }
        },

        _lastDrawComponentsCount: {
            value: null
        },

        _sortByLevel: {
            value: function (component1, component2) {
                return component1._treeLevel - component2._treeLevel;
            }
        },

        /**
         * @private
         * @function
         * @returns Boolean true if all the components that needed to draw have drawn
        */
        drawIfNeeded:{
            value: function drawIfNeeded() {
                var needsDrawList = this._readyToDrawList, component, i, j, start = 0, firstDrawEvent,
                    composerList = this._composerList, composer, composerListLength,
                    isDrawLoggerDebug = drawLogger.isDebug;

                needsDrawList.length = 0;
                this._readyToDrawListIndex.clear();

                // Process the composers first so that any components that need to be newly drawn due to composer changes
                // get added in this cycle
                if (composerList && (composerListLength = composerList.length) > 0) {
                    this._composerList = this.composerListSwap; // Swap between two arrays instead of creating a new array each draw cycle

                    for (i = 0; i < composerListLength; i++) {
                        composer = composerList[i];
                        composer.needsFrame = false;
                        composer.frame(this._frameTime);
                    }

                    composerList.length = 0;
                    this._composerListSwap = composerList;
                }

                this._drawIfNeeded(0);
                j = needsDrawList.length;

                //
                // willDraw
                //
                if (isDrawLoggerDebug) {
                    console.groupCollapsed("willDraw - " + needsDrawList.length +
                        (needsDrawList.length > 1 ? " components." : " component."));
                }
                while (start < j) {
                    for (i = start; i < j; i++) {
                        component = needsDrawList[i];
                        if (typeof component.willDraw === "function") {
                            component.willDraw(this._frameTime);
                        }
                        if (isDrawLoggerDebug) {
                            drawLogger.debug("Level " + component._treeLevel + " " + loggerToString(component));
                        }
                    }
                    this._drawIfNeeded(0);
                    start = j;
                    j = needsDrawList.length;
                }
                //
                // draw
                //
                if (isDrawLoggerDebug) {
                    console.groupEnd();
                    console.group("draw - " + needsDrawList.length +
                                        (needsDrawList.length > 1 ? " components." : " component."));
                }
                // Sort the needsDraw list so that any newly added items are drawn in the correct order re: parent-child
                needsDrawList.sort(this._sortByLevel);

                for (i = 0; i < j; i++) {
                    component = needsDrawList[i];
                    component.needsDraw = false;
                }
                this.requestedAnimationFrame = null; // Allow a needsDraw called during a draw to schedule the next draw
                // TODO: add the possibility to display = "none" the body during development (IKXARIA-3631).
                for (i = j-1; i >= 0; i--) {
                    component = needsDrawList[i];
                    component._draw(this._frameTime);
                    component.draw(this._frameTime);
                    if (isDrawLoggerDebug) {
                        drawLogger.debug("Level " + component._treeLevel + " " + loggerToString(component));
                    }
                }
                //
                // didDraw
                //
                if (isDrawLoggerDebug) {
                    console.groupEnd();
                    console.groupCollapsed("didDraw - " + needsDrawList.length +
                                        (needsDrawList.length > 1 ? " components." : " component."));
                }
                if (!this._didDrawEvent) {
                    this._didDrawEvent = new CustomEvent("didDraw", {
                        bubbles: false
                    });
                }
                for (i = 0; i < j; i++) {
                    component = needsDrawList[i];
                    component.dispatchEvent(this._didDrawEvent);
                    component.didDraw(this._frameTime);
                    if (!component._completedFirstDraw) {
                        firstDrawEvent = document.createEvent("CustomEvent");
                        firstDrawEvent.initCustomEvent("firstDraw", true, false, null);
                        component.dispatchEvent(firstDrawEvent);
                        component._completedFirstDraw = true;
                    }
                    if (isDrawLoggerDebug) {
                        drawLogger.debug("Level " + component._treeLevel + " " + loggerToString(component));
                    }
                }

                //Now root Component:
                if (!this._completedFirstDraw) {
                    firstDrawEvent = document.createEvent("CustomEvent");
                    firstDrawEvent.initCustomEvent("firstDraw", true, false, null);
                    this.dispatchEvent(firstDrawEvent);
                    this._completedFirstDraw = true;
                }

                if (isDrawLoggerDebug) {
                    console.groupEnd();
                }

                if (drawPerformanceLogger.isDebug) {
                    this._lastDrawComponentsCount = needsDrawList.length;
                }

                return !!needsDrawList.length;
            }
        },

        /**
         * @private
         * @type {DOMElement}
         * @default null
         */
        element: {
            get:function () {
                return this._element;
            },
            set:function (document) {
                defaultEventManager.registerEventHandlerForElement(this, document);
                this._element = document.documentElement;
                this._documentResources = DocumentResources.getInstanceForDocument(document);
            }
        }
    });

    exports.__root__ = rootComponent = new RootComponent().init();

    //https://github.com/kangax/html-minifier/issues/63
    //http://www.w3.org/TR/html-markup/global-attributes.html
    Component.addAttributes( /** @lends module:montage/ui/control.Control# */ {

    /**
        Specifies the shortcut key(s) that gives focuses to or activates the element.
        @see {@link http://www.w3.org/TR/html5/editing.html#the-accesskey-attribute}
        @type {string}
        @default null
    */
        accesskey: null,

    /**
        Specifies if the content is editable or not. Valid values are "true", "false", and "inherit".
        @see {@link http://www.w3.org/TR/html5/editing.html#contenteditable}
        @type {string}
        @default null

    */
        contenteditable: null,

    /**
        Specifies the ID of a <code>menu</code> element in the DOM to use as the element's context menu.
        @see  {@link http://www.w3.org/TR/html5/interactive-elements.html#attr-contextmenu}
        @type {string}
        @default null
    */
        contextmenu: null,

    /**
        Specifies the elements element's text directionality. Valid values are "ltr", "rtl", and "auto".
        @see {@link http://www.w3.org/TR/html5/elements.html#the-dir-attribute}
        @type {string}
        @default null
    */
        dir: null,

    /**
        Specifies if the element is draggable. Valid values are "true", "false", and "auto".
        @type {string}
        @default null
        @see {@link http://www.w3.org/TR/html5/dnd.html#the-draggable-attribute}
    */
        draggable: null,

    /**
        Specifies the behavior that's taken when an item is dropped on the element. Valid values are "copy", "move", and "link".
        @type {string}
        @see {@link http://www.w3.org/TR/html5/dnd.html#the-dropzone-attribute}
    */
        dropzone: null,

    /**
        When specified on an element, it indicates that the element should not be displayed.
        @type {boolean}
        @default false
    */
        hidden: {dataType: 'boolean'},
        //id: null,

    /**
        Specifies the primary language for the element's contents and for any of the element's attributes that contain text.
        @type {string}
        @default null
        @see {@link http://www.w3.org/TR/html5/elements.html#attr-lang}
    */
        lang: null,

    /**
        Specifies if element should have its spelling and grammar checked by the browser. Valid values are "true", "false".
        @type {string}
        @default null
        @see {@link http://www.w3.org/TR/html5/editing.html#attr-spellcheck}
    */
        spellcheck: null,

    // /**
    //     The CSS styling attribute.
    //     @type {string}
    //     @default null
    //     @see {@link http://www.w3.org/TR/html5/elements.html#the-style-attribute}
    // */
    //     style: null,

    /**
         Specifies the relative order of the element for the purposes of sequential focus navigation.
         @type {number}
         @default null
         @see {@link http://www.w3.org/TR/html5/editing.html#attr-tabindex}
    */
        tabindex: null,

    /**
        Specifies advisory information about the element, used as a tooltip when hovering over the element, and other purposes.
        @type {string}
        @default null
        @see {@link http://www.w3.org/TR/html5/elements.html#the-title-attribute}
    */
        title: null
    });