Diff
Modified: trunk/LayoutTests/ChangeLog (223057 => 223058)
--- trunk/LayoutTests/ChangeLog 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/LayoutTests/ChangeLog 2017-10-09 19:32:28 UTC (rev 223058)
@@ -1,3 +1,14 @@
+2017-10-09 Joseph Pecoraro <[email protected]>
+
+ Web Inspector: Network Tab - Cookies Detail View
+ https://bugs.webkit.org/show_bug.cgi?id=177988
+ <rdar://problem/34071927>
+
+ Reviewed by Brian Burg.
+
+ * inspector/unit-tests/cookie-expected.txt: Added.
+ * inspector/unit-tests/cookie.html: Added.
+
2017-10-09 Matt Lewis <[email protected]>
Marked inspector/dom/csp-big5-hash.html as flaky.
Added: trunk/LayoutTests/inspector/unit-tests/cookie-expected.txt (0 => 223058)
--- trunk/LayoutTests/inspector/unit-tests/cookie-expected.txt (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/cookie-expected.txt 2017-10-09 19:32:28 UTC (rev 223058)
@@ -0,0 +1,203 @@
+Testing WI.Cookie.
+
+
+== Running test suite: Cookie
+-- Running test case: WI.Cookie.parseCookieRequestHeader
+HEADER: Cookie:
+PASS: Should have 0 cookies.
+
+HEADER: Cookie: foo=bar
+PASS: Should have 1 cookies.
+PASS: Value should be a WI.Cookie.
+PASS: cookie.type should be WI.Cookie.Type.Request.
+PASS: cookie.name should be 'foo'.
+PASS: cookie.value should be 'bar'.
+
+HEADER: Cookie: foo=bar; alpha=beta
+PASS: Should have 2 cookies.
+PASS: Value should be a WI.Cookie.
+PASS: cookie.type should be WI.Cookie.Type.Request.
+PASS: cookie.name should be 'foo'.
+PASS: cookie.value should be 'bar'.
+PASS: Value should be a WI.Cookie.
+PASS: cookie.type should be WI.Cookie.Type.Request.
+PASS: cookie.name should be 'alpha'.
+PASS: cookie.value should be 'beta'.
+
+HEADER: Cookie: foo=a,b,c, d, e, f,g; alpha=123423 qwerty; beta=gamma
+PASS: Should have 3 cookies.
+PASS: Value should be a WI.Cookie.
+PASS: cookie.type should be WI.Cookie.Type.Request.
+PASS: cookie.name should be 'foo'.
+PASS: cookie.value should be 'a,b,c, d, e, f,g'.
+PASS: Value should be a WI.Cookie.
+PASS: cookie.type should be WI.Cookie.Type.Request.
+PASS: cookie.name should be 'alpha'.
+PASS: cookie.value should be '123423 qwerty'.
+PASS: Value should be a WI.Cookie.
+PASS: cookie.type should be WI.Cookie.Type.Request.
+PASS: cookie.name should be 'beta'.
+PASS: cookie.value should be 'gamma'.
+
+
+-- Running test case: WI.Cookie.parseSetCookieResponseHeader
+PASS: Empty header should produce null.
+HEADER: Set-Cookie: name=value
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=value; path=/foo
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be '/foo'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=value; domain=example.com
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be 'example.com'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=value; secure
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'true'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=value; Secure
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'true'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=value; HttpOnly
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'true'.
+
+HEADER: Set-Cookie: name=value; expires=Fri 06-Oct-2017 03:20:27 GMT; Max-Age=3600
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'Thu Oct 05 2017 20:20:27 GMT-0700 (PDT)'.
+PASS: cookie.maxAge should be '3600'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=value; expires=Fri 06-Oct-2017 03:43:47 GMT; Max-Age=5000; path=/foo; domain=example.com; secure; HttpOnly
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'Thu Oct 05 2017 20:43:47 GMT-0700 (PDT)'.
+PASS: cookie.maxAge should be '5000'.
+PASS: cookie.path should be '/foo'.
+PASS: cookie.domain should be 'example.com'.
+PASS: cookie.secure should be 'true'.
+PASS: cookie.httpOnly should be 'true'.
+
+HEADER: Set-Cookie: name=value; Unknown; path=/one/two
+WARN: Unknown Cookie attribute: Unknown
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be '/one/two'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=value; Unknown=Ignored; path=/one/two
+WARN: Unknown Cookie attribute: Unknown=Ignored
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'value'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be '/one/two'.
+PASS: cookie.domain should be 'null'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name=somewhat longer value than normal with spaces, and commas; domain=other.example.com
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be 'somewhat longer value than normal with spaces, and commas'.
+PASS: cookie.expires should be 'null'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be 'other.example.com'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+HEADER: Set-Cookie: name==value=;Domain=.example.com;Expires=Wed, 04-Apr-2018 03:34:02 GMT
+PASS: Value should be a WI.Cookie.
+PASS: cookie.rawHeader should be the original header text.
+PASS: cookie.type should be WI.Cookie.Type.Response.
+PASS: cookie.name should be 'name'.
+PASS: cookie.value should be '=value='.
+PASS: cookie.expires should be 'Tue Apr 03 2018 20:34:02 GMT-0700 (PDT)'.
+PASS: cookie.maxAge should be 'null'.
+PASS: cookie.path should be 'null'.
+PASS: cookie.domain should be '.example.com'.
+PASS: cookie.secure should be 'false'.
+PASS: cookie.httpOnly should be 'false'.
+
+
Added: trunk/LayoutTests/inspector/unit-tests/cookie.html (0 => 223058)
--- trunk/LayoutTests/inspector/unit-tests/cookie.html (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/cookie.html 2017-10-09 19:32:28 UTC (rev 223058)
@@ -0,0 +1,220 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+ let suite = InspectorTest.createSyncSuite("Cookie");
+
+ suite.addTestCase({
+ name: "WI.Cookie.parseCookieRequestHeader",
+ description: "Cookie request header.",
+ test() {
+ function test(header, expectedCookies) {
+ InspectorTest.log(`HEADER: Cookie: ${header}`);
+ let cookies = WI.Cookie.parseCookieRequestHeader(header);
+ InspectorTest.expectEqual(cookies.length, expectedCookies.length, `Should have ${expectedCookies.length} cookies.`);
+ for (let i = 0; i < cookies.length; ++i) {
+ let cookie = cookies[i];
+ let expected = expectedCookies[i];
+ InspectorTest.expectThat(cookie instanceof WI.Cookie, `Value should be a WI.Cookie.`);
+ InspectorTest.expectEqual(cookie.type, WI.Cookie.Type.Request, `cookie.type should be WI.Cookie.Type.Request.`);
+ InspectorTest.expectEqual(cookie.name, expected.name, `cookie.name should be '${expected.name}'.`);
+ InspectorTest.expectEqual(cookie.value, expected.value, `cookie.value should be '${expected.value}'.`);
+ }
+ InspectorTest.log("");
+ }
+
+ test("", []);
+
+ test(`foo=bar`, [
+ {name: "foo", value: "bar"},
+ ]);
+
+ test(`foo=bar; alpha=beta`, [
+ {name: "foo", value: "bar"},
+ {name: "alpha", value: "beta"},
+ ]);
+
+ test(`foo=a,b,c, d, e, f,g; alpha=123423 qwerty; beta=gamma`, [
+ {name: "foo", value: "a,b,c, d, e, f,g"},
+ {name: "alpha", value: "123423 qwerty"},
+ {name: "beta", value: "gamma"},
+ ]);
+
+ return true;
+ }
+ });
+
+ suite.addTestCase({
+ name: "WI.Cookie.parseSetCookieResponseHeader",
+ description: "Set-Cookie response headers.",
+ test() {
+ function test(header, expected) {
+ InspectorTest.log(`HEADER: Set-Cookie: ${header}`);
+ let cookie = WI.Cookie.parseSetCookieResponseHeader(header);
+ InspectorTest.expectThat(cookie instanceof WI.Cookie, `Value should be a WI.Cookie.`);
+ InspectorTest.expectEqual(cookie.rawHeader, header, `cookie.rawHeader should be the original header text.`);
+ InspectorTest.expectEqual(cookie.type, WI.Cookie.Type.Response, `cookie.type should be WI.Cookie.Type.Response.`);
+ InspectorTest.expectEqual(cookie.name, expected.name, `cookie.name should be '${expected.name}'.`);
+ InspectorTest.expectEqual(cookie.value, expected.value, `cookie.value should be '${expected.value}'.`);
+ if (cookie.expires && expected.expires)
+ InspectorTest.expectEqual(cookie.expires.getTime(), expected.expires.getTime(), `cookie.expires should be '${expected.expires}'.`);
+ else
+ InspectorTest.expectEqual(cookie.expires, expected.expires, `cookie.expires should be '${expected.expires}'.`);
+ InspectorTest.expectEqual(cookie.maxAge, expected.maxAge, `cookie.maxAge should be '${expected.maxAge}'.`);
+ InspectorTest.expectEqual(cookie.path, expected.path, `cookie.path should be '${expected.path}'.`);
+ InspectorTest.expectEqual(cookie.domain, expected.domain, `cookie.domain should be '${expected.domain}'.`);
+ InspectorTest.expectEqual(cookie.secure, expected.secure, `cookie.secure should be '${expected.secure}'.`);
+ InspectorTest.expectEqual(cookie.httpOnly, expected.httpOnly, `cookie.httpOnly should be '${expected.httpOnly}'.`);
+ InspectorTest.log("");
+ }
+
+ InspectorTest.expectNull(WI.Cookie.parseSetCookieResponseHeader(""), "Empty header should produce null.");
+
+ test(`name=value`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: null,
+ domain: null,
+ secure: false,
+ httpOnly: false,
+ });
+
+ test(`name=value; path=/foo`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: "/foo",
+ domain: null,
+ secure: false,
+ httpOnly: false,
+ });
+
+ test(`name=value; domain=example.com`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: null,
+ domain: "example.com",
+ secure: false,
+ httpOnly: false,
+ });
+
+ test(`name=value; secure`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: null,
+ domain: null,
+ secure: true,
+ httpOnly: false,
+ });
+
+ test(`name=value; Secure`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: null,
+ domain: null,
+ secure: true,
+ httpOnly: false,
+ });
+
+ test(`name=value; HttpOnly`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: null,
+ domain: null,
+ secure: false,
+ httpOnly: true,
+ });
+
+ test(`name=value; expires=Fri 06-Oct-2017 03:20:27 GMT; Max-Age=3600`, {
+ name: "name",
+ value: "value",
+ expires: new Date("Fri 06-Oct-2017 03:20:27 GMT"),
+ maxAge: 3600,
+ path: null,
+ domain: null,
+ secure: false,
+ httpOnly: false,
+ });
+
+ test(`name=value; expires=Fri 06-Oct-2017 03:43:47 GMT; Max-Age=5000; path=/foo; domain=example.com; secure; HttpOnly`, {
+ name: "name",
+ value: "value",
+ expires: new Date("Fri 06-Oct-2017 03:43:47 GMT"),
+ maxAge: 5000,
+ path: "/foo",
+ domain: "example.com",
+ secure: true,
+ httpOnly: true,
+ });
+
+ test(`name=value; Unknown; path=/one/two`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: "/one/two",
+ domain: null,
+ secure: false,
+ httpOnly: false,
+ });
+
+ test(`name=value; Unknown=Ignored; path=/one/two`, {
+ name: "name",
+ value: "value",
+ expires: null,
+ maxAge: null,
+ path: "/one/two",
+ domain: null,
+ secure: false,
+ httpOnly: false,
+ });
+
+ test(`name=somewhat longer value than normal with spaces, and commas; domain=other.example.com`, {
+ name: "name",
+ value: "somewhat longer value than normal with spaces, and commas",
+ expires: null,
+ maxAge: null,
+ path: null,
+ domain: "other.example.com",
+ secure: false,
+ httpOnly: false,
+ });
+
+ // Some servers omit the required space after the semicolon.
+ test(`name==value=;Domain=.example.com;Expires=Wed, 04-Apr-2018 03:34:02 GMT`, {
+ name: "name",
+ value: "=value=",
+ expires: new Date("Wed, 04-Apr-2018 03:34:02 GMT"),
+ maxAge: null,
+ path: null,
+ domain: ".example.com",
+ secure: false,
+ httpOnly: false,
+ });
+
+ return true;
+ }
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Testing WI.Cookie.</p>
+</body>
+</html>
Modified: trunk/Source/WebInspectorUI/ChangeLog (223057 => 223058)
--- trunk/Source/WebInspectorUI/ChangeLog 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/ChangeLog 2017-10-09 19:32:28 UTC (rev 223058)
@@ -1,5 +1,93 @@
2017-10-09 Joseph Pecoraro <[email protected]>
+ Web Inspector: Network Tab - Cookies Detail View
+ https://bugs.webkit.org/show_bug.cgi?id=177988
+ <rdar://problem/34071927>
+
+ Reviewed by Brian Burg.
+
+ * Localizations/en.lproj/localizedStrings.js:
+ * UserInterface/Main.html:
+ * UserInterface/Test.html:
+ New strings and resources.
+
+ * UserInterface/Models/Cookie.js: Added.
+ (WI.Cookie):
+ (WI.Cookie.parseCookieRequestHeader):
+ (WI.Cookie.parseSetCookieResponseHeader):
+ Encapsulation for Cookie attributes.
+
+ * UserInterface/Models/Resource.js:
+ (WI.Resource.prototype.get requestCookies):
+ (WI.Resource.prototype.get responseCookies):
+ (WI.Resource.prototype.updateForRedirectResponse):
+ (WI.Resource.prototype.updateForResponse):
+ (WI.Resource.prototype.updateWithMetrics):
+ New computed accessors for requestCookies and responseCookies.
+
+ * UserInterface/Views/NetworkResourceDetailView.js:
+ (WI.NetworkResourceDetailView.prototype._showContentViewForNavigationItem):
+ Show the new Cookie View.
+
+ * UserInterface/Views/NetworkTableContentView.css:
+ (.content-view.network .network-table .icon):
+ (.network-table li:not(.filler) .cell.name):
+ (.network-table .cache-type):
+ (.network-table .error):
+ (body[dir=ltr] .network-table .cell.name > .status):
+ (body[dir=rtl] .network-table .cell.name > .status):
+ (.network-table .cell.name > .status .indeterminate-progress-spinner):
+ (.showing-detail .network-table .cell:not(.name)):
+ (.showing-detail .network-table .resizer:not(:first-of-type)):
+ (.network-table :not(.header) .cell:first-of-type):
+ Rework these styles to be specific to the .network-table.
+
+ * UserInterface/Views/Table.css:
+ (.table :not(.header) .cell:first-of-type): Deleted.
+ Move this to the network table styles, it shouldn't apply to all tables.
+
+ * UserInterface/Views/ResourceCookiesContentView.css:
+ (.resource-cookies > section > .details.has-table):
+ (.resource-cookies .table):
+ (.resource-cookies .table > .header):
+ Styles for Cookies view and table.
+
+ * UserInterface/Views/ResourceCookiesContentView.js: Added.
+ (WI.ResourceCookiesContentView):
+ (WI.ResourceCookiesContentView.prototype.tableNumberOfRows):
+ (WI.ResourceCookiesContentView.prototype.tableSortChanged):
+ (WI.ResourceCookiesContentView.prototype.tablePopulateCell):
+ (WI.ResourceCookiesContentView.prototype.initialLayout):
+ (WI.ResourceCookiesContentView.prototype._incompleteSectionWithMessage):
+ (WI.ResourceCookiesContentView.prototype._incompleteSectionWithLoadingIndicator):
+ (WI.ResourceCookiesContentView.prototype._dataSourceForTable):
+ (WI.ResourceCookiesContentView.prototype._generateSortComparator):
+ (WI.ResourceCookiesContentView.prototype._refreshRequestCookiesSection):
+ (WI.ResourceCookiesContentView.prototype._refreshResponseCookiesSection):
+ (WI.ResourceCookiesContentView.prototype._sizeForTable):
+ (WI.ResourceCookiesContentView.prototype._resourceRequestHeadersDidChange):
+ (WI.ResourceCookiesContentView.prototype._resourceResponseReceived):
+ Tables for Request and Response cookies. They are simliar with slightly different columns.
+ Handle simple display and sorting for the tables.
+
+ * UserInterface/Views/ResourceHeadersContentView.js:
+ (WI.ResourceHeadersContentView.prototype._refreshResponseHeadersSection):
+ Break out Set-Cookie headers as multiple headers. THey should never be combined.
+
+ * UserInterface/Views/Table.js:
+ (WI.Table.prototype.showColumn):
+ (WI.Table.prototype.hideColumn):
+ (WI.Table.prototype._handleHeaderContextMenu):
+ * UserInterface/Views/TableColumn.js:
+ (WI.TableColumn.prototype.get hideable):
+ (WI.TableColumn.prototype.set hidden):
+ (WI.TableColumn):
+ (WI.TableColumn.prototype.setHidden): Deleted.
+ Make it so some columns can not be hidden. For example the "value" column
+ in the Cookie tables.
+
+2017-10-09 Joseph Pecoraro <[email protected]>
+
Web Inspector: Network Tab - Search Headers Detail View
https://bugs.webkit.org/show_bug.cgi?id=177981
Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (223057 => 223058)
--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -609,9 +609,11 @@
localizedStrings["No canvas contexts found"] = "No canvas contexts found";
localizedStrings["No matching ARIA role"] = "No matching ARIA role";
localizedStrings["No preview available"] = "No preview available";
+localizedStrings["No request cookies."] = "No request cookies.";
localizedStrings["No request headers"] = "No request headers";
localizedStrings["No request, served from the disk cache."] = "No request, served from the disk cache.";
localizedStrings["No request, served from the memory cache."] = "No request, served from the memory cache.";
+localizedStrings["No response cookies."] = "No response cookies.";
localizedStrings["No response headers"] = "No response headers";
localizedStrings["Node"] = "Node";
localizedStrings["Node Removed"] = "Node Removed";
@@ -724,6 +726,7 @@
localizedStrings["Repeating Radial Gradient"] = "Repeating Radial Gradient";
localizedStrings["Request"] = "Request";
localizedStrings["Request & Response"] = "Request & Response";
+localizedStrings["Request Cookies"] = "Request Cookies";
localizedStrings["Request Data"] = "Request Data";
localizedStrings["Request Headers"] = "Request Headers";
localizedStrings["Requesting: %s"] = "Requesting: %s";
@@ -738,6 +741,7 @@
localizedStrings["Resource was served from the cache."] = "Resource was served from the cache.";
localizedStrings["Resources"] = "Resources";
localizedStrings["Response"] = "Response";
+localizedStrings["Response Cookies"] = "Response Cookies";
localizedStrings["Response Headers"] = "Response Headers";
localizedStrings["Restart (%s)"] = "Restart (%s)";
localizedStrings["Restart animation"] = "Restart animation";
Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Main.html 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html 2017-10-09 19:32:28 UTC (rev 223058)
@@ -153,6 +153,7 @@
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
+ <link rel="stylesheet" href=""
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
<link rel="stylesheet" href=""
@@ -352,6 +353,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
@@ -696,6 +698,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Added: trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js (0 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -0,0 +1,175 @@
+/*
+ * 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.Cookie = class Cookie
+{
+ constructor(type, name, value, raw, expires, maxAge, path, domain, secure, httpOnly)
+ {
+ console.assert(Object.values(WI.Cookie.Type).includes(type));
+ console.assert(typeof name === "string");
+ console.assert(typeof value === "string");
+ console.assert(!raw || typeof raw === "string");
+ console.assert(!expires || expires instanceof Date);
+ console.assert(!maxAge || typeof maxAge === "number");
+ console.assert(!path || typeof path === "string");
+ console.assert(!domain || typeof domain === "string");
+ console.assert(!secure || typeof secure === "boolean");
+ console.assert(!httpOnly || typeof httpOnly === "boolean");
+
+ this.type = type;
+ this.name = name || "";
+ this.value = value || "";
+
+ if (this.type === WI.Cookie.Type.Response) {
+ this.rawHeader = raw || "";
+ this.expires = expires || null;
+ this.maxAge = maxAge || null;
+ this.path = path || null;
+ this.domain = domain || null;
+ this.secure = secure || false;
+ this.httpOnly = httpOnly || false;
+ }
+ }
+
+ // Static
+
+ // RFC 6265 defines the HTTP Cookie and Set-Cookie header fields:
+ // https://www.ietf.org/rfc/rfc6265.txt
+
+ static parseCookieRequestHeader(header)
+ {
+ if (!header)
+ return [];
+
+ header = header.trim();
+ if (!header)
+ return [];
+
+ let cookies = [];
+
+ // Cookie: <name> = <value> ( ";" SP <name> = <value> )*?
+ // NOTE: Just name/value pairs.
+
+ let pairs = header.split(/; /);
+ for (let pair of pairs) {
+ let match = pair.match(/^(?<name>[^\s=]+)[ \t]*=[ \t]*(?<value>.*)$/);
+ if (!match) {
+ WI.reportInternalError("Failed to parse Cookie pair", {header, pair});
+ continue;
+ }
+
+ let {name, value} = match.groups;
+ cookies.push(new WI.Cookie(WI.Cookie.Type.Request, name, value));
+ }
+
+ return cookies;
+ }
+
+ static parseSetCookieResponseHeader(header)
+ {
+ if (!header)
+ return null;
+
+ // Set-Cookie: <name> = <value> ( ";" SP <attr-maybe-pair> )*?
+ // NOTE: Some attributes can have pairs (e.g. "Path=/"), some are only a
+ // single word (e.g. "Secure").
+
+ // Parse name/value.
+ let nameValueMatch = header.match(/^(?<name>[^\s=]+)[ \t]*=[ \t]*(?<value>[^;]*)/);
+ if (!nameValueMatch) {
+ WI.reportInternalError("Failed to parse Set-Cookie header", {header});
+ return null;
+ }
+
+ let {name, value} = nameValueMatch.groups;
+ let expires = null;
+ let maxAge = null;
+ let path = null;
+ let domain = null;
+ let secure = false;
+ let httpOnly = false;
+
+ // Parse Attributes
+ let remaining = header.substr(nameValueMatch[0].length);
+ let attributes = remaining.split(/; ?/);
+ for (let attribute of attributes) {
+ if (!attribute)
+ continue;
+
+ let match = attribute.match(/^(?<name>[^\s=]+)(?:=(?<value>.*))?$/);
+ if (!match) {
+ console.error("Failed to parse Set-Cookie attribute:", attribute);
+ continue;
+ }
+
+ let attributeName = match.groups.name;
+ let attributeValue = match.groups.value;
+ switch (attributeName.toLowerCase()) {
+ case "expires":
+ console.assert(attributeValue);
+ expires = new Date(attributeValue);
+ if (isNaN(expires.getTime())) {
+ console.warn("Invalid Expires date:", attributeValue);
+ expires = null;
+ }
+ break;
+ case "max-age":
+ console.assert(attributeValue);
+ maxAge = parseInt(attributeValue, 10);
+ if (isNaN(maxAge) || !/^\d+$/.test(attributeValue)) {
+ console.warn("Invalid MaxAge value:", attributeValue);
+ maxAge = null;
+ }
+ break;
+ case "path":
+ console.assert(attributeValue);
+ path = attributeValue;
+ break;
+ case "domain":
+ console.assert(attributeValue);
+ domain = attributeValue;
+ break;
+ case "secure":
+ console.assert(!attributeValue);
+ secure = true;
+ break;
+ case "httponly":
+ console.assert(!attributeValue);
+ httpOnly = true;
+ break;
+ default:
+ console.warn("Unknown Cookie attribute:", attribute);
+ break;
+ }
+ }
+
+ return new WI.Cookie(WI.Cookie.Type.Response, name, value, header, expires, maxAge, path, domain, secure, httpOnly);
+ }
+}
+
+WI.Cookie.Type = {
+ Request: "request",
+ Response: "response",
+};
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -46,6 +46,8 @@
this._requestData = requestData || null;
this._requestHeaders = requestHeaders || {};
this._responseHeaders = {};
+ this._requestCookies = null;
+ this._responseCookies = null;
this._parentFrame = null;
this._initiatorSourceCodeLocation = initiatorSourceCodeLocation || null;
this._initiatedResources = [];
@@ -423,6 +425,36 @@
return this._responseHeaders;
}
+ get requestCookies()
+ {
+ if (!this._requestCookies)
+ this._requestCookies = WI.Cookie.parseCookieRequestHeader(this._requestHeaders.valueForCaseInsensitiveKey("Cookie"));
+
+ return this._requestCookies;
+ }
+
+ get responseCookies()
+ {
+ if (!this._responseCookies) {
+ // FIXME: The backend sends multiple "Set-Cookie" headers in one "Set-Cookie" with multiple values
+ // separated by ", ". This doesn't allow us to safely distinguish between a ", " that separates
+ // multiple headers or one that may be valid part of a Cookie's value or attribute, such as the
+ // ", " in the the date format "Expires=Tue, 03-Oct-2017 04:39:21 GMT". To improve heuristics
+ // we do a negative lookahead for numbers, but we can still fail on cookie values containing ", ".
+ let rawCombinedHeader = this._responseHeaders.valueForCaseInsensitiveKey("Set-Cookie") || "";
+ let setCookieHeaders = rawCombinedHeader.split(/, (?![0-9])/);
+ let cookies = [];
+ for (let header of setCookieHeaders) {
+ let cookie = WI.Cookie.parseSetCookieResponseHeader(header);
+ if (cookie)
+ cookies.push(cookie);
+ }
+ this._responseCookies = cookies;
+ }
+
+ return this._responseCookies;
+ }
+
get requestSentTimestamp()
{
return this._requestSentTimestamp;
@@ -605,6 +637,7 @@
this._url = url;
this._requestHeaders = requestHeaders || {};
+ this._requestCookies = null;
this._lastRedirectReceivedTimestamp = elapsedTime || NaN;
if (oldURL !== url) {
@@ -642,6 +675,7 @@
this._statusCode = statusCode;
this._statusText = statusText;
this._responseHeaders = responseHeaders || {};
+ this._responseCookies = null;
this._responseReceivedTimestamp = elapsedTime || NaN;
this._timingData = WI.ResourceTimingData.fromPayload(timingData, this);
@@ -702,6 +736,7 @@
this._connectionIdentifier = WI.Resource.connectionIdentifierFromPayload(metrics.connectionIdentifier);
if (metrics.requestHeaders) {
this._requestHeaders = metrics.requestHeaders;
+ this._requestCookies = null;
this.dispatchEventToListeners(WI.Resource.Event.RequestHeadersDidChange);
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Test.html (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Test.html 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Test.html 2017-10-09 19:32:28 UTC (rev 223058)
@@ -116,6 +116,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -161,9 +161,8 @@
this._contentBrowser.showContentView(this._headersContentView);
break;
case "cookies":
- // FIXME: Provide a Resource Cookies View.
if (!this._cookiesContentView)
- this._cookiesContentView = new WI.DebugContentView("Cookies");
+ this._cookiesContentView = new WI.ResourceCookiesContentView(this._resource);
this._contentBrowser.showContentView(this._cookiesContentView);
break;
case "timing":
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css 2017-10-09 19:32:28 UTC (rev 223058)
@@ -23,7 +23,7 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
-.content-view.network .table .icon {
+.content-view.network .network-table .icon {
position: relative;
width: 16px;
height: 16px;
@@ -32,38 +32,42 @@
-webkit-margin-end: 4px;
}
-.content-view.network .table li:not(.filler) .cell.name {
+.network-table li:not(.filler) .cell.name {
cursor: pointer;
}
-.content-view.network .table .cache-type {
+.network-table .cache-type {
color: var(--text-color-gray-medium);
}
-.content-view.network .table .error {
+.network-table .error {
color: var(--error-text-color);
}
-body[dir=ltr] .content-view.network .table .cell.name > .status {
+body[dir=ltr] .network-table .cell.name > .status {
float: right;
margin-left: 4px;
}
-body[dir=rtl] .content-view.network .table .cell.name > .status {
+body[dir=rtl] .network-table .cell.name > .status {
float: left;
margin-right: 4px;
}
-.content-view.network .table .cell.name > .status .indeterminate-progress-spinner {
+.network-table .cell.name > .status .indeterminate-progress-spinner {
margin-top: 3px;
width: 14px;
height: 14px;
}
-.showing-detail .table .cell:not(.name) {
+.showing-detail .network-table .cell:not(.name) {
display: none;
}
-.showing-detail .table .resizer:not(:first-of-type) {
+.showing-detail .network-table .resizer:not(:first-of-type) {
display: none;
}
+
+.network-table :not(.header) .cell:first-of-type {
+ background: rgba(0, 0, 0, 0.07);
+}
Copied: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.css (from rev 223057, trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css) (0 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.css (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.css 2017-10-09 19:32:28 UTC (rev 223058)
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+.resource-cookies.resource-details > section > .details.has-table {
+ border-left: none;
+}
+
+.resource-cookies .table {
+ border: 1px solid var(--border-color);
+}
+
+.resource-cookies .table > .header {
+ -webkit-user-select: none;
+}
Added: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js (0 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -0,0 +1,287 @@
+/*
+ * 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.ResourceCookiesContentView = class ResourceCookiesContentView extends WI.ContentView
+{
+ constructor(resource)
+ {
+ super(null);
+
+ console.assert(resource instanceof WI.Resource);
+
+ this._resource = resource;
+ this._resource.addEventListener(WI.Resource.Event.RequestHeadersDidChange, this._resourceRequestHeadersDidChange, this);
+ this._resource.addEventListener(WI.Resource.Event.ResponseReceived, this._resourceResponseReceived, this);
+
+ this.element.classList.add("resource-details", "resource-cookies");
+ }
+
+ // Table dataSource
+
+ tableNumberOfRows(table)
+ {
+ return this._dataSourceForTable(table).length;
+ }
+
+ tableSortChanged(table)
+ {
+ let sortComparator = this._generateSortComparator(table);
+ if (!sortComparator)
+ return;
+
+ let dataSource = this._dataSourceForTable(table);
+ dataSource.sort(sortComparator);
+ table.reloadData();
+ }
+
+ // Table delegate
+
+ tablePopulateCell(table, cell, column, rowIndex)
+ {
+ let cookie = this._dataSourceForTable(table)[rowIndex];
+
+ const checkmark = "\u2713";
+
+ switch (column.identifier) {
+ case "name":
+ cell.textContent = cookie.name;
+ break;
+ case "value":
+ cell.textContent = cookie.value;
+ break;
+ case "domain":
+ cell.textContent = cookie.domain || emDash;
+ break;
+ case "path":
+ cell.textContent = cookie.path || emDash;
+ break;
+ case "expires":
+ cell.textContent = cookie.expires ? cookie.expires.toLocaleString() : WI.UIString("Session");
+ break;
+ case "maxAge":
+ cell.textContent = cookie.maxAge || emDash;
+ break;
+ case "secure":
+ cell.textContent = cookie.secure ? checkmark : zeroWidthSpace;
+ break;
+ case "httpOnly":
+ cell.textContent = cookie.httpOnly ? checkmark : zeroWidthSpace;
+ break;
+ }
+
+ return cell;
+ }
+
+ // Protected
+
+ initialLayout()
+ {
+ super.initialLayout();
+
+ this._requestCookiesSection = new WI.ResourceDetailsSection(WI.UIString("Request Cookies"));
+ this.element.appendChild(this._requestCookiesSection.element);
+ this._refreshRequestCookiesSection();
+
+ this._responseCookiesSection = new WI.ResourceDetailsSection(WI.UIString("Response Cookies"));
+ this.element.appendChild(this._responseCookiesSection.element);
+ this._refreshResponseCookiesSection();
+ }
+
+ // Private
+
+ _markIncompleteSectionWithMessage(section, message)
+ {
+ section.toggleIncomplete(true);
+
+ let p = section.detailsElement.appendChild(document.createElement("p"));
+ p.textContent = message;
+ }
+
+ _markIncompleteSectionWithLoadingIndicator(section)
+ {
+ section.toggleIncomplete(true);
+
+ let p = section.detailsElement.appendChild(document.createElement("p"));
+ let spinner = new WI.IndeterminateProgressSpinner;
+ p.appendChild(spinner.element);
+ }
+
+ _dataSourceForTable(table)
+ {
+ return table === this._requestCookiesTable ? this._requestCookiesDataSource : this._responseCookiesDataSource;
+ }
+
+ _generateSortComparator(table)
+ {
+ let sortColumnIdentifier = table.sortColumnIdentifier;
+ if (!sortColumnIdentifier)
+ return null;
+
+ let comparator;
+
+ switch (sortColumnIdentifier) {
+ case "name":
+ case "value":
+ case "domain":
+ case "path":
+ // String.
+ comparator = (a, b) => (a[sortColumnIdentifier] || "").extendedLocaleCompare(b[sortColumnIdentifier] || "");
+ break;
+
+ case "maxAge":
+ // Number.
+ comparator = (a, b) => {
+ let aValue = a[sortColumnIdentifier];
+ if (isNaN(aValue))
+ return 1;
+ let bValue = b[sortColumnIdentifier];
+ if (isNaN(bValue))
+ return -1;
+ return aValue - bValue;
+ }
+ break;
+
+ case "httpOnly":
+ case "secure":
+ // Boolean.
+ comparator = (a, b) => (a[sortColumnIdentifier] - b[sortColumnIdentifier]);
+ break;
+
+ case "expires":
+ // Date.
+ comparator = (a, b) => {
+ let aExpires = a.expires;
+ if (!aExpires)
+ return 1;
+ let bExpires = b.expires;
+ if (!bExpires)
+ return -1;
+ return aExpires.getTime() - bExpires.getTime();
+ }
+ break;
+
+ default:
+ console.assert("Unexpected sort column", sortColumnIdentifier);
+ return null;
+ }
+
+ let reverseFactor = table.sortOrder === WI.Table.SortOrder.Ascending ? 1 : -1;
+ return (a, b) => reverseFactor * comparator(a, b);
+ }
+
+ _refreshRequestCookiesSection()
+ {
+ let detailsElement = this._requestCookiesSection.detailsElement;
+ detailsElement.removeChildren();
+
+ if (this._resource.responseSource === WI.Resource.ResponseSource.MemoryCache) {
+ this._markIncompleteSectionWithMessage(this._requestCookiesSection, WI.UIString("No request, served from the memory cache."));
+ return;
+ }
+
+ this._requestCookiesDataSource = this._resource.requestCookies;
+
+ if (!this._requestCookiesTable) {
+ this._requestCookiesTable = new WI.Table("request-cookies", this, this, 20);
+ this._requestCookiesTable.addColumn(new WI.TableColumn("name", WI.UIString("Name"), {minWidth: 150, maxWidth: 300, initialWidth: 200, resizeType: WI.TableColumn.ResizeType.Locked}));
+ this._requestCookiesTable.addColumn(new WI.TableColumn("value", WI.UIString("Value"), {minWidth: 150, hideable: false}));
+ if (!this._requestCookiesTable.sortColumnIdentifier) {
+ this._requestCookiesTable.sortOrder = WI.Table.SortOrder.Ascending;
+ this._requestCookiesTable.sortColumnIdentifier = "name";
+ }
+ }
+
+ if (!this._requestCookiesDataSource.length) {
+ if (this._requestCookiesTable.isAttached)
+ this.removeSubview(this._requestCookiesTable);
+ this._markIncompleteSectionWithMessage(this._requestCookiesSection, WI.UIString("No request cookies."));
+ } else {
+ this._requestCookiesSection.toggleIncomplete(false);
+ this._requestCookiesTable.element.style.height = this._sizeForTable(this._requestCookiesTable) + "px";
+ this.addSubview(this._requestCookiesTable);
+ detailsElement.classList.add("has-table");
+ detailsElement.appendChild(this._requestCookiesTable.element);
+ }
+ }
+
+ _refreshResponseCookiesSection()
+ {
+ let detailsElement = this._responseCookiesSection.detailsElement;
+ detailsElement.removeChildren();
+
+ if (!this._resource.hasResponse()) {
+ this._markIncompleteSectionWithLoadingIndicator(this._responseCookiesSection);
+ return;
+ }
+
+ this._responseCookiesDataSource = this._resource.responseCookies;
+
+ if (!this._responseCookiesTable) {
+ this._responseCookiesTable = new WI.Table("request-cookies", this, this, 20);
+ this._responseCookiesTable.addColumn(new WI.TableColumn("name", WI.UIString("Name"), {minWidth: 150, maxWidth: 300, initialWidth: 200, resizeType: WI.TableColumn.ResizeType.Locked}));
+ this._responseCookiesTable.addColumn(new WI.TableColumn("value", WI.UIString("Value"), {minWidth: 150, hideable: false}));
+ this._responseCookiesTable.addColumn(new WI.TableColumn("domain", WI.unlocalizedString("Domain"), {}));
+ this._responseCookiesTable.addColumn(new WI.TableColumn("path", WI.unlocalizedString("Path"), {}));
+ this._responseCookiesTable.addColumn(new WI.TableColumn("expires", WI.unlocalizedString("Expires"), {maxWidth: 150}));
+ this._responseCookiesTable.addColumn(new WI.TableColumn("maxAge", WI.unlocalizedString("Max-Age"), {maxWidth: 90, align: "right"}));
+ this._responseCookiesTable.addColumn(new WI.TableColumn("secure", WI.unlocalizedString("Secure"), {minWidth: 55, maxWidth: 65, align: "center"}));
+ this._responseCookiesTable.addColumn(new WI.TableColumn("httpOnly", WI.unlocalizedString("HttpOnly"), {minWidth: 55, maxWidth: 65, align: "center"}));
+ if (!this._responseCookiesTable.sortColumnIdentifier) {
+ this._responseCookiesTable.sortOrder = WI.Table.SortOrder.Ascending;
+ this._responseCookiesTable.sortColumnIdentifier = "name";
+ }
+ }
+
+ if (!this._responseCookiesDataSource.length) {
+ if (this._responseCookiesTable.isAttached)
+ this.removeSubview(this._responseCookiesTable);
+ this._markIncompleteSectionWithMessage(this._responseCookiesSection, WI.UIString("No response cookies."));
+ } else {
+ this._responseCookiesSection.toggleIncomplete(false);
+ this._responseCookiesTable.element.style.height = this._sizeForTable(this._responseCookiesTable) + "px";
+ this.addSubview(this._responseCookiesTable);
+ detailsElement.classList.add("has-table");
+ detailsElement.appendChild(this._responseCookiesTable.element);
+ }
+ }
+
+ _sizeForTable(table)
+ {
+ const headerHeight = 28;
+ const borderHeight = 3;
+ let rowsHeight = this._dataSourceForTable(table).length * table.rowHeight;
+ return rowsHeight + headerHeight + borderHeight;
+ }
+
+ _resourceRequestHeadersDidChange(event)
+ {
+ this._refreshRequestCookiesSection();
+ }
+
+ _resourceResponseReceived(event)
+ {
+ this._refreshResponseCookiesSection();
+ }
+};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.js (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.js 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -201,7 +201,7 @@
// Private
- _incompleteSectionWithMessage(section, message)
+ _markIncompleteSectionWithMessage(section, message)
{
section.toggleIncomplete(true);
@@ -209,7 +209,7 @@
p.textContent = message;
}
- _incompleteSectionWithLoadingIndicator(section)
+ _markIncompleteSectionWithLoadingIndicator(section)
{
section.toggleIncomplete(true);
@@ -282,11 +282,11 @@
// A revalidation request still sends a request even though we served from cache, so show the request.
if (this._resource.statusCode !== 304) {
if (this._resource.responseSource === WI.Resource.ResponseSource.MemoryCache) {
- this._incompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request, served from the memory cache."));
+ this._markIncompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request, served from the memory cache."));
return;
}
if (this._resource.responseSource === WI.Resource.ResponseSource.DiskCache) {
- this._incompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request, served from the disk cache."));
+ this._markIncompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request, served from the disk cache."));
return;
}
}
@@ -312,7 +312,7 @@
this._appendKeyValuePair(detailsElement, key, requestHeaders[key], "header");
if (!detailsElement.firstChild)
- this._incompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request headers"));
+ this._markIncompleteSectionWithMessage(this._requestHeadersSection, WI.UIString("No request headers"));
}
_refreshResponseHeadersSection()
@@ -321,7 +321,7 @@
detailsElement.removeChildren();
if (!this._resource.hasResponse()) {
- this._incompleteSectionWithLoadingIndicator(this._responseHeadersSection);
+ this._markIncompleteSectionWithLoadingIndicator(this._responseHeadersSection);
return;
}
@@ -340,11 +340,21 @@
}
let responseHeaders = this._resource.responseHeaders;
- for (let key in responseHeaders)
+ for (let key in responseHeaders) {
+ // Split multiple Set-Cookie response headers out into their multiple headers instead of as a combined value.
+ if (key.toLowerCase() === "set-cookie") {
+ let responseCookies = this._resource.responseCookies;
+ console.assert(responseCookies.length > 0);
+ for (let cookie of responseCookies)
+ this._appendKeyValuePair(detailsElement, key, cookie.rawHeader, "header");
+ continue;
+ }
+
this._appendKeyValuePair(detailsElement, key, responseHeaders[key], "header");
+ }
if (!detailsElement.firstChild)
- this._incompleteSectionWithMessage(this._responseHeadersSection, WI.UIString("No response headers"));
+ this._markIncompleteSectionWithMessage(this._responseHeadersSection, WI.UIString("No response headers"));
}
_refreshQueryStringSection()
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Table.css (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/Table.css 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Table.css 2017-10-09 19:32:28 UTC (rev 223058)
@@ -160,10 +160,6 @@
border-right: var(--table-column-border-start);
}
-.table :not(.header) .cell:first-of-type {
- background: rgba(0, 0, 0, 0.07);
-}
-
.table .cell.align-right {
text-align: right;
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Table.js (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/Table.js 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Table.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -44,7 +44,7 @@
// synchronized scrolling between multiple elements, or making `position: sticky`
// respect different vertical / horizontal scroll containers.
- this.element.classList.add("table");
+ this.element.classList.add("table", identifier);
this.element.tabIndex = 0;
this.element.addEventListener("keydown", this._handleKeyDown.bind(this));
@@ -334,7 +334,7 @@
if (!column.hidden)
return;
- column.setHidden(false);
+ column.hidden = false;
let columnIndex = this._hiddenColumns.indexOf(column);
this._hiddenColumns.splice(columnIndex, 1);
@@ -388,10 +388,14 @@
if (column.locked)
return;
+ console.assert(column.hideable, "Column is not hideable so should always be shown.");
+ if (!column.hideable)
+ return;
+
if (column.hidden)
return;
- column.setHidden(true);
+ column.hidden = true;
this._hiddenColumns.push(column);
@@ -1174,6 +1178,8 @@
for (let [columnIdentifier, column] of this._columnSpecs) {
if (column.locked)
continue;
+ if (!column.hideable)
+ continue;
let checked = !column.hidden;
contextMenu.appendCheckboxItem(column.name, () => {
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TableColumn.js (223057 => 223058)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TableColumn.js 2017-10-09 19:27:40 UTC (rev 223057)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TableColumn.js 2017-10-09 19:32:28 UTC (rev 223058)
@@ -25,7 +25,7 @@
WI.TableColumn = class TableColumn extends WI.Object
{
- constructor(identifier, name, {initialWidth, minWidth, maxWidth, hidden, sortable, align, resizeType} = {})
+ constructor(identifier, name, {initialWidth, minWidth, maxWidth, hidden, sortable, hideable, align, resizeType} = {})
{
super();
@@ -44,6 +44,7 @@
this._hidden = hidden || false;
this._defaultHidden = hidden || false;
this._sortable = typeof sortable === "boolean" ? sortable : true;
+ this._hideable = typeof hideable === "boolean" ? hideable : true;
this._align = align || null;
this._resizeType = resizeType || TableColumn.ResizeType.Auto;
@@ -60,6 +61,7 @@
get maxWidth() { return this._maxWidth; }
get defaultHidden() { return this._defaultHidden; }
get sortable() { return this._sortable; }
+ get hideable() { return this._hideable; }
get align() { return this._align; }
get locked() { return this._resizeType === TableColumn.ResizeType.Locked; }
@@ -90,8 +92,9 @@
return this._hidden;
}
- setHidden(x)
+ set hidden(x)
{
+ console.assert(!this.locked && this._hideable, "Should not be able to hide a non-hideable column.");
this._hidden = x;
}
};