| /* |
| * Copyright (C) 2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WI.WebSocketContentView = class WebSocketContentView extends WI.ContentView |
| { |
| constructor(resource) |
| { |
| console.assert(resource instanceof WI.WebSocketResource, resource); |
| |
| super(resource); |
| |
| this._updateFramesDebouncer = new Debouncer(() => { |
| this._updateFrames(); |
| }); |
| |
| this._resource = resource; |
| this._framesRendered = 0; |
| this._lastRenderedReadyState = null; |
| |
| // COMPATIBILITY (iOS 10.3): `walltime` did not exist in 10.3 and earlier. |
| this._showTimeColumn = InspectorBackend.hasEvent("Network.webSocketWillSendHandshakeRequest", "walltime"); |
| |
| this.element.classList.add("web-socket", "resource"); |
| |
| let columns = {data: {}}; |
| columns.data.title = WI.UIString("Data"); |
| columns.data.sortable = false; |
| columns.data.width = "85%"; |
| |
| if (this._showTimeColumn) |
| columns.time = {title: WI.UIString("Time"), sortable: true}; |
| |
| this._dataGrid = new WI.DataGrid(columns); |
| this._dataGrid.variableHeightRows = true; |
| this.addSubview(this._dataGrid); |
| |
| this._addRow(WI.UIString("WebSocket Connection Established"), this._resource.walltime); |
| |
| this._dataGrid.updateLayout(); |
| } |
| |
| // Static |
| |
| static textForOpcode(opcode) |
| { |
| switch (opcode) { |
| case WI.WebSocketResource.OpCodes.ContinuationFrame: |
| return WI.UIString("Continuation Frame"); |
| case WI.WebSocketResource.OpCodes.TextFrame: |
| return WI.UIString("Text Frame"); |
| case WI.WebSocketResource.OpCodes.BinaryFrame: |
| return WI.UIString("Binary Frame"); |
| case WI.WebSocketResource.OpCodes.ConnectionCloseFrame: |
| return WI.UIString("Connection Close Frame"); |
| case WI.WebSocketResource.OpCodes.PingFrame: |
| return WI.UIString("Ping Frame"); |
| case WI.WebSocketResource.OpCodes.PongFrame: |
| return WI.UIString("Pong Frame"); |
| } |
| } |
| |
| // Public |
| |
| shown() |
| { |
| this._updateFramesDebouncer.force(); |
| this._resource.addEventListener(WI.WebSocketResource.Event.FrameAdded, this._updateFramesSoon, this); |
| this._resource.addEventListener(WI.WebSocketResource.Event.ReadyStateChanged, this._updateFramesSoon, this); |
| } |
| |
| hidden() |
| { |
| this._resource.removeEventListener(WI.WebSocketResource.Event.FrameAdded, this._updateFramesSoon, this); |
| this._resource.removeEventListener(WI.WebSocketResource.Event.ReadyStateChanged, this._updateFramesSoon, this); |
| } |
| |
| // Private |
| |
| _updateFramesSoon() |
| { |
| this._updateFramesDebouncer.delayForFrame(); |
| } |
| |
| _updateFrames() |
| { |
| let shouldScrollToBottom = this._dataGrid.isScrolledToLastRow(); |
| |
| let framesLength = this._resource.frames.length; |
| for (let index = this._framesRendered; index < framesLength; index++) { |
| let frame = this._resource.frames[index]; |
| let {data, isOutgoing, opcode, walltime} = frame; |
| this._addFrame(data, isOutgoing, opcode, walltime); |
| } |
| |
| this._framesRendered = framesLength; |
| |
| if (this._lastRenderedReadyState !== this._resource.readyState) { |
| if (this._resource.readyState === WI.WebSocketResource.ReadyState.Closed) |
| this._dataGrid.appendChild(new WI.SpanningDataGridNode(WI.UIString("Connection Closed"))); |
| |
| this._lastRenderedReadyState = this._resource.readyState; |
| } |
| |
| if (shouldScrollToBottom) { |
| if (!this._scrollToLastRowDebouncer) { |
| this._scrollToLastRowDebouncer = new Debouncer(() => { |
| this._dataGrid.scrollToLastRow(); |
| }); |
| } |
| |
| this._scrollToLastRowDebouncer.delayForFrame(); |
| } |
| } |
| |
| _addFrame(data, isOutgoing, opcode, time) |
| { |
| let nodeText; |
| let isText = opcode === WI.WebSocketResource.OpCodes.TextFrame; |
| if (isText) |
| nodeText = data; |
| else |
| nodeText = WI.WebSocketContentView.textForOpcode(opcode); |
| |
| this._addRow(nodeText, time, {isOutgoing, isText}); |
| } |
| |
| _addRow(data, time, attributes = {}) |
| { |
| let node; |
| if (this._showTimeColumn) |
| node = new WI.WebSocketDataGridNode({...attributes, data, time}); |
| else |
| node = new WI.WebSocketDataGridNode({...attributes, data}); |
| |
| this._dataGrid.appendChild(node); |
| |
| if (attributes.isText) |
| node.element.classList.add("text-frame"); |
| else |
| node.element.classList.add("non-text-frame"); |
| |
| if (attributes.isOutgoing) |
| node.element.classList.add("outgoing"); |
| else |
| node.element.classList.add("incoming"); |
| } |
| }; |