(function() {
    var logger = ST.logger.forClass('inspector/Inspect'),
        debug = ST.debug,
        instance;
 
    ST.inspector = {};
 
    ST.inspector.Inspect = ST.define({
        socketEventQueue: [],
        componentTree: null,
        componentTreeTimeStamp: null,
 
        statics: {
            getInstance: function () {
                if (!instance) {
                    instance = new ST.inspector.Inspect();
                    instance.initInspect();
                }
                return instance;
            }
        },
 
        hasExt: function () {
            return !!(Ext && Ext.define);
        },
 
        getClassName: function (node) { 
            var className = '';
 
            // let's make sure it's actually an element, and not some other fragment 
            if (node && node.nodeType === 1) { 
                // we'll use getAttribute() so that things like SVG elements won't bomb 
                className = node.getAttribute('class');
            }
 
            return className;
        },
 
        initInspect: function() {
            /**
             * setup css styles for highlighter (from themerInspect.js)
             */
            var styles = document.createElement('style');
            styles.innerHTML =
                '.c-highlighter-hover { border: 1px dashed #FF8080; z-index: 99998; }' +
                '.c-highlighter-select { border: 1px solid #FF8080; z-index: 99999 !important; }' +
                '.c-highlighter-query { border: 1px solid #FF4040; z-index: 99999 !important; }' +
                '.c-highlighter-onematch { border: 2px solid #40FF40; z-index: 99999 !important; }' +
                '.c-highlighter-hover, .c-highlighter-query, .c-highlighter-select ' +
                '{ box-shadow: none !important; background-color: white; position: absolute !important;}';
            document.head.insertBefore(styles, document.head.firstChild);
 
            this.Highlighter = function (type) {
                var cls = 'c-highlighter-' + (type || 'hover') + ' c-no-inspect';
 
                var createFn = function (cfg) {
                    var el = document.createElement('DIV')
                    el.style.position = 'absolute';
                    el.style.top = 0;
                    el.style.left = 0;
                    el.setAttribute('class', cls);
 
                    for (var key in cfg) {
                        el.style[key] = cfg[key];
                    }
 
                    return document.body.appendChild(el);
                };
 
                this.target = undefined;
                this.topEl      = createFn({height: '2px'});
                this.bottomEl   = createFn({height: '2px'});
                this.leftEl     = createFn({width: '2px'});
                this.rightEl    = createFn({width: '2px'});
            }
 
            this.Highlighter.prototype = {
                toggle: function (direction) {
                    var event = function (target) {
                        target.style.display = direction === 'hide' ? 'none' : 'inline';
                    }
 
                    event(this.topEl);
                    event(this.bottomEl);
                    event(this.leftEl);
                    event(this.rightEl);
                },
 
                hide: function() {
                    this.toggle('hide');
                },
 
                show: function() {
                    this.toggle('show');
                },
 
                /**
                 * Highlights a specific element
                 * @param {HTMLElement} [target] The element to highlight.  If null the highlighter will be hidden.
                 */
                highlight: function(target) {
                    var me = this;
 
                    me.target = target;
 
                    if (!target) {
                        me.hide();
                        return;
                    }
 
                    me.show();
 
                    var target = ST.fly(target),
                        rect = me.getRect(target.dom),
                        xy = [rect.left, rect.top];
 
                    me.setXY(me.topEl, [xy[0] - 4, xy[1] - 4]);
                    me.setWidth(me.topEl, rect.width + 8);
 
                    me.setXY(me.bottomEl, [xy[0] - 4, xy[1] + rect.height + 2]);
                    me.setWidth(me.bottomEl, rect.width + 8);
 
                    me.setXY(me.leftEl, [xy[0] - 4, xy[1] - 4]);
                    me.setHeight(me.leftEl, rect.height + 8);
 
                    me.setXY(me.rightEl, [xy[0] + rect.width + 2, xy[1] - 4]);
                    me.setHeight(me.rightEl, rect.height + 8);
                },
 
                destroy: function () {
                    var me = this;
 
                    me.topEl.parentNode.removeChild(me.topEl);
                    me.bottomEl.parentNode.removeChild(me.bottomEl);
                    me.leftEl.parentNode.removeChild(me.leftEl);
                    me.rightEl.parentNode.removeChild(me.rightEl);
                    delete me.topEl;
                    delete me.bottomEl;
                    delete me.leftEl;
                    delete me.rightEl;
                },
 
                reHighlight: function() {
                    this.highlight(this.target);
                },
 
                getRect: function (target) {
                    var rect = target.getBoundingClientRect(),
                        opts = ST.apply({}, rect);
 
 
                    opts.left = rect.left + window.pageXOffset;
                    opts.top = rect.top + window.pageYOffset;
 
                    return opts;
                },
 
                getY: function (target) {
                    return target.scrollTop;
                },
 
                getX: function (target) {
                    return target.scrollLeft;
                },
 
                setXY: function (target, xy) {
                    target.style.left = xy[0] + 'px';
                    target.style.top = xy[1] + 'px';
                },
 
                setWidth: function (target, width) {
                    target.style.width = width + 'px';
                },
 
                setHeight: function (target, height) {
                    target.style.height = height + 'px';
                }
            };
 
            this.watchComponentTree();
            this.inspectEnabled = true;
            this.strategy = new ST.locator.Strategy();
 
            this.initElementSelectors();
 
            function addEvent(obj, evt, fn) {
                if (obj.addEventListener) {
                    obj.addEventListener(evt, fn, false);
                }
                else if (obj.attachEvent) {
                    obj.attachEvent("on" + evt, fn);
                }
            }
            // Toggle inspect on 'CmdOrCtrl+I' inside the app directly. 
            addEvent(document, 'keydown', function(e) {
                if (((Ext.isMac && e.metaKey) || (!Ext.isMac && e.ctrlKey)) && e.key === 'i') {
                    this.toggleInspectEnabled({value: !this.inspectEnabled});
                    e.preventDefault();
                    e.stopPropagation();
                }
            }.bind(this));
 
            addEvent(document, 'mouseout', function(e) {
                e = e || window.event;
                var from = e.relatedTarget || e.toElement;
                if (!from || from.nodeName === 'HTML') {
                    this.hoverHighlighter.hide();
                }
            }.bind(this));
 
            addEvent(document, 'mouseover', function(e) {
                if (this.inspectEnabled) {
                    this.hoverHighlighter.show();
                }
            }.bind(this));
        },
 
        /**
         * Watches the component tree for changes
         */
        watchComponentTree: function () {
            var me = this;
 
            try {
                new MutationObserver(function(mutations) {
                    var changed = 0,
                        m, mutation, i, node, className;
 
                    for (= 0; m < mutations.length; m++) {
                        mutation = mutations[m];
                        if (mutation.addedNodes.length) {
                            for (i=0; i<mutation.addedNodes.length; i++) {
                                node = mutation.addedNodes[i];
                                className = me.getClassName(node);
                                if (className && className.indexOf('c-no-inspect') != -1) {
                                    // do nothing                                     
                                } else {
                                    changed++;
                                }
                            }
                        }
                        if (mutation.removedNodes.length) {
                            for (i=0; i<mutation.removedNodes.length; i++) {
                                node = mutation.removedNodes[i];
                                className = me.getClassName(node);
                                if (className && className.indexOf('c-no-inspect') != -1) {
                                    // do nothing 
                                } else {
                                    changed++;
                                }
                            }
                        }
                    }
                    if (changed) {
                        console.log('mutationobserver found a change, null out componentTree');
                        // TODO do the same for domTree 
                        this.componentTree = null;
                    }
                }.bind(this)).observe(document.body, {
                    childList: true,
                    subtree: true
                });
            } catch (e) {
                // expected for some older browsers, don't complain 
            }
        },
 
        /**
         * toggles highlight on a component in a viewer or app upon hovering a node in the tree
         * @param {Object} data - node data
         */
        toggleHighlight : function (data) {
            var cmpId = data.cmpId;
            if (cmpId) {
                var cmp = Ext.get(data.cmpId);
                if (cmp) {
                    data.highlight ? this.hoverHighlighter.highlight(cmp) : this.hoverHighlighter.hide(cmp);
                }
            }
        },
 
        socketEmit: function(event, data) {
            var me = this;
 
            if(me.socket) {
                me.socket.emit(event, data);
            } else if (window._inspectCallback) {
                window._inspectCallback({event: event, data: data});
            } else {
                me.socketEventQueue.push({event: event, data: data});
            }
        },
 
        privates: {
            /**
             * The currently selected element
             * @property {HTMLElement}
             */
            selectedEl: null,
 
            /**
             * @property {Boolean}
             * True if the inspect toggle button is checked
             */
            inspectEnabled: false
        },
 
        // if target is a component then get it's element, otherwise just return the target 
        getElement: function (target) {
            if (target.isElement) {
                return target;
            }
            if (this.hasExt()) {
                if (Ext.versions.touch) {
                    return target.element || target;
                } else {
                    return target.el || target;
                }
            } else {
                return target;
            }
        },
 
        /**
         * Creates the element selectors for inspect mode
         */
        initElementSelectors: function() {
            var me = this;
 
            me.selectHighlighter = new me.Highlighter('select');
            me.hoverHighlighter = new me.Highlighter('hover');
 
            document.body.addEventListener('mouseover', function(event) {
                if (!me.inspectEnabled) return;
                var target = event.target,
                    fly = ST.fly(target),
                    cmp = fly && fly.getComponent();
 
                if (cmp) {
                    target = me.getElement(cmp);
                }
 
                me.hoverEl = target;
 
                if (target && !ST.fly(target).hasCls('c-no-inspect')) {
                    me.hoverHighlighter.highlight(target);
                } else {
                    me.hoverHighlighter.hide();
                    target = null;
                }
            });
 
            // prevent itemtap events in modern when inspect is enabled 
            document.body.addEventListener('mouseup', function(event) {
                if (me.inspectEnabled) event.stopPropagation();
            }, true);
 
            document.body.addEventListener('click', function(event) {
                var fly = ST.fly(me.hoverEl),
                    target, xpath;
 
                if (fly) { 
                    target = fly.getComponent() || fly;
                }
                
                if (me.inspectEnabled && me.hoverEl && target && ((target.hasCls && !target.hasCls('c-no-inspect')) || !target.hasCls)) {
                    xpath = target.isElement ? fly.getXPath() : null;
                    var msg = {
                        componentTree: JSON.stringify(me.getComponentTree()),
                        domTree: JSON.stringify(me.getDomTree()),
                        cmpId: target.id || xpath,
                        xpath: xpath,
                        url: window.location.toString()
                    };
                    me.socketEmit('inspectEvent', msg);
 
                    me.selectHighlighter.highlight(me.hoverEl);
                    me.highlightQuery(); // remove existing query highlights 
                    event.stopPropagation(); // don't bubble 
                    event.preventDefault(); // don't do the default!!! 
                }
            }, true);
        },
 
        matchesDomSelector: function(el) {
            for (var selector in this.inspectData.domSelectors) {
                if (el.matches(selector)) {
                    return true;
                }
            }
        },
 
        /**
         * Turns inspection on or off
         * @param {Boolean} enabled 
         * @param {Object} inspectData The contents of inspect.json
         */
        toggleInspectEnabled: function(data) {
            var me = this;
 
            me.inspectEnabled = data.value;
 
            if (!me.inspectEnabled) {
                me.highlightQuery();
                me.hoverHighlighter.hide();
            }
            me.socketEmit('inspectEnabled',{
                inspectEnabled: me.inspectEnabled
            });
        },
 
        batchQuery: function (locators) {
            var me = this,
                i, results, result, data;
 
            for (i=0; i<locators.length; i++) {
                data = locators[i];
                // set defaults for xtype and futureType in case errors occur 
                data.xtype = data.xtype || 'Unavailable';
                data.futureType = data.futureType || data.type;
 
                try {
                    results = ST.Locator.findAll(data.locator, true /* wrap */, null, null, true /* returnComponents */);
 
                    if (results.length == 1) {
                        result = results[0];
 
                        if (!result) {
                            data.matches = 0;
                        } else {
                            data.matches = 1;
                            if (result.isElement) {
                                ST.apply(data, me.getFullNodeForElement(result.dom));
                            } else if (result.isComponent || result.isWidget) {
                                ST.apply(data, me.getCompNodeForComp(result));
                            } else {
                                data.error = 'result was not an element, component or widget';
                            }
                            if (data.altLocator) {
                                results = ST.Locator.findAll(data.altLocator, true, null, null, true);
                                data.matches = results ? results.length : 0;
                                data.error = results && (results.length > 1) && (results.length + ' matches');    
                            }
                        }
                    } else {
                        data.error = results.length + ' matches';
                        data.matches = results.length;
                    }
                } catch (e) {
                    data.error = e.toString();
                }
            }
 
            return locators;
        },
 
        /**
         * @param query - if null, remove previous highlights and don't highlight anything
         */
        highlightQuery: function (query) {
            var me = this,
                lights = me.queryHighlights || [],
                highlightType = 'query',
                results, visibleCount = 0,
                hasExt = me.hasExt();
 
            me.selectHighlighter.hide(); // less confusing if we remove the last clicked item 
 
            for (var i in lights) {
                lights[i].hide();
                lights[i].destroy();
            }
            me.queryHighlights = lights = [];
 
            if (!query) {
                return { matches: 0, visibleCount: 0 }; // don't highlight anything 
            }
 
            try {
                results = ST.Locator.findAll(query, true);
            } catch (e) {
                return {
                    matches: 0,
                    visibleCount: 0,
                    error: e.toString()
                }
            }
            
 
            if (results.length === 1) {
                highlightType = 'onematch';
            }
 
            if (results.length > 30) {
                return {
                    matches: results.length,
                    visibleCount: visibleCount,
                    tooMany: true
                }
            }
 
            for (var i in results) {
                if (!results.hasOwnProperty(i)) continue
                var result = results[i],
                    el = me.getElement(result),
                    light = el && el.dom && new this.Highlighter(highlightType);
 
                if (!light) {
                    continue
                }
 
                if (!el) {
                    light.destroy();
                    continue
                }
 
                if (result.isVisible && result.isHidden) {
                    visibleCount += Ext.toolkit === 'classic' ? result.isVisible() : !result.isHidden(true);
                } else if (el.isVisible && el.dom) {
                    visibleCount += el.isVisible();
                }
 
                lights.push(light);
                light.highlight(el.dom);
            }
 
            return {
                matches: results.length,
                xtype: results.length === 1 ? results[0].xtype : '',
                cmpId: results.length === 1 ? results[0].cmpId : '',
                visibleCount: visibleCount
            };
        },
 
        /**
         * Extracts data for the component tree
         */
        getComponentTree: function() {
            var me = this;
 
            if (!me.componentTree) {
                if (!Ext || !Ext.ComponentManager) {
                    return []; // no Ext so return empty set 
                }
 
                if (Ext.ComponentManager.getAll) {
                    me.componentTree = me.getComponentTreeNodes(Ext.ComponentManager.getAll());
                } else {
                    me.componentTree = me.getComponentTreeNodes(Ext.ComponentManager.all.getArray());
                }
                me.componentTreeTimeStamp = new Date().getTime();
            }
 
            return me.componentTree;
        },
 
        getDomTree: function (node, parentxpath) {
            var me = this,
                nodes = [],
                children = [],
                node = node || document.body,
                blackList = ['style', 'script'],
                xpath = parentxpath || '//body',
                tagMap = {},
                nodeData, child, childCls, noInspect, childFragment;
 
            nodeData = me.getNodeForElement(node);
            nodeData.xpath = parentxpath;
            nodeData.cmpId = parentxpath;
 
            if (node.hasChildNodes()) {
                for (var j = 0; j < node.childNodes.length; j++) {
                    child = node.childNodes[j];
                    childCls = me.getClassName(child);
                    noInspect = typeof childCls === 'string' && childCls.indexOf('c-no-inspect') !== -1;
 
                    if (child.nodeType === 1
                        && blackList.indexOf(child.tagName.toLowerCase()) === -1
                        && !noInspect
                    ) {
                        tagMap[child.tagName] = tagMap[child.tagName] + 1 || 1;
                        childFragment = child.tagName.toLowerCase() + '[' + tagMap[child.tagName] + ']';
                        children.push(me.getDomTree(child, xpath + '/' + childFragment));
                    }
                }
 
                if (children.length) {
                    nodeData.children = children;
                } else {
                    nodeData.leaf = true;
                }
 
                return nodeData;
            } else {
                nodeData.leaf = true;
            }
 
            return nodeData;
            // getNodeForElement 
        },
 
        /**
         * recursive function to loop through items / docked items and children. compiles master list of components.
         * @param {Array} comps - app / viewer components
         */
        getComponentTreeNodes: function(comps) {
            var me = this,
                tree = [],
                inTree = {};
            if (!comps) return;
 
            if (!Ext.isArray(comps)) {
                comps = [comps];
            }
 
            var addCompToTree = function(comp, child) {
                var node = inTree[comp.id];
                if (node) {
                    if (child) {
                        node.children.push(child);
                        delete node.leaf;
                    }
                    return;
                }
 
                node = me.getCompNodeForComp(comp);
                inTree[comp.id] = node;
 
                if (child) {
                    node.children.push(child);
                } else {
                    node.leaf = true;
                }
 
                var parent = comp.up && comp.up();
                if (parent) {
                    addCompToTree(parent, node);
                } else {
                    tree.push(node);
                }
            }
 
 
            Ext.each(comps, function(comp) {
                // don't show unused rows in modern buffered grids 
                if (Ext.grid && Ext.grid.Row && comp instanceof Ext.grid.Row && comp.$hidden) return;
 
                // don't show the highlighters 
                if (comp.hideFromComponentTree) return;
 
                addCompToTree(comp);
            });
 
            return tree;
        },
 
        getNodeForElement: function (el) {
            var tag = el.tagName && el.tagName.toLowerCase(),
                text = '&lt;' + tag,
                className = this.getClassName(el);
 
            text += className ? ' class="' + className + '"' : '';
            text += el.id ? ' id="' + el.id + '"' : '';
            text += '&gt;&lt;/' + tag + '&gt;';
 
            return {
                text: text,
                xtype: tag,
                futureType: tag && tag === 'table' ? 'table' : 'element'
            };
        },
 
        getFullNodeForElement: function (el) {
            var me = this,
                data = me.getNodeForElement(el),
                targets = [],
                hasAttributes = el && el.hasAttributes ? el.hasAttributes() : false,
                fly = ST.fly(el),
                attributes, i, locator, xpath;
 
            data.xpath = fly.getXPath();
            me.strategy.locate(el, targets, null /* event */, true /* noComponents */);
            data.locators = [];
 
            for (in targets) {
                var item = {};
                locator = targets[i];
                fly = ST.fly(locator[0]);
                xpath = fly.getXPath();
                locator = targets[i];
 
                ST.apply(item, {
                    name: 'locator',
                    cmpId: xpath,
                    xpath: xpath,
                    value: locator[1],
                });
                data.locators.push(item);
            }
 
            data.properties = [];
            attributes = hasAttributes ? el.attributes : [];
 
            for (var x = 0; x < attributes.length; x++) {
                node = attributes[x];
 
                data.properties.push({
                    name: node.name,
                    value: node.value
                });
            }
 
            return data;
        },
 
        /**
         * returns an active ui for a component
         * @param {Ext.Component} component - ext component
         */
        getActiveUI : function (comp) {
            if (Ext.list && Ext.list.TreeItem && comp instanceof Ext.list.TreeItem) {
                return this.getActiveUI(comp.parent); // the UI is defined for the tree item, but configured on the treelist 
            }
 
            return comp.getUi ? comp.getUi() : comp.getUI ? comp.getUI() : comp.activeUI;
        },
 
        /**
         * retrieves best match for future api component type.
         * @param {Component} comp - component
         * @return {String} futureType 
         */
        getFutureAPIMatch: function (comp) {
            var mappings = [{
                future: 'button',
                matchers: ['button']
            }, {
                future: 'checkBox',
                matchers: ['checkboxfield']
            }, {
                future: 'select',
                matchers: ['selectfield']
            }, {
                future: 'comboBox',
                matchers: ['combobox']
            }, {
                future: 'picker',
                matchers: ['pickerfield']
            }, {
                future: 'textField',
                matchers: ['textfield']
            }, {
                future: 'slider',
                matchers: ['sliderfield', 'multislider']
            }, {
                future: 'field',
                matchers: ['field']
            }, {
                future: 'grid',
                matchers: ['grid']
            }, {
                future: 'dataView',
                matchers: ['dataview']
            }, {
                future: 'panel',
                matchers: ['panel']
            }, {
                future: 'component',
                matchers: ['component']
            }];
 
            if (!comp.getXTypes) {
                return 'component';
            }
 
            var list = comp.getXTypes().split('/').reverse(),
                match, list, matchers, map, i, x;
 
            for (i=0; i<mappings.length; i++) {
                map = mappings[i];
                matchers = map.matchers;
 
                for (x=0; x<matchers.length; x++) {
                    if (Ext.Array.contains(list, matchers[x])) {
                        match = map.future;
                        break;
                    }
                }
 
                if (match) {
                    break;
                }
            }
 
            return match || 'component';
        },
 
        /**
         * retrieves component details.
         * @param {Component} comp - component
         */
        getCompNodeForComp : function (comp) {
            var me = this,
                propMatrix = {
                    'component': ['itemId', 'userCls', 'ui'],
                    'field': ['label', 'value', 'name'],
                    'textfield': ['autoComplete', 'readOnly'],
                    'treelistitem': ['text'],
                    'button': ['text']
                },
                ignoreList = ['plugins', 'frameIdRegex', 'idCleanRegex', 'validIdRe'],
                xtype = comp.xtype,
                xtypes,
                properties,
                propNames = {}, // keep track of property names already handled 
                locators = [], targets = [],
                el, config, data; // collect config properties 
 
            if (ST && this.getElement(comp)) {
                el = this.getElement(comp);
 
                try {
                    this.strategy.locate(el.dom, targets, null /* no event available */);
                } catch (e) {
                    console.log(e);
                }
                for(var x in targets) {
                    var target = targets[x];
                    locators.push({
                        name: 'locator',
                        cmpId: target[0].id || '',
                        value: target[1]
                    });
                }
            }
 
            if (comp.getXTypes) {
                xtypes = comp.getXTypes().split('/');
            } else {
                xtypes = [xtype];
            }
 
            propNames.id = true;
            properties = [{
                name: 'id',
                value: comp.id
            }];
 
            for (var i=0; i < xtypes.length; i++) {
                var xtype = xtypes[i],
                    props = propMatrix[xtype];
 
                if (props) {
                    for (var j=0; j < props.length; j++) {
                        var propName = props[j],
                            value = comp[propName];
 
                        if (typeof value === 'undefined' && propName !== 'itemId') {
                            var getter = 'get' + propName[0].toUpperCase() + (propName.length > 1 ? propName.slice(1) : '');
                            value = comp[getter] && comp[getter].apply(comp);
                        }
 
                        if (typeof value !== 'undefined' && value !== null) { // !== null??? 
                            propNames[propName] = true; // keep track so we don't repeat them below when parsing comp.config 
                            properties.push({
                                name: propName,
                                value: value
                            })
                        }
                    }
                }
            }
 
            // stolen from Dan Gallo's Google Chrome extension 
            config = comp.config || {};
            data = {};
 
            for (var key in config) {
                if (config.hasOwnProperty(key)) {
                    var value = config[key];
                    // skip all tpl and xyzTpl 
                    if (/(^tpl)|(.*Tpl)$/.test(key)) {
                        continue;
                    }
 
                    if (value != null
                        && typeof value != 'undefined'
                        && !Ext.Array.contains(ignoreList, key)
                        && !Ext.isEmpty(value)
                        && !Ext.isObject(value)
                        && !Ext.isArray(value)
                        && !Ext.isFunction(value)
                        && !propNames[key]) {
                        properties.push({
                            name: key,
                            value: value.toString()
                        });
                        // data[key] = value.toString(); 
                    }
                }
            }
 
            xtype = comp.$reactorComponentName || xtype;
 
            return {
                cmpId: comp.id || '',
                xtype: xtype,
                text: xtype,
                properties: properties,
                children: [],
                xtypes: xtypes,
                locators: locators,
                futureType: this.getFutureAPIMatch(comp)
            };
        },
 
        /**
         * Returns the name of the Ext JS class from which the component extends.
         * @param {Ext.Base} comp
         * @return {String}
         */
        getClassForComp: function(comp) {
            var proto = comp.__proto__;
 
            while (proto && proto.$className.indexOf('Ext.') !== 0) {
                proto = proto.__proto__;
            }
 
            return proto;
        },
 
        initPolyfills: function() {
            // Element.matches 
            if (!Element.prototype.matches) {
                Element.prototype.matches =
                    Element.prototype.matchesSelector ||
                    Element.prototype.mozMatchesSelector ||
                    Element.prototype.msMatchesSelector ||
                    Element.prototype.oMatchesSelector ||
                    Element.prototype.webkitMatchesSelector ||
                    function(s) {
                        var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                            i = matches.length;
                        while (-->= 0 && matches.item(i) !== this) {}
                        return i > -1;
                    };
            }
        }
    });
}());