Commit 705b2aa3 authored by Tsuyoshi Horo's avatar Tsuyoshi Horo Committed by Commit Bot

Show SignedExchange related information in DevTool's Preview panel

SignedExchangeDevToolsProxy sends the SignedExchangeHeader and the certificate
information and the error messages to DevTools.
When the user clicks a signed exchange response in Network panel, these
information will be displayed in the Preview panel.

And the network grid view will show “from SignedExchange” in the size column of
the content of the signed exchange like “from disk cache”.

See the doc for more details: https://bit.ly/2HXVbCU

This CL depends on https://crrev.com/c/1029755

Bug: 830505
Change-Id: Ice3743f2e1675b6d604eeaba731ebd8494c2b5a1
Reviewed-on: https://chromium-review.googlesource.com/1004890Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Reviewed-by: default avatarKunihiko Sakamoto <ksakamoto@chromium.org>
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#559363}
parent fa8d03be
......@@ -31,6 +31,7 @@
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_package/signed_exchange_header.h"
#include "content/common/navigation_params.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
......@@ -1622,7 +1623,10 @@ void NetworkHandler::LoadingComplete(
void NetworkHandler::OnSignedExchangeReceived(
base::Optional<const base::UnguessableToken> devtools_navigation_token,
const GURL& outer_request_url,
const network::ResourceResponseHead& outer_response) {
const network::ResourceResponseHead& outer_response,
const base::Optional<SignedExchangeHeader>& header,
const base::Optional<net::SSLInfo>& ssl_info,
const std::vector<std::string>& error_messages) {
if (!enabled_)
return;
std::unique_ptr<Network::SignedExchangeInfo> signed_exchange_info =
......@@ -1630,6 +1634,41 @@ void NetworkHandler::OnSignedExchangeReceived(
.SetOuterResponse(BuildResponse(outer_request_url, outer_response))
.Build();
if (header) {
std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create());
for (const auto it : header->response_headers())
headers_dict->setString(it.first, it.second);
const SignedExchangeHeaderParser::Signature& sig = header->signature();
std::unique_ptr<Array<Network::SignedExchangeSignature>> signatures =
Array<Network::SignedExchangeSignature>::create();
signatures->addItem(Network::SignedExchangeSignature::Create()
.SetLabel(sig.label)
.SetIntegrity(sig.integrity)
.SetCertUrl(sig.cert_url.spec())
.SetValidityUrl(sig.validity_url.spec())
.SetDate(sig.date)
.SetExpires(sig.expires)
.Build());
signed_exchange_info->SetHeader(
Network::SignedExchangeHeader::Create()
.SetRequestUrl(header->request_url().spec())
.SetRequestMethod(header->request_method())
.SetResponseCode(header->response_code())
.SetResponseHeaders(Object::fromValue(headers_dict.get(), nullptr))
.SetSignatures(std::move(signatures))
.Build());
}
if (ssl_info)
signed_exchange_info->SetSecurityDetails(BuildSecurityDetails(*ssl_info));
if (error_messages.size()) {
std::unique_ptr<Array<String>> errors = Array<String>::create();
for (const auto& message : error_messages)
errors->addItem(message);
signed_exchange_info->SetErrors(std::move(errors));
}
frontend_->SignedExchangeReceived(
devtools_navigation_token ? devtools_navigation_token->ToString() : "",
std::move(signed_exchange_info));
......
......@@ -6,11 +6,13 @@
#define CONTENT_BROWSER_DEVTOOLS_PROTOCOL_NETWORK_HANDLER_H_
#include <memory>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "content/browser/devtools/devtools_url_loader_interceptor.h"
#include "content/browser/devtools/protocol/devtools_domain_handler.h"
#include "content/browser/devtools/protocol/network.h"
......@@ -26,6 +28,7 @@ class UnguessableToken;
namespace net {
class HttpRequestHeaders;
class URLRequest;
class SSLInfo;
} // namespace net
namespace network {
......@@ -43,6 +46,7 @@ class InterceptionHandle;
class NavigationHandle;
class NavigationRequest;
class NavigationThrottle;
class SignedExchangeHeader;
class StoragePartition;
struct GlobalRequestID;
struct InterceptedRequestInfo;
......@@ -160,7 +164,10 @@ class NetworkHandler : public DevToolsDomainHandler,
void OnSignedExchangeReceived(
base::Optional<const base::UnguessableToken> devtools_navigation_token,
const GURL& outer_request_url,
const network::ResourceResponseHead& outer_response);
const network::ResourceResponseHead& outer_response,
const base::Optional<SignedExchangeHeader>& header,
const base::Optional<net::SSLInfo>& ssl_info,
const std::vector<std::string>& error_messages);
bool enabled() const { return enabled_; }
......
......@@ -41,6 +41,7 @@
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_package/signed_exchange_header.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
......@@ -54,6 +55,7 @@
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/ssl/ssl_info.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
......@@ -228,10 +230,14 @@ void RenderFrameDevToolsAgentHost::OnSignedExchangeReceived(
FrameTreeNode* frame_tree_node,
base::Optional<const base::UnguessableToken> devtools_navigation_token,
const GURL& outer_request_url,
const network::ResourceResponseHead& outer_response) {
DispatchToAgents(
frame_tree_node, &protocol::NetworkHandler::OnSignedExchangeReceived,
devtools_navigation_token, outer_request_url, outer_response);
const network::ResourceResponseHead& outer_response,
const base::Optional<SignedExchangeHeader>& header,
const base::Optional<net::SSLInfo>& ssl_info,
const std::vector<std::string>& error_messages) {
DispatchToAgents(frame_tree_node,
&protocol::NetworkHandler::OnSignedExchangeReceived,
devtools_navigation_token, outer_request_url, outer_response,
header, ssl_info, error_messages);
}
// static
......
......@@ -7,6 +7,7 @@
#include <map>
#include <memory>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
......@@ -39,6 +40,10 @@ namespace viz {
class CompositorFrameMetadata;
}
namespace net {
class SSLInfo;
}
namespace content {
class BrowserContext;
......@@ -48,6 +53,7 @@ class NavigationHandleImpl;
class NavigationRequest;
class NavigationThrottle;
class RenderFrameHostImpl;
class SignedExchangeHeader;
class CONTENT_EXPORT RenderFrameDevToolsAgentHost
: public DevToolsAgentHostImpl,
......@@ -93,7 +99,10 @@ class CONTENT_EXPORT RenderFrameDevToolsAgentHost
FrameTreeNode* frame_tree_node,
base::Optional<const base::UnguessableToken> devtools_navigation_token,
const GURL& outer_request_url,
const network::ResourceResponseHead& outer_response);
const network::ResourceResponseHead& outer_response,
const base::Optional<SignedExchangeHeader>& header,
const base::Optional<net::SSLInfo>& ssl_info,
const std::vector<std::string>& error_messages);
static void OnSignedExchangeCertificateRequestSent(
FrameTreeNode* frame_tree_node,
const base::UnguessableToken& request_id,
......
......@@ -71,14 +71,17 @@ void OnSignedExchangeReceivedOnUI(
base::RepeatingCallback<int(void)> frame_tree_node_id_getter,
const GURL& outer_request_url,
scoped_refptr<network::ResourceResponse> outer_response,
base::Optional<const base::UnguessableToken> devtools_navigation_token) {
base::Optional<const base::UnguessableToken> devtools_navigation_token,
base::Optional<SignedExchangeHeader> header,
base::Optional<net::SSLInfo> ssl_info,
std::vector<std::string> error_messages) {
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_getter.Run());
if (!frame_tree_node)
return;
RenderFrameDevToolsAgentHost::OnSignedExchangeReceived(
frame_tree_node, devtools_navigation_token, outer_request_url,
outer_response->head);
outer_response->head, header, ssl_info, error_messages);
}
} // namespace
......@@ -104,6 +107,7 @@ SignedExchangeDevToolsProxy::~SignedExchangeDevToolsProxy() {
void SignedExchangeDevToolsProxy::ReportErrorMessage(
const std::string& message) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
error_messages_.push_back(message);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&AddErrorMessageToConsoleOnUI, frame_tree_node_id_getter_,
......@@ -156,22 +160,25 @@ void SignedExchangeDevToolsProxy::CertificateRequestCompleted(
}
void SignedExchangeDevToolsProxy::OnSignedExchangeReceived(
const base::Optional<SignedExchangeHeader>& header) {
const base::Optional<SignedExchangeHeader>& header,
const net::SSLInfo* ssl_info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!devtools_enabled_)
return;
base::Optional<net::SSLInfo> ssl_info_opt;
if (ssl_info)
ssl_info_opt = *ssl_info;
// Make a deep copy of ResourceResponseHead before passing it cross-thread.
auto resource_response = base::MakeRefCounted<network::ResourceResponse>();
resource_response->head = outer_response_;
// TODO(crbug/830505): Send |header| information to DevTools.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&OnSignedExchangeReceivedOnUI, frame_tree_node_id_getter_,
outer_request_url_, resource_response->DeepCopy(),
devtools_navigation_token_));
devtools_navigation_token_, header,
std::move(ssl_info_opt), std::move(error_messages_)));
}
} // namespace content
......@@ -6,6 +6,7 @@
#define CONTENT_BROWSER_WEB_PACKAGE_SIGNED_EXCHANGE_DEVTOOLS_PROXY_H_
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
......@@ -20,6 +21,10 @@ namespace base {
class UnguessableToken;
} // namespace base
namespace net {
class SSLInfo;
} // namespace net
namespace network {
struct ResourceRequest;
struct ResourceResponseHead;
......@@ -62,7 +67,8 @@ class CONTENT_EXPORT SignedExchangeDevToolsProxy {
const network::URLLoaderCompletionStatus& status);
void OnSignedExchangeReceived(
const base::Optional<SignedExchangeHeader>& header);
const base::Optional<SignedExchangeHeader>& header,
const net::SSLInfo* ssl_info);
private:
const GURL outer_request_url_;
......@@ -70,6 +76,7 @@ class CONTENT_EXPORT SignedExchangeDevToolsProxy {
const base::RepeatingCallback<int(void)> frame_tree_node_id_getter_;
const base::Optional<const base::UnguessableToken> devtools_navigation_token_;
const bool devtools_enabled_;
std::vector<std::string> error_messages_;
DISALLOW_COPY_AND_ASSIGN(SignedExchangeDevToolsProxy);
};
......
......@@ -255,7 +255,7 @@ bool SignedExchangeHandler::ParseHeadersAndFetchCertificate() {
void SignedExchangeHandler::RunErrorCallback(net::Error error) {
DCHECK_NE(state_, State::kHeadersCallbackCalled);
if (devtools_proxy_)
devtools_proxy_->OnSignedExchangeReceived(header_);
devtools_proxy_->OnSignedExchangeReceived(header_, nullptr);
std::move(headers_callback_)
.Run(error, GURL(), std::string(), network::ResourceResponseHead(),
nullptr);
......@@ -375,7 +375,7 @@ void SignedExchangeHandler::OnCertVerifyComplete(int result) {
!net::IsCertStatusMinorError(ssl_info.cert_status);
if (devtools_proxy_)
devtools_proxy_->OnSignedExchangeReceived(header_);
devtools_proxy_->OnSignedExchangeReceived(header_, &ssl_info);
response_head.ssl_info = std::move(ssl_info);
// TODO(https://crbug.com/815025): Verify the Certificate Transparency status.
......
......@@ -6,6 +6,11 @@ inspected-page.html:1 Failed to fetch the certificate.
failed: true
statusCode: 200
resourceType: document
SignedExchangeInfo
Request URL: https://www.127.0.0.1/not_found_cert.html
Certificate URL: http://localhost:8000/loading/htxg/resources/not_found_cert.pem.msg
Error: Invalid reponse code: 404
Error: Failed to fetch the certificate.
* http://localhost:8000/loading/htxg/resources/not_found_cert.pem.msg
failed: false
statusCode: 404
......
......@@ -14,12 +14,6 @@
BrowserSDK.networkLog.reset();
await TestRunner.addIframe('/loading/htxg/resources/htxg-cert-not-found.htxg');
ConsoleTestRunner.dumpConsoleMessages();
for (var request of BrowserSDK.networkLog.requests()) {
TestRunner.addResult(`* ${request.url()}`);
TestRunner.addResult(` failed: ${!!request.failed}`);
TestRunner.addResult(` statusCode: ${request.statusCode}`);
TestRunner.addResult(` resourceType: ${request.resourceType().name()}`);
// TODO(crbug/830505): Check the existance of signed exchange information.
}
NetworkTestRunner.dumpNetworkRequestsWithSignedExchangeInfo();
TestRunner.completeTest();
})();
......@@ -4,6 +4,11 @@ Tests the signed exchange information are available when the navigation succeede
failed: false
statusCode: 200
resourceType: signed-exchange
SignedExchangeInfo
Request URL: https://www.127.0.0.1/test.html
Certificate URL: http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
Certificate Subject: 127.0.0.1
Certificate Issuer: web-platform-tests
* http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
failed: false
statusCode: 200
......
......@@ -6,6 +6,11 @@ inspected-page.html:1 Failed to verify the signed exchange header.
failed: true
statusCode: 200
resourceType: document
SignedExchangeInfo
Request URL: https://www.127.0.0.1/test.html
Certificate URL: http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
Error: Invalid timestamp. creation_time: 1522540800, expires_time: 1523145600, verification_time: 1523318460
Error: Failed to verify the signed exchange header.
* http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
failed: false
statusCode: 200
......
......@@ -14,12 +14,6 @@
BrowserSDK.networkLog.reset();
await TestRunner.addIframe('/loading/htxg/resources/htxg-location.htxg');
ConsoleTestRunner.dumpConsoleMessages();
for (var request of BrowserSDK.networkLog.requests()) {
TestRunner.addResult(`* ${request.url()}`);
TestRunner.addResult(` failed: ${!!request.failed}`);
TestRunner.addResult(` statusCode: ${request.statusCode}`);
TestRunner.addResult(` resourceType: ${request.resourceType().name()}`);
// TODO(crbug/830505): Check the existance of signed exchange information.
}
NetworkTestRunner.dumpNetworkRequestsWithSignedExchangeInfo();
TestRunner.completeTest();
})();
......@@ -14,12 +14,6 @@
BrowserSDK.networkLog.reset();
await TestRunner.addIframe('/loading/htxg/resources/htxg-location.htxg');
ConsoleTestRunner.dumpConsoleMessages();
for (var request of BrowserSDK.networkLog.requests()) {
TestRunner.addResult(`* ${request.url()}`);
TestRunner.addResult(` failed: ${!!request.failed}`);
TestRunner.addResult(` statusCode: ${request.statusCode}`);
TestRunner.addResult(` resourceType: ${request.resourceType().name()}`);
// TODO(crbug/830505): Check the existance of signed exchange information.
}
NetworkTestRunner.dumpNetworkRequestsWithSignedExchangeInfo();
TestRunner.completeTest();
})();
......@@ -4,6 +4,11 @@ Tests the signed exchange information are available when the prefetch succeeded.
failed: false
statusCode: 200
resourceType: signed-exchange
SignedExchangeInfo
Request URL: https://www.127.0.0.1/test.html
Certificate URL: http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
Certificate Subject: 127.0.0.1
Certificate Issuer: web-platform-tests
* http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
failed: false
statusCode: 200
......
......@@ -4,6 +4,11 @@ Tests the signed exchange information are available when the prefetch failed.
failed: true
statusCode: 200
resourceType: other
SignedExchangeInfo
Request URL: https://www.127.0.0.1/test.html
Certificate URL: http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
Error: Invalid timestamp. creation_time: 1522540800, expires_time: 1523145600, verification_time: 1523318460
Error: Failed to verify the signed exchange header.
* http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg
failed: false
statusCode: 200
......
......@@ -31,12 +31,6 @@
})()
`);
await promise;
for (var request of BrowserSDK.networkLog.requests()) {
TestRunner.addResult(`* ${request.url()}`);
TestRunner.addResult(` failed: ${!!request.failed}`);
TestRunner.addResult(` statusCode: ${request.statusCode}`);
TestRunner.addResult(` resourceType: ${request.resourceType().name()}`);
// TODO(crbug/830505): Check the existance of signed exchange information.
}
NetworkTestRunner.dumpNetworkRequestsWithSignedExchangeInfo();
TestRunner.completeTest();
})();
......@@ -31,12 +31,6 @@
})()
`);
await promise;
for (var request of BrowserSDK.networkLog.requests()) {
TestRunner.addResult(`* ${request.url()}`);
TestRunner.addResult(` failed: ${!!request.failed}`);
TestRunner.addResult(` statusCode: ${request.statusCode}`);
TestRunner.addResult(` resourceType: ${request.resourceType().name()}`);
// TODO(crbug/830505): Check the existance of signed exchange information.
}
NetworkTestRunner.dumpNetworkRequestsWithSignedExchangeInfo();
TestRunner.completeTest();
})();
......@@ -3658,11 +3658,49 @@ domain Network
# Stage at wich to begin intercepting requests. Default is Request.
optional InterceptionStage interceptionStage
# Information about a signed exchange signature.
# https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#rfc.section.3.1
experimental type SignedExchangeSignature extends object
properties
# Signed exchange signature label.
string label
# Signed exchange signature integrity.
string integrity
# Signed exchange signature cert Url.
string certUrl
# Signed exchange signature validity Url.
string validityUrl
# Signed exchange signature date.
integer date
# Signed exchange signature expires.
integer expires
# Information about a signed exchange header.
# https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#cbor-representation
experimental type SignedExchangeHeader extends object
properties
# Signed exchange request URL.
string requestUrl
# Signed exchange request method.
string requestMethod
# Signed exchange response code.
integer responseCode
# Signed exchange response headers.
Headers responseHeaders
# Signed exchange response signature.
array of SignedExchangeSignature signatures
# Information about a signed exchange response.
experimental type SignedExchangeInfo extends object
properties
# The outer response of signed HTTP exchange which was received from network.
Response outerResponse
# Information about the signed exchange header.
optional SignedExchangeHeader header
# Security details for the signed exchange header.
optional SecurityDetails securityDetails
# Errors occurred while handling the signed exchagne.
optional array of string errors
# Tells whether clearing browser cache is supported.
deprecated command canClearBrowserCache
......
......@@ -427,6 +427,9 @@ all_devtools_files = [
"front_end/network/RequestResponseView.js",
"front_end/network/RequestTimingView.js",
"front_end/network/ResourceWebSocketFrameView.js",
"front_end/network/signedExchangeInfoTree.css",
"front_end/network/signedExchangeInfoView.css",
"front_end/network/SignedExchangeInfoView.js",
"front_end/network/webSocketFrameView.css",
"front_end/network_test_runner/module.json",
"front_end/network_test_runner/NetworkTestRunner.js",
......
......@@ -946,6 +946,9 @@ Network.NetworkRequestNode = class extends Network.NetworkNode {
if (this._request.fetchedViaServiceWorker) {
this._setTextAndTitle(cell, Common.UIString('(from ServiceWorker)'));
cell.classList.add('network-dim-cell');
} else if (this._request.redirectSource() && this._request.redirectSource().signedExchangeInfo()) {
this._setTextAndTitle(cell, Common.UIString('(from SignedExchange)'));
cell.classList.add('network-dim-cell');
} else if (this._request.cached()) {
if (this._request.cachedInMemory())
this._setTextAndTitle(cell, Common.UIString('(from memory cache)'));
......
......@@ -55,6 +55,12 @@ Network.NetworkItemView = class extends UI.TabbedPane {
this._responseView = new Network.RequestResponseView(request);
const previewView = new Network.RequestPreviewView(request);
this.appendTab(Network.NetworkItemView.Tabs.Preview, Common.UIString('Preview'), previewView);
if (request.signedExchangeInfo() && request.signedExchangeInfo().errors &&
request.signedExchangeInfo().errors.length) {
const icon = UI.Icon.create('smallicon-error');
icon.title = Common.UIString('SignedExchange error');
this.setTabIcon(Network.NetworkItemView.Tabs.Preview, icon);
}
this.appendTab(Network.NetworkItemView.Tabs.Response, Common.UIString('Response'), this._responseView);
}
......
......@@ -79,6 +79,10 @@ Network.RequestPreviewView = class extends Network.RequestResponseView {
* @return {!Promise<!UI.Widget>}
*/
async createPreview() {
const signedExchangeInfo = this.request.signedExchangeInfo();
if (signedExchangeInfo)
return new Network.SignedExchangeInfoView(signedExchangeInfo);
const htmlErrorPreview = await this._htmlPreview();
if (htmlErrorPreview)
return htmlErrorPreview;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Network.SignedExchangeInfoView = class extends UI.VBox {
/**
* @param {!Protocol.Network.SignedExchangeInfo} signedExchangeInfo
*/
constructor(signedExchangeInfo) {
super();
this.registerRequiredCSS('network/signedExchangeInfoView.css');
this.element.classList.add('signed-exchange-info-view');
const root = new UI.TreeOutlineInShadow();
root.registerRequiredCSS('network/signedExchangeInfoTree.css');
root.element.classList.add('signed-exchange-info-tree');
root.setFocusable(false);
root.makeDense();
root.expandTreeElementsWhenArrowing = true;
this.element.appendChild(root.element);
if (signedExchangeInfo.errors && signedExchangeInfo.errors.length) {
const errorMessagesCategory = new Network.SignedExchangeInfoView.Category(root, Common.UIString('Errors'));
for (const errorMessage of signedExchangeInfo.errors) {
const fragment = createDocumentFragment();
fragment.appendChild(UI.Icon.create('smallicon-error', 'prompt-icon'));
fragment.createChild('div', 'error-log').textContent = errorMessage;
errorMessagesCategory.createLeaf(fragment);
}
}
if (signedExchangeInfo.header) {
const header = signedExchangeInfo.header;
const headerCategory = new Network.SignedExchangeInfoView.Category(root, Common.UIString('Signed HTTP exchange'));
headerCategory.createLeaf(this._formatHeader(Common.UIString('Request URL'), header.requestUrl));
headerCategory.createLeaf(this._formatHeader(Common.UIString('Request method'), header.requestMethod));
headerCategory.createLeaf(this._formatHeader(Common.UIString('Response code'), header.responseCode + ''));
this._responseHeadersItem =
headerCategory.createLeaf(this._formatHeader(Common.UIString('Response headers'), ''));
const responseHeaders = header.responseHeaders;
for (const name in responseHeaders) {
const headerTreeElement = new UI.TreeElement(this._formatHeader(name, responseHeaders[name]));
headerTreeElement.selectable = false;
this._responseHeadersItem.appendChild(headerTreeElement);
}
this._responseHeadersItem.expand();
for (const signature of header.signatures) {
const signatureCategory = new Network.SignedExchangeInfoView.Category(root, Common.UIString('Signature'));
signatureCategory.createLeaf(this._formatHeader(Common.UIString('Label'), signature.label));
signatureCategory.createLeaf(this._formatHeader(Common.UIString('Integrity'), signature.integrity));
signatureCategory.createLeaf(this._formatHeader(Common.UIString('Certificate URL'), signature.certUrl));
signatureCategory.createLeaf(this._formatHeader(Common.UIString('Validity URL'), signature.validityUrl));
signatureCategory.createLeaf().title =
this._formatHeader(Common.UIString('Date'), new Date(1000 * signature.date).toUTCString());
signatureCategory.createLeaf().title =
this._formatHeader(Common.UIString('Expires'), new Date(1000 * signature.expires).toUTCString());
}
}
if (signedExchangeInfo.securityDetails) {
const securityDetails = signedExchangeInfo.securityDetails;
const securityCategory = new Network.SignedExchangeInfoView.Category(root, Common.UIString('Certificate'));
securityCategory.createLeaf(this._formatHeader(Common.UIString('Subject'), securityDetails.subjectName));
securityCategory.createLeaf(
this._formatHeader(Common.UIString('Valid from'), new Date(1000 * securityDetails.validFrom).toUTCString()));
securityCategory.createLeaf(
this._formatHeader(Common.UIString('Valid until'), new Date(1000 * securityDetails.validTo).toUTCString()));
securityCategory.createLeaf(this._formatHeader(Common.UIString('Issuer'), securityDetails.issuer));
}
}
/**
* @param {string} name
* @param {string} value
* @return {!DocumentFragment}
*/
_formatHeader(name, value) {
const fragment = createDocumentFragment();
fragment.createChild('div', 'header-name').textContent = name + ': ';
fragment.createChild('span', 'header-separator');
fragment.createChild('div', 'header-value source-code').textContent = value;
return fragment;
}
};
/**
* @unrestricted
*/
Network.SignedExchangeInfoView.Category = class extends UI.TreeElement {
/**
* @param {!UI.TreeOutline} root
* @param {string} title
*/
constructor(root, title) {
super(title, true);
this.selectable = false;
this.toggleOnClick = true;
this.expanded = true;
root.appendChild(this);
}
/**
* @param {(string|!Node)=} title
*/
createLeaf(title) {
const leaf = new UI.TreeElement(title);
leaf.selectable = false;
this.appendChild(leaf);
return leaf;
}
};
......@@ -190,6 +190,7 @@
"RequestPreviewView.js",
"RequestTimingView.js",
"ResourceWebSocketFrameView.js",
"SignedExchangeInfoView.js",
"NetworkPanel.js"
],
"resources": [
......@@ -205,6 +206,8 @@
"requestHeadersTree.css",
"requestHeadersView.css",
"requestHTMLView.css",
"signedExchangeInfoTree.css",
"signedExchangeInfoView.css",
"webSocketFrameView.css"
]
}
/*
* Copyright 2018 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
.tree-outline {
padding-left: 0;
}
.tree-outline > ol {
padding-bottom: 5px;
border-bottom: solid 1px #e0e0e0;
}
.tree-outline > .parent {
-webkit-user-select: none;
font-weight: bold;
color: #616161;
margin-top: -1px;
height: 20px;
display: flex;
align-items: center;
height: 26px;
}
.tree-outline li {
display: block;
padding-left: 5px;
line-height: 20px;
}
.tree-outline li:not(.parent) {
margin-left: 10px;
}
.tree-outline li:not(.parent)::before {
display: none;
}
.tree-outline .header-name {
color: rgb(33%, 33%, 33%);
display: inline-block;
margin-right: 0.25em;
font-weight: bold;
vertical-align: top;
white-space: pre-wrap;
}
.tree-outline .header-separator {
user-select: none;
}
.tree-outline .header-value {
display: inline;
margin-right: 1em;
white-space: pre-wrap;
word-break: break-all;
margin-top: 1px;
}
.tree-outline .error-log {
color: red;
display: inline-block;
margin-right: 0.25em;
margin-left: 0.25em;
font-weight: bold;
vertical-align: top;
white-space: pre-wrap;
}
/*
* Copyright (c) 2018 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
.signed-exchange-info-view {
-webkit-user-select: text;
overflow: auto;
}
.signed-exchange-info-tree {
flex-grow: 1;
overflow-y: auto;
margin: 0;
}
......@@ -70,6 +70,33 @@ NetworkTestRunner.dumpNetworkRequests = function() {
TestRunner.addResult(requests[i].url());
};
NetworkTestRunner.dumpNetworkRequestsWithSignedExchangeInfo = function() {
for (const request of BrowserSDK.networkLog.requests()) {
TestRunner.addResult(`* ${request.url()}`);
TestRunner.addResult(` failed: ${!!request.failed}`);
TestRunner.addResult(` statusCode: ${request.statusCode}`);
TestRunner.addResult(` resourceType: ${request.resourceType().name()}`);
if (request.signedExchangeInfo()) {
TestRunner.addResult(' SignedExchangeInfo');
if (request.signedExchangeInfo().header) {
const header = request.signedExchangeInfo().header;
TestRunner.addResult(` Request URL: ${header.requestUrl}`);
for (const signature of header.signatures)
TestRunner.addResult(` Certificate URL: ${signature.certUrl}`);
}
if (request.signedExchangeInfo().securityDetails) {
const securityDetails = request.signedExchangeInfo().securityDetails;
TestRunner.addResult(` Certificate Subject: ${securityDetails.subjectName}`);
TestRunner.addResult(` Certificate Issuer: ${securityDetails.issuer}`);
}
if (request.signedExchangeInfo().errors) {
for (const errorMessage of request.signedExchangeInfo().errors)
TestRunner.addResult(` Error: ${errorMessage}`);
}
}
}
};
NetworkTestRunner.findRequestsByURLPattern = function(urlPattern) {
return NetworkTestRunner.networkRequests().filter(function(value) {
return urlPattern.test(value.url());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment