blob: b24ab10f5cdaa6c89dd7390e2e4638a77f3b1e1c [file] [log] [blame]
/*
* 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");
}
};