<commit_before>/*
 * Copyright (C) 2014 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @constructor
 * @implements {WebInspector.FlameChartDataProvider}
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 */
WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
{
    WebInspector.FlameChartDataProvider.call(this);
    this._model = model;
    this._frameModel = frameModel;
    this._font = "bold 12px " + WebInspector.fontFamily();
    this._linkifier = new WebInspector.Linkifier();
}

WebInspector.TimelineFlameChartDataProvider.prototype = {
    /**
     * @return {number}
     */
    barHeight: function()
    {
        return 20;
    },

    /**
     * @return {number}
     */
    textBaseline: function()
    {
        return 6;
    },

    /**
     * @return {number}
     */
    textPadding: function()
    {
        return 5;
    },

    /**
     * @param {number} entryIndex
     * @return {string}
     */
    entryFont: function(entryIndex)
    {
        return this._font;
    },

    /**
     * @param {number} entryIndex
     * @return {?string}
     */
    entryTitle: function(entryIndex)
    {
        var record = this._records[entryIndex];
        if (record === this._cpuThreadRecord)
            return WebInspector.UIString("CPU");
        else if (record === this._gpuThreadRecord)
            return WebInspector.UIString("GPU");
        var details = WebInspector.TimelineUIUtils.buildDetailsNode(record, this._linkifier, this._model.loadedFromFile());
        return details ? WebInspector.UIString("%s (%s)", record.title(), details.textContent) : record.title();
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     * @return {?Array.<number>}
     */
    dividerOffsets: function(startTime, endTime)
    {
        if (endTime - startTime < 16 || endTime - startTime > 300)
            return null;

        var frames = this._frameModel.filteredFrames(startTime, endTime);
        if (frames.length > 10)
            return null;

        if (frames.length < 3)
            return null;

        var offsets = [];
        for (var i = 0; i < frames.length; ++i)
            offsets.push(frames[i].startTime);
        // Push one more offset so grid will be able to calculate the duration for the last frame.
        offsets.push(frames.peekLast.endTime)
        return offsets;
    },

    reset: function()
    {
        this._timelineData = null;
    },

    /**
     * @return {!WebInspector.FlameChart.TimelineData}
     */
    timelineData: function()
    {
        if (this._timelineData)
            return this._timelineData;

        this._linkifier.reset();

        /**
         * @type {?WebInspector.FlameChart.TimelineData}
         */
        this._timelineData = {
            entryLevels: [],
            entryTotalTimes: [],
            entryOffsets: []
        };

        this._records = [];
        this._entryThreadDepths = {};
        this._zeroTime = this._model.minimumRecordTime();

        var cpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
        this._cpuThreadRecord = new WebInspector.TimelineModel.Record(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (cpuThreadRecordPayload), null);
        this._pushRecord(this._cpuThreadRecord, 0, this.zeroTime(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.zeroTime()));

        var gpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
        this._gpuThreadRecord = new WebInspector.TimelineModel.Record(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (gpuThreadRecordPayload), null);
        this._pushRecord(this._gpuThreadRecord, 0, this.zeroTime(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.zeroTime()));

        var records = this._model.records();
        for (var i = 0; i < records.length; ++i) {
            var record = records[i];
            var thread = record.thread;
            if (thread === "gpu")
                continue;
            if (!thread) {
                for (var j = 0; j < record.children.length; ++j)
                    this._appendRecord(record.children[j], 1);
            } else {
                this._appendRecord(records[i], 1);
            }
        }

        var cpuStackDepth = Math.max(4, this._entryThreadDepths[undefined]);
        delete this._entryThreadDepths[undefined];

        var threadBaselines = {};
        var threadBaseline = cpuStackDepth + 2;

        for (var thread in this._entryThreadDepths) {
            threadBaselines[thread] = threadBaseline;
            threadBaseline += this._entryThreadDepths[thread];
        }
        this._maxStackDepth = threadBaseline;

        for (var i = 0; i < this._records.length; ++i) {
            var record = this._records[i];
            var level = this._timelineData.entryLevels[i];
            if (record === this._cpuThreadRecord)
                level = 0;
            else if (record === this._gpuThreadRecord)
                level = cpuStackDepth + 2;
            else if (record.thread)
                level += threadBaselines[record.thread];
            this._timelineData.entryLevels[i] = level;
        }
        return this._timelineData;
    },

    /**
     * @return {number}
     */
    zeroTime: function()
    {
        return this._zeroTime;
    },

    /**
     * @return {number}
     */
    totalTime: function()
    {
        return Math.max(1000, this._model.maximumRecordTime() - this._model.minimumRecordTime());
    },

    /**
     * @return {number}
     */
    maxStackDepth: function()
    {
        return this._maxStackDepth;
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     * @param {number} level
     */
    _appendRecord: function(record, level)
    {
        if (!this._model.isVisible(record)) {
            for (var i = 0; i < record.children.length; ++i)
                this._appendRecord(record.children[i], level);
            return;
        }

        this._pushRecord(record, level, record.startTime, record.endTime);
        for (var i = 0; i < record.children.length; ++i)
            this._appendRecord(record.children[i], level + 1);
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     * @param {number} level
     * @param {number} startTime
     * @param {number} endTime
     * @return {number}
     */
    _pushRecord: function(record, level, startTime, endTime)
    {
        var index = this._records.length;
        this._records.push(record);
        this._timelineData.entryOffsets[index] = startTime - this._zeroTime;
        this._timelineData.entryLevels[index] = level;
        this._timelineData.entryTotalTimes[index] = endTime - startTime;
        this._entryThreadDepths[record.thread] = Math.max(level, this._entryThreadDepths[record.thread] || 0);
        return index;
    },

    /**
     * @param {number} entryIndex
     * @return {?Array.<!{title: string, text: string}>}
     */
    prepareHighlightedEntryInfo: function(entryIndex)
    {
        return null;
    },

    /**
     * @param {number} entryIndex
     * @return {boolean}
     */
    canJumpToEntry: function(entryIndex)
    {
        return false;
    },

    /**
     * @param {number} entryIndex
     * @return {!string}
     */
    entryColor: function(entryIndex)
    {
        var record = this._records[entryIndex];
        if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
            return "#555";

        var category = WebInspector.TimelineUIUtils.categoryForRecord(record);
        return category.fillColorStop1;
    },

    /**
     * @param {number} entryIndex
     * @param {!CanvasRenderingContext2D} context
     * @param {?string} text
     * @param {number} barX
     * @param {number} barY
     * @param {number} barWidth
     * @param {number} barHeight
     * @param {function(number):number} offsetToPosition
     * @return {boolean}
     */
    decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
    {
        if (barWidth < 5)
            return false;

        var record = this._records[entryIndex];
        var timelineData = this._timelineData;

        var decorated = false;
        if (record.children.length) {
            var category = WebInspector.TimelineUIUtils.categoryForRecord(record);
            // Paint text using white color on dark background.
            if (text) {
                context.fillStyle = "white";
                context.font = this._font;
                context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
            }

            var entryOffset = timelineData.entryOffsets[entryIndex];
            var barSelf = offsetToPosition(entryOffset + record.selfTime)

            context.beginPath();
            context.fillStyle = category.backgroundColor;
            context.rect(barSelf, barY, barX + barWidth - barSelf, barHeight);
            context.fill();

            // Paint text using dark color on light background.
            if (text) {
                context.save();
                context.clip();
                context.fillStyle = category.borderColor;
                context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
                context.restore();
            }

            decorated = true;
        }

        if (record.warnings() || record.childHasWarnings()) {
            context.save();

            context.rect(barX, barY, barWidth, this.barHeight());
            context.clip();

            context.beginPath();
            context.fillStyle = record.warnings() ? "red" : "rgba(255, 0, 0, 0.5)";
            context.moveTo(barX + barWidth - 15, barY + 1);
            context.lineTo(barX + barWidth - 1, barY + 1);
            context.lineTo(barX + barWidth - 1, barY + 15);
            context.fill();

            context.restore();
            decorated = true;
        }

        return decorated;
    },

    /**
     * @param {number} entryIndex
     * @return {boolean}
     */
    forceDecoration: function(entryIndex)
    {
        var record = this._records[entryIndex];
        return record.childHasWarnings() || !!record.warnings();
    },

    /**
     * @param {number} entryIndex
     * @return {?{startTimeOffset: number, endTimeOffset: number}}
     */
    highlightTimeRange: function(entryIndex)
    {
        var record = this._records[entryIndex];
        if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
            return null;
        return {
            startTimeOffset: record.startTime - this._zeroTime,
            endTimeOffset: record.endTime - this._zeroTime
        };
    },

    /**
     * @return {number}
     */
    paddingLeft: function()
    {
        return 0;
    },

    /**
     * @param {number} entryIndex
     * @return {!string}
     */
    textColor: function(entryIndex)
    {
        return "white";
    }
}

/**
 * @constructor
 * @extends {WebInspector.VBox}
 * @implements {WebInspector.TimelineModeView}
 * @implements {WebInspector.FlameChartDelegate}
 * @param {!WebInspector.TimelineModeViewDelegate} delegate
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 */
WebInspector.TimelineFlameChart = function(delegate, model, frameModel)
{
    WebInspector.VBox.call(this);
    this.element.classList.add("timeline-flamechart");
    this.registerRequiredCSS("flameChart.css");
    this._delegate = delegate;
    this._model = model;
    this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(model, frameModel);
    this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true, true);
    this._mainView.show(this.element);
    this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
    this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
}

WebInspector.TimelineFlameChart.prototype = {
    dispose: function()
    {
        this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
        this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
    },

    /**
     * @param {number} windowStartTime
     * @param {number} windowEndTime
     */
    requestWindowTimes: function(windowStartTime, windowEndTime)
    {
        this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
    },

    /**
     * @param {?RegExp} textFilter
     */
    refreshRecords: function(textFilter)
    {
        this._dataProvider.reset();
        this._mainView._scheduleUpdate();
    },

    wasShown: function()
    {
        this._mainView._scheduleUpdate();
    },

    /**
     * @return {!WebInspector.View}
     */
    view: function()
    {
        return this;
    },

    reset: function()
    {
        this._automaticallySizeWindow = true;
        this._dataProvider.reset();
        this._mainView.setWindowTimes(0, Infinity);
    },

    _onRecordingStarted: function()
    {
        this._automaticallySizeWindow = true;
        this._mainView.reset();
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     */
    addRecord: function(record)
    {
        this._dataProvider.reset();
        if (this._automaticallySizeWindow) {
            var minimumRecordTime = this._model.minimumRecordTime();
            if (record.startTime > (minimumRecordTime + 1000)) {
                this._automaticallySizeWindow = false;
                this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
            }
            this._mainView._scheduleUpdate();
        } else {
            if (!this._pendingUpdateTimer)
                this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300);
        }
    },

    _updateOnAddRecord: function()
    {
        delete this._pendingUpdateTimer;
        this._mainView._scheduleUpdate();
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     */
    setWindowTimes: function(startTime, endTime)
    {
        this._mainView.setWindowTimes(startTime, endTime);
    },

    /**
     * @param {number} width
     */
    setSidebarSize: function(width)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     * @param {string=} regex
     * @param {boolean=} selectRecord
     */
    highlightSearchResult: function(record, regex, selectRecord)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     */
    setSelectedRecord: function(record)
    {
        var entryRecords = this._dataProvider._records;
        for (var entryIndex = 0; entryIndex < entryRecords.length; ++entryIndex) {
            if (entryRecords[entryIndex] === record) {
                this._mainView.setSelectedEntry(entryIndex);
                return;
            }
        }
        this._mainView.setSelectedEntry(-1);
    },

    /**
     * @param {!WebInspector.Event} event
     */
    _onEntrySelected: function(event)
    {
        var entryIndex = event.data;
        var record = this._dataProvider._records[entryIndex];
        this._delegate.selectRecord(record);
    },

    __proto__: WebInspector.VBox.prototype
}
<commit_msg>DevTools: mark only text-decorated timeline entries as such.<commit_after>/*
 * Copyright (C) 2014 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @constructor
 * @implements {WebInspector.FlameChartDataProvider}
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 */
WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
{
    WebInspector.FlameChartDataProvider.call(this);
    this._model = model;
    this._frameModel = frameModel;
    this._font = "bold 12px " + WebInspector.fontFamily();
    this._linkifier = new WebInspector.Linkifier();
}

WebInspector.TimelineFlameChartDataProvider.prototype = {
    /**
     * @return {number}
     */
    barHeight: function()
    {
        return 20;
    },

    /**
     * @return {number}
     */
    textBaseline: function()
    {
        return 6;
    },

    /**
     * @return {number}
     */
    textPadding: function()
    {
        return 5;
    },

    /**
     * @param {number} entryIndex
     * @return {string}
     */
    entryFont: function(entryIndex)
    {
        return this._font;
    },

    /**
     * @param {number} entryIndex
     * @return {?string}
     */
    entryTitle: function(entryIndex)
    {
        var record = this._records[entryIndex];
        if (record === this._cpuThreadRecord)
            return WebInspector.UIString("CPU");
        else if (record === this._gpuThreadRecord)
            return WebInspector.UIString("GPU");
        var details = WebInspector.TimelineUIUtils.buildDetailsNode(record, this._linkifier, this._model.loadedFromFile());
        return details ? WebInspector.UIString("%s (%s)", record.title(), details.textContent) : record.title();
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     * @return {?Array.<number>}
     */
    dividerOffsets: function(startTime, endTime)
    {
        if (endTime - startTime < 16 || endTime - startTime > 300)
            return null;

        var frames = this._frameModel.filteredFrames(startTime, endTime);
        if (frames.length > 10)
            return null;

        if (frames.length < 3)
            return null;

        var offsets = [];
        for (var i = 0; i < frames.length; ++i)
            offsets.push(frames[i].startTime);
        // Push one more offset so grid will be able to calculate the duration for the last frame.
        offsets.push(frames.peekLast.endTime)
        return offsets;
    },

    reset: function()
    {
        this._timelineData = null;
    },

    /**
     * @return {!WebInspector.FlameChart.TimelineData}
     */
    timelineData: function()
    {
        if (this._timelineData)
            return this._timelineData;

        this._linkifier.reset();

        /**
         * @type {?WebInspector.FlameChart.TimelineData}
         */
        this._timelineData = {
            entryLevels: [],
            entryTotalTimes: [],
            entryOffsets: []
        };

        this._records = [];
        this._entryThreadDepths = {};
        this._zeroTime = this._model.minimumRecordTime();

        var cpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
        this._cpuThreadRecord = new WebInspector.TimelineModel.Record(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (cpuThreadRecordPayload), null);
        this._pushRecord(this._cpuThreadRecord, 0, this.zeroTime(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.zeroTime()));

        var gpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
        this._gpuThreadRecord = new WebInspector.TimelineModel.Record(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (gpuThreadRecordPayload), null);
        this._pushRecord(this._gpuThreadRecord, 0, this.zeroTime(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.zeroTime()));

        var records = this._model.records();
        for (var i = 0; i < records.length; ++i) {
            var record = records[i];
            var thread = record.thread;
            if (thread === "gpu")
                continue;
            if (!thread) {
                for (var j = 0; j < record.children.length; ++j)
                    this._appendRecord(record.children[j], 1);
            } else {
                this._appendRecord(records[i], 1);
            }
        }

        var cpuStackDepth = Math.max(4, this._entryThreadDepths[undefined]);
        delete this._entryThreadDepths[undefined];

        var threadBaselines = {};
        var threadBaseline = cpuStackDepth + 2;

        for (var thread in this._entryThreadDepths) {
            threadBaselines[thread] = threadBaseline;
            threadBaseline += this._entryThreadDepths[thread];
        }
        this._maxStackDepth = threadBaseline;

        for (var i = 0; i < this._records.length; ++i) {
            var record = this._records[i];
            var level = this._timelineData.entryLevels[i];
            if (record === this._cpuThreadRecord)
                level = 0;
            else if (record === this._gpuThreadRecord)
                level = cpuStackDepth + 2;
            else if (record.thread)
                level += threadBaselines[record.thread];
            this._timelineData.entryLevels[i] = level;
        }
        return this._timelineData;
    },

    /**
     * @return {number}
     */
    zeroTime: function()
    {
        return this._zeroTime;
    },

    /**
     * @return {number}
     */
    totalTime: function()
    {
        return Math.max(1000, this._model.maximumRecordTime() - this._model.minimumRecordTime());
    },

    /**
     * @return {number}
     */
    maxStackDepth: function()
    {
        return this._maxStackDepth;
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     * @param {number} level
     */
    _appendRecord: function(record, level)
    {
        if (!this._model.isVisible(record)) {
            for (var i = 0; i < record.children.length; ++i)
                this._appendRecord(record.children[i], level);
            return;
        }

        this._pushRecord(record, level, record.startTime, record.endTime);
        for (var i = 0; i < record.children.length; ++i)
            this._appendRecord(record.children[i], level + 1);
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     * @param {number} level
     * @param {number} startTime
     * @param {number} endTime
     * @return {number}
     */
    _pushRecord: function(record, level, startTime, endTime)
    {
        var index = this._records.length;
        this._records.push(record);
        this._timelineData.entryOffsets[index] = startTime - this._zeroTime;
        this._timelineData.entryLevels[index] = level;
        this._timelineData.entryTotalTimes[index] = endTime - startTime;
        this._entryThreadDepths[record.thread] = Math.max(level, this._entryThreadDepths[record.thread] || 0);
        return index;
    },

    /**
     * @param {number} entryIndex
     * @return {?Array.<!{title: string, text: string}>}
     */
    prepareHighlightedEntryInfo: function(entryIndex)
    {
        return null;
    },

    /**
     * @param {number} entryIndex
     * @return {boolean}
     */
    canJumpToEntry: function(entryIndex)
    {
        return false;
    },

    /**
     * @param {number} entryIndex
     * @return {!string}
     */
    entryColor: function(entryIndex)
    {
        var record = this._records[entryIndex];
        if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
            return "#555";

        var category = WebInspector.TimelineUIUtils.categoryForRecord(record);
        return category.fillColorStop1;
    },

    /**
     * @param {number} entryIndex
     * @param {!CanvasRenderingContext2D} context
     * @param {?string} text
     * @param {number} barX
     * @param {number} barY
     * @param {number} barWidth
     * @param {number} barHeight
     * @param {function(number):number} offsetToPosition
     * @return {boolean}
     */
    decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
    {
        if (barWidth < 5)
            return false;

        var record = this._records[entryIndex];
        var timelineData = this._timelineData;

        if (record.children.length) {
            var category = WebInspector.TimelineUIUtils.categoryForRecord(record);
            // Paint text using white color on dark background.
            if (text) {
                context.fillStyle = "white";
                context.font = this._font;
                context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
            }

            var entryOffset = timelineData.entryOffsets[entryIndex];
            var barSelf = offsetToPosition(entryOffset + record.selfTime)

            context.beginPath();
            context.fillStyle = category.backgroundColor;
            context.rect(barSelf, barY, barX + barWidth - barSelf, barHeight);
            context.fill();

            // Paint text using dark color on light background.
            if (text) {
                context.save();
                context.clip();
                context.fillStyle = category.borderColor;
                context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
                context.restore();
            }
        }

        if (record.warnings() || record.childHasWarnings()) {
            context.save();

            context.rect(barX, barY, barWidth, this.barHeight());
            context.clip();

            context.beginPath();
            context.fillStyle = record.warnings() ? "red" : "rgba(255, 0, 0, 0.5)";
            context.moveTo(barX + barWidth - 15, barY + 1);
            context.lineTo(barX + barWidth - 1, barY + 1);
            context.lineTo(barX + barWidth - 1, barY + 15);
            context.fill();

            context.restore();
        }

        return record.children.length;
    },

    /**
     * @param {number} entryIndex
     * @return {boolean}
     */
    forceDecoration: function(entryIndex)
    {
        var record = this._records[entryIndex];
        return record.childHasWarnings() || !!record.warnings();
    },

    /**
     * @param {number} entryIndex
     * @return {?{startTimeOffset: number, endTimeOffset: number}}
     */
    highlightTimeRange: function(entryIndex)
    {
        var record = this._records[entryIndex];
        if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
            return null;
        return {
            startTimeOffset: record.startTime - this._zeroTime,
            endTimeOffset: record.endTime - this._zeroTime
        };
    },

    /**
     * @return {number}
     */
    paddingLeft: function()
    {
        return 0;
    },

    /**
     * @param {number} entryIndex
     * @return {!string}
     */
    textColor: function(entryIndex)
    {
        return "white";
    }
}

/**
 * @constructor
 * @extends {WebInspector.VBox}
 * @implements {WebInspector.TimelineModeView}
 * @implements {WebInspector.FlameChartDelegate}
 * @param {!WebInspector.TimelineModeViewDelegate} delegate
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 */
WebInspector.TimelineFlameChart = function(delegate, model, frameModel)
{
    WebInspector.VBox.call(this);
    this.element.classList.add("timeline-flamechart");
    this.registerRequiredCSS("flameChart.css");
    this._delegate = delegate;
    this._model = model;
    this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(model, frameModel);
    this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true, true);
    this._mainView.show(this.element);
    this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
    this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
}

WebInspector.TimelineFlameChart.prototype = {
    dispose: function()
    {
        this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
        this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
    },

    /**
     * @param {number} windowStartTime
     * @param {number} windowEndTime
     */
    requestWindowTimes: function(windowStartTime, windowEndTime)
    {
        this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
    },

    /**
     * @param {?RegExp} textFilter
     */
    refreshRecords: function(textFilter)
    {
        this._dataProvider.reset();
        this._mainView._scheduleUpdate();
    },

    wasShown: function()
    {
        this._mainView._scheduleUpdate();
    },

    /**
     * @return {!WebInspector.View}
     */
    view: function()
    {
        return this;
    },

    reset: function()
    {
        this._automaticallySizeWindow = true;
        this._dataProvider.reset();
        this._mainView.setWindowTimes(0, Infinity);
    },

    _onRecordingStarted: function()
    {
        this._automaticallySizeWindow = true;
        this._mainView.reset();
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     */
    addRecord: function(record)
    {
        this._dataProvider.reset();
        if (this._automaticallySizeWindow) {
            var minimumRecordTime = this._model.minimumRecordTime();
            if (record.startTime > (minimumRecordTime + 1000)) {
                this._automaticallySizeWindow = false;
                this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
            }
            this._mainView._scheduleUpdate();
        } else {
            if (!this._pendingUpdateTimer)
                this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300);
        }
    },

    _updateOnAddRecord: function()
    {
        delete this._pendingUpdateTimer;
        this._mainView._scheduleUpdate();
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     */
    setWindowTimes: function(startTime, endTime)
    {
        this._mainView.setWindowTimes(startTime, endTime);
    },

    /**
     * @param {number} width
     */
    setSidebarSize: function(width)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     * @param {string=} regex
     * @param {boolean=} selectRecord
     */
    highlightSearchResult: function(record, regex, selectRecord)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     */
    setSelectedRecord: function(record)
    {
        var entryRecords = this._dataProvider._records;
        for (var entryIndex = 0; entryIndex < entryRecords.length; ++entryIndex) {
            if (entryRecords[entryIndex] === record) {
                this._mainView.setSelectedEntry(entryIndex);
                return;
            }
        }
        this._mainView.setSelectedEntry(-1);
    },

    /**
     * @param {!WebInspector.Event} event
     */
    _onEntrySelected: function(event)
    {
        var entryIndex = event.data;
        var record = this._dataProvider._records[entryIndex];
        this._delegate.selectRecord(record);
    },

    __proto__: WebInspector.VBox.prototype
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2014 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @constructor
 * @implements {WebInspector.FlameChartDataProvider}
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 * @param {boolean} mainThread
 */
WebInspector.TimelineFlameChartDataProvider = function(model, frameModel, mainThread)
{
    WebInspector.FlameChartDataProvider.call(this);
    this._model = model;
    this._frameModel = frameModel;
    this._mainThread = mainThread;
    this._font = (this.barHeight() - 4) + "px " + WebInspector.fontFamily();

    this._colorGenerator = new WebInspector.FlameChart.ColorGenerator();
    var categories = WebInspector.TimelineUIUtils.categories();
    for (var category in categories)
        this._colorGenerator.setColorForID(category, categories[category].fillColorStop1);
}

WebInspector.TimelineFlameChartDataProvider.prototype = {
    /**
     * @return {number}
     */
    barHeight: function()
    {
        return 15;
    },

    /**
     * @param {number} entryIndex
     * @return {string}
     */
    entryFont: function(entryIndex)
    {
        return this._font;
    },

    /**
     * @param {number} entryIndex
     * @return {?string}
     */
    entryTitle: function(entryIndex)
    {
        return this._entryTitles[entryIndex];
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     * @return {?Array.<number>}
     */
    dividerOffsets: function(startTime, endTime)
    {
        var frames = this._frameModel.filteredFrames(this._model.minimumRecordTime() + startTime, this._model.minimumRecordTime() + endTime);
        if (frames.length > 30)
            return null;
        var offsets = [];
        for (var i = 0; i < frames.length; ++i)
            offsets.push(frames[i].startTimeOffset);
        return frames.length ? offsets : null;
    },

    reset: function()
    {
        this._timelineData = null;
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     */
    addRecord: function(record)
    {
        WebInspector.TimelineModel.forAllRecords([record], this._appendRecord.bind(this));
    },

    /**
     * @return {!WebInspector.FlameChart.ColorGenerator}
     */
    colorGenerator: function()
    {
        return this._colorGenerator;
    },

    /**
     * @return {!WebInspector.FlameChart.TimelineData}
     */
    timelineData: function()
    {
        if (!this._timelineData) {
            this._resetData();
            this._model.forAllRecords(this._appendRecord.bind(this));
            this._zeroTime = this._model.minimumRecordTime();
        }
        return /** @type {!WebInspector.FlameChart.TimelineData} */(this._timelineData);
    },

    /**
     * @return {number}
     */
    zeroTime: function()
    {
        return this._zeroTime;
    },

    /**
     * @return {number}
     */
    totalTime: function()
    {
        return this._totalTime;
    },

    /**
     * @return {number}
     */
    maxStackDepth: function()
    {
        return this._maxStackDepth;
    },

    _resetData: function()
    {
        this._startTime = 0;
        this._endTime = 0;
        this._maxStackDepth = 5;
        this._totalTime = 1000;
        this._zeroTime = 0;

        /**
         * @type {?WebInspector.FlameChart.TimelineData}
         */
        this._timelineData = {
            entryLevels: [],
            entryTotalTimes: [],
            entryOffsets: [],
        };

        /** @type {!Array.<string>} */
        this._entryTitles =  [];

        /** @type {!Array.<string>} */
        this._entryColors = [];
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     * @param {number} depth
     */
    _appendRecord: function(record, depth)
    {
        var timelineData = this.timelineData();

        this._startTime = this._startTime ? Math.min(this._startTime, record.startTime) : record.startTime;
        var startTime = this._startTime;
        this._zeroTime = startTime;
        var endTime = record.endTime || record.startTime - startTime;
        this._endTime = Math.max(this._endTime, endTime);
        this._totalTime = Math.max(1000, this._endTime - this._startTime);

        if (this._mainThread) {
            if (record.type === WebInspector.TimelineModel.RecordType.GPUTask || !!record.thread)
                return;
        } else {
            if (record.type === WebInspector.TimelineModel.RecordType.Program || !record.thread)
                return;
        }

        var index = this._entryTitles.length;
        this._entryTitles[index] = record.type;
        timelineData.entryOffsets[index] = record.startTime - startTime;
        timelineData.entryLevels[index] = depth;
        timelineData.entryTotalTimes[index] = endTime - record.startTime;
        this._entryColors[index] = this._colorGenerator.colorForID(WebInspector.TimelineUIUtils.categoryForRecord(record).name);
        this._maxStackDepth = Math.max(this._maxStackDepth, depth + 1);
    },

    /**
     * @param {number} entryIndex
     * @return {?Array.<!{title: string, text: string}>}
     */
    prepareHighlightedEntryInfo: function(entryIndex)
    {
        return null;
    },

    /**
     * @param {number} entryIndex
     * @return {boolean}
     */
    canJumpToEntry: function(entryIndex)
    {
        return false;
    },

    /**
     * @param {number} entryIndex
     * @return {?Object}
     */
    entryData: function(entryIndex)
    {
        return null;
    },

    /**
     * @param {number} entryIndex
     * @return {!string}
     */
    entryColor: function(entryIndex)
    {
        return this._entryColors[entryIndex];
    }
}

/**
 * @constructor
 * @extends {WebInspector.View}
 * @implements {WebInspector.TimelineModeView}
 * @implements {WebInspector.TimeRangeController}
 * @param {!WebInspector.TimelineModeViewDelegate} delegate
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 * @param {boolean} mainThread
 */
WebInspector.TimelineFlameChart = function(delegate, model, frameModel, mainThread)
{
    WebInspector.View.call(this);
    this._delegate = delegate;
    this._model = model;
    this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(model, frameModel, mainThread);
    this._mainView = new WebInspector.FlameChart.MainPane(this._dataProvider, this, true, true);
    this._mainView.show(this.element);
    this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
}

WebInspector.TimelineFlameChart.prototype = {
    /**
     * @param {number} windowStartTime
     * @param {number} windowEndTime
     */
    requestWindowTimes: function(windowStartTime, windowEndTime)
    {
        this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
    },

    /**
     * @param {?RegExp} textFilter
     */
    refreshRecords: function(textFilter)
    {
        this._dataProvider.reset();
        this._mainView._scheduleUpdate();
    },

    reset: function()
    {
        this._automaticallySizeWindow = true;
        this._dataProvider.reset();
        this._mainView.setWindowTimes(0, Infinity);
    },

    _onRecordingStarted: function()
    {
        this._automaticallySizeWindow = true;
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     */
    addRecord: function(record)
    {
        this._dataProvider.addRecord(record);
        if (this._automaticallySizeWindow) {
            var minimumRecordTime = this._model.minimumRecordTime();
            if (record.startTime > (minimumRecordTime + 1000)) {
                this._automaticallySizeWindow = false;
                this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
            }
            this._mainView._scheduleUpdate();
        }
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     */
    setWindowTimes: function(startTime, endTime)
    {
        this._startTime = startTime;
        this._endTime = endTime;
        this._updateWindow();
    },

    _updateWindow: function()
    {
        var minimumRecordTime = this._model.minimumRecordTime();
        var timeRange = this._model.maximumRecordTime() - minimumRecordTime;
        if (timeRange === 0)
            this._mainView.setWindowTimes(0, Infinity);
        else
            this._mainView.setWindowTimes(this._startTime, this._endTime);
    },

    /**
     * @param {number} width
     */
    setSidebarSize: function(width)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     * @param {string=} regex
     * @param {boolean=} selectRecord
     */
    highlightSearchResult: function(record, regex, selectRecord)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     */
    setSelectedRecord: function(record)
    {
    },

    __proto__: WebInspector.View.prototype
}
<commit_msg>TimelineFlameChart: draw parent bar with different color if there is a child on top of it.<commit_after>/*
 * Copyright (C) 2014 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @constructor
 * @implements {WebInspector.FlameChartDataProvider}
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 * @param {boolean} mainThread
 */
WebInspector.TimelineFlameChartDataProvider = function(model, frameModel, mainThread)
{
    WebInspector.FlameChartDataProvider.call(this);
    this._model = model;
    this._frameModel = frameModel;
    this._mainThread = mainThread;
    this._font = (this.barHeight() - 4) + "px " + WebInspector.fontFamily();

    this._colorGenerator = new WebInspector.FlameChart.ColorGenerator();
    var categories = WebInspector.TimelineUIUtils.categories();
    for (var category in categories) {
        this._colorGenerator.setColorForID(category, categories[category].fillColorStop1);
        this._colorGenerator.setColorForID(category + " child", categories[category].fillColorStop0);
    }
}

WebInspector.TimelineFlameChartDataProvider.prototype = {
    /**
     * @return {number}
     */
    barHeight: function()
    {
        return 15;
    },

    /**
     * @param {number} entryIndex
     * @return {string}
     */
    entryFont: function(entryIndex)
    {
        return this._font;
    },

    /**
     * @param {number} entryIndex
     * @return {?string}
     */
    entryTitle: function(entryIndex)
    {
        return this._entryTitles[entryIndex];
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     * @return {?Array.<number>}
     */
    dividerOffsets: function(startTime, endTime)
    {
        var frames = this._frameModel.filteredFrames(this._model.minimumRecordTime() + startTime, this._model.minimumRecordTime() + endTime);
        if (frames.length > 30)
            return null;
        var offsets = [];
        for (var i = 0; i < frames.length; ++i)
            offsets.push(frames[i].startTimeOffset);
        return frames.length ? offsets : null;
    },

    reset: function()
    {
        this._timelineData = null;
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     */
    addRecord: function(record)
    {
        this._appendRecord(record, 0);
    },

    /**
     * @return {!WebInspector.FlameChart.ColorGenerator}
     */
    colorGenerator: function()
    {
        return this._colorGenerator;
    },

    /**
     * @return {!WebInspector.FlameChart.TimelineData}
     */
    timelineData: function()
    {
        if (!this._timelineData) {
            this._resetData();
            var records = this._model.records();
            for (var i = 0; i < records.length; ++i)
                this._appendRecord(records[i], 0);
            this._zeroTime = this._model.minimumRecordTime();
        }
        return /** @type {!WebInspector.FlameChart.TimelineData} */(this._timelineData);
    },

    /**
     * @return {number}
     */
    zeroTime: function()
    {
        return this._zeroTime;
    },

    /**
     * @return {number}
     */
    totalTime: function()
    {
        return this._totalTime;
    },

    /**
     * @return {number}
     */
    maxStackDepth: function()
    {
        return this._maxStackDepth;
    },

    _resetData: function()
    {
        this._startTime = 0;
        this._endTime = 0;
        this._maxStackDepth = 5;
        this._totalTime = 1000;
        this._zeroTime = 0;

        /**
         * @type {?WebInspector.FlameChart.TimelineData}
         */
        this._timelineData = {
            entryLevels: [],
            entryTotalTimes: [],
            entryOffsets: [],
        };

        /** @type {!Array.<string>} */
        this._entryTitles =  [];

        /** @type {!Array.<string>} */
        this._entryColors = [];
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     * @param {number} level
     */
    _appendRecord: function(record, level)
    {
        var timelineData = this.timelineData();

        this._startTime = this._startTime ? Math.min(this._startTime, record.startTime) : record.startTime;
        this._zeroTime = this._startTime;
        var recordEndTime = record.endTime || record.startTime;
        this._endTime = Math.max(this._endTime, recordEndTime);
        this._totalTime = Math.max(1000, this._endTime - this._startTime);

        if (this._mainThread) {
            if (record.type === WebInspector.TimelineModel.RecordType.GPUTask || !!record.thread)
                return;
        } else {
            if (record.type === WebInspector.TimelineModel.RecordType.Program || !record.thread)
                return;
        }

        var color = this._colorGenerator.colorForID(WebInspector.TimelineUIUtils.categoryForRecord(record).name);
        var colorChild = this._colorGenerator.colorForID(WebInspector.TimelineUIUtils.categoryForRecord(record).name + " child");

        var currentTime = record.startTime;
        for (var i = 0; i < record.children.length; ++i) {
            var childRecord = record.children[i];
            var childStartTime = childRecord.startTime;
            if (currentTime !== childStartTime)
                this._pushRecord(record, level, color, currentTime, childStartTime);
            var childEndTime = childRecord.endTime || childRecord.startTime;
            this._pushRecord(record, level, colorChild, childStartTime, childEndTime);
            this._appendRecord(childRecord, level + 1);
            currentTime = childEndTime;
        }
        if (recordEndTime !== currentTime || record.children.length === 0)
            this._pushRecord(record, level, color, currentTime, recordEndTime);

        this._maxStackDepth = Math.max(this._maxStackDepth, level + 2);
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     * @param {number} level
     * @param {string} color
     * @param {number} startTime
     * @param {number} endTime
     */
    _pushRecord: function(record, level, color, startTime, endTime)
    {
        var index = this._entryTitles.length;
        this._entryTitles[index] = record.type;
        this._timelineData.entryOffsets[index] = startTime - this._zeroTime;
        this._timelineData.entryLevels[index] = level;
        this._timelineData.entryTotalTimes[index] = endTime - startTime;
        this._entryColors[index] = color;
    },

    /**
     * @param {number} entryIndex
     * @return {?Array.<!{title: string, text: string}>}
     */
    prepareHighlightedEntryInfo: function(entryIndex)
    {
        return null;
    },

    /**
     * @param {number} entryIndex
     * @return {boolean}
     */
    canJumpToEntry: function(entryIndex)
    {
        return false;
    },

    /**
     * @param {number} entryIndex
     * @return {?Object}
     */
    entryData: function(entryIndex)
    {
        return null;
    },

    /**
     * @param {number} entryIndex
     * @return {!string}
     */
    entryColor: function(entryIndex)
    {
        return this._entryColors[entryIndex];
    }
}

/**
 * @constructor
 * @extends {WebInspector.View}
 * @implements {WebInspector.TimelineModeView}
 * @implements {WebInspector.TimeRangeController}
 * @param {!WebInspector.TimelineModeViewDelegate} delegate
 * @param {!WebInspector.TimelineModel} model
 * @param {!WebInspector.TimelineFrameModel} frameModel
 * @param {boolean} mainThread
 */
WebInspector.TimelineFlameChart = function(delegate, model, frameModel, mainThread)
{
    WebInspector.View.call(this);
    this._delegate = delegate;
    this._model = model;
    this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(model, frameModel, mainThread);
    this._mainView = new WebInspector.FlameChart.MainPane(this._dataProvider, this, true, true);
    this._mainView.show(this.element);
    this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
}

WebInspector.TimelineFlameChart.prototype = {
    /**
     * @param {number} windowStartTime
     * @param {number} windowEndTime
     */
    requestWindowTimes: function(windowStartTime, windowEndTime)
    {
        this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
    },

    /**
     * @param {?RegExp} textFilter
     */
    refreshRecords: function(textFilter)
    {
        this._dataProvider.reset();
        this._mainView._scheduleUpdate();
    },

    reset: function()
    {
        this._automaticallySizeWindow = true;
        this._dataProvider.reset();
        this._mainView.setWindowTimes(0, Infinity);
    },

    _onRecordingStarted: function()
    {
        this._automaticallySizeWindow = true;
    },

    /**
     * @param {!WebInspector.TimelineModel.Record} record
     */
    addRecord: function(record)
    {
        this._dataProvider.addRecord(record);
        if (this._automaticallySizeWindow) {
            var minimumRecordTime = this._model.minimumRecordTime();
            if (record.startTime > (minimumRecordTime + 1000)) {
                this._automaticallySizeWindow = false;
                this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
            }
            this._mainView._scheduleUpdate();
        }
    },

    /**
     * @param {number} startTime
     * @param {number} endTime
     */
    setWindowTimes: function(startTime, endTime)
    {
        this._startTime = startTime;
        this._endTime = endTime;
        this._updateWindow();
    },

    _updateWindow: function()
    {
        var minimumRecordTime = this._model.minimumRecordTime();
        var timeRange = this._model.maximumRecordTime() - minimumRecordTime;
        if (timeRange === 0)
            this._mainView.setWindowTimes(0, Infinity);
        else
            this._mainView.setWindowTimes(this._startTime, this._endTime);
    },

    /**
     * @param {number} width
     */
    setSidebarSize: function(width)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     * @param {string=} regex
     * @param {boolean=} selectRecord
     */
    highlightSearchResult: function(record, regex, selectRecord)
    {
    },

    /**
     * @param {?WebInspector.TimelineModel.Record} record
     */
    setSelectedRecord: function(record)
    {
    },

    __proto__: WebInspector.View.prototype
}
<|endoftext|>


<commit_before>(function () {
    'use strict';

    angular
        .module('dpShared')
        .constant('API_CONFIG', {
            ROOT: 'https://api.datapunt.amsterdam.nl/',
            AUTH: 'https://api.datapunt.amsterdam.nl/authenticatie/'
        });
})();
<commit_msg>Talk to api-acc for testing geolocation hr<commit_after>(function () {
    'use strict';

    angular
        .module('dpShared')
        .constant('API_CONFIG', {
            ROOT: 'https://api-acc.datapunt.amsterdam.nl/',
            AUTH: 'https://api.datapunt.amsterdam.nl/authenticatie/'
        });
})();
<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular
    .module('users')
    .config(UsersConfig);

  /* @ngInject */
  function UsersConfig($httpProvider) {

    // Config HTTP Error Handling
    // Set the httpProvider "not authorized" interceptor
    $httpProvider.interceptors.push(['$q', '$location', 'Authentication',
      function ($q, $location, Authentication) {
        return {
          responseError: function (rejection) {
            switch (rejection.status) {
              case 401:
                // Deauthenticate the global user
                Authentication.user = null;

                // Redirect to signin page
                $location.path('/signin');
                break;
              case 403:
                // Add unauthorized behaviour
                break;
            }

            return $q.reject(rejection);
          }
        };
      }
    ]);
  }

}());
<commit_msg>Limit httpProvider interceptor to API only (#825)<commit_after>(function () {
  'use strict';

  angular
    .module('users')
    .config(UsersConfig);

  /* @ngInject */
  function UsersConfig($httpProvider) {

    // Config HTTP Error Handling
    // Set the httpProvider "not authorized" interceptor
    $httpProvider.interceptors.push(['$q', '$location', 'Authentication',
      function ($q, $location, Authentication) {
        return {
          responseError: function (rejection) {
            if (rejection.config.url.startsWith('/api/')) {
              switch (rejection.status) {
                case 401:
                  // Deauthenticate the global user
                  Authentication.user = null;

                  // Redirect to signin page
                  $location.path('/signin');
                  break;
                case 403:
                  // Add unauthorized behaviour
                  break;
              }
            }
            return $q.reject(rejection);
          }
        };
      }
    ]);
  }

}());
<|endoftext|>


<commit_before>var http = require('http');
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var pty = require('node-pty');
var cors = require('cors');
app.use(cors());
app.options('*', cors());
var terminals = ''; //global terminals

/*
##########################################################
###################FOR TERMINAL DEMO ONLY ################
##########################################################
*/
  app.use('/build', express.static(__dirname + '/../build'));

  app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
  });

  app.get('/style.css', function(req, res){
    res.sendFile(__dirname + '/style.css');
  });

  app.get('/main.js', function(req, res){
    res.sendFile(__dirname + '/main.js');
  });
/*
##########################################################
###################FOR TERMINAL DEMO ONLY ################
##########################################################
*/

/*
##########get user info and .pem file path###############
*/
function getTerm(token) {
    return new Promise((resolve, reject) => {
      return http.get({
          host: '35.154.96.42',
          port : 7000,
          path: '/api/v1/users/me?token=' + token
      }, function(response) {
          // Continuously update stream with data
          var body = '';
          response.on('data', function(d) {
              body += d;
          });
          response.on('end', function() {
              return resolve(JSON.parse(body));
          });
      });
    })
}

/*
##########xterm over WebSocket###############
##########Do not edit code below if you are not sure & know what you are doing ###############
*/
app.ws('/terminals/:pid', function (ws, req) {
  getTerm(req.params.pid)
    .then(user_info => {
      console.log(user_info);
      if(terminals[req.params.pid]){
        var term = terminals[req.params.pid];
      }else {
        var term = pty.spawn('ssh', ["-i", user_info.pem_file, user_info.user_host], {
          name: 'xterm-color',
          cwd: process.env.PWD,
          env: process.env
        });
        terminals[req.params.pid] = term;
      }

      term.on('data', function(data) {
        ws.send(data);
      });

      ws.on('message', function(msg) {
        term.write(msg);
      });

      ws.on('close', function () {
        // process.kill(term.pid);
        // delete terminals[req.params.pid];
        // delete logs[req.params.pid];
      });
    })
    .catch(err => {
      // console.log(err);
    })
});

/*
##########listen server at port 30000################
*/
console.log('App listening to *:3000');
app.listen(3000);
<commit_msg>Changed ip for API server<commit_after>var http = require('http');
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var pty = require('node-pty');
var cors = require('cors');
app.use(cors());
app.options('*', cors());
var terminals = ''; //global terminals

/*
##########################################################
###################FOR TERMINAL DEMO ONLY ################
##########################################################
*/
  app.use('/build', express.static(__dirname + '/../build'));

  app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
  });

  app.get('/style.css', function(req, res){
    res.sendFile(__dirname + '/style.css');
  });

  app.get('/main.js', function(req, res){
    res.sendFile(__dirname + '/main.js');
  });
/*
##########################################################
###################FOR TERMINAL DEMO ONLY ################
##########################################################
*/

/*
##########get user info and .pem file path###############
*/
function getTerm(token) {
    return new Promise((resolve, reject) => {
      return http.get({
          host: '35.154.189.94',
          port : 7000,
          path: '/api/v1/users/me?token=' + token
      }, function(response) {
          // Continuously update stream with data
          var body = '';
          response.on('data', function(d) {
              body += d;
          });
          response.on('end', function() {
              return resolve(JSON.parse(body));
          });
      });
    })
}

/*
##########xterm over WebSocket###############
##########Do not edit code below if you are not sure & know what you are doing ###############
*/
app.ws('/terminals/:pid', function (ws, req) {
  getTerm(req.params.pid)
    .then(user_info => {
      console.log(user_info);
      if(terminals[req.params.pid]){
        var term = terminals[req.params.pid];
      }else {
        var term = pty.spawn('ssh', ["-i", user_info.pem_file, user_info.user_host], {
          name: 'xterm-color',
          cwd: process.env.PWD,
          env: process.env
        });
        terminals[req.params.pid] = term;
      }

      term.on('data', function(data) {
        ws.send(data);
      });

      ws.on('message', function(msg) {
        term.write(msg);
      });

      ws.on('close', function () {
        // process.kill(term.pid);
        // delete terminals[req.params.pid];
        // delete logs[req.params.pid];
      });
    })
    .catch(err => {
      // console.log(err);
    })
});

/*
##########listen server at port 30000################
*/
console.log('App listening to *:3000');
app.listen(3000);
<|endoftext|>


<commit_before>jest.mock('../build');
jest.mock('../server');
jest.mock('../utils/getWebpackVersion');

import last from 'lodash/last';
import styleguidist from '../index';

const getDefaultWebpackConfig = () => styleguidist().makeWebpackConfig();

const cwd = process.cwd();
afterEach(() => {
	process.chdir(cwd);
});

it('should return API methods', () => {
	const api = styleguidist(require('../../test/data/styleguide.config.js'));
	expect(api).toBeTruthy();
	expect(typeof api.build).toBe('function');
	expect(typeof api.server).toBe('function');
	expect(typeof api.makeWebpackConfig).toBe('function');
});

it('makeWebpackConfig should return development Webpack config', () => {
	const api = styleguidist();
	const result = api.makeWebpackConfig('development');
	expect(result).toBeTruthy();
	expect(result.output.filename).toBe('build/bundle.js');
});

it('makeWebpackConfig should return production Webpack config', () => {
	const api = styleguidist();
	const result = api.makeWebpackConfig('production');
	expect(result).toBeTruthy();
	expect(result.output.filename).toBe('build/bundle.js');
});

it('makeWebpackConfig should merge webpackConfig config option', () => {
	const defaultWebpackConfig = getDefaultWebpackConfig();
	const api = styleguidist({
		webpackConfig: {
			resolve: {
				extensions: ['.scss'],
			},
		},
	});
	const result = api.makeWebpackConfig();

	expect(result).toBeTruthy();
	expect(result.resolve.extensions.length).toEqual(defaultWebpackConfig.resolve.extensions.length + 1);
	expect(last(result.resolve.extensions)).toEqual('.scss');
});

it('makeWebpackConfig should merge webpackConfig but ignore output section', () => {
	const defaultWebpackConfig = getDefaultWebpackConfig();
	const api = styleguidist({
		webpackConfig: {
			resolve: {
				extensions: ['.scss'],
			},
			output: {
				filename: 'broken.js',
			},
		},
	});
	const result = api.makeWebpackConfig();

	expect(result.output.filename).toEqual(defaultWebpackConfig.output.filename);
});

it('makeWebpackConfig should merge webpackConfig config option as a function', () => {
	const api = styleguidist({
		webpackConfig: env => ({
			_env: env,
		}),
	});
	const result = api.makeWebpackConfig();

	expect(result).toBeTruthy();
	expect(result._env).toEqual('production');
});

it('makeWebpackConfig should apply updateWebpackConfig config option', () => {
	/* eslint-disable no-console */
	const originalWarn = console.warn;

	console.warn = jest.fn();
	const defaultWebpackConfig = getDefaultWebpackConfig();
	const api = styleguidist({
		updateWebpackConfig: (webpackConfig, env) => {
			webpackConfig.resolve.extensions.push(env);
			return webpackConfig;
		},
	});
	const result = api.makeWebpackConfig();

	expect(result).toBeTruthy();
	expect(result.resolve.extensions.length).toEqual(defaultWebpackConfig.resolve.extensions.length + 1);
	expect(last(result.resolve.extensions)).toEqual('production');

	console.warn = originalWarn;
	/* eslint-enable no-console */
});

it('makeWebpackConfig should merge create-react-app Webpack config', () => {
	process.chdir('test/apps/cra');
	const api = styleguidist();
	const result = api.makeWebpackConfig();

	expect(result).toBeTruthy();
	expect(result.cra).toBeTruthy();
});

it('makeWebpackConfig should add json-loader', () => {
	const api = styleguidist();
	const result = api.makeWebpackConfig();

	expect(result.module.loaders).toHaveLength(1);
	expect(last(result.module.loaders).loader).toEqual('json-loader');
});

it('build() should pass style guide config and stats to callback', () => {
	const config = {
		components: '*.js',
	};
	const callback = jest.fn();
	const api = styleguidist(config);
	api.build(callback);

	expect(callback).toBeCalled();
	expect(callback.mock.calls[0][1].components).toBe(config.components);
	expect(callback.mock.calls[0][2]).toEqual({ stats: true });
});

it('server() should pass style guide config to callback', () => {
	const config = {
		components: '*.js',
	};
	const callback = jest.fn();
	const api = styleguidist(config);
	api.server(callback);

	expect(callback).toBeCalled();
	expect(callback.mock.calls[0][1].components).toBe(config.components);
});
<commit_msg>Test: Group tests<commit_after>jest.mock('../build');
jest.mock('../server');
jest.mock('../utils/getWebpackVersion');

import last from 'lodash/last';
import styleguidist from '../index';

const getDefaultWebpackConfig = () => styleguidist().makeWebpackConfig();

const cwd = process.cwd();
afterEach(() => {
	process.chdir(cwd);
});

it('should return API methods', () => {
	const api = styleguidist(require('../../test/data/styleguide.config.js'));
	expect(api).toBeTruthy();
	expect(typeof api.build).toBe('function');
	expect(typeof api.server).toBe('function');
	expect(typeof api.makeWebpackConfig).toBe('function');
});

describe('makeWebpackConfig', () => {
	it('should return development Webpack config', () => {
		const api = styleguidist();
		const result = api.makeWebpackConfig('development');
		expect(result).toBeTruthy();
		expect(result.output.filename).toBe('build/bundle.js');
	});

	it('should return production Webpack config', () => {
		const api = styleguidist();
		const result = api.makeWebpackConfig('production');
		expect(result).toBeTruthy();
		expect(result.output.filename).toBe('build/bundle.js');
	});

	it('should merge webpackConfig config option', () => {
		const defaultWebpackConfig = getDefaultWebpackConfig();
		const api = styleguidist({
			webpackConfig: {
				resolve: {
					extensions: ['.scss'],
				},
			},
		});
		const result = api.makeWebpackConfig();

		expect(result).toBeTruthy();
		expect(result.resolve.extensions.length).toEqual(defaultWebpackConfig.resolve.extensions.length + 1);
		expect(last(result.resolve.extensions)).toEqual('.scss');
	});

	it('should merge webpackConfig but ignore output section', () => {
		const defaultWebpackConfig = getDefaultWebpackConfig();
		const api = styleguidist({
			webpackConfig: {
				resolve: {
					extensions: ['.scss'],
				},
				output: {
					filename: 'broken.js',
				},
			},
		});
		const result = api.makeWebpackConfig();

		expect(result.output.filename).toEqual(defaultWebpackConfig.output.filename);
	});

	it('should merge webpackConfig config option as a function', () => {
		const api = styleguidist({
			webpackConfig: env => ({
				_env: env,
			}),
		});
		const result = api.makeWebpackConfig();

		expect(result).toBeTruthy();
		expect(result._env).toEqual('production');
	});

	it('should apply updateWebpackConfig config option', () => {
		/* eslint-disable no-console */
		const originalWarn = console.warn;

		console.warn = jest.fn();
		const defaultWebpackConfig = getDefaultWebpackConfig();
		const api = styleguidist({
			updateWebpackConfig: (webpackConfig, env) => {
				webpackConfig.resolve.extensions.push(env);
				return webpackConfig;
			},
		});
		const result = api.makeWebpackConfig();

		expect(result).toBeTruthy();
		expect(result.resolve.extensions.length).toEqual(defaultWebpackConfig.resolve.extensions.length + 1);
		expect(last(result.resolve.extensions)).toEqual('production');

		console.warn = originalWarn;
		/* eslint-enable no-console */
	});

	it('should merge create-react-app Webpack config', () => {
		process.chdir('test/apps/cra');
		const api = styleguidist();
		const result = api.makeWebpackConfig();

		expect(result).toBeTruthy();
		expect(result.cra).toBeTruthy();
	});

	it('should add json-loader', () => {
		const api = styleguidist();
		const result = api.makeWebpackConfig();

		expect(result.module.loaders).toHaveLength(1);
		expect(last(result.module.loaders).loader).toEqual('json-loader');
	});
});

describe('build', () => {
	it('should pass style guide config and stats to callback', () => {
		const config = {
			components: '*.js',
		};
		const callback = jest.fn();
		const api = styleguidist(config);
		api.build(callback);

		expect(callback).toBeCalled();
		expect(callback.mock.calls[0][1].components).toBe(config.components);
		expect(callback.mock.calls[0][2]).toEqual({ stats: true });
	});
});

describe('server', () => {
	it('should pass style guide config to callback', () => {
		const config = {
			components: '*.js',
		};
		const callback = jest.fn();
		const api = styleguidist(config);
		api.server(callback);

		expect(callback).toBeCalled();
		expect(callback.mock.calls[0][1].components).toBe(config.components);
	});
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

var fs = require('fs');
var gm = require('gm');
var when = require('when');

var ansi = require('ansi');
var cursor = ansi(process.stdout);
var args = require('./lib/args');

var parser = require('./lib/parser');
var sanitize = require('sanitize-filename');

var argv = args();

var log = {
	valid: argv.verbose || argv.parsed,
	invalid: argv.verbose && !argv.parsed
};

var chunkSize = argv.spawn || 50;
var chunk = 0;
var counter = {
	err: 0,
	ok: 0
};

main();

function main() {
	verifySourceFile(argv.image);
	verifySourceFile(argv.css);
	argv.output = resolveOutputPath(argv.output);

	var parsed = parser.parseCss(argv.css);
	var rules = parsed.stylesheet.rules;
	console.log('found %d css entries, parsing...', rules.length);

	var valids = validate(rules);
	console.log('parsed %d valid rules out of a total %d, starting split...',
		valids.length, rules.length);

	processNextChunk(valids);
}

function processNextChunk(valids) {
	var slice = valids.slice(chunk*chunkSize, (chunk+1)*chunkSize);
	if (!slice.length) {
		logResult();
		return;
	}

	var promises = queue(slice);
	when.all(promises)
		.then(function() {
			if (log.valid) logChunk(chunk);
			++chunk;
			processNextChunk(valids);
		});
}

function validate(rules) {
	var valids = {};

	rules.forEach(function(rule) {
		if (rule.type !== 'rule') return;
		if (!rule.declarations) return;

		var name = parser.parseRuleName(rule);
		var decs = {};

		rule.declarations.forEach(function(dec) {
			switch(dec.property) {
				case 'width':
				case 'height':
					decs[dec.property] = parser.parseDimension(dec.value);
					break;
				case 'background':
					decs.pos = parser.parseBGShorthand(dec.value);
					break;
				case 'background-position':
					decs.pos = parser.parseBGPosition(dec.value);
					break;
			}

			if (isRuleValid(decs)) {
				var key = [decs.width, decs.height, decs.pos.x, decs.pos.y].join('_');
				if (valids[key]) {
					if (log.invalid) logDuplicate(name, valids[key].name);
				} else {
					valids[key] = {name: name, decs: decs}
					if (log.valid) logValid(name, decs);
				}
			} else {
				if (log.invalid) logInvalid(name);
			}
		});
	});

	// extract values
	return Object.keys(valids).map(function (key) {return valids[key]});
}

function queue(valids) {
	var promises = [];

	valids.forEach(function(valid) {
		var dfd = when.defer();
		promises.push(dfd.promise);

		var sanitized = sanitize(valid.name);
		var target = argv.output + sanitized + '.png';
		split(valid.decs, target, dfd);
	});

	return promises;
}

function split(decs, target, dfd) {
	gm(argv.image)
		.crop(decs.width, decs.height, decs.pos.x, decs.pos.y)
		.write(target, function(err) {
			if (err) {
				counter.err++;
				if (log.valid) logSplitError(err);
			} else {
				counter.ok++;
			}

			dfd.resolve();
		});
}

function verifySourceFile(path) {
	if (!fs.existsSync(path)) {
		cursor.red();
		console.log('File not found: %s', path);
		cursor.reset();
		process.exit(0);
	}
}

function resolveOutputPath(userArg) {
	var targetdir = (userArg || 'split') + '/';
	if (!fs.existsSync(targetdir)) {
		fs.mkdirSync(targetdir);
	}

	return targetdir;
}

function isRuleValid(decs) {
	return decs.width !== undefined &&
	decs.height !== undefined &&
	decs.pos !== undefined;
}

function logValid(name, decs) {
	cursor.green().write('rule ' + name + ' OK, saved for split...').reset();
	console.log(' (%d, %d, %d, %d)', decs.width, decs.height, decs.pos.x, decs.pos.y);
}

function logDuplicate(name1, name2) {
	cursor.yellow();
	console.log('rule %s is a duplicate of rule %s, skipping...', name1, name2);
	cursor.reset();
}

function logInvalid(name) {
	cursor.yellow();
	console.log('rule %s invalid for split, skipping...', name);
	cursor.reset();
}

function logSplitError(err) {
	cursor
		.red()
		.write('error while splitting: ' + err)
		.reset()
		.write('\n');
}

function logChunk(chunk) {
	cursor
		.green()
		.write('processed chunk ' + (chunk+1))
		.reset()
		.write('\n');
}

function logResult() {
	cursor
		.write('all done! ')
		.green()
		.write(counter.ok + ' successfully split images')
		.reset()
		.write(' and ');

	if (counter.err > 0)
		cursor.red();

	cursor
		.write(counter.err + ' errors.')
		.reset()
		.write('\n');
}
<commit_msg>rule validation bugfix<commit_after>#!/usr/bin/env node

var fs = require('fs');
var gm = require('gm');
var when = require('when');

var ansi = require('ansi');
var cursor = ansi(process.stdout);
var args = require('./lib/args');

var parser = require('./lib/parser');
var sanitize = require('sanitize-filename');

var argv = args();

var log = {
	valid: argv.verbose || argv.parsed,
	invalid: argv.verbose && !argv.parsed
};

var chunkSize = argv.spawn || 50;
var chunk = 0;
var counter = {
	err: 0,
	ok: 0
};

main();

function main() {
	verifySourceFile(argv.image);
	verifySourceFile(argv.css);
	argv.output = resolveOutputPath(argv.output);

	var parsed = parser.parseCss(argv.css);
	var rules = parsed.stylesheet.rules;
	console.log('found %d css entries, parsing...', rules.length);

	var valids = validate(rules);
	console.log('parsed %d valid rules out of a total %d, starting split...',
		valids.length, rules.length);

	processNextChunk(valids);
}

function processNextChunk(valids) {
	var slice = valids.slice(chunk*chunkSize, (chunk+1)*chunkSize);
	if (!slice.length) {
		logResult();
		return;
	}

	var promises = queue(slice);
	when.all(promises)
		.then(function() {
			if (log.valid) logChunk(chunk);
			++chunk;
			processNextChunk(valids);
		});
}

function validate(rules) {
	var valids = {};

	rules.forEach(function(rule) {
		if (rule.type !== 'rule') return;
		if (!rule.declarations) return;

		var name = parser.parseRuleName(rule);
		var decs = {};

		rule.declarations.forEach(function(dec) {
			switch(dec.property) {
				case 'width':
				case 'height':
					decs[dec.property] = parser.parseDimension(dec.value);
					break;
				case 'background':
					decs.pos = parser.parseBGShorthand(dec.value);
					break;
				case 'background-position':
					decs.pos = parser.parseBGPosition(dec.value);
					break;
			}
		});

		if (isRuleValid(decs)) {
			var key = [decs.width, decs.height, decs.pos.x, decs.pos.y].join('_');
			if (valids[key]) {
				if (log.invalid) logDuplicate(name, valids[key].name);
			} else {
				valids[key] = {name: name, decs: decs}
				if (log.valid) logValid(name, decs);
			}
		} else {
			if (log.invalid) logInvalid(name);
		}
	});

	// extract values
	return Object.keys(valids).map(function (key) {return valids[key]});
}

function queue(valids) {
	var promises = [];

	valids.forEach(function(valid) {
		var dfd = when.defer();
		promises.push(dfd.promise);

		var sanitized = sanitize(valid.name);
		var target = argv.output + sanitized + '.png';
		split(valid.decs, target, dfd);
	});

	return promises;
}

function split(decs, target, dfd) {
	gm(argv.image)
		.crop(decs.width, decs.height, decs.pos.x, decs.pos.y)
		.write(target, function(err) {
			if (err) {
				counter.err++;
				if (log.valid) logSplitError(err);
			} else {
				counter.ok++;
			}

			dfd.resolve();
		});
}

function verifySourceFile(path) {
	if (!fs.existsSync(path)) {
		cursor.red();
		console.log('File not found: %s', path);
		cursor.reset();
		process.exit(0);
	}
}

function resolveOutputPath(userArg) {
	var targetdir = (userArg || 'split') + '/';
	if (!fs.existsSync(targetdir)) {
		fs.mkdirSync(targetdir);
	}

	return targetdir;
}

function isRuleValid(decs) {
	return decs.width !== undefined &&
	decs.height !== undefined &&
	decs.pos !== undefined;
}

function logValid(name, decs) {
	cursor.green().write('rule ' + name + ' OK, saved for split...').reset();
	console.log(' (%d, %d, %d, %d)', decs.width, decs.height, decs.pos.x, decs.pos.y);
}

function logDuplicate(name1, name2) {
	cursor.yellow();
	console.log('rule %s is a duplicate of rule %s, skipping...', name1, name2);
	cursor.reset();
}

function logInvalid(name) {
	cursor.yellow();
	console.log('rule %s invalid for split, skipping...', name);
	cursor.reset();
}

function logSplitError(err) {
	cursor
		.red()
		.write('error while splitting: ' + err)
		.reset()
		.write('\n');
}

function logChunk(chunk) {
	cursor
		.green()
		.write('processed chunk ' + (chunk+1))
		.reset()
		.write('\n');
}

function logResult() {
	cursor
		.write('all done! ')
		.green()
		.write(counter.ok + ' successfully split images')
		.reset()
		.write(' and ');

	if (counter.err > 0)
		cursor.red();

	cursor
		.write(counter.err + ' errors.')
		.reset()
		.write('\n');
}
<|endoftext|>