Source/JavaScriptCore/ChangeLog

 12020-03-20 Devin Rousso <drousso@apple.com>
 2
 3 Web Inspector: support editing cookie key/values from inspector
 4 https://bugs.webkit.org/show_bug.cgi?id=31157
 5 <rdar://problem/19281523>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 * inspector/protocol/Page.json:
 10 Add a `session` parameter to `Page.Cookie` type and a new `Page.setCookie` command.
 11 Remove the `size` parameter from `Page.Cookie` as this can be calculated in the frontend.
 12
1132020-03-20 Ross Kirsling <ross.kirsling@sony.com>
214
315 hasObservableSideEffectsForRegExpSplit doesn't check for @@match override

Source/WebCore/ChangeLog

 12020-03-20 Devin Rousso <drousso@apple.com>
 2
 3 Web Inspector: support editing cookie key/values from inspector
 4 https://bugs.webkit.org/show_bug.cgi?id=31157
 5 <rdar://problem/19281523>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 Test: http/tests/inspector/page/setCookie.html
 10
 11 * inspector/agents/InspectorPageAgent.h:
 12 * inspector/agents/InspectorPageAgent.cpp:
 13 (WebCore::buildObjectForCookie):
 14 (WebCore::parseCookieObject): Added.
 15 (WebCore::InspectorPageAgent::setCookie): Added.
 16
 17 * loader/CookieJar.h:
 18 * loader/CookieJar.cpp:
 19 (WebCore::CookieJar::setRawCookie): Added.
 20
1212020-03-21 Philippe Normand <pnormand@igalia.com>
222
323 Make the MediaSample::toJSONString method generic

Source/WebInspectorUI/ChangeLog

 12020-03-20 Devin Rousso <drousso@apple.com>
 2
 3 Web Inspector: support editing cookie key/values from inspector
 4 https://bugs.webkit.org/show_bug.cgi?id=31157
 5 <rdar://problem/19281523>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 * UserInterface/Models/Cookie.js:
 10 (WI.Cookie):
 11 (WI.Cookie.fromPayload):
 12 (WI.Cookie.parseSetCookieResponseHeader):
 13 (WI.Cookie.prototype.get session): Added.
 14 (WI.Cookie.prototype.expirationDate):
 15 (WI.Cookie.prototype.equals): Added.
 16 (WI.Cookie.prototype.toProtocol): Added.
 17 Add `session` value in addition to the existing `expires` value. Create helper methods for
 18 comparing `WI.Cookie` objects and for using the `WI.Cookie` as a `Page.Cookie` type when
 19 invoking protocol commands (right now just `Page.setCookie`).
 20
 21 * UserInterface/Views/CookieStorageContentView.js:
 22 (WI.CookieStorageContentView):
 23 (WI.CookieStorageContentView.prototype.get navigationItems):
 24 (WI.CookieStorageContentView.prototype.tableCellContextMenuClicked):
 25 (WI.CookieStorageContentView.prototype.willDismissPopover): Added.
 26 (WI.CookieStorageContentView.prototype.async _willDismissCookiePopover): Added.
 27 (WI.CookieStorageContentView.prototype._handleSetCookieButtonClick): Added.
 28 (WI.CookieStorageContentView.prototype._reloadCookies):
 29 (WI.CookieStorageContentView.prototype._formatCookiePropertyForColumn):
 30 Add a + navigation item that shows a popover for creating a new cookie. When contextmenu
 31 clicking on a table row, add an "Edit" item that shows a popover for creating a new cookie
 32 with the values from the existing cookie, which will "replace" (delete and set) the existing
 33 cookie upon being dismissed.
 34
 35 * UserInterface/Views/ResourceCookiesContentView.js:
 36 (WI.ResourceCookiesContentView.prototype.tablePopulateCell):
 37 If only use the `expires` value if `session` is not set.
 38
 39 * UserInterface/Views/CookiePopover.js: Added.
 40 (WI.CookiePopover):
 41 (WI.CookiePopover.prototype.get serializedData):
 42 (WI.CookiePopover.prototype.show.createRow):
 43 (WI.CookiePopover.prototype.show.createInputRow):
 44 (WI.CookiePopover.prototype.show):
 45 (WI.CookiePopover.prototype._presentOverTargetElement):
 46 (WI.CookiePopover.prototype._defaultExpires):
 47 (WI.CookiePopover.prototype._parseExpires):
 48 (WI.CookiePopover.prototype._handleInputKeyDown):
 49 * UserInterface/Views/CookiePopover.css: Added.
 50 (.popover .cookie-popover-content):
 51 (.popover .cookie-popover-content > table):
 52 (.popover .cookie-popover-content > table > tr > th):
 53 (.popover .cookie-popover-content > table > tr > td):
 54 (.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"])):
 55 (.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"]).invalid):
 56 (@media (prefers-color-scheme: dark) .popover .cookie-popover-content > table > tr > th):
 57 Show an `<input>` (or `<select>`) for each configuration option when creating a cookie.
 58 Hide the `<input>` for `expires` if the `<input type="checkbox">` for `session` is checked.
 59 Indicate when the value in the `<input>` for `expires` is not a valid date.
 60
 61 * UserInterface/Main.html:
 62 * Localizations/en.lproj/localizedStrings.js:
 63
1642020-03-21 Devin Rousso <drousso@apple.com>
265
366 Web Inspector: REGRESSION(r257380, r257759): focusing the inspected page when docked dims most of the interface

Source/WebKit/ChangeLog

 12020-03-20 Devin Rousso <drousso@apple.com>
 2
 3 Web Inspector: support editing cookie key/values from inspector
 4 https://bugs.webkit.org/show_bug.cgi?id=31157
 5 <rdar://problem/19281523>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 * WebProcess/WebPage/WebCookieJar.h:
 10 * WebProcess/WebPage/WebCookieJar.cpp:
 11 (WebKit::WebCookieJar::setRawCookie):
 12
 13 * NetworkProcess/NetworkConnectionToWebProcess.messages.in:
 14 * NetworkProcess/NetworkConnectionToWebProcess.h:
 15 * NetworkProcess/NetworkConnectionToWebProcess.cpp:
 16 (WebKit::NetworkConnectionToWebProcess::setRawCookie): Added.
 17
1182020-03-20 Wenson Hsieh <wenson_hsieh@apple.com>
219
320 [iPadOS] Yahoo! search results are sometimes zoomed in a little

Source/JavaScriptCore/inspector/protocol/Page.json

104104 { "name": "domain", "type": "string", "description": "Cookie domain." },
105105 { "name": "path", "type": "string", "description": "Cookie path." },
106106 { "name": "expires", "type": "number", "description": "Cookie expires." },
107  { "name": "size", "type": "integer", "description": "Cookie size." },
 107 { "name": "session", "type": "boolean", "description": "True in case of session cookie." },
108108 { "name": "httpOnly", "type": "boolean", "description": "True if cookie is http-only." },
109109 { "name": "secure", "type": "boolean", "description": "True if cookie is secure." },
110  { "name": "session", "type": "boolean", "description": "True in case of session cookie." },
111110 { "name": "sameSite", "$ref": "CookieSameSitePolicy", "description": "Cookie Same-Site policy." }
112111 ]
113112 }

158157 { "name": "cookies", "type": "array", "items": { "$ref": "Cookie"}, "description": "Array of cookie objects." }
159158 ]
160159 },
 160 {
 161 "name": "setCookie",
 162 "description": "Sets a new browser cookie with the given name, domain, and path.",
 163 "parameters": [
 164 { "name": "cookie", "$ref": "Cookie" }
 165 ]
 166 },
161167 {
162168 "name": "deleteCookie",
163  "description": "Deletes browser cookie with given name, domain and path.",
 169 "description": "Deletes browser cookie with given name, domain, and path.",
164170 "parameters": [
165171 { "name": "cookieName", "type": "string", "description": "Name of the cookie to remove." },
166172 { "name": "url", "type": "string", "description": "URL to match cookie domain and path." }

Source/WebCore/inspector/agents/InspectorPageAgent.cpp

@@static Ref<Inspector::Protocol::Page::Cookie> buildObjectForCookie(const Cookie&
489489 .setDomain(cookie.domain)
490490 .setPath(cookie.path)
491491 .setExpires(cookie.expires.valueOr(0))
492  .setSize((cookie.name.length() + cookie.value.length()))
 492 .setSession(cookie.session)
493493 .setHttpOnly(cookie.httpOnly)
494494 .setSecure(cookie.secure)
495  .setSession(cookie.session)
496495 .setSameSite(cookieSameSitePolicyJSON(cookie.sameSite))
497496 .release();
498497}

@@void InspectorPageAgent::getCookies(ErrorString&, RefPtr<JSON::ArrayOf<Inspector
560559 cookies = JSON::ArrayOf<Inspector::Protocol::Page::Cookie>::create();
561560}
562561
 562static Optional<Cookie> parseCookieObject(ErrorString& errorString, const JSON::Object& cookieObject)
 563{
 564 Cookie cookie;
 565
 566 if (!cookieObject.getString("name"_s, cookie.name)) {
 567 errorString = "Invalid value for key name in given cookie";
 568 return WTF::nullopt;
 569 }
 570
 571 if (!cookieObject.getString("value"_s, cookie.value)) {
 572 errorString = "Invalid value for key value in given cookie";
 573 return WTF::nullopt;
 574 }
 575
 576 if (!cookieObject.getString("domain"_s, cookie.domain)) {
 577 errorString = "Invalid value for key domain in given cookie";
 578 return WTF::nullopt;
 579 }
 580
 581 if (!cookieObject.getString("path"_s, cookie.path)) {
 582 errorString = "Invalid value for key path in given cookie";
 583 return WTF::nullopt;
 584 }
 585
 586 if (!cookieObject.getBoolean("httpOnly"_s, cookie.httpOnly)) {
 587 errorString = "Invalid value for key httpOnly in given cookie";
 588 return WTF::nullopt;
 589 }
 590
 591 if (!cookieObject.getBoolean("secure"_s, cookie.secure)) {
 592 errorString = "Invalid value for key secure in given cookie";
 593 return WTF::nullopt;
 594 }
 595
 596 bool validSession = cookieObject.getBoolean("session"_s, cookie.session);
 597 cookie.expires = cookieObject.getNumber<double>("expires"_s);
 598 if (!validSession && !cookie.expires) {
 599 errorString = "Invalid value for key expires in given cookie";
 600 return WTF::nullopt;
 601 }
 602
 603 String sameSiteString;
 604 if (!cookieObject.getString("sameSite"_s, sameSiteString)) {
 605 errorString = "Invalid value for key sameSite in given cookie";
 606 return WTF::nullopt;
 607 }
 608
 609 auto sameSite = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::Page::CookieSameSitePolicy>(sameSiteString);
 610 if (!sameSite) {
 611 errorString = "Invalid value for key sameSite in given cookie";
 612 return WTF::nullopt;
 613 }
 614
 615 switch (sameSite.value()) {
 616 case Inspector::Protocol::Page::CookieSameSitePolicy::None:
 617 cookie.sameSite = Cookie::SameSitePolicy::None;
 618
 619 break;
 620 case Inspector::Protocol::Page::CookieSameSitePolicy::Lax:
 621 cookie.sameSite = Cookie::SameSitePolicy::Lax;
 622
 623 break;
 624 case Inspector::Protocol::Page::CookieSameSitePolicy::Strict:
 625 cookie.sameSite = Cookie::SameSitePolicy::Strict;
 626 break;
 627 }
 628
 629 return cookie;
 630}
 631
 632void InspectorPageAgent::setCookie(ErrorString& errorString, const JSON::Object& cookieObject)
 633{
 634 auto cookie = parseCookieObject(errorString, cookieObject);
 635 if (!cookie)
 636 return;
 637
 638 for (auto* frame = &m_inspectedPage.mainFrame(); frame; frame = frame->tree().traverseNext()) {
 639 if (auto* document = frame->document()) {
 640 if (auto* page = document->page())
 641 page->cookieJar().setRawCookie(*document, cookie.value());
 642 }
 643 }
 644}
 645
563646void InspectorPageAgent::deleteCookie(ErrorString&, const String& cookieName, const String& url)
564647{
565648 URL parsedURL({ }, url);

Source/WebCore/inspector/agents/InspectorPageAgent.h

@@public:
101101 void overrideUserAgent(ErrorString&, const String* value) override;
102102 void overrideSetting(ErrorString&, const String& setting, const bool* value) override;
103103 void getCookies(ErrorString&, RefPtr<JSON::ArrayOf<Inspector::Protocol::Page::Cookie>>& cookies) override;
 104 void setCookie(ErrorString&, const JSON::Object& cookieObject) override;
104105 void deleteCookie(ErrorString&, const String& cookieName, const String& url) override;
105106 void getResourceTree(ErrorString&, RefPtr<Inspector::Protocol::Page::FrameResourceTree>&) override;
106107 void getResourceContent(ErrorString&, const String& frameId, const String& url, String* content, bool* base64Encoded) override;

Source/WebCore/loader/CookieJar.cpp

@@bool CookieJar::getRawCookies(const Document& document, const URL& url, Vector<C
171171 return false;
172172}
173173
 174void CookieJar::setRawCookie(const Document&, const Cookie& cookie)
 175{
 176 if (auto* session = m_storageSessionProvider->storageSession())
 177 session->setCookie(cookie);
 178 else
 179 ASSERT_NOT_REACHED();
 180}
 181
174182void CookieJar::deleteCookie(const Document&, const URL& url, const String& cookieName)
175183{
176184 if (auto* session = m_storageSessionProvider->storageSession())

Source/WebCore/loader/CookieJar.h

@@public:
5858 virtual bool cookiesEnabled(const Document&) const;
5959 virtual std::pair<String, SecureCookiesAccessed> cookieRequestHeaderFieldValue(const URL& firstParty, const SameSiteInfo&, const URL&, Optional<FrameIdentifier>, Optional<PageIdentifier>, IncludeSecureCookies) const;
6060 virtual bool getRawCookies(const Document&, const URL&, Vector<Cookie>&) const;
 61 virtual void setRawCookie(const Document&, const Cookie&);
6162 virtual void deleteCookie(const Document&, const URL&, const String& cookieName);
6263
6364 // Cookie Cache.

Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

@@localizedStrings["Add %s Rule"] = "Add %s Rule";
9999localizedStrings["Add Action"] = "Add Action";
100100localizedStrings["Add Breakpoint"] = "Add Breakpoint";
101101localizedStrings["Add Breakpoints"] = "Add Breakpoints";
 102localizedStrings["Add Cookie"] = "Add Cookie";
102103localizedStrings["Add Header"] = "Add Header";
103104localizedStrings["Add New"] = "Add New";
104105localizedStrings["Add New Class"] = "Add New Class";

Source/WebInspectorUI/UserInterface/Main.html

7979 <link rel="stylesheet" href="Views/ContentBrowserTabContentView.css">
8080 <link rel="stylesheet" href="Views/ContentView.css">
8181 <link rel="stylesheet" href="Views/ContentViewContainer.css">
 82 <link rel="stylesheet" href="Views/CookiePopover.css">
8283 <link rel="stylesheet" href="Views/CookieStorageContentView.css">
8384 <link rel="stylesheet" href="Views/DOMBreakpointTreeElement.css">
8485 <link rel="stylesheet" href="Views/DOMEventsBreakdownView.css">

642643 <script src="Views/ContentViewContainer.js"></script>
643644 <script src="Views/ContextMenu.js"></script>
644645 <script src="Views/ContextMenuUtilities.js"></script>
 646 <script src="Views/CookiePopover.js"></script>
645647 <script src="Views/CookieStorageContentView.js"></script>
646648 <script src="Views/CookieStorageTreeElement.js"></script>
647649 <script src="Views/DOMBreakpointTreeElement.js"></script>

Source/WebInspectorUI/UserInterface/Models/Cookie.js

2525
2626WI.Cookie = class Cookie
2727{
28  constructor(type, name, value, {header, expires, maxAge, path, domain, secure, httpOnly, sameSite, size} = {})
 28 constructor(type, name, value, {header, expires, session, maxAge, path, domain, secure, httpOnly, sameSite} = {})
2929 {
3030 console.assert(Object.values(WI.Cookie.Type).includes(type));
3131 console.assert(typeof name === "string");
3232 console.assert(typeof value === "string");
3333 console.assert(!header || typeof header === "string");
3434 console.assert(!expires || expires instanceof Date);
 35 console.assert(!session || typeof session === "boolean");
3536 console.assert(!maxAge || typeof maxAge === "number");
3637 console.assert(!path || typeof path === "string");
3738 console.assert(!domain || typeof domain === "string");
3839 console.assert(!secure || typeof secure === "boolean");
3940 console.assert(!httpOnly || typeof httpOnly === "boolean");
4041 console.assert(!sameSite || Object.values(WI.Cookie.SameSiteType).includes(sameSite));
41  console.assert(!size || typeof size === "number");
4242
4343 this._type = type;
4444 this._name = name;
4545 this._value = value;
46  this._size = size || this._name.length + this._value.length;
 46 this._size = this._name.length + this._value.length;
4747
4848 if (this._type === WI.Cookie.Type.Response) {
4949 this._header = header || "";
50  this._expires = expires || null;
 50 this._expires = (!session && expires) || null;
 51 this._session = session || false;
5152 this._maxAge = maxAge || null;
5253 this._path = path || null;
5354 this._domain = domain || null;

@@WI.Cookie = class Cookie
6263 static fromPayload(payload)
6364 {
6465 let {name, value, ...options} = payload;
65  options.expires = options.expires ? new Date(options.expires) : null;
 66 options.expires = options.expires ? new Date(options.expires.maxDecimals(-3)) : null;
6667
6768 return new WI.Cookie(WI.Cookie.Type.Response, name, value, options);
6869 }

@@WI.Cookie = class Cookie
148149
149150 let {name, value} = nameValueMatch.groups;
150151 let expires = null;
 152 let session = false;
151153 let maxAge = null;
152154 let path = null;
153155 let domain = null;

@@WI.Cookie = class Cookie
212214 }
213215 }
214216
215  return new WI.Cookie(WI.Cookie.Type.Response, name, value, {header, expires, maxAge, path, domain, secure, httpOnly, sameSite});
 217 if (!expires)
 218 session = true;
 219
 220 return new WI.Cookie(WI.Cookie.Type.Response, name, value, {header, expires, session, maxAge, path, domain, secure, httpOnly, sameSite});
216221 }
217222
218223 // Public

@@WI.Cookie = class Cookie
222227 get value() { return this._value; }
223228 get header() { return this._header; }
224229 get expires() { return this._expires; }
 230 get session() { return this._session; }
225231 get maxAge() { return this._maxAge; }
226232 get path() { return this._path; }
227233 get domain() { return this._domain; }

@@WI.Cookie = class Cookie
240246
241247 expirationDate(requestSentDate)
242248 {
 249 if (this._session)
 250 return null;
 251
243252 if (this._maxAge) {
244253 let startDate = requestSentDate || new Date;
245254 return new Date(startDate.getTime() + (this._maxAge * 1000));

@@WI.Cookie = class Cookie
247256
248257 return this._expires;
249258 }
 259
 260 equals(other)
 261 {
 262 return this._type === other.type
 263 && this._name === other.name
 264 && this._value === other.value
 265 && this._header === other.header
 266 && this._expires?.getTime() === other.expires?.getTime()
 267 && this._session === other.session
 268 && this._maxAge === other.maxAge
 269 && this._path === other.path
 270 && this._domain === other.domain
 271 && this._secure === other.secure
 272 && this._httpOnly === other.httpOnly
 273 && this._sameSite === other.sameSite;
 274 }
 275
 276 toProtocol()
 277 {
 278 if (typeof this._name !== "string")
 279 return null;
 280
 281 if (typeof this._value !== "string")
 282 return null;
 283
 284 if (typeof this._domain !== "string")
 285 return null;
 286
 287 if (typeof this._path !== "string")
 288 return null;
 289
 290 if (!this._session && !this._expires)
 291 return null;
 292
 293 if (!Object.values(WI.Cookie.SameSiteType).includes(this._sameSite))
 294 return null;
 295
 296 let json = {
 297 name: this._name,
 298 value: this._value,
 299 domain: this._domain,
 300 path: this._path,
 301 expires: this._expires?.getTime(),
 302 session: this._session,
 303 httpOnly: !!this._httpOnly,
 304 secure: !!this._secure,
 305 sameSite: this._sameSite,
 306 };
 307
 308 return json;
 309 }
250310};
251311
252312WI.Cookie.Type = {

Source/WebInspectorUI/UserInterface/Views/CookiePopover.css

 1/*
 2 * Copyright (C) 2020 Apple Inc. All rights reserved.
 3 *
 4 * Redistribution and use in source and binary forms, with or without
 5 * modification, are permitted provided that the following conditions
 6 * are met:
 7 * 1. Redistributions of source code must retain the above copyright
 8 * notice, this list of conditions and the following disclaimer.
 9 * 2. Redistributions in binary form must reproduce the above copyright
 10 * notice, this list of conditions and the following disclaimer in the
 11 * documentation and/or other materials provided with the distribution.
 12 *
 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 23 * THE POSSIBILITY OF SUCH DAMAGE.
 24 */
 25
 26.popover .cookie-popover-content {
 27 max-width: 420px;
 28 padding: 4px 8px;
 29}
 30
 31.popover .cookie-popover-content > table {
 32 width: 100%;
 33}
 34
 35.popover .cookie-popover-content > table > tr > th {
 36 font-weight: bold;
 37 text-align: end;
 38 line-height: 23px;
 39 vertical-align: top;
 40 white-space: nowrap;
 41 color: hsl(0, 0%, 34%);
 42}
 43
 44.popover .cookie-popover-content > table > tr > td {
 45 -webkit-padding-start: 4px;
 46}
 47
 48/* FIXME: <https://webkit.org/b/209389> Web Inspector: use native datetime-local picker for changing `expires` value in cookie popover */
 49
 50.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"]) {
 51 width: 100%;
 52 padding: 3px 4px 2px;
 53 font-family: Menlo, monospace;
 54 background-color: var(--background-color-code);
 55 border: 1px solid var(--text-color-quaternary);
 56 -webkit-appearance: none;
 57}
 58
 59.popover .cookie-popover-content > table > tr > td > input:matches([type="text"], [type="datetime-local"]).invalid {
 60 color: var(--error-text-color);
 61}
 62
 63@media (prefers-color-scheme: dark) {
 64 .popover .cookie-popover-content > table > tr > th {
 65 color: var(--text-color-secondary);
 66 }
 67}

Source/WebInspectorUI/UserInterface/Views/CookiePopover.js

 1/*
 2 * Copyright (C) 2020 Apple Inc. All rights reserved.
 3 *
 4 * Redistribution and use in source and binary forms, with or without
 5 * modification, are permitted provided that the following conditions
 6 * are met:
 7 * 1. Redistributions of source code must retain the above copyright
 8 * notice, this list of conditions and the following disclaimer.
 9 * 2. Redistributions in binary form must reproduce the above copyright
 10 * notice, this list of conditions and the following disclaimer in the
 11 * documentation and/or other materials provided with the distribution.
 12 *
 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 23 * THE POSSIBILITY OF SUCH DAMAGE.
 24 */
 25
 26WI.CookiePopover = class CookiePopover extends WI.Popover
 27{
 28 constructor(delegate)
 29 {
 30 super(delegate);
 31
 32 this._nameInputElement = null;
 33 this._valueInputElement = null;
 34 this._domainInputElement = null;
 35 this._pathInputElement = null;
 36 this._sessionCheckboxElement = null;
 37 this._expiresInputElement = null;
 38 this._httpOnlyCheckboxElement = null;
 39 this._secureCheckboxElement = null;
 40 this._sameSiteSelectElement = null;
 41
 42 this._serializedDataWhenShown = null;
 43
 44 this.windowResizeHandler = this._presentOverTargetElement.bind(this);
 45 }
 46
 47 // Public
 48
 49 get serializedData()
 50 {
 51 if (!this._targetElement)
 52 return null;
 53
 54 let name = this._nameInputElement.value || this._nameInputElement.placeholder;
 55 if (!name)
 56 return null;
 57
 58 let value = this._valueInputElement.value || this._valueInputElement.placeholder;
 59 if (!value)
 60 return null;
 61
 62 let domain = this._domainInputElement.value || this._domainInputElement.placeholder;
 63 if (!domain)
 64 return null;
 65
 66 let path = this._pathInputElement.value || this._pathInputElement.placeholder;
 67 if (!path)
 68 return null;
 69
 70 let session = this._sessionCheckboxElement.checked;
 71 let expires = this._parseExpires();
 72 if (!session && isNaN(expires))
 73 return null;
 74
 75 // If a full URL is entered in the domain input, parse it to get just the domain.
 76 try {
 77 let url = new URL(domain);
 78 domain = url.hostname;
 79 } catch { }
 80
 81 if (!path.startsWith("/"))
 82 path = "/" + path;
 83
 84 let data = {
 85 name,
 86 value,
 87 domain,
 88 path,
 89 httpOnly: this._httpOnlyCheckboxElement.checked,
 90 secure: this._secureCheckboxElement.checked,
 91 sameSite: this._sameSiteSelectElement.value,
 92 };
 93
 94 if (session)
 95 data.session = true;
 96 else
 97 data.expires = expires;
 98
 99 if (JSON.stringify(data) === JSON.stringify(this._serializedDataWhenShown))
 100 return null;
 101
 102 return data;
 103 }
 104
 105 show(cookie, targetElement, preferredEdges)
 106 {
 107 console.assert(!cookie || cookie instanceof WI.Cookie, cookie);
 108 console.assert(targetElement instanceof Element, targetElement);
 109 console.assert(Array.isArray(preferredEdges), preferredEdges);
 110
 111 this._targetElement = targetElement;
 112 this._preferredEdges = preferredEdges;
 113
 114 let data = {};
 115 if (cookie) {
 116 data.name = cookie.name;
 117 data.value = cookie.value;
 118 data.domain = cookie.domain;
 119 data.path = cookie.path;
 120 data.expires = (cookie.expires || this._defaultExpires()).toLocaleString();
 121 data.session = cookie.session;
 122 data.httpOnly = cookie.httpOnly;
 123 data.secure = cookie.secure;
 124 data.sameSite = cookie.sameSite;
 125 } else {
 126 let urlComponents = WI.networkManager.mainFrame.mainResource.urlComponents;
 127 data.name = WI.unlocalizedString("name");
 128 data.value = WI.unlocalizedString("value");
 129 data.domain = urlComponents.host;
 130 data.path = urlComponents.path;
 131 data.expires = this._defaultExpires().toLocaleString();
 132 data.session = true;
 133 data.httpOnly = false;
 134 data.secure = false;
 135 data.sameSite = WI.Cookie.SameSiteType.None;
 136 }
 137
 138 let popoverContentElement = document.createElement("div");
 139 popoverContentElement.className = "cookie-popover-content";
 140
 141 let tableElement = popoverContentElement.appendChild(document.createElement("table"));
 142
 143 function createRow(id, label, editorElement) {
 144 id = `cookie-popover-${id}-editor`;
 145
 146 let rowElement = tableElement.appendChild(document.createElement("tr"));
 147
 148 let headerElement = rowElement.appendChild(document.createElement("th"));
 149
 150 let labelElement = headerElement.appendChild(document.createElement("label"));
 151 labelElement.setAttribute("for", id);
 152 labelElement.textContent = label;
 153
 154 let dataElement = rowElement.appendChild(document.createElement("td"));
 155
 156 editorElement.id = id;
 157 dataElement.appendChild(editorElement);
 158
 159 return {rowElement};
 160 }
 161
 162 let boundHandleInputKeyDown = this._handleInputKeyDown.bind(this);
 163
 164 function createInputRow(id, label, type, value) {
 165 let inputElement = document.createElement("input");
 166 inputElement.type = type;
 167
 168 if (type === "checkbox")
 169 inputElement.checked = value;
 170 else {
 171 if (cookie)
 172 inputElement.value = value;
 173 inputElement.placeholder = value;
 174 inputElement.addEventListener("keydown", boundHandleInputKeyDown);
 175 }
 176
 177 let rowElement = createRow(id, label, inputElement).rowElement;
 178
 179 return {inputElement, rowElement};
 180 }
 181
 182 this._nameInputElement = createInputRow("name", WI.UIString("Name"), "text", data.name).inputElement;
 183
 184 this._valueInputElement = createInputRow("value", WI.UIString("Value"), "text", data.value).inputElement;
 185
 186 this._domainInputElement = createInputRow("domain", WI.unlocalizedString("Domain"), "text", data.domain).inputElement;
 187
 188 this._pathInputElement = createInputRow("path", WI.unlocalizedString("Path"), "text", data.path).inputElement;
 189
 190 this._sessionCheckboxElement = createInputRow("session", WI.unlocalizedString("Session"), "checkbox", data.session).inputElement;
 191
 192 let expiresInputRow = createInputRow("expires", WI.unlocalizedString("Expires"), "datetime-local", data.expires);
 193 this._expiresInputElement = expiresInputRow.inputElement;
 194 this._expiresInputElement.addEventListener("input", (event) => {
 195 this._expiresInputElement.classList.toggle("invalid", isNaN(this._parseExpires()));
 196 });
 197
 198 this._httpOnlyCheckboxElement = createInputRow("http-only", WI.unlocalizedString("HttpOnly"), "checkbox", data.httpOnly).inputElement;
 199
 200 this._secureCheckboxElement = createInputRow("secure", WI.unlocalizedString("Secure"), "checkbox", data.secure).inputElement;
 201
 202 this._sameSiteSelectElement = document.createElement("select");
 203 for (let sameSiteType of Object.values(WI.Cookie.SameSiteType)) {
 204 let optionElement = this._sameSiteSelectElement.appendChild(document.createElement("option"));
 205 optionElement.textContent = sameSiteType;
 206 }
 207 createRow("same-site", WI.unlocalizedString("SameSite"), this._sameSiteSelectElement);
 208
 209 let toggleExpiresRow = () => {
 210 expiresInputRow.rowElement.hidden = this._sessionCheckboxElement.checked;
 211
 212 this.update();
 213 };
 214
 215 this._sessionCheckboxElement.addEventListener("change", (event) => {
 216 toggleExpiresRow();
 217 });
 218
 219 toggleExpiresRow();
 220
 221 this._serializedDataWhenShown = this.serializedData;
 222
 223 this.content = popoverContentElement;
 224 this._presentOverTargetElement();
 225 }
 226
 227 // Private
 228
 229 _presentOverTargetElement()
 230 {
 231 if (!this._targetElement)
 232 return;
 233
 234 let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect());
 235 this.present(targetFrame.pad(2), this._preferredEdges);
 236 }
 237
 238 _defaultExpires()
 239 {
 240 return new Date(Date.now() + (1000 * 60 * 60 * 24)); // one day in the future
 241 }
 242
 243 _parseExpires()
 244 {
 245 let timestamp = Date.parse(this._expiresInputElement.value || this._expiresInputElement.placeholder);
 246 if (timestamp < Date.now())
 247 return NaN;
 248 return timestamp;
 249 }
 250
 251 _handleInputKeyDown(event)
 252 {
 253 if (event.key === "Enter" || event.key === "Esc")
 254 this.dismiss();
 255 }
 256};

Source/WebInspectorUI/UserInterface/Views/CookieStorageContentView.js

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
3535 this._sortComparator = null;
3636 this._table = null;
3737
 38 if (InspectorBackend.hasCommand("Page.setCookie")) {
 39 this._setCookieButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-set-cookie", WI.UIString("Add Cookie"), "Images/Plus15.svg", 15, 15);
 40 this._setCookieButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleSetCookieButtonClick, this);
 41 }
 42
3843 this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-refresh", WI.UIString("Refresh"), "Images/ReloadFull.svg", 13, 13);
3944 this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._refreshButtonClicked, this);
4045 }

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
4348
4449 get navigationItems()
4550 {
46  return [this._refreshButtonNavigationItem];
 51 let navigationItems = [];
 52 if (this._setCookieButtonNavigationItem)
 53 navigationItems.push(this._setCookieButtonNavigationItem);
 54 navigationItems.push(this._refreshButtonNavigationItem);
 55 return navigationItems;
4756 }
4857
4958 saveToCookie(cookie)

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
111120 let contextMenu = WI.ContextMenu.createFromEvent(event);
112121
113122 contextMenu.appendSeparator();
 123
 124 if (InspectorBackend.hasCommand("Page.setCookie")) {
 125 contextMenu.appendItem(WI.UIString("Edit"), () => {
 126 console.assert(!this._editingCookie);
 127 this._editingCookie = this._cookies[rowIndex];
 128
 129 let popover = new WI.CookiePopover(this);
 130 popover.show(this._editingCookie, this._table.cellForRowAndColumn(rowIndex, this._table.columns[0]), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_X]);
 131 });
 132 }
 133
114134 contextMenu.appendItem(WI.UIString("Copy"), () => {
115135 let rowIndexes;
116136 if (table.isRowSelected(rowIndex))

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
128148 else
129149 table.removeRow(rowIndex);
130150 });
 151
131152 contextMenu.appendSeparator();
132153 }
133154

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
157178 return cell;
158179 }
159180
 181 // Popover delegate
 182
 183 willDismissPopover(popover)
 184 {
 185 if (popover instanceof WI.CookiePopover) {
 186 this._willDismissCookiePopover(popover);
 187 return;
 188 }
 189
 190 console.assert();
 191 }
 192
160193 // Protected
161194
162195 initialLayout()

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
315348 this._sortComparator = (a, b) => reverseFactor * comparator(a, b);
316349 }
317350
 351 async _willDismissCookiePopover(popover)
 352 {
 353 let editingCookie = this._editingCookie;
 354 this._editingCookie = null;
 355
 356 let serializedData = popover.serializedData;
 357 if (!serializedData) {
 358 InspectorFrontendHost.beep();
 359 return;
 360 }
 361
 362 let cookieToSet = WI.Cookie.fromPayload(serializedData);
 363
 364 let cookieProtocolPayload = cookieToSet.toProtocol();
 365 if (!cookieProtocolPayload) {
 366 InspectorFrontendHost.beep();
 367 return;
 368 }
 369
 370 let target = WI.assumingMainTarget();
 371
 372 let promises = [];
 373 if (editingCookie)
 374 promises.push(target.PageAgent.deleteCookie(editingCookie.name, editingCookie.url));
 375 promises.push(target.PageAgent.setCookie(cookieProtocolPayload));
 376 promises.push(this._reloadCookies());
 377 await Promise.all(promises);
 378
 379 let index = this._cookies.findIndex((existingCookie) => cookieToSet.equals(existingCookie));
 380 if (index >= 0)
 381 this._table.selectRow(index);
 382 }
 383
 384 _handleSetCookieButtonClick(event)
 385 {
 386 let popover = new WI.CookiePopover(this);
 387 popover.show(null, this._setCookieButtonNavigationItem.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_X]);
 388 }
 389
318390 _refreshButtonClicked(event)
319391 {
320392 this._reloadCookies();

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
323395 _reloadCookies()
324396 {
325397 let target = WI.assumingMainTarget();
326  target.PageAgent.getCookies().then((payload) => {
 398 return target.PageAgent.getCookies().then((payload) => {
327399 this._cookies = this._filterCookies(payload.cookies.map(WI.Cookie.fromPayload));
328400 this._updateSort();
329401 this._table.reloadData();

@@WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentV
384456 case "path":
385457 return cookie.path || missingValue;
386458 case "expires":
387  return cookie.expires ? new Date(cookie.expires).toLocaleString() : WI.UIString("Session");
 459 return (!cookie.session && cookie.expires) ? cookie.expires.toLocaleString() : WI.UIString("Session");
388460 case "size":
389461 return Number.bytesToString(cookie.size);
390462 case "secure":

Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js

@@WI.ResourceCookiesContentView = class ResourceCookiesContentView extends WI.Cont
9898 cell.textContent = cookie.path || emDash;
9999 break;
100100 case "expires":
101  cell.textContent = cookie.expires ? cookie.expires.toLocaleString() : WI.UIString("Session");
 101 cell.textContent = (!cookie.session && cookie.expires) ? cookie.expires.toLocaleString() : WI.UIString("Session");
102102 break;
103103 case "maxAge":
104104 cell.textContent = cookie.maxAge || emDash;

Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp

@@void NetworkConnectionToWebProcess::getRawCookies(const URL& firstParty, const S
654654 completionHandler(WTFMove(result));
655655}
656656
 657void NetworkConnectionToWebProcess::setRawCookie(const WebCore::Cookie& cookie)
 658{
 659 auto* networkStorageSession = storageSession();
 660 if (!networkStorageSession)
 661 return;
 662
 663 networkStorageSession->setCookie(cookie);
 664}
 665
657666void NetworkConnectionToWebProcess::deleteCookie(const URL& url, const String& cookieName)
658667{
659668 auto* networkStorageSession = storageSession();

Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h

@@private:
209209 void setCookiesFromDOM(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, WebCore::FrameIdentifier, WebCore::PageIdentifier, WebCore::ShouldAskITP, const String&);
210210 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)>&&);
211211 void getRawCookies(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, Optional<WebCore::FrameIdentifier>, Optional<WebCore::PageIdentifier>, WebCore::ShouldAskITP, CompletionHandler<void(Vector<WebCore::Cookie>&&)>&&);
 212 void setRawCookie(const WebCore::Cookie&);
212213 void deleteCookie(const URL&, const String& cookieName);
213214
214215 void registerFileBlobURL(const URL&, const String& path, SandboxExtension::Handle&&, const String& contentType);

Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in

@@messages -> NetworkConnectionToWebProcess LegacyReceiver {
3939 SetCookiesFromDOM(URL firstParty, struct WebCore::SameSiteInfo sameSiteInfo, URL url, WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID, enum:bool WebCore::ShouldAskITP shouldAskITP, String cookieString)
4040 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
4141 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
 42 SetRawCookie(struct WebCore::Cookie cookie)
4243 DeleteCookie(URL url, String cookieName)
4344 DomCookiesForHost(String host, bool subscribeToCookieChangeNotifications) -> (Vector<WebCore::Cookie> cookies) Synchronous
4445#if HAVE(COOKIE_CHANGE_LISTENER_API)

Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp

@@bool WebCookieJar::getRawCookies(const WebCore::Document& document, const URL& u
228228 return true;
229229}
230230
 231void WebCookieJar::setRawCookie(const WebCore::Document& document, const Cookie& cookie)
 232{
 233 WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::SetRawCookie(cookie), 0);
 234}
 235
231236void WebCookieJar::deleteCookie(const WebCore::Document& document, const URL& url, const String& cookieName)
232237{
233238 WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::DeleteCookie(url, cookieName), 0);

Source/WebKit/WebProcess/WebPage/WebCookieJar.h

@@public:
4545 bool cookiesEnabled(const WebCore::Document&) const final;
4646 std::pair<String, WebCore::SecureCookiesAccessed> cookieRequestHeaderFieldValue(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, Optional<WebCore::FrameIdentifier>, Optional<WebCore::PageIdentifier>, WebCore::IncludeSecureCookies) const final;
4747 bool getRawCookies(const WebCore::Document&, const URL&, Vector<WebCore::Cookie>&) const final;
 48 void setRawCookie(const WebCore::Document&, const WebCore::Cookie&) final;
4849 void deleteCookie(const WebCore::Document&, const URL&, const String& cookieName) final;
4950
5051 void cookiesAdded(const String& host, const Vector<WebCore::Cookie>&);

LayoutTests/ChangeLog

 12020-03-20 Devin Rousso <drousso@apple.com>
 2
 3 Web Inspector: support editing cookie key/values from inspector
 4 https://bugs.webkit.org/show_bug.cgi?id=31157
 5 <rdar://problem/19281523>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 * http/tests/inspector/page/setCookie.html: Added.
 10 * http/tests/inspector/page/setCookie-expected.txt: Added.
 11
 12 * inspector/unit-tests/number-utilities.html:
 13 * inspector/unit-tests/number-utilities-expected.txt:
 14 Drive-by: add tests for `Number.prototype.maxDecimals`.
 15
1162020-03-21 Simon Fraser <simon.fraser@apple.com>
217
318 LayoutTest fast/scrolling/scroll-container-horizontally.html frequently times out & fails

LayoutTests/http/tests/inspector/page/setCookie-expected.txt

 1Test for the Page.setCookie.
 2
 3
 4== Running test suite: Page.setCookie
 5-- Running test case: Page.setCookie.Valid
 6PASS: Should have been able to set all cookies.
 7
 8-- Running test case: Page.setCookie.Invalid
 9Setting cookie {} ...
 10PASS: Should produce an exception.
 11Error: Invalid value for key name in given cookie
 12
 13Setting cookie {"name":null} ...
 14PASS: Should produce an exception.
 15Error: Invalid value for key name in given cookie
 16
 17Setting cookie {"name":-1} ...
 18PASS: Should produce an exception.
 19Error: Invalid value for key name in given cookie
 20
 21Setting cookie {"name":"name"} ...
 22PASS: Should produce an exception.
 23Error: Invalid value for key value in given cookie
 24
 25Setting cookie {"name":"name","value":null} ...
 26PASS: Should produce an exception.
 27Error: Invalid value for key value in given cookie
 28
 29Setting cookie {"name":"name","value":-1} ...
 30PASS: Should produce an exception.
 31Error: Invalid value for key value in given cookie
 32
 33Setting cookie {"name":"name","value":"value"} ...
 34PASS: Should produce an exception.
 35Error: Invalid value for key domain in given cookie
 36
 37Setting cookie {"name":"name","value":"value","domain":null} ...
 38PASS: Should produce an exception.
 39Error: Invalid value for key domain in given cookie
 40
 41Setting cookie {"name":"name","value":"value","domain":-1} ...
 42PASS: Should produce an exception.
 43Error: Invalid value for key domain in given cookie
 44
 45Setting cookie {"name":"name","value":"value","domain":"webkit.org"} ...
 46PASS: Should produce an exception.
 47Error: Invalid value for key path in given cookie
 48
 49Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":null} ...
 50PASS: Should produce an exception.
 51Error: Invalid value for key path in given cookie
 52
 53Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":-1} ...
 54PASS: Should produce an exception.
 55Error: Invalid value for key path in given cookie
 56
 57Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/"} ...
 58PASS: Should produce an exception.
 59Error: Invalid value for key httpOnly in given cookie
 60
 61Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":null} ...
 62PASS: Should produce an exception.
 63Error: Invalid value for key httpOnly in given cookie
 64
 65Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":"INVALID"} ...
 66PASS: Should produce an exception.
 67Error: Invalid value for key httpOnly in given cookie
 68
 69Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000} ...
 70PASS: Should produce an exception.
 71Error: Invalid value for key httpOnly in given cookie
 72
 73Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":null} ...
 74PASS: Should produce an exception.
 75Error: Invalid value for key httpOnly in given cookie
 76
 77Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":"INVALID"} ...
 78PASS: Should produce an exception.
 79Error: Invalid value for key httpOnly in given cookie
 80
 81Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true} ...
 82PASS: Should produce an exception.
 83Error: Invalid value for key httpOnly in given cookie
 84
 85Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":null} ...
 86PASS: Should produce an exception.
 87Error: Invalid value for key httpOnly in given cookie
 88
 89Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":"INVALID"} ...
 90PASS: Should produce an exception.
 91Error: Invalid value for key httpOnly in given cookie
 92
 93Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true} ...
 94PASS: Should produce an exception.
 95Error: Invalid value for key secure in given cookie
 96
 97Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":null} ...
 98PASS: Should produce an exception.
 99Error: Invalid value for key secure in given cookie
 100
 101Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":"INVALID"} ...
 102PASS: Should produce an exception.
 103Error: Invalid value for key secure in given cookie
 104
 105Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true} ...
 106PASS: Should produce an exception.
 107Error: Invalid value for key sameSite in given cookie
 108
 109Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true,"sameSite":null} ...
 110PASS: Should produce an exception.
 111Error: Invalid value for key sameSite in given cookie
 112
 113Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true,"sameSite":-1} ...
 114PASS: Should produce an exception.
 115Error: Invalid value for key sameSite in given cookie
 116
 117Setting cookie {"name":"name","value":"value","domain":"webkit.org","path":"/","expires":10000000000,"session":true,"httpOnly":true,"secure":true,"sameSite":"INVALID"} ...
 118PASS: Should produce an exception.
 119Error: Invalid value for key sameSite in given cookie
 120
 121

LayoutTests/http/tests/inspector/page/setCookie.html

 1<!DOCTYPE html>
 2<html>
 3<head>
 4<script src="../resources/inspector-test.js"></script>
 5<script>
 6if (window.testRunner)
 7 testRunner.setAlwaysAcceptCookies(true);
 8
 9function test()
 10{
 11 const millisecondsPerDay = 1000 * 60 * 60 * 24;
 12 let tomorrow = new Date(Date.now().maxDecimals(-3) + millisecondsPerDay);
 13
 14 let suite = InspectorTest.createAsyncSuite("Page.setCookie");
 15
 16 suite.addTestCase({
 17 name: "Page.setCookie.Valid",
 18 description: "Check that setting a valid cookies works.",
 19 async test() {
 20 let cookiesToSet = [
 21 {
 22 expires: tomorrow,
 23 session: false,
 24 path: "/",
 25 domain: "127.0.0.1",
 26 secure: false,
 27 httpOnly: false,
 28 sameSite: WI.Cookie.SameSiteType.Lax,
 29 },
 30 {
 31 expires: tomorrow,
 32 session: true,
 33 path: "/inspector/",
 34 domain: "127.0.0.1",
 35 secure: true,
 36 httpOnly: true,
 37 sameSite: WI.Cookie.SameSiteType.Strict,
 38 },
 39 ].map((options, i) => new WI.Cookie(WI.Cookie.Type.Response, "TestCookieName" + i, "TestCookieValue" + i, options));
 40
 41 for (let cookieToSet of cookiesToSet)
 42 await PageAgent.setCookie(cookieToSet.toProtocol());
 43
 44 let getCookiesResult = await PageAgent.getCookies();
 45 let setCookies = getCookiesResult.cookies.map(WI.Cookie.fromPayload);
 46
 47 let found = true;
 48 for (let cookieToSet of cookiesToSet) {
 49 if (!setCookies.find((setCookie) => cookieToSet.equals(setCookie))) {
 50 found = false;
 51 InspectorTest.fail("Missing cookie:");
 52 InspectorTest.json(cookieToSet);
 53 }
 54 }
 55 InspectorTest.expectTrue(found, "Should have been able to set all cookies.");
 56 },
 57 });
 58
 59 suite.addTestCase({
 60 name: "Page.setCookie.Invalid",
 61 description: "Check that the PageAgent correctly parses the given cookie JSON object.",
 62 async test() {
 63 const invalidValue = null;
 64 const invalidString = -1;
 65 const invalidNumber = "INVALID";
 66 const invalidBoolean = invalidNumber;
 67
 68 const cookies = [
 69 {},
 70
 71 {name: invalidValue},
 72 {name: invalidString},
 73 {name: "name"},
 74
 75 {name: "name", value: invalidValue},
 76 {name: "name", value: invalidString},
 77 {name: "name", value: "value"},
 78
 79 {name: "name", value: "value", domain: invalidValue},
 80 {name: "name", value: "value", domain: invalidString},
 81 {name: "name", value: "value", domain: "webkit.org"},
 82
 83 {name: "name", value: "value", domain: "webkit.org", path: invalidValue},
 84 {name: "name", value: "value", domain: "webkit.org", path: invalidString},
 85 {name: "name", value: "value", domain: "webkit.org", path: "/"},
 86
 87 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: invalidValue},
 88 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: invalidNumber},
 89 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000},
 90
 91 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: invalidValue},
 92 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: invalidBoolean},
 93 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true},
 94
 95 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: invalidValue},
 96 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: invalidBoolean},
 97 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true},
 98
 99 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: invalidValue},
 100 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: invalidBoolean},
 101 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true},
 102
 103 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true, sameSite: invalidValue},
 104 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true, sameSite: invalidString},
 105 {name: "name", value: "value", domain: "webkit.org", path: "/", expires: 10000000000, session: true, httpOnly: true, secure: true, sameSite: "INVALID"},
 106 ];
 107 for (let cookie of cookies) {
 108 InspectorTest.log("Setting cookie " + JSON.stringify(cookie) + " ...");
 109 await InspectorTest.expectException(() => PageAgent.setCookie(cookie));
 110 InspectorTest.newline();
 111 }
 112 },
 113 });
 114
 115 suite.runTestCasesAndFinish();
 116}
 117</script>
 118</head>
 119<body onload="runTestHTTPS()">
 120 <p>Test for the Page.setCookie.</p>
 121</body>
 122</html>

LayoutTests/inspector/unit-tests/number-utilities-expected.txt

@@PASS: -100000000 should have 9 digits
8787PASS: 1000000000 should have 10 digits
8888PASS: -1000000000 should have 10 digits
8989
 90-- Running test case: Number.prototype.maxDecimals
 91PASS: maxDecimals with a negative argument more than the number of digits should be 0
 92PASS: maxDecimals with a negative argument more equal to the number of digits should be 0
 93PASS: maxDecimals with -2 should truncate that 2 units in front of the decimal
 94PASS: maxDecimals with -1 should truncate that 1 units in front of the decimal
 95PASS: maxDecimals with 0 should round the value
 96PASS: maxDecimals with 1 should round after the 1st decimal
 97PASS: maxDecimals with 2 should round after the 2nd decimal
 98PASS: maxDecimals with a positive argument equal to the number of digits should not modify the number
 99PASS: maxDecimals with a positive argument greater than the number of digits should not modify the number
 100

LayoutTests/inspector/unit-tests/number-utilities.html

@@function test()
134134 }
135135 });
136136
 137 suite.addTestCase({
 138 name: "Number.prototype.maxDecimals",
 139 test() {
 140 const n = 123.456;
 141
 142 InspectorTest.expectEqual(n.maxDecimals(-4), 0, "maxDecimals with a negative argument more than the number of digits should be 0");
 143 InspectorTest.expectEqual(n.maxDecimals(-3), 0, "maxDecimals with a negative argument more equal to the number of digits should be 0");
 144 InspectorTest.expectEqual(n.maxDecimals(-2), 100, "maxDecimals with -2 should truncate that 2 units in front of the decimal");
 145 InspectorTest.expectEqual(n.maxDecimals(-1), 120, "maxDecimals with -1 should truncate that 1 units in front of the decimal");
 146 InspectorTest.expectEqual(n.maxDecimals(0), 123, "maxDecimals with 0 should round the value");
 147 InspectorTest.expectEqual(n.maxDecimals(1), 123.5, "maxDecimals with 1 should round after the 1st decimal");
 148 InspectorTest.expectEqual(n.maxDecimals(2), 123.46, "maxDecimals with 2 should round after the 2nd decimal");
 149 InspectorTest.expectEqual(n.maxDecimals(3), 123.456, "maxDecimals with a positive argument equal to the number of digits should not modify the number");
 150 InspectorTest.expectEqual(n.maxDecimals(4), 123.456, "maxDecimals with a positive argument greater than the number of digits should not modify the number");
 151 },
 152 });
 153
137154 suite.runTestCasesAndFinish();
138155}
139156</script>