23bee1698f
Change-Id: I2087c3c9d25cdf3266142ba57bb8dbc57bc3fc46 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194002 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
288 lines
7.7 KiB
Dart
288 lines
7.7 KiB
Dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'dart:convert';
|
|
|
|
import '../log/log.dart';
|
|
import '../server.dart';
|
|
|
|
typedef Writer = void Function(StringSink sink);
|
|
|
|
/// A class used to write an HTML page.
|
|
abstract class PageWriter {
|
|
/// The object used to escape special HTML characters.
|
|
static final HtmlEscape htmlEscape = HtmlEscape();
|
|
|
|
/// Initialize a newly create page writer.
|
|
PageWriter();
|
|
|
|
/// Return the length of the common prefix for time stamps associated with the
|
|
/// given log [entries].
|
|
int computePrefixLength(List<LogEntry> entries) {
|
|
var length = entries.length;
|
|
if (length < 2) {
|
|
return 0;
|
|
}
|
|
var firstTime = entries[0].timeStamp.toString();
|
|
var lastTime = entries[length - 1].timeStamp.toString();
|
|
var prefixLength = 0;
|
|
var timeLength = firstTime.length;
|
|
while (prefixLength < timeLength &&
|
|
firstTime.codeUnitAt(prefixLength) ==
|
|
lastTime.codeUnitAt(prefixLength)) {
|
|
prefixLength++;
|
|
}
|
|
return prefixLength;
|
|
}
|
|
|
|
/// Return an escaped version of the given [unsafe] text.
|
|
String escape(String unsafe) {
|
|
// We double escape single quotes because the escaped characters are
|
|
// processed as part of reading the HTML, which means that single quotes
|
|
// end up terminating string literals too early when they appear in event
|
|
// handlers (which in turn leads to JavaScript syntax errors).
|
|
return htmlEscape.convert(unsafe).replaceAll(''', '&#39;');
|
|
}
|
|
|
|
/// Write the body of the page (without the 'body' tag) to the given [sink].
|
|
void writeBody(StringSink sink);
|
|
|
|
/// Write the given [date] to the given [sink].
|
|
void writeDate(StringSink sink, DateTime date) {
|
|
var isoString = date.toIso8601String();
|
|
var index = isoString.indexOf('T');
|
|
var dateString = isoString.substring(0, index);
|
|
var timeString = isoString.substring(index + 1);
|
|
sink.write(dateString);
|
|
sink.write(' at ');
|
|
sink.write(timeString);
|
|
}
|
|
|
|
/// Write the body of the page (without the 'body' tag) to the given [sink].
|
|
void writeMenu(StringSink sink) {
|
|
sink.writeln('<div class="menu">');
|
|
sink.write('<a href="${WebServer.logPath}" class="menuItem">Log</a>');
|
|
sink.write(' • ');
|
|
sink.write('<a href="${WebServer.statsPath}" class="menuItem">Stats</a>');
|
|
sink.writeln('</div>');
|
|
}
|
|
|
|
/// Write the contents of the instrumentation log to the given [sink].
|
|
void writePage(StringSink sink) {
|
|
sink.writeln('<!DOCTYPE html>');
|
|
sink.writeln('<html lang="en-US">');
|
|
sink.writeln('<head>');
|
|
sink.writeln('<meta charset="utf-8">');
|
|
sink.writeln(
|
|
'<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0">');
|
|
sink.writeln('<title>Instrumentation Log</title>');
|
|
sink.writeln('<style>');
|
|
writeStyleSheet(sink);
|
|
sink.writeln('</style>');
|
|
sink.writeln('<script>');
|
|
writeScripts(sink);
|
|
sink.writeln('</script>');
|
|
sink.writeln('</head>');
|
|
sink.writeln('<body>');
|
|
writeBody(sink);
|
|
sink.writeln('</body>');
|
|
sink.writeln('</html>');
|
|
}
|
|
|
|
/// Write the scripts for the page (without the 'script' tag) to the given
|
|
/// [sink].
|
|
void writeScripts(StringSink sink) {
|
|
// No common scripts.
|
|
}
|
|
|
|
/// Write the content of the style sheet (without the 'script' tag) for the
|
|
/// page to the given [sink].
|
|
void writeStyleSheet(StringSink sink) {
|
|
sink.writeln(r'''
|
|
a {
|
|
color: #000000;
|
|
text-decoration: none;
|
|
}
|
|
a.menuItem {
|
|
font-weight: bold;
|
|
}
|
|
body {
|
|
font-family: sans-serif;
|
|
height: 100%;
|
|
margin: 0px;
|
|
overflow: hidden;
|
|
padding: 0px;
|
|
width: 100%;
|
|
}
|
|
div.columnHeader {
|
|
}
|
|
div.button {
|
|
display: inline-block;
|
|
border-radius: 4px;
|
|
border: 1px solid;
|
|
height: 16px;
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
width: 16px;
|
|
}
|
|
div.inset {
|
|
padding: 10px;
|
|
}
|
|
div.menu {
|
|
background-color: #cce6ff;
|
|
padding: 5px;
|
|
}
|
|
html {
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
span.button {
|
|
border-radius: 5px;
|
|
border: 1px solid;
|
|
height: 16px;
|
|
width: 16px;
|
|
}
|
|
span.error {
|
|
color: #ff0000;
|
|
}
|
|
span.gray {
|
|
color: #777777;
|
|
}
|
|
span.label {
|
|
font-weight: bold;
|
|
}
|
|
table.fullWidth {
|
|
border: 0px;
|
|
width: 100%;
|
|
}
|
|
td.halfWidth {
|
|
width: 50%;
|
|
vertical-align: top;
|
|
}
|
|
td.int {
|
|
text-align: right;
|
|
}
|
|
th {
|
|
text-align: left;
|
|
}
|
|
th.narrow {
|
|
width: 16px;
|
|
}
|
|
|
|
#container {
|
|
height: 100%;
|
|
min-height: 100%;
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
#content {
|
|
height: 90%;
|
|
width: 100%;
|
|
}
|
|
''');
|
|
}
|
|
|
|
/// Write to the given [sink] the HTML required to display content in two
|
|
/// columns. The content of the columns will be written by the functions
|
|
/// [writeLeftColumn], [writeCenterColumn] and [writeRightColumn] and will be
|
|
/// contained in 'div' elements with the id's [leftColumnId], [centerColumnId]
|
|
/// and [rightColumnId].
|
|
void writeThreeColumns(
|
|
StringSink sink,
|
|
String leftColumnId,
|
|
Writer writeLeftColumn,
|
|
String centerColumnId,
|
|
Writer writeCenterColumn,
|
|
String rightColumnId,
|
|
Writer writeRightColumn) {
|
|
sink.writeln('<div>');
|
|
sink.writeln(' <div>');
|
|
sink.writeln(' <div id="$leftColumnId">');
|
|
sink.writeln(' <div class="inset">');
|
|
writeLeftColumn(sink);
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' <div id="$rightColumnId">');
|
|
sink.writeln(' <div class="inset">');
|
|
writeRightColumn(sink);
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' <div id="$centerColumnId">');
|
|
sink.writeln(' <div class="inset">');
|
|
writeCenterColumn(sink);
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' </div>');
|
|
sink.writeln('</div>');
|
|
}
|
|
|
|
/// Writeto the given [sink] the styles needed by a three column section where
|
|
/// the columns have the ids [leftColumnId], [centerColumnId] and
|
|
/// [rightColumnId].
|
|
void writeThreeColumnStyles(StringSink sink, String leftColumnId,
|
|
String centerColumnId, String rightColumnId) {
|
|
sink.writeln('''
|
|
#$leftColumnId {
|
|
float: left;
|
|
height: 100%;
|
|
overflow: auto;
|
|
width: 33%;
|
|
}
|
|
#$centerColumnId {
|
|
height: 100%;
|
|
overflow: auto;
|
|
width: 33%;
|
|
}
|
|
#$rightColumnId {
|
|
float: right;
|
|
height: 100%;
|
|
overflow: auto;
|
|
width: 33%;
|
|
}
|
|
''');
|
|
}
|
|
|
|
/// Write to the given [sink] the HTML required to display content in two
|
|
/// columns. The content of the columns will be written by the functions
|
|
/// [writeLeftColumn] and [writeRightColumn] and will be contained in 'div'
|
|
/// elements with the id's [leftColumnId] and [rightColumnId].
|
|
void writeTwoColumns(StringSink sink, String leftColumnId,
|
|
Writer writeLeftColumn, String rightColumnId, Writer writeRightColumn) {
|
|
sink.writeln('<div id="container">');
|
|
sink.writeln(' <div id="content">');
|
|
sink.writeln(' <div id="$leftColumnId">');
|
|
sink.writeln(' <div class="inset">');
|
|
writeLeftColumn(sink);
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' <div id="$rightColumnId">');
|
|
sink.writeln(' <div class="inset">');
|
|
writeRightColumn(sink);
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' </div>');
|
|
sink.writeln(' </div>');
|
|
sink.writeln('</div>');
|
|
}
|
|
|
|
/// Writeto the given [sink] the styles needed by a two column section where
|
|
/// the columns have the ids [leftColumnId] and [rightColumnId].
|
|
void writeTwoColumnStyles(
|
|
StringSink sink, String leftColumnId, String rightColumnId) {
|
|
sink.writeln('''
|
|
#$leftColumnId {
|
|
float: left;
|
|
height: 100%;
|
|
overflow: auto;
|
|
width: 50%;
|
|
}
|
|
#$rightColumnId {
|
|
float: right;
|
|
height: 100%;
|
|
overflow: auto;
|
|
width: 50%;
|
|
}
|
|
''');
|
|
}
|
|
}
|