1 | import {bind, provide, Provider} from 'angular2/src/core/di';
|
2 | import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
3 | import {
|
4 | Json,
|
5 | isPresent,
|
6 | isBlank,
|
7 | RegExpWrapper,
|
8 | StringWrapper,
|
9 | NumberWrapper
|
10 | } from 'angular2/src/facade/lang';
|
11 | import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
12 |
|
13 | import {WebDriverExtension, PerfLogFeatures} from '../web_driver_extension';
|
14 | import {WebDriverAdapter} from '../web_driver_adapter';
|
15 | import {Options} from '../common_options';
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | export class ChromeDriverExtension extends WebDriverExtension {
|
25 |
|
26 | static get BINDINGS(): Provider[] { return _PROVIDERS; }
|
27 |
|
28 | private _majorChromeVersion: number;
|
29 |
|
30 | constructor(private _driver: WebDriverAdapter, userAgent: string) {
|
31 | super();
|
32 | this._majorChromeVersion = this._parseChromeVersion(userAgent);
|
33 | }
|
34 |
|
35 | private _parseChromeVersion(userAgent: string): number {
|
36 | if (isBlank(userAgent)) {
|
37 | return -1;
|
38 | }
|
39 | var v = StringWrapper.split(userAgent, /Chrom(e|ium)\//g)[2];
|
40 | if (isBlank(v)) {
|
41 | return -1;
|
42 | }
|
43 | v = v.split('.')[0];
|
44 | if (isBlank(v)) {
|
45 | return -1;
|
46 | }
|
47 | return NumberWrapper.parseInt(v, 10);
|
48 | }
|
49 |
|
50 | gc() { return this._driver.executeScript('window.gc()'); }
|
51 |
|
52 | timeBegin(name: string): Promise<any> {
|
53 | return this._driver.executeScript(`console.time('${name}');`);
|
54 | }
|
55 |
|
56 | timeEnd(name: string, restartName: string = null): Promise<any> {
|
57 | var script = `console.timeEnd('${name}');`;
|
58 | if (isPresent(restartName)) {
|
59 | script += `console.time('${restartName}');`
|
60 | }
|
61 | return this._driver.executeScript(script);
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 | readPerfLog(): Promise<any> {
|
67 |
|
68 |
|
69 | return this._driver.executeScript('1+1')
|
70 | .then((_) => this._driver.logs('performance'))
|
71 | .then((entries) => {
|
72 | var events = [];
|
73 | entries.forEach(entry => {
|
74 | var message = Json.parse(entry['message'])['message'];
|
75 | if (StringWrapper.equals(message['method'], 'Tracing.dataCollected')) {
|
76 | events.push(message['params']);
|
77 | }
|
78 | if (StringWrapper.equals(message['method'], 'Tracing.bufferUsage')) {
|
79 | throw new BaseException('The DevTools trace buffer filled during the test!');
|
80 | }
|
81 | });
|
82 | return this._convertPerfRecordsToEvents(events);
|
83 | });
|
84 | }
|
85 |
|
86 | private _convertPerfRecordsToEvents(chromeEvents: Array<{[key: string]: any}>,
|
87 | normalizedEvents: Array<{[key: string]: any}> = null) {
|
88 | if (isBlank(normalizedEvents)) {
|
89 | normalizedEvents = [];
|
90 | }
|
91 | var majorGCPids = {};
|
92 | chromeEvents.forEach((event) => {
|
93 | var categories = this._parseCategories(event['cat']);
|
94 | var name = event['name'];
|
95 | if (this._isEvent(categories, name, ['blink.console'])) {
|
96 | normalizedEvents.push(normalizeEvent(event, {'name': name}));
|
97 | } else if (this._isEvent(categories, name, ['benchmark'],
|
98 | 'BenchmarkInstrumentation::ImplThreadRenderingStats')) {
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | var frameCount = event['args']['data']['frame_count'];
|
107 | if (frameCount > 1) {
|
108 | throw new BaseException('multi-frame render stats not supported');
|
109 | }
|
110 | if (frameCount == 1) {
|
111 | normalizedEvents.push(normalizeEvent(event, {'name': 'frame'}));
|
112 | }
|
113 | } else if (this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
114 | 'Rasterize') ||
|
115 | this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
116 | 'CompositeLayers')) {
|
117 | normalizedEvents.push(normalizeEvent(event, {'name': 'render'}));
|
118 | } else if (this._majorChromeVersion < 45) {
|
119 | var normalizedEvent = this._processAsPreChrome45Event(event, categories, majorGCPids);
|
120 | if (normalizedEvent != null) normalizedEvents.push(normalizedEvent);
|
121 | } else {
|
122 | var normalizedEvent = this._processAsPostChrome44Event(event, categories);
|
123 | if (normalizedEvent != null) normalizedEvents.push(normalizedEvent);
|
124 | }
|
125 | });
|
126 | return normalizedEvents;
|
127 | }
|
128 |
|
129 | private _processAsPreChrome45Event(event, categories, majorGCPids) {
|
130 | var name = event['name'];
|
131 | var args = event['args'];
|
132 | var pid = event['pid'];
|
133 | var ph = event['ph'];
|
134 | if (this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
135 | 'FunctionCall') &&
|
136 | (isBlank(args) || isBlank(args['data']) ||
|
137 | !StringWrapper.equals(args['data']['scriptName'], 'InjectedScript'))) {
|
138 | return normalizeEvent(event, {'name': 'script'});
|
139 | } else if (this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
140 | 'RecalculateStyles') ||
|
141 | this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
142 | 'Layout') ||
|
143 | this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
144 | 'UpdateLayerTree') ||
|
145 | this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
146 | 'Paint')) {
|
147 | return normalizeEvent(event, {'name': 'render'});
|
148 | } else if (this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'],
|
149 | 'GCEvent')) {
|
150 | var normArgs = {
|
151 | 'usedHeapSize': isPresent(args['usedHeapSizeAfter']) ? args['usedHeapSizeAfter'] :
|
152 | args['usedHeapSizeBefore']
|
153 | };
|
154 | if (StringWrapper.equals(ph, 'E')) {
|
155 | normArgs['majorGc'] = isPresent(majorGCPids[pid]) && majorGCPids[pid];
|
156 | }
|
157 | majorGCPids[pid] = false;
|
158 | return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
|
159 | } else if (this._isEvent(categories, name, ['v8'], 'majorGC') &&
|
160 | StringWrapper.equals(ph, 'B')) {
|
161 | majorGCPids[pid] = true;
|
162 | }
|
163 | return null;
|
164 | }
|
165 |
|
166 | private _processAsPostChrome44Event(event, categories) {
|
167 | var name = event['name'];
|
168 | var args = event['args'];
|
169 | if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) {
|
170 | var normArgs = {
|
171 | 'majorGc': true,
|
172 | 'usedHeapSize': isPresent(args['usedHeapSizeAfter']) ? args['usedHeapSizeAfter'] :
|
173 | args['usedHeapSizeBefore']
|
174 | };
|
175 | return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
|
176 | } else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MinorGC')) {
|
177 | var normArgs = {
|
178 | 'majorGc': false,
|
179 | 'usedHeapSize': isPresent(args['usedHeapSizeAfter']) ? args['usedHeapSizeAfter'] :
|
180 | args['usedHeapSizeBefore']
|
181 | };
|
182 | return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
|
183 | } else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'FunctionCall') &&
|
184 | (isBlank(args) || isBlank(args['data']) ||
|
185 | (!StringWrapper.equals(args['data']['scriptName'], 'InjectedScript') &&
|
186 | !StringWrapper.equals(args['data']['scriptName'], '')))) {
|
187 | return normalizeEvent(event, {'name': 'script'});
|
188 | } else if (this._isEvent(categories, name, ['devtools.timeline', 'blink'],
|
189 | 'UpdateLayoutTree')) {
|
190 | return normalizeEvent(event, {'name': 'render'});
|
191 | } else if (this._isEvent(categories, name, ['devtools.timeline'], 'UpdateLayerTree') ||
|
192 | this._isEvent(categories, name, ['devtools.timeline'], 'Layout') ||
|
193 | this._isEvent(categories, name, ['devtools.timeline'], 'Paint')) {
|
194 | return normalizeEvent(event, {'name': 'render'});
|
195 | } else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceReceivedData')) {
|
196 | let normArgs = {'encodedDataLength': args['data']['encodedDataLength']};
|
197 | return normalizeEvent(event, {'name': 'receivedData', 'args': normArgs});
|
198 | } else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceSendRequest')) {
|
199 | let data = args['data'];
|
200 | let normArgs = {'url': data['url'], 'method': data['requestMethod']};
|
201 | return normalizeEvent(event, {'name': 'sendRequest', 'args': normArgs});
|
202 | } else if (this._isEvent(categories, name, ['blink.user_timing'], 'navigationStart')) {
|
203 | return normalizeEvent(event, {'name': name});
|
204 | }
|
205 | return null;
|
206 | }
|
207 |
|
208 | private _parseCategories(categories: string): string[] { return categories.split(','); }
|
209 |
|
210 | private _isEvent(eventCategories: string[], eventName: string, expectedCategories: string[],
|
211 | expectedName: string = null): boolean {
|
212 | var hasCategories = expectedCategories.reduce(
|
213 | (value, cat) => { return value && ListWrapper.contains(eventCategories, cat); }, true);
|
214 | return isBlank(expectedName) ? hasCategories :
|
215 | hasCategories && StringWrapper.equals(eventName, expectedName);
|
216 | }
|
217 |
|
218 | perfLogFeatures(): PerfLogFeatures {
|
219 | return new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
|
220 | }
|
221 |
|
222 | supports(capabilities: {[key: string]: any}): boolean {
|
223 | return this._majorChromeVersion != -1 &&
|
224 | StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'chrome');
|
225 | }
|
226 | }
|
227 |
|
228 | function normalizeEvent(chromeEvent: {[key: string]: any},
|
229 | data: {[key: string]: any}): {[key: string]: any} {
|
230 | var ph = chromeEvent['ph'];
|
231 | if (StringWrapper.equals(ph, 'S')) {
|
232 | ph = 'b';
|
233 | } else if (StringWrapper.equals(ph, 'F')) {
|
234 | ph = 'e';
|
235 | }
|
236 | var result =
|
237 | {'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000};
|
238 | if (chromeEvent['ph'] === 'X') {
|
239 | var dur = chromeEvent['dur'];
|
240 | if (isBlank(dur)) {
|
241 | dur = chromeEvent['tdur'];
|
242 | }
|
243 | result['dur'] = isBlank(dur) ? 0.0 : dur / 1000;
|
244 | }
|
245 | StringMapWrapper.forEach(data, (value, prop) => { result[prop] = value; });
|
246 | return result;
|
247 | }
|
248 |
|
249 | var _PROVIDERS = [
|
250 | bind(ChromeDriverExtension)
|
251 | .toFactory((driver, userAgent) => new ChromeDriverExtension(driver, userAgent),
|
252 | [WebDriverAdapter, Options.USER_AGENT])
|
253 | ];
|