Diff
Modified: trunk/LayoutTests/ChangeLog (259172 => 259173)
--- trunk/LayoutTests/ChangeLog 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/LayoutTests/ChangeLog 2020-03-29 03:07:52 UTC (rev 259173)
@@ -1,5 +1,20 @@
2020-03-28 Devin Rousso <[email protected]>
+ Web Inspector: support editing cookie key/values from inspector
+ https://bugs.webkit.org/show_bug.cgi?id=31157
+ <rdar://problem/19281523>
+
+ Reviewed by Timothy Hatcher.
+
+ * http/tests/inspector/page/setCookie.html: Added.
+ * http/tests/inspector/page/setCookie-expected.txt: Added.
+
+ * inspector/unit-tests/number-utilities.html:
+ * inspector/unit-tests/number-utilities-expected.txt:
+ Drive-by: add tests for `Number.prototype.maxDecimals`.
+
+2020-03-28 Devin Rousso <[email protected]>
+
Web Inspector: CSS: create visual editor for `box-shadow`
https://bugs.webkit.org/show_bug.cgi?id=208380
Added: trunk/LayoutTests/http/tests/inspector/page/setCookie-expected.txt (0 => 259173)
--- trunk/LayoutTests/http/tests/inspector/page/setCookie-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/page/setCookie-expected.txt 2020-03-29 03:07:52 UTC (rev 259173)
@@ -0,0 +1,121 @@
+Test for the Page.setCookie.
+
+
+== Running test suite: Page.setCookie
+-- Running test case: Page.setCookie.Valid
+PASS: Should have been able to set all cookies.
+
+-- Running test case: Page.setCookie.Invalid
+Setting cookie {} ...
+PASS: Should produce an exception.
+Error: Invalid value for key name in given cookie
+
+Setting cookie {"name":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key name in given cookie
+
+Setting cookie {"name":-1} ...
+PASS: Should produce an exception.
+Error: Invalid value for key name in given cookie
+
+Setting cookie {"name":"name"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key value in given cookie
+
+Setting cookie {"name":"name","value":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key value in given cookie
+
+Setting cookie {"name":"name","value":-1} ...
+PASS: Should produce an exception.
+Error: Invalid value for key value in given cookie
+
+Setting cookie {"name":"name","value":"value"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key domain in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key domain in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":-1} ...
+PASS: Should produce an exception.
+Error: Invalid value for key domain in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key path in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key path in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":-1} ...
+PASS: Should produce an exception.
+Error: Invalid value for key path in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":"INVALID"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":"INVALID"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":"INVALID"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key httpOnly in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true} ...
+PASS: Should produce an exception.
+Error: Invalid value for key secure in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key secure in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":"INVALID"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key secure in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true} ...
+PASS: Should produce an exception.
+Error: Invalid value for key sameSite in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true,"sameSite":null} ...
+PASS: Should produce an exception.
+Error: Invalid value for key sameSite in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true,"sameSite":-1} ...
+PASS: Should produce an exception.
+Error: Invalid value for key sameSite in given cookie
+
+Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true,"sameSite":"INVALID"} ...
+PASS: Should produce an exception.
+Error: Invalid value for key sameSite in given cookie
+
+
Added: trunk/LayoutTests/http/tests/inspector/page/setCookie.html (0 => 259173)
--- trunk/LayoutTests/http/tests/inspector/page/setCookie.html (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/page/setCookie.html 2020-03-29 03:07:52 UTC (rev 259173)
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+if (window.testRunner)
+ testRunner.setAlwaysAcceptCookies(true);
+
+function test()
+{
+ const millisecondsPerDay = 1000 * 60 * 60 * 24;
+ let tomorrow = new Date(Date.now().maxDecimals(-3) + millisecondsPerDay);
+
+ let suite = InspectorTest.createAsyncSuite("Page.setCookie");
+
+ suite.addTestCase({
+ name: "Page.setCookie.Valid",
+ description: "Check that setting a valid cookies works.",
+ async test() {
+ let cookiesToSet = [
+ {
+ expires: tomorrow,
+ session: false,
+ path: "/",
+ domain: "127.0.0.1",
+ secure: false,
+ httpOnly: false,
+ sameSite: WI.Cookie.SameSiteType.Lax,
+ },
+ {
+ expires: tomorrow,
+ session: true,
+ path: "/inspector/",
+ domain: "127.0.0.1",
+ secure: true,
+ httpOnly: true,
+ sameSite: WI.Cookie.SameSiteType.Strict,
+ },
+ ].map((options, i) => new WI.Cookie(WI.Cookie.Type.Response, "TestCookieName" + i, "TestCookieValue" + i, options));
+
+ for (let cookieToSet of cookiesToSet)
+ await PageAgent.setCookie(cookieToSet.toProtocol());
+
+ let getCookiesResult = await PageAgent.getCookies();
+ let setCookies = getCookiesResult.cookies.map(WI.Cookie.fromPayload);
+
+ let found = true;
+ for (let cookieToSet of cookiesToSet) {
+ if (!setCookies.find((setCookie) => cookieToSet.equals(setCookie))) {
+ found = false;
+ InspectorTest.fail("Missing cookie:");
+ InspectorTest.json(cookieToSet);
+ }
+ }
+ InspectorTest.expectTrue(found, "Should have been able to set all cookies.");
+ },
+ });
+
+ suite.addTestCase({
+ name: "Page.setCookie.Invalid",
+ description: "Check that the PageAgent correctly parses the given cookie JSON object.",
+ async test() {
+ const invalidValue = null;
+ const invalidString = -1;
+ const invalidNumber = "INVALID";
+ const invalidBoolean = invalidNumber;
+
+ const cookies = [
+ {},
+
+ {name: invalidValue},
+ {name: invalidString},
+ {name: "name"},
+
+ {name: "name", value: invalidValue},
+ {name: "name", value: invalidString},
+ {name: "name", value: "value"},
+
+ {name: "name", value: "value", domain: invalidValue},
+ {name: "name", value: "value", domain: invalidString},
+ {name: "name", value: "value", domain: "webkit.org"},
+
+ {name: "name", value: "value", domain: "webkit.org", path: invalidValue},
+ {name: "name", value: "value", domain: "webkit.org", path: invalidString},
+ {name: "name", value: "value", domain: "webkit.org", path: "/"},
+
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: invalidValue},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: invalidNumber},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000},
+
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: invalidValue},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: invalidBoolean},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true},
+
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: invalidValue},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: invalidBoolean},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true},
+
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: invalidValue},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: invalidBoolean},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true},
+
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true, sameSite: invalidValue},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true, sameSite: invalidString},
+ {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true, sameSite: "INVALID"},
+ ];
+ for (let cookie of cookies) {
+ InspectorTest.log("Setting cookie " + JSON.stringify(cookie) + " ...");
+ await InspectorTest.expectException(() => PageAgent.setCookie(cookie));
+ InspectorTest.newline();
+ }
+ },
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTestHTTPS()">
+ <p>Test for the Page.setCookie.</p>
+</body>
+</html>
Modified: trunk/LayoutTests/inspector/unit-tests/number-utilities-expected.txt (259172 => 259173)
--- trunk/LayoutTests/inspector/unit-tests/number-utilities-expected.txt 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/LayoutTests/inspector/unit-tests/number-utilities-expected.txt 2020-03-29 03:07:52 UTC (rev 259173)
@@ -87,3 +87,14 @@
PASS: 1000000000 should have 10 digits
PASS: -1000000000 should have 10 digits
+-- Running test case: Number.prototype.maxDecimals
+PASS: maxDecimals with a negative argument more than the number of digits should be 0
+PASS: maxDecimals with a negative argument more equal to the number of digits should be 0
+PASS: maxDecimals with -2 should truncate that 2 units in front of the decimal
+PASS: maxDecimals with -1 should truncate that 1 units in front of the decimal
+PASS: maxDecimals with 0 should round the value
+PASS: maxDecimals with 1 should round after the 1st decimal
+PASS: maxDecimals with 2 should round after the 2nd decimal
+PASS: maxDecimals with a positive argument equal to the number of digits should not modify the number
+PASS: maxDecimals with a positive argument greater than the number of digits should not modify the number
+
Modified: trunk/LayoutTests/inspector/unit-tests/number-utilities.html (259172 => 259173)
--- trunk/LayoutTests/inspector/unit-tests/number-utilities.html 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/LayoutTests/inspector/unit-tests/number-utilities.html 2020-03-29 03:07:52 UTC (rev 259173)
@@ -134,6 +134,23 @@
}
});
+ suite.addTestCase({
+ name: "Number.prototype.maxDecimals",
+ test() {
+ const n = 123.456;
+
+ InspectorTest.expectEqual(n.maxDecimals(-4), 0, "maxDecimals with a negative argument more than the number of digits should be 0");
+ InspectorTest.expectEqual(n.maxDecimals(-3), 0, "maxDecimals with a negative argument more equal to the number of digits should be 0");
+ InspectorTest.expectEqual(n.maxDecimals(-2), 100, "maxDecimals with -2 should truncate that 2 units in front of the decimal");
+ InspectorTest.expectEqual(n.maxDecimals(-1), 120, "maxDecimals with -1 should truncate that 1 units in front of the decimal");
+ InspectorTest.expectEqual(n.maxDecimals(0), 123, "maxDecimals with 0 should round the value");
+ InspectorTest.expectEqual(n.maxDecimals(1), 123.5, "maxDecimals with 1 should round after the 1st decimal");
+ InspectorTest.expectEqual(n.maxDecimals(2), 123.46, "maxDecimals with 2 should round after the 2nd decimal");
+ InspectorTest.expectEqual(n.maxDecimals(3), 123.456, "maxDecimals with a positive argument equal to the number of digits should not modify the number");
+ InspectorTest.expectEqual(n.maxDecimals(4), 123.456, "maxDecimals with a positive argument greater than the number of digits should not modify the number");
+ },
+ });
+
suite.runTestCasesAndFinish();
}
</script>
Modified: trunk/Source/_javascript_Core/ChangeLog (259172 => 259173)
--- trunk/Source/_javascript_Core/ChangeLog 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/_javascript_Core/ChangeLog 2020-03-29 03:07:52 UTC (rev 259173)
@@ -1,3 +1,15 @@
+2020-03-28 Devin Rousso <[email protected]>
+
+ Web Inspector: support editing cookie key/values from inspector
+ https://bugs.webkit.org/show_bug.cgi?id=31157
+ <rdar://problem/19281523>
+
+ Reviewed by Timothy Hatcher.
+
+ * inspector/protocol/Page.json:
+ Add a `session` parameter to `Page.Cookie` type and a new `Page.setCookie` command.
+ Remove the `size` parameter from `Page.Cookie` as this can be calculated in the frontend.
+
2020-03-27 Ross Kirsling <[email protected]>
[JSC] Make Operator an enum class to avoid Op* identifiers
Modified: trunk/Source/_javascript_Core/inspector/protocol/Page.json (259172 => 259173)
--- trunk/Source/_javascript_Core/inspector/protocol/Page.json 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/_javascript_Core/inspector/protocol/Page.json 2020-03-29 03:07:52 UTC (rev 259173)
@@ -104,10 +104,9 @@
{ "name": "domain", "type": "string", "description": "Cookie domain." },
{ "name": "path", "type": "string", "description": "Cookie path." },
{ "name": "expires", "type": "number", "description": "Cookie expires." },
- { "name": "size", "type": "integer", "description": "Cookie size." },
+ { "name": "session", "type": "boolean", "description": "True in case of session cookie." },
{ "name": "httpOnly", "type": "boolean", "description": "True if cookie is http-only." },
{ "name": "secure", "type": "boolean", "description": "True if cookie is secure." },
- { "name": "session", "type": "boolean", "description": "True in case of session cookie." },
{ "name": "sameSite", "$ref": "CookieSameSitePolicy", "description": "Cookie Same-Site policy." }
]
}
@@ -159,8 +158,15 @@
]
},
{
+ "name": "setCookie",
+ "description": "Sets a new browser cookie with the given name, domain, and path.",
+ "parameters": [
+ { "name": "cookie", "$ref": "Cookie" }
+ ]
+ },
+ {
"name": "deleteCookie",
- "description": "Deletes browser cookie with given name, domain and path.",
+ "description": "Deletes browser cookie with given name, domain, and path.",
"parameters": [
{ "name": "cookieName", "type": "string", "description": "Name of the cookie to remove." },
{ "name": "url", "type": "string", "description": "URL to match cookie domain and path." }
Modified: trunk/Source/WebCore/ChangeLog (259172 => 259173)
--- trunk/Source/WebCore/ChangeLog 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebCore/ChangeLog 2020-03-29 03:07:52 UTC (rev 259173)
@@ -1,3 +1,23 @@
+2020-03-28 Devin Rousso <[email protected]>
+
+ Web Inspector: support editing cookie key/values from inspector
+ https://bugs.webkit.org/show_bug.cgi?id=31157
+ <rdar://problem/19281523>
+
+ Reviewed by Timothy Hatcher.
+
+ Test: http/tests/inspector/page/setCookie.html
+
+ * inspector/agents/InspectorPageAgent.h:
+ * inspector/agents/InspectorPageAgent.cpp:
+ (WebCore::buildObjectForCookie):
+ (WebCore::parseCookieObject): Added.
+ (WebCore::InspectorPageAgent::setCookie): Added.
+
+ * loader/CookieJar.h:
+ * loader/CookieJar.cpp:
+ (WebCore::CookieJar::setRawCookie): Added.
+
2020-03-28 Simon Fraser <[email protected]>
Add a ScrollLatching log channel and improve some logging functionality
Modified: trunk/Source/WebCore/inspector/agents/InspectorPageAgent.cpp (259172 => 259173)
--- trunk/Source/WebCore/inspector/agents/InspectorPageAgent.cpp 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebCore/inspector/agents/InspectorPageAgent.cpp 2020-03-29 03:07:52 UTC (rev 259173)
@@ -489,10 +489,9 @@
.setDomain(cookie.domain)
.setPath(cookie.path)
.setExpires(cookie.expires.valueOr(0))
- .setSize((cookie.name.length() + cookie.value.length()))
+ .setSession(cookie.session)
.setHttpOnly(cookie.httpOnly)
.setSecure(cookie.secure)
- .setSession(cookie.session)
.setSameSite(cookieSameSitePolicyJSON(cookie.sameSite))
.release();
}
@@ -560,6 +559,90 @@
cookies = JSON::ArrayOf<Inspector::Protocol::Page::Cookie>::create();
}
+static Optional<Cookie> parseCookieObject(ErrorString& errorString, const JSON::Object& cookieObject)
+{
+ Cookie cookie;
+
+ if (!cookieObject.getString("name"_s, cookie.name)) {
+ errorString = "Invalid value for key name in given cookie";
+ return WTF::nullopt;
+ }
+
+ if (!cookieObject.getString("value"_s, cookie.value)) {
+ errorString = "Invalid value for key value in given cookie";
+ return WTF::nullopt;
+ }
+
+ if (!cookieObject.getString("domain"_s, cookie.domain)) {
+ errorString = "Invalid value for key domain in given cookie";
+ return WTF::nullopt;
+ }
+
+ if (!cookieObject.getString("path"_s, cookie.path)) {
+ errorString = "Invalid value for key path in given cookie";
+ return WTF::nullopt;
+ }
+
+ if (!cookieObject.getBoolean("httpOnly"_s, cookie.httpOnly)) {
+ errorString = "Invalid value for key httpOnly in given cookie";
+ return WTF::nullopt;
+ }
+
+ if (!cookieObject.getBoolean("secure"_s, cookie.secure)) {
+ errorString = "Invalid value for key secure in given cookie";
+ return WTF::nullopt;
+ }
+
+ bool validSession = cookieObject.getBoolean("session"_s, cookie.session);
+ cookie.expires = cookieObject.getNumber<double>("expires"_s);
+ if (!validSession && !cookie.expires) {
+ errorString = "Invalid value for key expires in given cookie";
+ return WTF::nullopt;
+ }
+
+ String sameSiteString;
+ if (!cookieObject.getString("sameSite"_s, sameSiteString)) {
+ errorString = "Invalid value for key sameSite in given cookie";
+ return WTF::nullopt;
+ }
+
+ auto sameSite = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::Page::CookieSameSitePolicy>(sameSiteString);
+ if (!sameSite) {
+ errorString = "Invalid value for key sameSite in given cookie";
+ return WTF::nullopt;
+ }
+
+ switch (sameSite.value()) {
+ case Inspector::Protocol::Page::CookieSameSitePolicy::None:
+ cookie.sameSite = Cookie::SameSitePolicy::None;
+
+ break;
+ case Inspector::Protocol::Page::CookieSameSitePolicy::Lax:
+ cookie.sameSite = Cookie::SameSitePolicy::Lax;
+
+ break;
+ case Inspector::Protocol::Page::CookieSameSitePolicy::Strict:
+ cookie.sameSite = Cookie::SameSitePolicy::Strict;
+ break;
+ }
+
+ return cookie;
+}
+
+void InspectorPageAgent::setCookie(ErrorString& errorString, const JSON::Object& cookieObject)
+{
+ auto cookie = parseCookieObject(errorString, cookieObject);
+ if (!cookie)
+ return;
+
+ for (auto* frame = &m_inspectedPage.mainFrame(); frame; frame = frame->tree().traverseNext()) {
+ if (auto* document = frame->document()) {
+ if (auto* page = document->page())
+ page->cookieJar().setRawCookie(*document, cookie.value());
+ }
+ }
+}
+
void InspectorPageAgent::deleteCookie(ErrorString&, const String& cookieName, const String& url)
{
URL parsedURL({ }, url);
Modified: trunk/Source/WebCore/inspector/agents/InspectorPageAgent.h (259172 => 259173)
--- trunk/Source/WebCore/inspector/agents/InspectorPageAgent.h 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebCore/inspector/agents/InspectorPageAgent.h 2020-03-29 03:07:52 UTC (rev 259173)
@@ -101,6 +101,7 @@
void overrideUserAgent(ErrorString&, const String* value) override;
void overrideSetting(ErrorString&, const String& setting, const bool* value) override;
void getCookies(ErrorString&, RefPtr<JSON::ArrayOf<Inspector::Protocol::Page::Cookie>>& cookies) override;
+ void setCookie(ErrorString&, const JSON::Object& cookieObject) override;
void deleteCookie(ErrorString&, const String& cookieName, const String& url) override;
void getResourceTree(ErrorString&, RefPtr<Inspector::Protocol::Page::FrameResourceTree>&) override;
void getResourceContent(ErrorString&, const String& frameId, const String& url, String* content, bool* base64Encoded) override;
Modified: trunk/Source/WebCore/loader/CookieJar.cpp (259172 => 259173)
--- trunk/Source/WebCore/loader/CookieJar.cpp 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebCore/loader/CookieJar.cpp 2020-03-29 03:07:52 UTC (rev 259173)
@@ -171,6 +171,14 @@
return false;
}
+void CookieJar::setRawCookie(const Document&, const Cookie& cookie)
+{
+ if (auto* session = m_storageSessionProvider->storageSession())
+ session->setCookie(cookie);
+ else
+ ASSERT_NOT_REACHED();
+}
+
void CookieJar::deleteCookie(const Document&, const URL& url, const String& cookieName)
{
if (auto* session = m_storageSessionProvider->storageSession())
Modified: trunk/Source/WebCore/loader/CookieJar.h (259172 => 259173)
--- trunk/Source/WebCore/loader/CookieJar.h 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebCore/loader/CookieJar.h 2020-03-29 03:07:52 UTC (rev 259173)
@@ -58,6 +58,7 @@
virtual bool cookiesEnabled(const Document&) const;
virtual std::pair<String, SecureCookiesAccessed> cookieRequestHeaderFieldValue(const URL& firstParty, const SameSiteInfo&, const URL&, Optional<FrameIdentifier>, Optional<PageIdentifier>, IncludeSecureCookies) const;
virtual bool getRawCookies(const Document&, const URL&, Vector<Cookie>&) const;
+ virtual void setRawCookie(const Document&, const Cookie&);
virtual void deleteCookie(const Document&, const URL&, const String& cookieName);
// Cookie Cache.
Modified: trunk/Source/WebInspectorUI/ChangeLog (259172 => 259173)
--- trunk/Source/WebInspectorUI/ChangeLog 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebInspectorUI/ChangeLog 2020-03-29 03:07:52 UTC (rev 259173)
@@ -1,5 +1,68 @@
2020-03-28 Devin Rousso <[email protected]>
+ Web Inspector: support editing cookie key/values from inspector
+ https://bugs.webkit.org/show_bug.cgi?id=31157
+ <rdar://problem/19281523>
+
+ Reviewed by Timothy Hatcher.
+
+ * UserInterface/Models/Cookie.js:
+ (WI.Cookie):
+ (WI.Cookie.fromPayload):
+ (WI.Cookie.parseSetCookieResponseHeader):
+ (WI.Cookie.prototype.get session): Added.
+ (WI.Cookie.prototype.expirationDate):
+ (WI.Cookie.prototype.equals): Added.
+ (WI.Cookie.prototype.toProtocol): Added.
+ Add `session` value in addition to the existing `expires` value. Create helper methods for
+ comparing `WI.Cookie` objects and for using the `WI.Cookie` as a `Page.Cookie` type when
+ invoking protocol commands (right now just `Page.setCookie`).
+
+ * UserInterface/Views/CookieStorageContentView.js:
+ (WI.CookieStorageContentView):
+ (WI.CookieStorageContentView.prototype.get navigationItems):
+ (WI.CookieStorageContentView.prototype.tableCellContextMenuClicked):
+ (WI.CookieStorageContentView.prototype.willDismissPopover): Added.
+ (WI.CookieStorageContentView.prototype.async _willDismissCookiePopover): Added.
+ (WI.CookieStorageContentView.prototype._handleSetCookieButtonClick): Added.
+ (WI.CookieStorageContentView.prototype._reloadCookies):
+ (WI.CookieStorageContentView.prototype._formatCookiePropertyForColumn):
+ Add a + navigation item that shows a popover for creating a new cookie. When contextmenu
+ clicking on a table row, add an "Edit" item that shows a popover for creating a new cookie
+ with the values from the existing cookie, which will "replace" (delete and set) the existing
+ cookie upon being dismissed.
+
+ * UserInterface/Views/ResourceCookiesContentView.js:
+ (WI.ResourceCookiesContentView.prototype.tablePopulateCell):
+ If only use the `expires` value if `session` is not set.
+
+ * UserInterface/Views/CookiePopover.js: Added.
+ (WI.CookiePopover):
+ (WI.CookiePopover.prototype.get serializedData):
+ (WI.CookiePopover.prototype.show.createRow):
+ (WI.CookiePopover.prototype.show.createInputRow):
+ (WI.CookiePopover.prototype.show):
+ (WI.CookiePopover.prototype._presentOverTargetElement):
+ (WI.CookiePopover.prototype._defaultExpires):
+ (WI.CookiePopover.prototype._parseExpires):
+ (WI.CookiePopover.prototype._handleInputKeyDown):
+ * UserInterface/Views/CookiePopover.css: Added.
+ (.popover .cookie-popover-content):
+ (.popover .cookie-popover-content > table):
+ (.popover .cookie-popover-content > table > tr > th):
+ (.popover .cookie-popover-content > table > tr > td):
+ (.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"])):
+ (.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"]).invalid):
+ (@media (prefers-color-scheme: dark) .popover .cookie-popover-content > table > tr > th):
+ Show an `<input>` (or `<select>`) for each configuration option when creating a cookie.
+ Hide the `<input>` for `expires` if the `<input type="checkbox">` for `session` is checked.
+ Indicate when the value in the `<input>` for `expires` is not a valid date.
+
+ * UserInterface/Main.html:
+ * Localizations/en.lproj/localizedStrings.js:
+
+2020-03-28 Devin Rousso <[email protected]>
+
Web Inspector: CSS: create visual editor for `box-shadow`
https://bugs.webkit.org/show_bug.cgi?id=208380
Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (259172 => 259173)
--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2020-03-29 03:07:52 UTC (rev 259173)
@@ -99,6 +99,7 @@
localizedStrings["Add Action"] = "Add Action";
localizedStrings["Add Breakpoint"] = "Add Breakpoint";
localizedStrings["Add Breakpoints"] = "Add Breakpoints";
+localizedStrings["Add Cookie"] = "Add Cookie";
localizedStrings["Add Header"] = "Add Header";
localizedStrings["Add New"] = "Add New";
localizedStrings["Add New Class"] = "Add New Class";
Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (259172 => 259173)
--- trunk/Source/WebInspectorUI/UserInterface/Main.html 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html 2020-03-29 03:07:52 UTC (rev 259173)
@@ -80,6 +80,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=""
@@ -645,6 +646,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js (259172 => 259173)
--- trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js 2020-03-29 03:07:52 UTC (rev 259173)
@@ -25,7 +25,7 @@
WI.Cookie = class Cookie
{
- constructor(type, name, value, {header, expires, maxAge, path, domain, secure, httpOnly, sameSite, size} = {})
+ constructor(type, name, value, {header, expires, session, maxAge, path, domain, secure, httpOnly, sameSite} = {})
{
console.assert(Object.values(WI.Cookie.Type).includes(type));
console.assert(typeof name === "string");
@@ -32,6 +32,7 @@
console.assert(typeof value === "string");
console.assert(!header || typeof header === "string");
console.assert(!expires || expires instanceof Date);
+ console.assert(!session || typeof session === "boolean");
console.assert(!maxAge || typeof maxAge === "number");
console.assert(!path || typeof path === "string");
console.assert(!domain || typeof domain === "string");
@@ -38,16 +39,16 @@
console.assert(!secure || typeof secure === "boolean");
console.assert(!httpOnly || typeof httpOnly === "boolean");
console.assert(!sameSite || Object.values(WI.Cookie.SameSiteType).includes(sameSite));
- console.assert(!size || typeof size === "number");
this._type = type;
this._name = name;
this._value = value;
- this._size = size || this._name.length + this._value.length;
+ this._size = this._name.length + this._value.length;
if (this._type === WI.Cookie.Type.Response) {
this._header = header || "";
- this._expires = expires || null;
+ this._expires = (!session && expires) || null;
+ this._session = session || false;
this._maxAge = maxAge || null;
this._path = path || null;
this._domain = domain || null;
@@ -62,7 +63,7 @@
static fromPayload(payload)
{
let {name, value, ...options} = payload;
- options.expires = options.expires ? new Date(options.expires) : null;
+ options.expires = options.expires ? new Date(options.expires.maxDecimals(-3)) : null;
return new WI.Cookie(WI.Cookie.Type.Response, name, value, options);
}
@@ -148,6 +149,7 @@
let {name, value} = nameValueMatch.groups;
let expires = null;
+ let session = false;
let maxAge = null;
let path = null;
let domain = null;
@@ -212,7 +214,10 @@
}
}
- return new WI.Cookie(WI.Cookie.Type.Response, name, value, {header, expires, maxAge, path, domain, secure, httpOnly, sameSite});
+ if (!expires)
+ session = true;
+
+ return new WI.Cookie(WI.Cookie.Type.Response, name, value, {header, expires, session, maxAge, path, domain, secure, httpOnly, sameSite});
}
// Public
@@ -222,6 +227,7 @@
get value() { return this._value; }
get header() { return this._header; }
get expires() { return this._expires; }
+ get session() { return this._session; }
get maxAge() { return this._maxAge; }
get path() { return this._path; }
get domain() { return this._domain; }
@@ -240,6 +246,9 @@
expirationDate(requestSentDate)
{
+ if (this._session)
+ return null;
+
if (this._maxAge) {
let startDate = requestSentDate || new Date;
return new Date(startDate.getTime() + (this._maxAge * 1000));
@@ -247,6 +256,57 @@
return this._expires;
}
+
+ equals(other)
+ {
+ return this._type === other.type
+ && this._name === other.name
+ && this._value === other.value
+ && this._header === other.header
+ && this._expires?.getTime() === other.expires?.getTime()
+ && this._session === other.session
+ && this._maxAge === other.maxAge
+ && this._path === other.path
+ && this._domain === other.domain
+ && this._secure === other.secure
+ && this._httpOnly === other.httpOnly
+ && this._sameSite === other.sameSite;
+ }
+
+ toProtocol()
+ {
+ if (typeof this._name !== "string")
+ return null;
+
+ if (typeof this._value !== "string")
+ return null;
+
+ if (typeof this._domain !== "string")
+ return null;
+
+ if (typeof this._path !== "string")
+ return null;
+
+ if (!this._session && !this._expires)
+ return null;
+
+ if (!Object.values(WI.Cookie.SameSiteType).includes(this._sameSite))
+ return null;
+
+ let json = {
+ name: this._name,
+ value: this._value,
+ domain: this._domain,
+ path: this._path,
+ expires: this._expires?.getTime(),
+ session: this._session,
+ httpOnly: !!this._httpOnly,
+ secure: !!this._secure,
+ sameSite: this._sameSite,
+ };
+
+ return json;
+ }
};
WI.Cookie.Type = {
Added: trunk/Source/WebInspectorUI/UserInterface/Views/CookiePopover.css (0 => 259173)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CookiePopover.css (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CookiePopover.css 2020-03-29 03:07:52 UTC (rev 259173)
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+.popover .cookie-popover-content {
+ max-width: 420px;
+ padding: 4px 8px;
+}
+
+.popover .cookie-popover-content > table {
+ width: 100%;
+}
+
+.popover .cookie-popover-content > table > tr > th {
+ font-weight: bold;
+ text-align: end;
+ line-height: 23px;
+ vertical-align: top;
+ white-space: nowrap;
+ color: hsl(0, 0%, 34%);
+}
+
+.popover .cookie-popover-content > table > tr > td {
+ -webkit-padding-start: 4px;
+}
+
+/* FIXME: <https://webkit.org/b/209389> Web Inspector: use native datetime-local picker for changing `expires` value in cookie popover */
+
+.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"]) {
+ width: 100%;
+ padding: 3px 4px 2px;
+ font-family: Menlo, monospace;
+ background-color: var(--background-color-code);
+ border: 1px solid var(--text-color-quaternary);
+ -webkit-appearance: none;
+}
+
+.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"]).invalid {
+ color: var(--error-text-color);
+}
+
+@media (prefers-color-scheme: dark) {
+ .popover .cookie-popover-content > table > tr > th {
+ color: var(--text-color-secondary);
+ }
+}
Added: trunk/Source/WebInspectorUI/UserInterface/Views/CookiePopover.js (0 => 259173)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CookiePopover.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CookiePopover.js 2020-03-29 03:07:52 UTC (rev 259173)
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 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.CookiePopover = class CookiePopover extends WI.Popover
+{
+ constructor(delegate)
+ {
+ super(delegate);
+
+ this._nameInputElement = null;
+ this._valueInputElement = null;
+ this._domainInputElement = null;
+ this._pathInputElement = null;
+ this._sessionCheckboxElement = null;
+ this._expiresInputElement = null;
+ this._httpOnlyCheckboxElement = null;
+ this._secureCheckboxElement = null;
+ this._sameSiteSelectElement = null;
+
+ this._serializedDataWhenShown = null;
+
+ this.windowResizeHandler = this._presentOverTargetElement.bind(this);
+ }
+
+ // Public
+
+ get serializedData()
+ {
+ if (!this._targetElement)
+ return null;
+
+ let name = this._nameInputElement.value || this._nameInputElement.placeholder;
+ if (!name)
+ return null;
+
+ let value = this._valueInputElement.value || this._valueInputElement.placeholder;
+ if (!value)
+ return null;
+
+ let domain = this._domainInputElement.value || this._domainInputElement.placeholder;
+ if (!domain)
+ return null;
+
+ let path = this._pathInputElement.value || this._pathInputElement.placeholder;
+ if (!path)
+ return null;
+
+ let session = this._sessionCheckboxElement.checked;
+ let expires = this._parseExpires();
+ if (!session && isNaN(expires))
+ return null;
+
+ // If a full URL is entered in the domain input, parse it to get just the domain.
+ try {
+ let url = "" URL(domain);
+ domain = url.hostname;
+ } catch { }
+
+ if (!path.startsWith("/"))
+ path = "/" + path;
+
+ let data = {
+ name,
+ value,
+ domain,
+ path,
+ httpOnly: this._httpOnlyCheckboxElement.checked,
+ secure: this._secureCheckboxElement.checked,
+ sameSite: this._sameSiteSelectElement.value,
+ };
+
+ if (session)
+ data.session = true;
+ else
+ data.expires = expires;
+
+ if (JSON.stringify(data) === JSON.stringify(this._serializedDataWhenShown))
+ return null;
+
+ return data;
+ }
+
+ show(cookie, targetElement, preferredEdges)
+ {
+ console.assert(!cookie || cookie instanceof WI.Cookie, cookie);
+ console.assert(targetElement instanceof Element, targetElement);
+ console.assert(Array.isArray(preferredEdges), preferredEdges);
+
+ this._targetElement = targetElement;
+ this._preferredEdges = preferredEdges;
+
+ let data = ""
+ if (cookie) {
+ data.name = cookie.name;
+ data.value = cookie.value;
+ data.domain = cookie.domain;
+ data.path = cookie.path;
+ data.expires = (cookie.expires || this._defaultExpires()).toLocaleString();
+ data.session = cookie.session;
+ data.httpOnly = cookie.httpOnly;
+ data.secure = cookie.secure;
+ data.sameSite = cookie.sameSite;
+ } else {
+ let urlComponents = WI.networkManager.mainFrame.mainResource.urlComponents;
+ data.name = WI.unlocalizedString("name");
+ data.value = WI.unlocalizedString("value");
+ data.domain = urlComponents.host;
+ data.path = urlComponents.path;
+ data.expires = this._defaultExpires().toLocaleString();
+ data.session = true;
+ data.httpOnly = false;
+ data.secure = false;
+ data.sameSite = WI.Cookie.SameSiteType.None;
+ }
+
+ let popoverContentElement = document.createElement("div");
+ popoverContentElement.className = "cookie-popover-content";
+
+ let tableElement = popoverContentElement.appendChild(document.createElement("table"));
+
+ function createRow(id, label, editorElement) {
+ id = `cookie-popover-${id}-editor`;
+
+ let rowElement = tableElement.appendChild(document.createElement("tr"));
+
+ let headerElement = rowElement.appendChild(document.createElement("th"));
+
+ let labelElement = headerElement.appendChild(document.createElement("label"));
+ labelElement.setAttribute("for", id);
+ labelElement.textContent = label;
+
+ let dataElement = rowElement.appendChild(document.createElement("td"));
+
+ editorElement.id = id;
+ dataElement.appendChild(editorElement);
+
+ return {rowElement};
+ }
+
+ let boundHandleInputKeyDown = this._handleInputKeyDown.bind(this);
+
+ function createInputRow(id, label, type, value) {
+ let inputElement = document.createElement("input");
+ inputElement.type = type;
+
+ if (type === "checkbox")
+ inputElement.checked = value;
+ else {
+ if (cookie)
+ inputElement.value = value;
+ inputElement.placeholder = value;
+ inputElement.addEventListener("keydown", boundHandleInputKeyDown);
+ }
+
+ let rowElement = createRow(id, label, inputElement).rowElement;
+
+ return {inputElement, rowElement};
+ }
+
+ this._nameInputElement = createInputRow("name", WI.UIString("Name"), "text", data.name).inputElement;
+
+ this._valueInputElement = createInputRow("value", WI.UIString("Value"), "text", data.value).inputElement;
+
+ this._domainInputElement = createInputRow("domain", WI.unlocalizedString("Domain"), "text", data.domain).inputElement;
+
+ this._pathInputElement = createInputRow("path", WI.unlocalizedString("Path"), "text", data.path).inputElement;
+
+ this._sessionCheckboxElement = createInputRow("session", WI.unlocalizedString("Session"), "checkbox", data.session).inputElement;
+
+ let expiresInputRow = createInputRow("expires", WI.unlocalizedString("Expires"), "datetime-local", data.expires);
+ this._expiresInputElement = expiresInputRow.inputElement;
+ this._expiresInputElement.addEventListener("input", (event) => {
+ this._expiresInputElement.classList.toggle("invalid", isNaN(this._parseExpires()));
+ });
+
+ this._httpOnlyCheckboxElement = createInputRow("http-only", WI.unlocalizedString("HttpOnly"), "checkbox", data.httpOnly).inputElement;
+
+ this._secureCheckboxElement = createInputRow("secure", WI.unlocalizedString("Secure"), "checkbox", data.secure).inputElement;
+
+ this._sameSiteSelectElement = document.createElement("select");
+ for (let sameSiteType of Object.values(WI.Cookie.SameSiteType)) {
+ let optionElement = this._sameSiteSelectElement.appendChild(document.createElement("option"));
+ optionElement.textContent = sameSiteType;
+ }
+ createRow("same-site", WI.unlocalizedString("SameSite"), this._sameSiteSelectElement);
+
+ let toggleExpiresRow = () => {
+ expiresInputRow.rowElement.hidden = this._sessionCheckboxElement.checked;
+
+ this.update();
+ };
+
+ this._sessionCheckboxElement.addEventListener("change", (event) => {
+ toggleExpiresRow();
+ });
+
+ toggleExpiresRow();
+
+ this._serializedDataWhenShown = this.serializedData;
+
+ this.content = popoverContentElement;
+ this._presentOverTargetElement();
+ }
+
+ // Private
+
+ _presentOverTargetElement()
+ {
+ if (!this._targetElement)
+ return;
+
+ let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect());
+ this.present(targetFrame.pad(2), this._preferredEdges);
+ }
+
+ _defaultExpires()
+ {
+ return new Date(Date.now() + (1000 * 60 * 60 * 24)); // one day in the future
+ }
+
+ _parseExpires()
+ {
+ let timestamp = Date.parse(this._expiresInputElement.value || this._expiresInputElement.placeholder);
+ if (timestamp < Date.now())
+ return NaN;
+ return timestamp;
+ }
+
+ _handleInputKeyDown(event)
+ {
+ if (event.key === "Enter" || event.key === "Esc")
+ this.dismiss();
+ }
+};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js (259172 => 259173)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js 2020-03-29 03:07:52 UTC (rev 259173)
@@ -35,6 +35,11 @@
this._sortComparator = null;
this._table = null;
+ if (InspectorBackend.hasCommand("Page.setCookie")) {
+ this._setCookieButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-set-cookie", WI.UIString("Add Cookie"), "Images/Plus15.svg", 15, 15);
+ this._setCookieButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleSetCookieButtonClick, this);
+ }
+
this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-refresh", WI.UIString("Refresh"), "Images/ReloadFull.svg", 13, 13);
this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._refreshButtonClicked, this);
}
@@ -43,7 +48,11 @@
get navigationItems()
{
- return [this._refreshButtonNavigationItem];
+ let navigationItems = [];
+ if (this._setCookieButtonNavigationItem)
+ navigationItems.push(this._setCookieButtonNavigationItem);
+ navigationItems.push(this._refreshButtonNavigationItem);
+ return navigationItems;
}
saveToCookie(cookie)
@@ -111,6 +120,17 @@
let contextMenu = WI.ContextMenu.createFromEvent(event);
contextMenu.appendSeparator();
+
+ if (InspectorBackend.hasCommand("Page.setCookie")) {
+ contextMenu.appendItem(WI.UIString("Edit"), () => {
+ console.assert(!this._editingCookie);
+ this._editingCookie = this._cookies[rowIndex];
+
+ let popover = new WI.CookiePopover(this);
+ popover.show(this._editingCookie, this._table.cellForRowAndColumn(rowIndex, this._table.columns[0]), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_X]);
+ });
+ }
+
contextMenu.appendItem(WI.UIString("Copy"), () => {
let rowIndexes;
if (table.isRowSelected(rowIndex))
@@ -128,6 +148,7 @@
else
table.removeRow(rowIndex);
});
+
contextMenu.appendSeparator();
}
@@ -157,6 +178,18 @@
return cell;
}
+ // Popover delegate
+
+ willDismissPopover(popover)
+ {
+ if (popover instanceof WI.CookiePopover) {
+ this._willDismissCookiePopover(popover);
+ return;
+ }
+
+ console.assert();
+ }
+
// Protected
initialLayout()
@@ -315,6 +348,45 @@
this._sortComparator = (a, b) => reverseFactor * comparator(a, b);
}
+ async _willDismissCookiePopover(popover)
+ {
+ let editingCookie = this._editingCookie;
+ this._editingCookie = null;
+
+ let serializedData = popover.serializedData;
+ if (!serializedData) {
+ InspectorFrontendHost.beep();
+ return;
+ }
+
+ let cookieToSet = WI.Cookie.fromPayload(serializedData);
+
+ let cookieProtocolPayload = cookieToSet.toProtocol();
+ if (!cookieProtocolPayload) {
+ InspectorFrontendHost.beep();
+ return;
+ }
+
+ let target = WI.assumingMainTarget();
+
+ let promises = [];
+ if (editingCookie)
+ promises.push(target.PageAgent.deleteCookie(editingCookie.name, editingCookie.url));
+ promises.push(target.PageAgent.setCookie(cookieProtocolPayload));
+ promises.push(this._reloadCookies());
+ await Promise.all(promises);
+
+ let index = this._cookies.findIndex((existingCookie) => cookieToSet.equals(existingCookie));
+ if (index >= 0)
+ this._table.selectRow(index);
+ }
+
+ _handleSetCookieButtonClick(event)
+ {
+ let popover = new WI.CookiePopover(this);
+ popover.show(null, this._setCookieButtonNavigationItem.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_X]);
+ }
+
_refreshButtonClicked(event)
{
this._reloadCookies();
@@ -323,7 +395,7 @@
_reloadCookies()
{
let target = WI.assumingMainTarget();
- target.PageAgent.getCookies().then((payload) => {
+ return target.PageAgent.getCookies().then((payload) => {
this._cookies = this._filterCookies(payload.cookies.map(WI.Cookie.fromPayload));
this._updateSort();
this._table.reloadData();
@@ -384,7 +456,7 @@
case "path":
return cookie.path || missingValue;
case "expires":
- return cookie.expires ? new Date(cookie.expires).toLocaleString() : WI.UIString("Session");
+ return (!cookie.session && cookie.expires) ? cookie.expires.toLocaleString() : WI.UIString("Session");
case "size":
return Number.bytesToString(cookie.size);
case "secure":
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js (259172 => 259173)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js 2020-03-29 03:07:52 UTC (rev 259173)
@@ -98,7 +98,7 @@
cell.textContent = cookie.path || emDash;
break;
case "expires":
- cell.textContent = cookie.expires ? cookie.expires.toLocaleString() : WI.UIString("Session");
+ cell.textContent = (!cookie.session && cookie.expires) ? cookie.expires.toLocaleString() : WI.UIString("Session");
break;
case "maxAge":
cell.textContent = cookie.maxAge || emDash;
Modified: trunk/Source/WebKit/ChangeLog (259172 => 259173)
--- trunk/Source/WebKit/ChangeLog 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebKit/ChangeLog 2020-03-29 03:07:52 UTC (rev 259173)
@@ -1,3 +1,20 @@
+2020-03-28 Devin Rousso <[email protected]>
+
+ Web Inspector: support editing cookie key/values from inspector
+ https://bugs.webkit.org/show_bug.cgi?id=31157
+ <rdar://problem/19281523>
+
+ Reviewed by Timothy Hatcher.
+
+ * WebProcess/WebPage/WebCookieJar.h:
+ * WebProcess/WebPage/WebCookieJar.cpp:
+ (WebKit::WebCookieJar::setRawCookie):
+
+ * NetworkProcess/NetworkConnectionToWebProcess.messages.in:
+ * NetworkProcess/NetworkConnectionToWebProcess.h:
+ * NetworkProcess/NetworkConnectionToWebProcess.cpp:
+ (WebKit::NetworkConnectionToWebProcess::setRawCookie): Added.
+
2020-03-28 David Kilzer <[email protected]>
REGRESSION (r258201): Use-after-move in UserMediaCaptureManager::Source::didFail()
Modified: trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp (259172 => 259173)
--- trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp 2020-03-29 03:07:52 UTC (rev 259173)
@@ -664,6 +664,15 @@
completionHandler(WTFMove(result));
}
+void NetworkConnectionToWebProcess::setRawCookie(const WebCore::Cookie& cookie)
+{
+ auto* networkStorageSession = storageSession();
+ if (!networkStorageSession)
+ return;
+
+ networkStorageSession->setCookie(cookie);
+}
+
void NetworkConnectionToWebProcess::deleteCookie(const URL& url, const String& cookieName)
{
auto* networkStorageSession = storageSession();
Modified: trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h (259172 => 259173)
--- trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h 2020-03-29 03:07:52 UTC (rev 259173)
@@ -209,6 +209,7 @@
void setCookiesFromDOM(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, WebCore::FrameIdentifier, WebCore::PageIdentifier, WebCore::ShouldAskITP, const String&);
void cookieRequestHeaderFieldValue(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, Optional<WebCore::FrameIdentifier>, Optional<WebCore::PageIdentifier>, WebCore::IncludeSecureCookies, WebCore::ShouldAskITP, CompletionHandler<void(String cookieString, bool secureCookiesAccessed)>&&);
void getRawCookies(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, Optional<WebCore::FrameIdentifier>, Optional<WebCore::PageIdentifier>, WebCore::ShouldAskITP, CompletionHandler<void(Vector<WebCore::Cookie>&&)>&&);
+ void setRawCookie(const WebCore::Cookie&);
void deleteCookie(const URL&, const String& cookieName);
void registerFileBlobURL(const URL&, const String& path, SandboxExtension::Handle&&, const String& contentType);
Modified: trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in (259172 => 259173)
--- trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in 2020-03-29 03:07:52 UTC (rev 259173)
@@ -39,6 +39,7 @@
SetCookiesFromDOM(URL firstParty, struct WebCore::SameSiteInfo sameSiteInfo, URL url, WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID, enum:bool WebCore::ShouldAskITP shouldAskITP, String cookieString)
CookieRequestHeaderFieldValue(URL firstParty, struct WebCore::SameSiteInfo sameSiteInfo, URL url, Optional<WebCore::FrameIdentifier> frameID, Optional<WebCore::PageIdentifier> pageID, enum:bool WebCore::IncludeSecureCookies includeSecureCookies, enum:bool WebCore::ShouldAskITP shouldAskITP) -> (String cookieString, bool didAccessSecureCookies) Synchronous
GetRawCookies(URL firstParty, struct WebCore::SameSiteInfo sameSiteInfo, URL url, Optional<WebCore::FrameIdentifier> frameID, Optional<WebCore::PageIdentifier> pageID, enum:bool WebCore::ShouldAskITP shouldAskITP) -> (Vector<WebCore::Cookie> cookies) Synchronous
+ SetRawCookie(struct WebCore::Cookie cookie)
DeleteCookie(URL url, String cookieName)
DomCookiesForHost(String host, bool subscribeToCookieChangeNotifications) -> (Vector<WebCore::Cookie> cookies) Synchronous
#if HAVE(COOKIE_CHANGE_LISTENER_API)
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp (259172 => 259173)
--- trunk/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp 2020-03-29 03:07:52 UTC (rev 259173)
@@ -228,6 +228,11 @@
return true;
}
+void WebCookieJar::setRawCookie(const WebCore::Document& document, const Cookie& cookie)
+{
+ WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::SetRawCookie(cookie), 0);
+}
+
void WebCookieJar::deleteCookie(const WebCore::Document& document, const URL& url, const String& cookieName)
{
WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::DeleteCookie(url, cookieName), 0);
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebCookieJar.h (259172 => 259173)
--- trunk/Source/WebKit/WebProcess/WebPage/WebCookieJar.h 2020-03-29 03:02:59 UTC (rev 259172)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebCookieJar.h 2020-03-29 03:07:52 UTC (rev 259173)
@@ -45,6 +45,7 @@
bool cookiesEnabled(const WebCore::Document&) const final;
std::pair<String, WebCore::SecureCookiesAccessed> cookieRequestHeaderFieldValue(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, Optional<WebCore::FrameIdentifier>, Optional<WebCore::PageIdentifier>, WebCore::IncludeSecureCookies) const final;
bool getRawCookies(const WebCore::Document&, const URL&, Vector<WebCore::Cookie>&) const final;
+ void setRawCookie(const WebCore::Document&, const WebCore::Cookie&) final;
void deleteCookie(const WebCore::Document&, const URL&, const String& cookieName) final;
void cookiesAdded(const String& host, const Vector<WebCore::Cookie>&);