1/*
2 * Copyright (C) 2013 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
26WebInspector.ScriptTimelineRecord = function(eventType, startTime, endTime, callFrames, sourceCodeLocation, details, profilePayload)
27{
28    WebInspector.TimelineRecord.call(this, WebInspector.TimelineRecord.Type.Script, startTime, endTime, callFrames, sourceCodeLocation);
29
30    console.assert(eventType);
31
32    if (eventType in WebInspector.ScriptTimelineRecord.EventType)
33        eventType = WebInspector.ScriptTimelineRecord.EventType[eventType];
34
35    this._eventType = eventType;
36    this._details = details || "";
37    this._profilePayload = profilePayload || null;
38    this._profile = null;
39};
40
41WebInspector.ScriptTimelineRecord.EventType = {
42    ScriptEvaluated: "script-timeline-record-script-evaluated",
43    EventDispatched: "script-timeline-record-event-dispatch",
44    ProbeSampleRecorded: "script-timeline-record-probe-sample-recorded",
45    TimerFired: "script-timeline-record-timer-fired",
46    TimerInstalled: "script-timeline-record-timer-installed",
47    TimerRemoved: "script-timeline-record-timer-removed",
48    AnimationFrameFired: "script-timeline-record-animation-frame-fired",
49    AnimationFrameRequested: "script-timeline-record-animation-frame-requested",
50    AnimationFrameCanceled: "script-timeline-record-animation-frame-canceled",
51    ConsoleProfileRecorded: "script-timeline-record-console-profile-recorded"
52};
53
54WebInspector.ScriptTimelineRecord.EventType.displayName = function(eventType, details, includeTimerIdentifierInMainTitle)
55{
56    if (details && !WebInspector.ScriptTimelineRecord._eventDisplayNames) {
57        // These display names are not localized because they closely represent
58        // the real API name, just with word spaces and Title Case.
59
60        var nameMap = new Map;
61        nameMap.set("DOMActivate", "DOM Activate");
62        nameMap.set("DOMCharacterDataModified", "DOM Character Data Modified");
63        nameMap.set("DOMContentLoaded", "DOM Content Loaded");
64        nameMap.set("DOMFocusIn", "DOM Focus In");
65        nameMap.set("DOMFocusOut", "DOM Focus Out");
66        nameMap.set("DOMNodeInserted", "DOM Node Inserted");
67        nameMap.set("DOMNodeInsertedIntoDocument", "DOM Node Inserted Into Document");
68        nameMap.set("DOMNodeRemoved", "DOM Node Removed");
69        nameMap.set("DOMNodeRemovedFromDocument", "DOM Node Removed From Document");
70        nameMap.set("DOMSubtreeModified", "DOM Sub-Tree Modified");
71        nameMap.set("addsourcebuffer", "Add Source Buffer");
72        nameMap.set("addstream", "Add Stream");
73        nameMap.set("addtrack", "Add Track");
74        nameMap.set("audioend", "Audio End");
75        nameMap.set("audioprocess", "Audio Process");
76        nameMap.set("audiostart", "Audio Start");
77        nameMap.set("beforecopy", "Before Copy");
78        nameMap.set("beforecut", "Before Cut");
79        nameMap.set("beforeload", "Before Load");
80        nameMap.set("beforepaste", "Before Paste");
81        nameMap.set("beforeunload", "Before Unload");
82        nameMap.set("canplay", "Can Play");
83        nameMap.set("canplaythrough", "Can Play Through");
84        nameMap.set("chargingchange", "Charging Change");
85        nameMap.set("chargingtimechange", "Charging Time Change");
86        nameMap.set("compositionend", "Composition End");
87        nameMap.set("compositionstart", "Composition Start");
88        nameMap.set("compositionupdate", "Composition Update");
89        nameMap.set("contextmenu", "Context Menu");
90        nameMap.set("cuechange", "Cue Change");
91        nameMap.set("datachannel", "Data Channel");
92        nameMap.set("dblclick", "Double Click");
93        nameMap.set("devicemotion", "Device Motion");
94        nameMap.set("deviceorientation", "Device Orientation");
95        nameMap.set("dischargingtimechange", "Discharging Time Change");
96        nameMap.set("dragend", "Drag End");
97        nameMap.set("dragenter", "Drag Enter");
98        nameMap.set("dragleave", "Drag Leave");
99        nameMap.set("dragover", "Drag Over");
100        nameMap.set("dragstart", "Drag Start");
101        nameMap.set("durationchange", "Duration Change");
102        nameMap.set("focusin", "Focus In");
103        nameMap.set("focusout", "Focus Out");
104        nameMap.set("gesturechange", "Gesture Change");
105        nameMap.set("gestureend", "Gesture End");
106        nameMap.set("gesturescrollend", "Gesture Scroll End");
107        nameMap.set("gesturescrollstart", "Gesture Scroll Start");
108        nameMap.set("gesturescrollupdate", "Gesture Scroll Update");
109        nameMap.set("gesturestart", "Gesture Start");
110        nameMap.set("gesturetap", "Gesture Tap");
111        nameMap.set("gesturetapdown", "Gesture Tap Down");
112        nameMap.set("hashchange", "Hash Change");
113        nameMap.set("icecandidate", "ICE Candidate");
114        nameMap.set("iceconnectionstatechange", "ICE Connection State Change");
115        nameMap.set("keydown", "Key Down");
116        nameMap.set("keypress", "Key Press");
117        nameMap.set("keyup", "Key Up");
118        nameMap.set("levelchange", "Level Change");
119        nameMap.set("loadeddata", "Loaded Data");
120        nameMap.set("loadedmetadata", "Loaded Metadata");
121        nameMap.set("loadend", "Load End");
122        nameMap.set("loadingdone", "Loading Done");
123        nameMap.set("loadstart", "Load Start");
124        nameMap.set("mousedown", "Mouse Down");
125        nameMap.set("mouseenter", "Mouse Enter");
126        nameMap.set("mouseleave", "Mouse Leave");
127        nameMap.set("mousemove", "Mouse Move");
128        nameMap.set("mouseout", "Mouse Out");
129        nameMap.set("mouseover", "Mouse Over");
130        nameMap.set("mouseup", "Mouse Up");
131        nameMap.set("mousewheel", "Mouse Wheel");
132        nameMap.set("negotiationneeded", "Negotiation Needed");
133        nameMap.set("nomatch", "No Match");
134        nameMap.set("noupdate", "No Update");
135        nameMap.set("orientationchange", "Orientation Change");
136        nameMap.set("overflowchanged", "Overflow Changed");
137        nameMap.set("pagehide", "Page Hide");
138        nameMap.set("pageshow", "Page Show");
139        nameMap.set("popstate", "Pop State");
140        nameMap.set("ratechange", "Rate Change");
141        nameMap.set("readystatechange", "Ready State Change");
142        nameMap.set("removesourcebuffer", "Remove Source Buffer");
143        nameMap.set("removestream", "Remove Stream");
144        nameMap.set("removetrack", "Remove Track");
145        nameMap.set("securitypolicyviolation", "Security Policy Violation");
146        nameMap.set("selectionchange", "Selection Change");
147        nameMap.set("selectstart", "Select Start");
148        nameMap.set("signalingstatechange", "Signaling State Change");
149        nameMap.set("soundend", "Sound End");
150        nameMap.set("soundstart", "Sound Start");
151        nameMap.set("sourceclose", "Source Close");
152        nameMap.set("sourceended", "Source Ended");
153        nameMap.set("sourceopen", "Source Open");
154        nameMap.set("speechend", "Speech End");
155        nameMap.set("speechstart", "Speech Start");
156        nameMap.set("textInput", "Text Input");
157        nameMap.set("timeupdate", "Time Update");
158        nameMap.set("tonechange", "Tone Change");
159        nameMap.set("touchcancel", "Touch Cancel");
160        nameMap.set("touchend", "Touch End");
161        nameMap.set("touchmove", "Touch Move");
162        nameMap.set("touchstart", "Touch Start");
163        nameMap.set("transitionend", "Transition End");
164        nameMap.set("updateend", "Update End");
165        nameMap.set("updateready", "Update Ready");
166        nameMap.set("updatestart", "Update Start");
167        nameMap.set("upgradeneeded", "Upgrade Needed");
168        nameMap.set("versionchange", "Version Change");
169        nameMap.set("visibilitychange", "Visibility Change");
170        nameMap.set("volumechange", "Volume Change");
171        nameMap.set("webglcontextcreationerror", "WebGL Context Creation Error");
172        nameMap.set("webglcontextlost", "WebGL Context Lost");
173        nameMap.set("webglcontextrestored", "WebGL Context Restored");
174        nameMap.set("webkitAnimationEnd", "Animation End");
175        nameMap.set("webkitAnimationIteration", "Animation Iteration");
176        nameMap.set("webkitAnimationStart", "Animation Start");
177        nameMap.set("webkitBeforeTextInserted", "Before Text Inserted");
178        nameMap.set("webkitEditableContentChanged", "Editable Content Changed");
179        nameMap.set("webkitTransitionEnd", "Transition End");
180        nameMap.set("webkitaddsourcebuffer", "Add Source Buffer");
181        nameMap.set("webkitbeginfullscreen", "Begin Fullscreen");
182        nameMap.set("webkitcurrentplaybacktargetiswirelesschanged", "Current Playback Target Is Wireless Changed");
183        nameMap.set("webkitdeviceproximity", "Device Proximity");
184        nameMap.set("webkitendfullscreen", "End Fullscreen");
185        nameMap.set("webkitfullscreenchange", "Fullscreen Change");
186        nameMap.set("webkitfullscreenerror", "Fullscreen Error");
187        nameMap.set("webkitkeyadded", "Key Added");
188        nameMap.set("webkitkeyerror", "Key Error");
189        nameMap.set("webkitkeymessage", "Key Message");
190        nameMap.set("webkitneedkey", "Need Key");
191        nameMap.set("webkitnetworkinfochange", "Network Info Change");
192        nameMap.set("webkitplaybacktargetavailabilitychanged", "Playback Target Availability Changed");
193        nameMap.set("webkitpointerlockchange", "Pointer Lock Change");
194        nameMap.set("webkitpointerlockerror", "Pointer Lock Error");
195        nameMap.set("webkitregionlayoutupdate", "Region Layout Update");    // COMPATIBILITY (iOS 7): regionLayoutUpdated was removed and replaced by regionOversetChanged.
196        nameMap.set("webkitregionoversetchange", "Region Overset Change");
197        nameMap.set("webkitremovesourcebuffer", "Remove Source Buffer");
198        nameMap.set("webkitresourcetimingbufferfull", "Resource Timing Buffer Full");
199        nameMap.set("webkitsourceclose", "Source Close");
200        nameMap.set("webkitsourceended", "Source Ended");
201        nameMap.set("webkitsourceopen", "Source Open");
202        nameMap.set("webkitspeechchange", "Speech Change");
203        nameMap.set("writeend", "Write End");
204        nameMap.set("writestart", "Write Start");
205
206        WebInspector.ScriptTimelineRecord._eventDisplayNames = nameMap;
207    }
208
209    switch(eventType) {
210    case WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated:
211        return WebInspector.UIString("Script Evaluated");
212    case WebInspector.ScriptTimelineRecord.EventType.EventDispatched:
213        if (details && (details instanceof String || typeof details === "string")) {
214            var eventDisplayName = WebInspector.ScriptTimelineRecord._eventDisplayNames.get(details) || details.capitalize();
215            return WebInspector.UIString("%s Event Dispatched").format(eventDisplayName);
216        }
217
218        return WebInspector.UIString("Event Dispatched");
219    case WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded:
220        return WebInspector.UIString("Probe Sample Recorded");
221    case WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded:
222        if (details && (details instanceof String || typeof details === "string"))
223            return WebInspector.UIString("“%s” Profile Recorded").format(details);
224        return WebInspector.UIString("Console Profile Recorded");
225    case WebInspector.ScriptTimelineRecord.EventType.TimerFired:
226        if (details && includeTimerIdentifierInMainTitle)
227            return WebInspector.UIString("Timer %s Fired").format(details);
228        return WebInspector.UIString("Timer Fired");
229    case WebInspector.ScriptTimelineRecord.EventType.TimerInstalled:
230        if (details && includeTimerIdentifierInMainTitle)
231            return WebInspector.UIString("Timer %s Installed").format(details);
232        return WebInspector.UIString("Timer Installed");
233    case WebInspector.ScriptTimelineRecord.EventType.TimerRemoved:
234        if (details && includeTimerIdentifierInMainTitle)
235            return WebInspector.UIString("Timer %s Removed").format(details);
236        return WebInspector.UIString("Timer Removed");
237    case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired:
238        return WebInspector.UIString("Animation Frame Fired");
239    case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested:
240        return WebInspector.UIString("Animation Frame Requested");
241    case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled:
242        return WebInspector.UIString("Animation Frame Canceled");
243    }
244};
245
246WebInspector.ScriptTimelineRecord.TypeIdentifier = "script-timeline-record";
247WebInspector.ScriptTimelineRecord.EventTypeCookieKey = "script-timeline-record-event-type";
248WebInspector.ScriptTimelineRecord.DetailsCookieKey = "script-timeline-record-details";
249
250WebInspector.ScriptTimelineRecord.prototype = {
251    constructor: WebInspector.ScriptTimelineRecord,
252
253    // Public
254
255    get eventType()
256    {
257        return this._eventType;
258    },
259
260    get details()
261    {
262        return this._details;
263    },
264
265    get profile()
266    {
267        this._initializeProfileFromPayload();
268        return this._profile;
269    },
270
271    saveIdentityToCookie: function(cookie)
272    {
273        WebInspector.TimelineRecord.prototype.saveIdentityToCookie.call(this, cookie);
274
275        cookie[WebInspector.ScriptTimelineRecord.EventTypeCookieKey] = this._eventType;
276        cookie[WebInspector.ScriptTimelineRecord.DetailsCookieKey] = this._details;
277    },
278
279    // Private
280
281    _initializeProfileFromPayload: function(payload)
282    {
283        if (this._profile || !this._profilePayload)
284            return;
285
286        var payload = this._profilePayload;
287        delete this._profilePayload;
288
289        console.assert(payload.rootNodes instanceof Array);
290
291        function profileNodeFromPayload(nodePayload)
292        {
293            console.assert("id" in nodePayload);
294            console.assert(nodePayload.calls instanceof Array);
295
296            if (nodePayload.url) {
297                var sourceCode = WebInspector.frameResourceManager.resourceForURL(nodePayload.url);
298                if (!sourceCode)
299                    sourceCode = WebInspector.debuggerManager.scriptsForURL(nodePayload.url)[0];
300
301                // The lineNumber is 1-based, but we expect 0-based.
302                var lineNumber = nodePayload.lineNumber - 1;
303
304                var sourceCodeLocation = sourceCode ? sourceCode.createLazySourceCodeLocation(lineNumber, nodePayload.columnNumber) : null;
305            }
306
307            var isProgramCode = nodePayload.functionName === "(program)";
308            var isAnonymousFunction = nodePayload.functionName === "(anonymous function)";
309
310            var type = isProgramCode ? WebInspector.ProfileNode.Type.Program : WebInspector.ProfileNode.Type.Function;
311            var functionName = !isProgramCode && !isAnonymousFunction && nodePayload.functionName !== "(unknown)" ? nodePayload.functionName : null;
312            var calls = nodePayload.calls.map(profileNodeCallFromPayload);
313
314            return new WebInspector.ProfileNode(nodePayload.id, type, functionName, sourceCodeLocation, calls, nodePayload.children);
315        }
316
317        function profileNodeCallFromPayload(nodeCallPayload)
318        {
319            console.assert("startTime" in nodeCallPayload);
320            console.assert("totalTime" in nodeCallPayload);
321
322            return new WebInspector.ProfileNodeCall(nodeCallPayload.startTime, nodeCallPayload.totalTime);
323        }
324
325        var rootNodes = payload.rootNodes;
326
327        // Iterate over the node tree using a stack. Doing this recursively can easily cause a stack overflow.
328        // We traverse the profile in post-order and convert the payloads in place until we get back to the root.
329        var stack = [{parent: {children: rootNodes}, index: 0, root: true}];
330        while (stack.length) {
331            var entry = stack.lastValue;
332
333            if (entry.index < entry.parent.children.length) {
334                var childNodePayload = entry.parent.children[entry.index];
335                if (childNodePayload.children && childNodePayload.children.length)
336                    stack.push({parent: childNodePayload, index: 0});
337
338                ++entry.index;
339            } else {
340                if (!entry.root)
341                    entry.parent.children = entry.parent.children.map(profileNodeFromPayload);
342                else
343                    rootNodes = rootNodes.map(profileNodeFromPayload);
344
345                stack.pop();
346            }
347        }
348
349        this._profile = new WebInspector.Profile(rootNodes, payload.idleTime);
350    }
351};
352
353WebInspector.ScriptTimelineRecord.prototype.__proto__ = WebInspector.TimelineRecord.prototype;
354