Modern IDB: Add -private.html variants of crypto/subtle IndexedDB tests.
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / MemoryTimelineView.js
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.MemoryTimelineView = class MemoryTimelineView extends WebInspector.TimelineView
27 {
28     constructor(timeline, extraArguments)
29     {
30         super(timeline, extraArguments);
31
32         console.assert(timeline.type === WebInspector.TimelineRecord.Type.Memory, timeline);
33
34         this.element.classList.add("memory");
35
36         let contentElement = this.element.appendChild(document.createElement("div"));
37         contentElement.classList.add("content");
38
39         let overviewElement = contentElement.appendChild(document.createElement("div"));
40         overviewElement.classList.add("overview");
41
42         function createChartContainer(parentElement, subtitle, tooltip)
43         {
44             let chartElement = parentElement.appendChild(document.createElement("div"));
45             chartElement.classList.add("chart");
46
47             let chartSubtitleElement = chartElement.appendChild(document.createElement("div"));
48             chartSubtitleElement.classList.add("subtitle");
49             chartSubtitleElement.textContent = subtitle;
50             chartSubtitleElement.title = tooltip;
51
52             let chartFlexContainerElement = chartElement.appendChild(document.createElement("div"));
53             chartFlexContainerElement.classList.add("container");
54             return chartFlexContainerElement;
55         }
56
57         let usageTooltip = WebInspector.UIString("Breakdown of each memory category at the end of the selected time range");
58         let usageChartContainerElement = createChartContainer(overviewElement, WebInspector.UIString("Breakdown"), usageTooltip);
59         this._usageCircleChart = new WebInspector.CircleChart({size: 120, innerRadiusRatio: 0.5});
60         usageChartContainerElement.appendChild(this._usageCircleChart.element);
61         this._usageLegendElement = usageChartContainerElement.appendChild(document.createElement("div"));
62         this._usageLegendElement.classList.add("legend", "usage");
63
64         let dividerElement = overviewElement.appendChild(document.createElement("div"));
65         dividerElement.classList.add("divider");
66
67         let maxComparisonTooltip = WebInspector.UIString("Comparison of total memory size at the end of the selected time range to the maximum memory size in this recording");
68         let maxComparisonChartContainerElement = createChartContainer(overviewElement, WebInspector.UIString("Max Comparison"), maxComparisonTooltip);
69         this._maxComparisonCircleChart = new WebInspector.CircleChart({size: 120, innerRadiusRatio: 0.5});
70         maxComparisonChartContainerElement.appendChild(this._maxComparisonCircleChart.element);
71         this._maxComparisonLegendElement = maxComparisonChartContainerElement.appendChild(document.createElement("div"));
72         this._maxComparisonLegendElement.classList.add("legend", "maximum");
73
74         let detailsContainerElement = this._detailsContainerElement = contentElement.appendChild(document.createElement("div"));
75         detailsContainerElement.classList.add("details");
76
77         this._timelineRuler = new WebInspector.TimelineRuler;
78         this.addSubview(this._timelineRuler);
79         detailsContainerElement.appendChild(this._timelineRuler.element);
80
81         let detailsSubtitleElement = detailsContainerElement.appendChild(document.createElement("div"));
82         detailsSubtitleElement.classList.add("subtitle");
83         detailsSubtitleElement.textContent = WebInspector.UIString("Categories");
84
85         this._didInitializeCategories = false;
86         this._categoryViews = [];
87         this._usageLegendSizeElementMap = new Map;
88
89         this._maxSize = 0;
90         this._maxComparisonMaximumSizeElement = null;
91         this._maxComparisonCurrentSizeElement = null;
92
93         timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._memoryTimelineRecordAdded, this);
94     }
95
96     // Static
97
98     static displayNameForCategory(category)
99     {
100         switch (category) {
101         case WebInspector.MemoryCategory.Type.JavaScript:
102             return WebInspector.UIString("JavaScript");
103         case WebInspector.MemoryCategory.Type.Images:
104             return WebInspector.UIString("Images");
105         case WebInspector.MemoryCategory.Type.Layers:
106             return WebInspector.UIString("Layers");
107         case WebInspector.MemoryCategory.Type.Page:
108             return WebInspector.UIString("Page");
109         }
110     }
111
112     // Public
113
114     shown()
115     {
116         super.shown();
117
118         this._timelineRuler.updateLayout(WebInspector.View.LayoutReason.Resize);
119     }
120
121     hidden()
122     {
123         super.hidden();
124     }
125
126     closed()
127     {
128         console.assert(this.representedObject instanceof WebInspector.Timeline);
129         this.representedObject.removeEventListener(null, null, this);
130     }
131
132     reset()
133     {
134         super.reset();
135
136         this._maxSize = 0;
137
138         this._cachedLegendRecord = null;
139         this._cachedLegendMaxSize = undefined;
140         this._cachedLegendCurrentSize = undefined;
141
142         this._usageCircleChart.clear();
143         this._usageCircleChart.needsLayout();
144         this._clearUsageLegend();
145
146         this._maxComparisonCircleChart.clear();
147         this._maxComparisonCircleChart.needsLayout();
148         this._clearMaxComparisonLegend();
149
150         for (let categoryView of this._categoryViews)
151             categoryView.clear();
152     }
153
154     get scrollableElements()
155     {
156         return [this.element];
157     }
158
159     // Protected
160
161     get showsFilterBar() { return false; }
162
163     layout()
164     {
165         // Always update timeline ruler.
166         this._timelineRuler.zeroTime = this.zeroTime;
167         this._timelineRuler.startTime = this.startTime;
168         this._timelineRuler.endTime = this.endTime;
169
170         if (!this._didInitializeCategories)
171             return;
172
173         let graphStartTime = this.startTime;
174         let graphEndTime = this.endTime;
175         let graphCurrentTime = this.currentTime;
176         let visibleEndTime = Math.min(this.endTime, this.currentTime);
177
178         let visibleRecords = this._visibleRecords(graphStartTime, visibleEndTime);
179         if (!visibleRecords.length)
180             return;
181
182         // Update total usage chart with the last record's data.
183         let lastRecord = visibleRecords.lastValue;
184         let values = [];
185         for (let {size} of lastRecord.categories)
186             values.push(size);
187         this._usageCircleChart.values = values;
188         this._usageCircleChart.updateLayout();
189         this._updateUsageLegend(lastRecord);
190
191         // Update maximum comparison chart.
192         this._maxComparisonCircleChart.values = [lastRecord.totalSize, this._maxSize - lastRecord.totalSize];
193         this._maxComparisonCircleChart.updateLayout();
194         this._updateMaxComparisonLegend(lastRecord.totalSize);
195
196         // FIXME: <https://webkit.org/b/153758> Web Inspector: Memory Timeline View should be responsive / resizable
197         const categoryViewWidth = 800;
198         const categoryViewHeight = 75;
199
200         let secondsPerPixel = (graphEndTime - graphStartTime) / categoryViewWidth;
201
202         let categoryDataMap = {};
203         for (let categoryView of this._categoryViews)
204             categoryDataMap[categoryView.category] = {dataPoints: [], max: -Infinity, min: Infinity};
205
206         for (let record of visibleRecords) {
207             let time = record.startTime;
208             for (let category of record.categories) {
209                 let categoryData = categoryDataMap[category.type];
210                 categoryData.dataPoints.push({time, size: category.size});
211                 categoryData.max = Math.max(categoryData.max, category.size);
212                 categoryData.min = Math.min(categoryData.min, category.size);
213             }
214         }
215
216         function layoutCategoryView(categoryView, categoryData) {
217             let {dataPoints, min, max} = categoryData;
218
219             if (min === Infinity)
220                 min = 0;
221             if (max === -Infinity)
222                 max = 0;
223
224             // Zoom in to the top of each graph to accentuate small changes.
225             let graphMin = min * 0.95;
226             let graphMax = (max * 1.05) - graphMin;
227
228             function xScale(time) {
229                 return (time - graphStartTime) / secondsPerPixel;
230             }
231             function yScale(size) {
232                 return categoryViewHeight - (((size - graphMin) / graphMax) * categoryViewHeight);
233             }
234
235             categoryView.layoutWithDataPoints(dataPoints, visibleEndTime, min, max, xScale, yScale);
236         }
237
238         for (let categoryView of this._categoryViews)
239             layoutCategoryView(categoryView, categoryDataMap[categoryView.category]);
240     }
241
242     // Private
243
244     _clearUsageLegend()
245     {
246         for (let sizeElement of this._usageLegendSizeElementMap.values())
247             sizeElement.textContent = emDash;
248
249         let totalElement = this._usageCircleChart.centerElement.firstChild;
250         if (totalElement) {
251             totalElement.firstChild.textContent = "";
252             totalElement.lastChild.textContent = "";
253         }
254     }
255
256     _updateUsageLegend(record)
257     {
258         if (this._cachedLegendRecord === record)
259             return;
260
261         this._cachedLegendRecord = record;
262
263         for (let {type, size} of record.categories) {
264             let sizeElement = this._usageLegendSizeElementMap.get(type);
265             sizeElement.textContent = Number.isFinite(size) ? Number.bytesToString(size) : emDash;
266         }
267
268         let centerElement = this._usageCircleChart.centerElement;
269         let totalElement = centerElement.firstChild;
270         if (!totalElement) {
271             totalElement = centerElement.appendChild(document.createElement("div"));
272             totalElement.classList.add("total-usage");
273             totalElement.appendChild(document.createElement("span")); // firstChild
274             totalElement.appendChild(document.createElement("br"));
275             totalElement.appendChild(document.createElement("span")); // lastChild
276         }
277
278         let totalSize = Number.bytesToString(record.totalSize).split(/\s+/);
279         totalElement.firstChild.textContent = totalSize[0];
280         totalElement.lastChild.textContent = totalSize[1];
281     }
282
283     _clearMaxComparisonLegend()
284     {
285         this._maxComparisonMaximumSizeElement.textContent = emDash;
286         this._maxComparisonCurrentSizeElement.textContent = emDash;
287
288         let totalElement = this._maxComparisonCircleChart.centerElement.firstChild;
289         if (totalElement)
290             totalElement.textContent = "";
291     }
292
293     _updateMaxComparisonLegend(currentSize)
294     {
295         if (this._cachedLegendMaxSize === this._maxSize && this._cachedLegendCurrentSize === currentSize)
296             return;
297
298         this._cachedLegendMaxSize = this._maxSize;
299         this._cachedLegendCurrentSize = currentSize;
300
301         this._maxComparisonMaximumSizeElement.textContent = Number.isFinite(this._maxSize) ? Number.bytesToString(this._maxSize) : emDash;
302         this._maxComparisonCurrentSizeElement.textContent = Number.isFinite(currentSize) ? Number.bytesToString(currentSize) : emDash;
303
304         let centerElement = this._maxComparisonCircleChart.centerElement;
305         let totalElement = centerElement.firstChild;
306         if (!totalElement) {
307             totalElement = centerElement.appendChild(document.createElement("div"));
308             totalElement.classList.add("max-percentage");
309         }
310
311         // The chart will only show a perfect circle if the current and max are really the same value.
312         // So do a little massaging to ensure 99.95 doesn't get rounded up to 100.
313         let percent = ((currentSize / this._maxSize) * 100);
314         totalElement.textContent = (percent === 100 ? percent : (percent - 0.05).toFixed(1)) + "%";
315     }
316
317     _visibleRecords(startTime, endTime)
318     {
319         let records = this.representedObject.records;
320         let lowerIndex = records.lowerBound(startTime, (time, record) => time - record.timestamp);
321         let upperIndex = records.upperBound(endTime, (time, record) => time - record.timestamp);
322
323         // Include the record right before the start time in case it extends into this range.
324         if (lowerIndex > 0)
325             lowerIndex--;
326         // FIXME: <https://webkit.org/b/153759> Web Inspector: Memory Timelines should better extend to future data
327         // if (upperIndex !== records.length)
328         //     upperIndex++;
329
330         return records.slice(lowerIndex, upperIndex);
331     }
332
333     _initializeCategoryViews(record)
334     {
335         console.assert(!this._didInitializeCategories, "Should only initialize category views once");
336         this._didInitializeCategories = true;
337
338         let segments = [];
339         let lastCategoryViewElement = null;
340
341         function appendLegendRow(legendElement, swatchClass, label, tooltip) {
342             let rowElement = legendElement.appendChild(document.createElement("div"));
343             rowElement.classList.add("row");
344             let swatchElement = rowElement.appendChild(document.createElement("div"));
345             swatchElement.classList.add("swatch", swatchClass);
346             let labelElement = rowElement.appendChild(document.createElement("p"));
347             labelElement.classList.add("label");
348             labelElement.textContent = label;
349             let sizeElement = rowElement.appendChild(document.createElement("p"));
350             sizeElement.classList.add("size");
351
352             if (tooltip)
353                 rowElement.title = tooltip;
354
355             return sizeElement;
356         }
357
358         for (let {type} of record.categories) {
359             segments.push(type);
360
361             // Per-category graph.
362             let categoryView = new WebInspector.MemoryCategoryView(type, WebInspector.MemoryTimelineView.displayNameForCategory(type));
363             this._categoryViews.push(categoryView);
364             if (!lastCategoryViewElement)
365                 this._detailsContainerElement.appendChild(categoryView.element);
366             else
367                 this._detailsContainerElement.insertBefore(categoryView.element, lastCategoryViewElement);
368             lastCategoryViewElement = categoryView.element;
369
370             // Usage legend rows.
371             let sizeElement = appendLegendRow.call(this, this._usageLegendElement, type, WebInspector.MemoryTimelineView.displayNameForCategory(type));
372             this._usageLegendSizeElementMap.set(type, sizeElement);
373         }
374
375         this._usageCircleChart.segments = segments;
376
377         // Max comparison legend rows.
378         this._maxComparisonCircleChart.segments = ["current", "remainder"];
379         this._maxComparisonMaximumSizeElement = appendLegendRow.call(this, this._maxComparisonLegendElement, "remainder", WebInspector.UIString("Maximum"), WebInspector.UIString("Maximum maximum memory size in this recording"));
380         this._maxComparisonCurrentSizeElement = appendLegendRow.call(this, this._maxComparisonLegendElement, "current", WebInspector.UIString("Current"), WebInspector.UIString("Total memory size at the end of the selected time range"));
381     }
382
383     _memoryTimelineRecordAdded(event)
384     {
385         let memoryTimelineRecord = event.data.record;
386         console.assert(memoryTimelineRecord instanceof WebInspector.MemoryTimelineRecord);
387
388         if (!this._didInitializeCategories)
389             this._initializeCategoryViews(memoryTimelineRecord);
390
391         this._maxSize = Math.max(this._maxSize, memoryTimelineRecord.totalSize);
392
393         if (memoryTimelineRecord.startTime >= this.startTime && memoryTimelineRecord.endTime <= this.endTime)
394             this.needsLayout();
395     }
396 };