| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Shared JavaScript code related to the heartbeat mechanism used by several |
| // GPU tests. Intended to be evaluated on commit in addition to whatever |
| // suite-specific code there is. |
| |
| const HEARTBEAT_THROTTLE_MS = 5000; |
| |
| class WebSocketWrapper { |
| constructor() { |
| this.queued_messages = []; |
| this.throttle_timer = null; |
| this.last_heartbeat = null; |
| this.socket = null; |
| |
| this._sendDelayedHeartbeat = this._sendDelayedHeartbeat.bind(this); |
| } |
| |
| setWebSocket(s) { |
| this.socket = s; |
| s.send('{"type": "CONNECTION_ACK"}'); |
| for (let qm of this.queued_messages) { |
| s.send(qm); |
| } |
| } |
| |
| _sendMessage(message) { |
| if (this.socket === null) { |
| this.queued_messages.push(message); |
| } else { |
| this.socket.send(message); |
| } |
| } |
| |
| _sendHeartbeat() { |
| this._sendMessage('{"type": "TEST_HEARTBEAT"}'); |
| } |
| |
| _sendDelayedHeartbeat() { |
| this.throttle_timer = null; |
| this.last_heartbeat = +new Date(); |
| this._sendHeartbeat(); |
| } |
| |
| sendHeartbeatThrottled() { |
| const now = +new Date(); |
| // Heartbeat already scheduled. |
| if (this.throttle_timer !== null) { |
| // If we've already passed the point in time where the heartbeat should |
| // have been sent, cancel it and send it immediately. This helps in cases |
| // where we've scheduled one, but the test is doing so much work that |
| // the callback doesn't fire in a reasonable amount of time. |
| if (this.last_heartbeat !== null && |
| now - this.last_heartbeat >= HEARTBEAT_THROTTLE_MS) { |
| this._clearPendingHeartbeat(); |
| this.last_heartbeat = now; |
| this._sendHeartbeat(); |
| } |
| return; |
| } |
| |
| // Send a heartbeat immediately. |
| if (this.last_heartbeat === null || |
| now - this.last_heartbeat >= HEARTBEAT_THROTTLE_MS){ |
| this.last_heartbeat = now; |
| this._sendHeartbeat(); |
| return; |
| } |
| // Schedule a heartbeat for the future. |
| this.throttle_timer = setTimeout( |
| this._sendDelayedHeartbeat, |
| HEARTBEAT_THROTTLE_MS - (now - this.last_heartbeat)); |
| } |
| |
| _clearPendingHeartbeat() { |
| if (this.throttle_timer !== null) { |
| clearTimeout(this.throttle_timer); |
| this.throttle_timer = null; |
| } |
| } |
| |
| sendTestStarted() { |
| this._sendMessage('{"type": "TEST_STARTED"}'); |
| } |
| |
| sendTestFinished() { |
| this._clearPendingHeartbeat(); |
| this._sendMessage('{"type": "TEST_FINISHED"}'); |
| } |
| |
| // Pixel test messages. |
| sendTestFinishedWithSuccessValue(success) { |
| this._clearPendingHeartbeat(); |
| this._sendMessage(`{"type": "TEST_FINISHED", "success": ${success}}`); |
| } |
| |
| sendPerformPageAction() { |
| this._sendMessage('{"type": "PERFORM_PAGE_ACTION"}'); |
| } |
| |
| sendTestContinue() { |
| this._sendMessage('{"type": "TEST_CONTINUE"}') |
| } |
| } |
| |
| if (window.parent.wrapper !== undefined) { |
| var wrapper = window.parent.wrapper; |
| var inIframe = true; |
| window.wrapper = window.parent.wrapper; |
| } else { |
| var wrapper = new WebSocketWrapper(); |
| var inIframe = false; |
| window.wrapper = wrapper; |
| } |
| |
| function connectWebsocket(port) { |
| let socket = new WebSocket('ws://127.0.0.1:' + port); |
| socket.addEventListener('open', () => { |
| wrapper.setWebSocket(socket); |
| }); |
| } |
| |
| function wrapFunctionInHeartbeat(prototype, key) { |
| const old = prototype[key]; |
| // Some functions are specific to a WebGL version, so don't try to wrap |
| // functions that don't exist in the current version's context prototype. |
| if (old === undefined) { |
| return; |
| } |
| prototype[key] = function (...args) { |
| wrapper.sendHeartbeatThrottled(); |
| return old.call(this, ...args); |
| } |
| } |