1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | (function () {
|
18 | var window = this;
|
19 | var config = window.attesterConfig || {};
|
20 | window.attesterConfig = null;
|
21 | var AttesterAPI = function (taskExecutionId) {
|
22 | this.__taskExecutionId = taskExecutionId || -1;
|
23 | this.currentTask = {};
|
24 | };
|
25 | var attesterPrototype = AttesterAPI.prototype = {};
|
26 |
|
27 | var location = window.location;
|
28 | var document = window.document;
|
29 | var statusBar = document.getElementById('status_text');
|
30 | var statusInfo = document.getElementById('status_info');
|
31 | var pauseResume = document.getElementById('pause_resume');
|
32 | var logs = document.getElementById('status_logs');
|
33 | var paused = false;
|
34 | var iframeParent = document.getElementById('content');
|
35 | var iframe;
|
36 | var baseUrl = location.protocol + "//" + location.host;
|
37 | var socketStatus = "loading";
|
38 | var testStatus = "waiting";
|
39 | var testInfo = "loading";
|
40 | var currentTask = null;
|
41 | var pendingTestStarts = null;
|
42 | var slaveId = (function () {
|
43 | var match = /(?:\?|\&)id=([^&]+)/.exec(location.search);
|
44 | if (match) {
|
45 | return match[1];
|
46 | }
|
47 | return null;
|
48 | })();
|
49 | var flags = (function () {
|
50 | var match = /(?:\?|\&)flags=([^&]+)/.exec(location.search);
|
51 | if (match) {
|
52 | return decodeURIComponent(match[1]);
|
53 | }
|
54 | return null;
|
55 | })();
|
56 | var beginning = new Date();
|
57 |
|
58 | var log = window.location.search.indexOf("log=true") !== -1 ?
|
59 | function (message) {
|
60 | var time = (new Date() - beginning) + "ms";
|
61 |
|
62 | if (window.console && window.console.log) {
|
63 | console.log(time, message);
|
64 | }
|
65 |
|
66 | logs.innerHTML = "<p><span class='timestamp'>" + time + "</span>" + message + "</p>" + logs.innerHTML;
|
67 | logs.firstChild.scrollIntoView(false);
|
68 | } : function () {};
|
69 |
|
70 | var reportLogToServer = function (level, args, taskExecutionId) {
|
71 | var time = new Date().getTime();
|
72 | var msg = [];
|
73 | for (var i = 0, l = args.length; i < l; i++) {
|
74 | msg.push(String(args[i]));
|
75 | }
|
76 | socket.send(JSON.stringify({
|
77 | type: 'log',
|
78 | taskExecutionId: taskExecutionId,
|
79 | event: {
|
80 | time: time,
|
81 | level: level,
|
82 | message: msg.join(" ")
|
83 | }
|
84 | }));
|
85 | };
|
86 |
|
87 | var updateStatus = function () {
|
88 | statusBar.innerHTML = socketStatus + " - " + testStatus;
|
89 | pauseResume.innerHTML = paused ? "Resume" : "Pause";
|
90 | statusInfo.innerHTML = "<span id='_info_" + testInfo + "'></span>";
|
91 | };
|
92 |
|
93 | var socketStatusUpdate = function (status, info) {
|
94 | socketStatus = status;
|
95 | testInfo = info || status;
|
96 | updateStatus();
|
97 | };
|
98 |
|
99 | var removeIframe = function () {
|
100 | if (iframe) {
|
101 | window.attester = new AttesterAPI();
|
102 | iframeParent.removeChild(iframe);
|
103 | iframe = null;
|
104 | }
|
105 | };
|
106 |
|
107 | var createIframe = function (src, taskExecutionId) {
|
108 | removeIframe();
|
109 | window.attester = new AttesterAPI(taskExecutionId);
|
110 | iframe = document.createElement("iframe");
|
111 | iframe.setAttribute("id", "iframe");
|
112 | iframe.setAttribute("src", src);
|
113 |
|
114 | iframe.setAttribute("frameBorder", "0");
|
115 | iframeParent.appendChild(iframe);
|
116 | };
|
117 |
|
118 | var stop = function () {
|
119 | currentTask = null;
|
120 | pendingTestStarts = null;
|
121 | removeIframe();
|
122 | testStatus = paused ? "paused" : "waiting";
|
123 | testInfo = "idle";
|
124 | updateStatus();
|
125 | };
|
126 |
|
127 | log("creating a socket");
|
128 |
|
129 | var socket;
|
130 |
|
131 | var onSocketOpen = function () {
|
132 | log("slave connected");
|
133 | socketStatusUpdate('connected');
|
134 | attesterPrototype.connected = true;
|
135 | stop();
|
136 | socket.send(JSON.stringify({
|
137 | type: 'slave',
|
138 | id: slaveId,
|
139 | paused: paused,
|
140 | userAgent: window.navigator.userAgent,
|
141 | documentMode: document.documentMode,
|
142 | flags: flags
|
143 | }));
|
144 | };
|
145 |
|
146 | var onSocketClose = function () {
|
147 | log("slave disconnected");
|
148 | socketStatusUpdate('disconnected');
|
149 | attesterPrototype.connected = false;
|
150 | stop();
|
151 | if (config.onDisconnect) {
|
152 | config.onDisconnect();
|
153 | }
|
154 | setTimeout(createConnection, 1000);
|
155 | };
|
156 |
|
157 | var messages = {
|
158 | slaveExecute: function (data) {
|
159 | currentTask = data;
|
160 | pendingTestStarts = {};
|
161 | removeIframe();
|
162 | testStatus = "executing " + data.name + " remaining " + data.stats.remainingTasks + " tasks in this campaign";
|
163 | if (data.stats.browserRemainingTasks != data.stats.remainingTasks) {
|
164 | testStatus += ", including " + data.stats.browserRemainingTasks + " tasks for this browser";
|
165 | }
|
166 | testInfo = "executing";
|
167 | updateStatus();
|
168 | log("<i>slave-execute</i> task <i>" + data.name + "</i>, " + data.stats.remainingTasks + " tasks left");
|
169 | createIframe(baseUrl + data.url, data.taskExecutionId);
|
170 | },
|
171 | slaveStop: stop,
|
172 | dispose: function () {
|
173 | if (config.onDispose) {
|
174 | config.onDispose();
|
175 | }
|
176 | }
|
177 | };
|
178 |
|
179 | var onSocketMessage = function (message) {
|
180 | var data = JSON.parse(message.data);
|
181 | var type = data.type;
|
182 | if (messages.hasOwnProperty(type)) {
|
183 | messages[type](data);
|
184 | }
|
185 | };
|
186 |
|
187 | var createConnection = function () {
|
188 | socketStatusUpdate('connecting');
|
189 | socket = new SockJS(location.protocol + '//' + location.host + '/sockjs');
|
190 | socket.onopen = onSocketOpen;
|
191 | socket.onclose = onSocketClose;
|
192 | socket.onmessage = onSocketMessage;
|
193 | };
|
194 |
|
195 | createConnection();
|
196 |
|
197 | pauseResume.onclick = function () {
|
198 | log("toggle pause status");
|
199 | paused = !paused;
|
200 | updateStatus();
|
201 | if (attesterPrototype.connected) {
|
202 | socket.send(JSON.stringify({
|
203 | type: 'pauseChanged',
|
204 | paused: paused
|
205 | }));
|
206 | }
|
207 | return false;
|
208 | };
|
209 |
|
210 | var checkTaskExecutionId = function (scope, name) {
|
211 | var res = currentTask && scope.__taskExecutionId === currentTask.taskExecutionId;
|
212 | if (!res) {
|
213 | var message = "ignoring call to attester." + name + " for a task that is not (or no longer) valid.";
|
214 | reportLogToServer("warn", [message]);
|
215 | log("<i>warning</i> " + message);
|
216 | }
|
217 | return res;
|
218 | };
|
219 |
|
220 | var sendTestUpdate = function (scope, name, info) {
|
221 | if (!checkTaskExecutionId(scope, name)) {
|
222 | return false;
|
223 | }
|
224 | if (!info) {
|
225 | info = {};
|
226 | }
|
227 | if (!info.time) {
|
228 | info.time = new Date().getTime();
|
229 | }
|
230 | info.event = name;
|
231 | log("sending test update <i class='event'>" + name + "</i> for test <i>" + info.name + "</i>");
|
232 | if (name === "testStarted") {
|
233 | if (pendingTestStarts.hasOwnProperty(info.testId)) {
|
234 | log("<i>warning</i> this <i>testStarted</i> is (wrongly) reusing a previous testId: <i>" + info.testId + "</i>");
|
235 | }
|
236 | pendingTestStarts[info.testId] = info;
|
237 | } else if (name === "testFinished") {
|
238 | var previousTestStart = pendingTestStarts[info.testId];
|
239 | if (!previousTestStart) {
|
240 | log("<i>warning</i> this <i>testFinished</i> is ignored as it has no previous <i>testStarted</i>");
|
241 | return false;
|
242 | }
|
243 | pendingTestStarts[info.testId] = false;
|
244 | info.duration = info.time - previousTestStart.time;
|
245 | }
|
246 | if (name === "error") {
|
247 | log("<i class='error'>error</i> message: <i>" + info.error.message + "</i>");
|
248 | }
|
249 | socket.send(JSON.stringify({
|
250 | type: 'testUpdate',
|
251 | event: info,
|
252 | taskExecutionId: currentTask.taskExecutionId
|
253 | }));
|
254 | return true;
|
255 | };
|
256 |
|
257 | attesterPrototype.testStart = function (info) {
|
258 | sendTestUpdate(this, 'testStarted', info);
|
259 | };
|
260 | attesterPrototype.testEnd = function (info) {
|
261 | sendTestUpdate(this, 'testFinished', info);
|
262 | };
|
263 | attesterPrototype.testError = function (info) {
|
264 | sendTestUpdate(this, 'error', info);
|
265 | };
|
266 | attesterPrototype.taskFinished = function () {
|
267 | var self = this;
|
268 | if (!checkTaskExecutionId(this, "taskFinished")) {
|
269 | return;
|
270 | }
|
271 | whenPendingXHRsFinished(function () {
|
272 | if (!checkTaskExecutionId(self, "taskFinished (callback)")) {
|
273 | return;
|
274 | }
|
275 | socket.send(JSON.stringify({
|
276 | type: 'taskFinished',
|
277 | taskExecutionId: currentTask.taskExecutionId
|
278 | }));
|
279 | });
|
280 | };
|
281 | attesterPrototype.stackTrace = function (exception) {
|
282 |
|
283 | return [];
|
284 | };
|
285 |
|
286 | var emptyFunction = function () {};
|
287 | var replaceConsoleFunction = function (console, name, scope) {
|
288 | var oldFunction = config.localConsole === false ? emptyFunction : console[name] || emptyFunction;
|
289 | console[name] = function () {
|
290 | var res;
|
291 | try {
|
292 |
|
293 | res = Function.prototype.apply.call(oldFunction, this, arguments);
|
294 | } catch (e) {}
|
295 | var taskExecutionId = scope.__taskExecutionId;
|
296 | if (!currentTask || currentTask.taskExecutionId !== taskExecutionId) {
|
297 | taskExecutionId = -1;
|
298 | }
|
299 | reportLogToServer(name, arguments, taskExecutionId);
|
300 | return res;
|
301 | };
|
302 | };
|
303 |
|
304 | attesterPrototype.installConsole = function (window) {
|
305 | var console = window.console;
|
306 | if (!console) {
|
307 | console = window.console = {};
|
308 | }
|
309 | replaceConsoleFunction(console, "debug", this);
|
310 | replaceConsoleFunction(console, "log", this);
|
311 | replaceConsoleFunction(console, "info", this);
|
312 | replaceConsoleFunction(console, "warn", this);
|
313 | replaceConsoleFunction(console, "error", this);
|
314 | };
|
315 |
|
316 | var pendingXHRs = 0;
|
317 | var pendingXHRsCallbacks = [];
|
318 | var whenPendingXHRsFinished = function (callback) {
|
319 | if (pendingXHRs > 0) {
|
320 | pendingXHRsCallbacks.push(callback);
|
321 | } else {
|
322 | callback();
|
323 | }
|
324 | };
|
325 | var callPendingXHRsCallbacks = function () {
|
326 | while (pendingXHRsCallbacks.length > 0) {
|
327 | var callback = pendingXHRsCallbacks.shift();
|
328 | callback();
|
329 | }
|
330 | };
|
331 |
|
332 |
|
333 | var send = function (url, data) {
|
334 | pendingXHRs++;
|
335 | var xhr = (window.ActiveXObject) ? new window.ActiveXObject("Microsoft.XMLHTTP") : new window.XMLHttpRequest();
|
336 | xhr.open('POST', url);
|
337 | xhr.setRequestHeader('Content-Type', 'application/json');
|
338 | xhr.onreadystatechange = function () {
|
339 | if (xhr && xhr.readyState === 4) {
|
340 | xhr = null;
|
341 | pendingXHRs--;
|
342 | if (pendingXHRs === 0) {
|
343 | callPendingXHRsCallbacks();
|
344 | }
|
345 | }
|
346 | };
|
347 | xhr.send(data);
|
348 | };
|
349 |
|
350 | attesterPrototype.coverage = function (window) {
|
351 | if (!checkTaskExecutionId(this, "coverage")) {
|
352 | return false;
|
353 | }
|
354 | var $$_l = window.$$_l;
|
355 | if ($$_l) {
|
356 | send('/__attester__/coverage/data/' + currentTask.campaignId + '/' + currentTask.taskId, JSON.stringify({
|
357 | name: "",
|
358 | run: $$_l.run,
|
359 | staticInfo: $$_l.staticInfo
|
360 | }));
|
361 | }
|
362 | };
|
363 |
|
364 |
|
365 |
|
366 | createIframe(baseUrl + location.pathname.replace(/\/[^\/]+$/, "/empty.html"));
|
367 | })();
|