Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(168)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/timeline_model/TimelineIRModel.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4
5 /** 4 /**
6 * @constructor 5 * @unrestricted
7 */ 6 */
8 WebInspector.TimelineIRModel = function() 7 WebInspector.TimelineIRModel = class {
9 { 8 constructor() {
10 this.reset(); 9 this.reset();
10 }
11
12 /**
13 * @param {!WebInspector.TracingModel.Event} event
14 * @return {!WebInspector.TimelineIRModel.Phases}
15 */
16 static phaseForEvent(event) {
17 return event[WebInspector.TimelineIRModel._eventIRPhase];
18 }
19
20 /**
21 * @param {?Array<!WebInspector.TracingModel.AsyncEvent>} inputLatencies
22 * @param {?Array<!WebInspector.TracingModel.AsyncEvent>} animations
23 */
24 populate(inputLatencies, animations) {
25 var eventTypes = WebInspector.TimelineIRModel.InputEvents;
26 var phases = WebInspector.TimelineIRModel.Phases;
27
28 this.reset();
29 if (!inputLatencies)
30 return;
31 this._processInputLatencies(inputLatencies);
32 if (animations)
33 this._processAnimations(animations);
34 var range = new WebInspector.SegmentedRange();
35 range.appendRange(this._drags); // Drags take lower precedence than animati on, as we can't detect them reliably.
36 range.appendRange(this._cssAnimations);
37 range.appendRange(this._scrolls);
38 range.appendRange(this._responses);
39 this._segments = range.segments();
40 }
41
42 /**
43 * @param {!Array<!WebInspector.TracingModel.AsyncEvent>} events
44 */
45 _processInputLatencies(events) {
46 var eventTypes = WebInspector.TimelineIRModel.InputEvents;
47 var phases = WebInspector.TimelineIRModel.Phases;
48 var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs;
49
50 var scrollStart;
51 var flingStart;
52 var touchStart;
53 var firstTouchMove;
54 var mouseWheel;
55 var mouseDown;
56 var mouseMove;
57
58 for (var i = 0; i < events.length; ++i) {
59 var event = events[i];
60 if (i > 0 && events[i].startTime < events[i - 1].startTime)
61 console.assert(false, 'Unordered input events');
62 var type = this._inputEventType(event.name);
63 switch (type) {
64 case eventTypes.ScrollBegin:
65 this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
66 scrollStart = event;
67 break;
68
69 case eventTypes.ScrollEnd:
70 if (scrollStart)
71 this._scrolls.append(this._segmentForEventRange(scrollStart, event, phases.Scroll));
72 else
73 this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
74 scrollStart = null;
75 break;
76
77 case eventTypes.ScrollUpdate:
78 touchStart = null; // Since we're scrolling now, disregard other touc h gestures.
79 this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
80 break;
81
82 case eventTypes.FlingStart:
83 if (flingStart) {
84 WebInspector.console.error(
85 WebInspector.UIString('Two flings at the same time? %s vs %s', f lingStart.startTime, event.startTime));
86 break;
87 }
88 flingStart = event;
89 break;
90
91 case eventTypes.FlingCancel:
92 // FIXME: also process renderer fling events.
93 if (!flingStart)
94 break;
95 this._scrolls.append(this._segmentForEventRange(flingStart, event, pha ses.Fling));
96 flingStart = null;
97 break;
98
99 case eventTypes.ImplSideFling:
100 this._scrolls.append(this._segmentForEvent(event, phases.Fling));
101 break;
102
103 case eventTypes.ShowPress:
104 case eventTypes.Tap:
105 case eventTypes.KeyDown:
106 case eventTypes.KeyDownRaw:
107 case eventTypes.KeyUp:
108 case eventTypes.Char:
109 case eventTypes.Click:
110 case eventTypes.ContextMenu:
111 this._responses.append(this._segmentForEvent(event, phases.Response));
112 break;
113
114 case eventTypes.TouchStart:
115 // We do not produce any response segment for TouchStart -- there's ei ther going to be one upon
116 // TouchMove for drag, or one for GestureTap.
117 if (touchStart) {
118 WebInspector.console.error(
119 WebInspector.UIString('Two touches at the same time? %s vs %s', touchStart.startTime, event.startTime));
120 break;
121 }
122 touchStart = event;
123 event.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = phases.Re sponse;
124 firstTouchMove = null;
125 break;
126
127 case eventTypes.TouchCancel:
128 touchStart = null;
129 break;
130
131 case eventTypes.TouchMove:
132 if (firstTouchMove) {
133 this._drags.append(this._segmentForEvent(event, phases.Drag));
134 } else if (touchStart) {
135 firstTouchMove = event;
136 this._responses.append(this._segmentForEventRange(touchStart, event, phases.Response));
137 }
138 break;
139
140 case eventTypes.TouchEnd:
141 touchStart = null;
142 break;
143
144 case eventTypes.MouseDown:
145 mouseDown = event;
146 mouseMove = null;
147 break;
148
149 case eventTypes.MouseMove:
150 if (mouseDown && !mouseMove && mouseDown.startTime + thresholdsMs.mous e > event.startTime) {
151 this._responses.append(this._segmentForEvent(mouseDown, phases.Respo nse));
152 this._responses.append(this._segmentForEvent(event, phases.Response) );
153 } else if (mouseDown) {
154 this._drags.append(this._segmentForEvent(event, phases.Drag));
155 }
156 mouseMove = event;
157 break;
158
159 case eventTypes.MouseUp:
160 this._responses.append(this._segmentForEvent(event, phases.Response));
161 mouseDown = null;
162 break;
163
164 case eventTypes.MouseWheel:
165 // Do not consider first MouseWheel as trace viewer's implementation d oes -- in case of MouseWheel it's not really special.
166 if (mouseWheel && canMerge(thresholdsMs.mouse, mouseWheel, event))
167 this._scrolls.append(this._segmentForEventRange(mouseWheel, event, p hases.Scroll));
168 else
169 this._scrolls.append(this._segmentForEvent(event, phases.Scroll));
170 mouseWheel = event;
171 break;
172 }
173 }
174
175 /**
176 * @param {number} threshold
177 * @param {!WebInspector.TracingModel.AsyncEvent} first
178 * @param {!WebInspector.TracingModel.AsyncEvent} second
179 * @return {boolean}
180 */
181 function canMerge(threshold, first, second) {
182 return first.endTime < second.startTime && second.startTime < first.endTim e + threshold;
183 }
184 }
185
186 /**
187 * @param {!Array<!WebInspector.TracingModel.AsyncEvent>} events
188 */
189 _processAnimations(events) {
190 for (var i = 0; i < events.length; ++i)
191 this._cssAnimations.append(this._segmentForEvent(events[i], WebInspector.T imelineIRModel.Phases.Animation));
192 }
193
194 /**
195 * @param {!WebInspector.TracingModel.AsyncEvent} event
196 * @param {!WebInspector.TimelineIRModel.Phases} phase
197 * @return {!WebInspector.Segment}
198 */
199 _segmentForEvent(event, phase) {
200 this._setPhaseForEvent(event, phase);
201 return new WebInspector.Segment(event.startTime, event.endTime, phase);
202 }
203
204 /**
205 * @param {!WebInspector.TracingModel.AsyncEvent} startEvent
206 * @param {!WebInspector.TracingModel.AsyncEvent} endEvent
207 * @param {!WebInspector.TimelineIRModel.Phases} phase
208 * @return {!WebInspector.Segment}
209 */
210 _segmentForEventRange(startEvent, endEvent, phase) {
211 this._setPhaseForEvent(startEvent, phase);
212 this._setPhaseForEvent(endEvent, phase);
213 return new WebInspector.Segment(startEvent.startTime, endEvent.endTime, phas e);
214 }
215
216 /**
217 * @param {!WebInspector.TracingModel.AsyncEvent} asyncEvent
218 * @param {!WebInspector.TimelineIRModel.Phases} phase
219 */
220 _setPhaseForEvent(asyncEvent, phase) {
221 asyncEvent.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = phase;
222 }
223
224 /**
225 * @return {!Array<!WebInspector.Segment>}
226 */
227 interactionRecords() {
228 return this._segments;
229 }
230
231 reset() {
232 var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs;
233
234 this._segments = [];
235 this._drags = new WebInspector.SegmentedRange(merge.bind(null, thresholdsMs. mouse));
236 this._cssAnimations = new WebInspector.SegmentedRange(merge.bind(null, thres holdsMs.animation));
237 this._responses = new WebInspector.SegmentedRange(merge.bind(null, 0));
238 this._scrolls = new WebInspector.SegmentedRange(merge.bind(null, thresholdsM s.animation));
239
240 /**
241 * @param {number} threshold
242 * @param {!WebInspector.Segment} first
243 * @param {!WebInspector.Segment} second
244 */
245 function merge(threshold, first, second) {
246 return first.end + threshold >= second.begin && first.data === second.data ? first : null;
247 }
248 }
249
250 /**
251 * @param {string} eventName
252 * @return {?WebInspector.TimelineIRModel.InputEvents}
253 */
254 _inputEventType(eventName) {
255 var prefix = 'InputLatency::';
256 if (!eventName.startsWith(prefix)) {
257 if (eventName === WebInspector.TimelineIRModel.InputEvents.ImplSideFling)
258 return /** @type {!WebInspector.TimelineIRModel.InputEvents} */ (eventNa me);
259 console.error('Unrecognized input latency event: ' + eventName);
260 return null;
261 }
262 return /** @type {!WebInspector.TimelineIRModel.InputEvents} */ (eventName.s ubstr(prefix.length));
263 }
11 }; 264 };
12 265
13 /** 266 /**
14 * @enum {string} 267 * @enum {string}
15 */ 268 */
16 WebInspector.TimelineIRModel.Phases = { 269 WebInspector.TimelineIRModel.Phases = {
17 Idle: "Idle", 270 Idle: 'Idle',
18 Response: "Response", 271 Response: 'Response',
19 Scroll: "Scroll", 272 Scroll: 'Scroll',
20 Fling: "Fling", 273 Fling: 'Fling',
21 Drag: "Drag", 274 Drag: 'Drag',
22 Animation: "Animation", 275 Animation: 'Animation',
23 Uncategorized: "Uncategorized" 276 Uncategorized: 'Uncategorized'
24 }; 277 };
25 278
26 /** 279 /**
27 * @enum {string} 280 * @enum {string}
28 */ 281 */
29 WebInspector.TimelineIRModel.InputEvents = { 282 WebInspector.TimelineIRModel.InputEvents = {
30 Char: "Char", 283 Char: 'Char',
31 Click: "GestureClick", 284 Click: 'GestureClick',
32 ContextMenu: "ContextMenu", 285 ContextMenu: 'ContextMenu',
33 FlingCancel: "GestureFlingCancel", 286 FlingCancel: 'GestureFlingCancel',
34 FlingStart: "GestureFlingStart", 287 FlingStart: 'GestureFlingStart',
35 ImplSideFling: WebInspector.TimelineModel.RecordType.ImplSideFling, 288 ImplSideFling: WebInspector.TimelineModel.RecordType.ImplSideFling,
36 KeyDown: "KeyDown", 289 KeyDown: 'KeyDown',
37 KeyDownRaw: "RawKeyDown", 290 KeyDownRaw: 'RawKeyDown',
38 KeyUp: "KeyUp", 291 KeyUp: 'KeyUp',
39 LatencyScrollUpdate: "ScrollUpdate", 292 LatencyScrollUpdate: 'ScrollUpdate',
40 MouseDown: "MouseDown", 293 MouseDown: 'MouseDown',
41 MouseMove: "MouseMove", 294 MouseMove: 'MouseMove',
42 MouseUp: "MouseUp", 295 MouseUp: 'MouseUp',
43 MouseWheel: "MouseWheel", 296 MouseWheel: 'MouseWheel',
44 PinchBegin: "GesturePinchBegin", 297 PinchBegin: 'GesturePinchBegin',
45 PinchEnd: "GesturePinchEnd", 298 PinchEnd: 'GesturePinchEnd',
46 PinchUpdate: "GesturePinchUpdate", 299 PinchUpdate: 'GesturePinchUpdate',
47 ScrollBegin: "GestureScrollBegin", 300 ScrollBegin: 'GestureScrollBegin',
48 ScrollEnd: "GestureScrollEnd", 301 ScrollEnd: 'GestureScrollEnd',
49 ScrollUpdate: "GestureScrollUpdate", 302 ScrollUpdate: 'GestureScrollUpdate',
50 ScrollUpdateRenderer: "ScrollUpdate", 303 ScrollUpdateRenderer: 'ScrollUpdate',
51 ShowPress: "GestureShowPress", 304 ShowPress: 'GestureShowPress',
52 Tap: "GestureTap", 305 Tap: 'GestureTap',
53 TapCancel: "GestureTapCancel", 306 TapCancel: 'GestureTapCancel',
54 TapDown: "GestureTapDown", 307 TapDown: 'GestureTapDown',
55 TouchCancel: "TouchCancel", 308 TouchCancel: 'TouchCancel',
56 TouchEnd: "TouchEnd", 309 TouchEnd: 'TouchEnd',
57 TouchMove: "TouchMove", 310 TouchMove: 'TouchMove',
58 TouchStart: "TouchStart" 311 TouchStart: 'TouchStart'
59 }; 312 };
60 313
61 WebInspector.TimelineIRModel._mergeThresholdsMs = { 314 WebInspector.TimelineIRModel._mergeThresholdsMs = {
62 animation: 1, 315 animation: 1,
63 mouse: 40, 316 mouse: 40,
64 }; 317 };
65 318
66 WebInspector.TimelineIRModel._eventIRPhase = Symbol("eventIRPhase"); 319 WebInspector.TimelineIRModel._eventIRPhase = Symbol('eventIRPhase');
67 320
68 /** 321
69 * @param {!WebInspector.TracingModel.Event} event
70 * @return {!WebInspector.TimelineIRModel.Phases}
71 */
72 WebInspector.TimelineIRModel.phaseForEvent = function(event)
73 {
74 return event[WebInspector.TimelineIRModel._eventIRPhase];
75 };
76
77 WebInspector.TimelineIRModel.prototype = {
78 /**
79 * @param {?Array<!WebInspector.TracingModel.AsyncEvent>} inputLatencies
80 * @param {?Array<!WebInspector.TracingModel.AsyncEvent>} animations
81 */
82 populate: function(inputLatencies, animations)
83 {
84 var eventTypes = WebInspector.TimelineIRModel.InputEvents;
85 var phases = WebInspector.TimelineIRModel.Phases;
86
87 this.reset();
88 if (!inputLatencies)
89 return;
90 this._processInputLatencies(inputLatencies);
91 if (animations)
92 this._processAnimations(animations);
93 var range = new WebInspector.SegmentedRange();
94 range.appendRange(this._drags); // Drags take lower precedence than anim ation, as we can't detect them reliably.
95 range.appendRange(this._cssAnimations);
96 range.appendRange(this._scrolls);
97 range.appendRange(this._responses);
98 this._segments = range.segments();
99 },
100
101 /**
102 * @param {!Array<!WebInspector.TracingModel.AsyncEvent>} events
103 */
104 _processInputLatencies: function(events)
105 {
106 var eventTypes = WebInspector.TimelineIRModel.InputEvents;
107 var phases = WebInspector.TimelineIRModel.Phases;
108 var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs;
109
110 var scrollStart;
111 var flingStart;
112 var touchStart;
113 var firstTouchMove;
114 var mouseWheel;
115 var mouseDown;
116 var mouseMove;
117
118 for (var i = 0; i < events.length; ++i) {
119 var event = events[i];
120 if (i > 0 && events[i].startTime < events[i - 1].startTime)
121 console.assert(false, "Unordered input events");
122 var type = this._inputEventType(event.name);
123 switch (type) {
124
125 case eventTypes.ScrollBegin:
126 this._scrolls.append(this._segmentForEvent(event, phases.Scroll) );
127 scrollStart = event;
128 break;
129
130 case eventTypes.ScrollEnd:
131 if (scrollStart)
132 this._scrolls.append(this._segmentForEventRange(scrollStart, event, phases.Scroll));
133 else
134 this._scrolls.append(this._segmentForEvent(event, phases.Scr oll));
135 scrollStart = null;
136 break;
137
138 case eventTypes.ScrollUpdate:
139 touchStart = null; // Since we're scrolling now, disregard other touch gestures.
140 this._scrolls.append(this._segmentForEvent(event, phases.Scroll) );
141 break;
142
143 case eventTypes.FlingStart:
144 if (flingStart) {
145 WebInspector.console.error(WebInspector.UIString("Two flings at the same time? %s vs %s", flingStart.startTime, event.startTime));
146 break;
147 }
148 flingStart = event;
149 break;
150
151 case eventTypes.FlingCancel:
152 // FIXME: also process renderer fling events.
153 if (!flingStart)
154 break;
155 this._scrolls.append(this._segmentForEventRange(flingStart, even t, phases.Fling));
156 flingStart = null;
157 break;
158
159 case eventTypes.ImplSideFling:
160 this._scrolls.append(this._segmentForEvent(event, phases.Fling)) ;
161 break;
162
163 case eventTypes.ShowPress:
164 case eventTypes.Tap:
165 case eventTypes.KeyDown:
166 case eventTypes.KeyDownRaw:
167 case eventTypes.KeyUp:
168 case eventTypes.Char:
169 case eventTypes.Click:
170 case eventTypes.ContextMenu:
171 this._responses.append(this._segmentForEvent(event, phases.Respo nse));
172 break;
173
174 case eventTypes.TouchStart:
175 // We do not produce any response segment for TouchStart -- ther e's either going to be one upon
176 // TouchMove for drag, or one for GestureTap.
177 if (touchStart) {
178 WebInspector.console.error(WebInspector.UIString("Two touche s at the same time? %s vs %s", touchStart.startTime, event.startTime));
179 break;
180 }
181 touchStart = event;
182 event.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = pha ses.Response;
183 firstTouchMove = null;
184 break;
185
186 case eventTypes.TouchCancel:
187 touchStart = null;
188 break;
189
190 case eventTypes.TouchMove:
191 if (firstTouchMove) {
192 this._drags.append(this._segmentForEvent(event, phases.Drag) );
193 } else if (touchStart) {
194 firstTouchMove = event;
195 this._responses.append(this._segmentForEventRange(touchStart , event, phases.Response));
196 }
197 break;
198
199 case eventTypes.TouchEnd:
200 touchStart = null;
201 break;
202
203 case eventTypes.MouseDown:
204 mouseDown = event;
205 mouseMove = null;
206 break;
207
208 case eventTypes.MouseMove:
209 if (mouseDown && !mouseMove && mouseDown.startTime + thresholdsM s.mouse > event.startTime) {
210 this._responses.append(this._segmentForEvent(mouseDown, phas es.Response));
211 this._responses.append(this._segmentForEvent(event, phases.R esponse));
212 } else if (mouseDown) {
213 this._drags.append(this._segmentForEvent(event, phases.Drag) );
214 }
215 mouseMove = event;
216 break;
217
218 case eventTypes.MouseUp:
219 this._responses.append(this._segmentForEvent(event, phases.Respo nse));
220 mouseDown = null;
221 break;
222
223 case eventTypes.MouseWheel:
224 // Do not consider first MouseWheel as trace viewer's implementa tion does -- in case of MouseWheel it's not really special.
225 if (mouseWheel && canMerge(thresholdsMs.mouse, mouseWheel, event ))
226 this._scrolls.append(this._segmentForEventRange(mouseWheel, event, phases.Scroll));
227 else
228 this._scrolls.append(this._segmentForEvent(event, phases.Scr oll));
229 mouseWheel = event;
230 break;
231 }
232 }
233
234 /**
235 * @param {number} threshold
236 * @param {!WebInspector.TracingModel.AsyncEvent} first
237 * @param {!WebInspector.TracingModel.AsyncEvent} second
238 * @return {boolean}
239 */
240 function canMerge(threshold, first, second)
241 {
242 return first.endTime < second.startTime && second.startTime < first. endTime + threshold;
243 }
244 },
245
246 /**
247 * @param {!Array<!WebInspector.TracingModel.AsyncEvent>} events
248 */
249 _processAnimations: function(events)
250 {
251 for (var i = 0; i < events.length; ++i)
252 this._cssAnimations.append(this._segmentForEvent(events[i], WebInspe ctor.TimelineIRModel.Phases.Animation));
253 },
254
255 /**
256 * @param {!WebInspector.TracingModel.AsyncEvent} event
257 * @param {!WebInspector.TimelineIRModel.Phases} phase
258 * @return {!WebInspector.Segment}
259 */
260 _segmentForEvent: function(event, phase)
261 {
262 this._setPhaseForEvent(event, phase);
263 return new WebInspector.Segment(event.startTime, event.endTime, phase);
264 },
265
266 /**
267 * @param {!WebInspector.TracingModel.AsyncEvent} startEvent
268 * @param {!WebInspector.TracingModel.AsyncEvent} endEvent
269 * @param {!WebInspector.TimelineIRModel.Phases} phase
270 * @return {!WebInspector.Segment}
271 */
272 _segmentForEventRange: function(startEvent, endEvent, phase)
273 {
274 this._setPhaseForEvent(startEvent, phase);
275 this._setPhaseForEvent(endEvent, phase);
276 return new WebInspector.Segment(startEvent.startTime, endEvent.endTime, phase);
277 },
278
279 /**
280 * @param {!WebInspector.TracingModel.AsyncEvent} asyncEvent
281 * @param {!WebInspector.TimelineIRModel.Phases} phase
282 */
283 _setPhaseForEvent: function(asyncEvent, phase)
284 {
285 asyncEvent.steps[0][WebInspector.TimelineIRModel._eventIRPhase] = phase;
286 },
287
288 /**
289 * @return {!Array<!WebInspector.Segment>}
290 */
291 interactionRecords: function()
292 {
293 return this._segments;
294 },
295
296 reset: function()
297 {
298 var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs;
299
300 this._segments = [];
301 this._drags = new WebInspector.SegmentedRange(merge.bind(null, threshold sMs.mouse));
302 this._cssAnimations = new WebInspector.SegmentedRange(merge.bind(null, t hresholdsMs.animation));
303 this._responses = new WebInspector.SegmentedRange(merge.bind(null, 0));
304 this._scrolls = new WebInspector.SegmentedRange(merge.bind(null, thresho ldsMs.animation));
305
306 /**
307 * @param {number} threshold
308 * @param {!WebInspector.Segment} first
309 * @param {!WebInspector.Segment} second
310 */
311 function merge(threshold, first, second)
312 {
313 return first.end + threshold >= second.begin && first.data === secon d.data ? first : null;
314 }
315 },
316
317 /**
318 * @param {string} eventName
319 * @return {?WebInspector.TimelineIRModel.InputEvents}
320 */
321 _inputEventType: function(eventName)
322 {
323 var prefix = "InputLatency::";
324 if (!eventName.startsWith(prefix)) {
325 if (eventName === WebInspector.TimelineIRModel.InputEvents.ImplSideF ling)
326 return /** @type {!WebInspector.TimelineIRModel.InputEvents} */ (eventName);
327 console.error("Unrecognized input latency event: " + eventName);
328 return null;
329 }
330 return /** @type {!WebInspector.TimelineIRModel.InputEvents} */ (eventNa me.substr(prefix.length));
331 }
332 };
333
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698