1/* 2 * Copyright (C) 2013 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 26WebInspector.CookieStorageContentView = function(representedObject) 27{ 28 WebInspector.ContentView.call(this, representedObject); 29 30 this.element.classList.add(WebInspector.CookieStorageContentView.StyleClassName); 31 32 this.update(); 33}; 34 35WebInspector.CookieStorageContentView.StyleClassName = "cookie-storage"; 36 37WebInspector.CookieStorageContentView.prototype = { 38 constructor: WebInspector.CookieStorageContentView, 39 40 // Public 41 42 update: function() 43 { 44 function callback(error, cookies) 45 { 46 if (error) 47 return; 48 49 this._cookies = this._filterCookies(cookies); 50 this._rebuildTable(); 51 } 52 53 PageAgent.getCookies(callback.bind(this)); 54 }, 55 56 updateLayout: function() 57 { 58 if (this._dataGrid) 59 this._dataGrid.updateLayout(); 60 }, 61 62 saveToCookie: function(cookie) 63 { 64 cookie.type = WebInspector.ContentViewCookieType.CookieStorage; 65 cookie.host = this.representedObject.host; 66 }, 67 68 get scrollableElements() 69 { 70 if (!this._dataGrid) 71 return []; 72 return [this._dataGrid.scrollContainer]; 73 }, 74 75 // Private 76 77 _rebuildTable: function() 78 { 79 // FIXME: If there are no cookies, do we want to show an empty datagrid, or do something like the old 80 // inspector and show some text saying there are no cookies? 81 if (!this._dataGrid) { 82 var columns = {name: {}, value: {}, domain: {}, path: {}, expires: {}, size: {}, http: {}, secure: {}}; 83 84 columns.name.title = WebInspector.UIString("Name"); 85 columns.name.sortable = true; 86 columns.name.width = "24%"; 87 88 columns.value.title = WebInspector.UIString("Value"); 89 columns.value.sortable = true; 90 columns.value.width = "34%"; 91 92 columns.domain.title = WebInspector.UIString("Domain"); 93 columns.domain.sortable = true; 94 columns.domain.width = "7%"; 95 96 columns.path.title = WebInspector.UIString("Path"); 97 columns.path.sortable = true; 98 columns.path.width = "7%"; 99 100 columns.expires.title = WebInspector.UIString("Expires"); 101 columns.expires.sortable = true; 102 columns.expires.width = "7%"; 103 104 columns.size.title = WebInspector.UIString("Size"); 105 columns.size.aligned = "right"; 106 columns.size.sortable = true; 107 columns.size.width = "7%"; 108 109 columns.http.title = WebInspector.UIString("HTTP"); 110 columns.http.aligned = "centered"; 111 columns.http.sortable = true; 112 columns.http.width = "7%"; 113 114 columns.secure.title = WebInspector.UIString("Secure"); 115 columns.secure.aligned = "centered"; 116 columns.secure.sortable = true; 117 columns.secure.width = "7%"; 118 119 this._dataGrid = new WebInspector.DataGrid(columns, null, this._deleteCallback.bind(this)); 120 this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, this._sortDataGrid, this); 121 122 this.element.appendChild(this._dataGrid.element); 123 this._dataGrid.updateLayout(); 124 } 125 126 console.assert(this._dataGrid); 127 this._dataGrid.removeChildren(); 128 129 for (var cookie of this._cookies) { 130 const checkmark = "\u2713"; 131 var data = { 132 "name": cookie.name, 133 "value": cookie.value, 134 "domain": cookie.domain || "", 135 "path": cookie.path || "", 136 "expires": "", 137 "size": Number.bytesToString(cookie.size), 138 "http": cookie.httpOnly ? checkmark : "", 139 "secure": cookie.secure ? checkmark : "", 140 }; 141 142 if (cookie.type !== WebInspector.CookieType.Request) 143 data["expires"] = cookie.session ? WebInspector.UIString("Session") : new Date(cookie.expires).toLocaleString(); 144 145 var node = new WebInspector.DataGridNode(data); 146 node.selectable = true; 147 node.cookie = cookie; 148 149 this._dataGrid.appendChild(node); 150 } 151 152 this._dataGrid.sortColumnIdentifier = "name"; 153 }, 154 155 _filterCookies: function(cookies) 156 { 157 var filteredCookies = []; 158 var resourcesForDomain = []; 159 160 var frames = WebInspector.frameResourceManager.frames; 161 for (var i = 0; i < frames.length; ++i) { 162 var resources = frames[i].resources; 163 for (var j = 0; j < resources.length; ++j) { 164 var urlComponents = resources[j].urlComponents; 165 if (urlComponents && urlComponents.host && urlComponents.host === this.representedObject.host) 166 resourcesForDomain.push(resources[j].url); 167 } 168 169 // The main resource isn't always in the list of resources, make sure to add it to the list of resources 170 // we get the URLs from. 171 var mainResourceURLComponents = frames[i].mainResource.urlComponents; 172 if (mainResourceURLComponents && mainResourceURLComponents.host && mainResourceURLComponents.host == this.representedObject.host) 173 resourcesForDomain.push(frames[i].mainResource.url); 174 } 175 176 for (var i = 0; i < cookies.length; ++i) { 177 for (var j = 0; j < resourcesForDomain.length; ++j) { 178 if (WebInspector.cookieMatchesResourceURL(cookies[i], resourcesForDomain[j])) { 179 filteredCookies.push(cookies[i]); 180 break; 181 } 182 } 183 } 184 185 return filteredCookies; 186 }, 187 188 _sortDataGrid: function() 189 { 190 function localeCompare(field, nodeA, nodeB) 191 { 192 return (nodeA.data[field] + "").localeCompare(nodeB.data[field] + ""); 193 } 194 195 function numberCompare(field, nodeA, nodeB) 196 { 197 return nodeA.cookie[field] - nodeB.cookie[field]; 198 } 199 200 function expiresCompare(nodeA, nodeB) 201 { 202 if (nodeA.cookie.session !== nodeB.cookie.session) 203 return nodeA.cookie.session ? 1 : -1; 204 205 if (nodeA.cookie.session) 206 return 0; 207 208 return nodeA.data["expires"] - nodeB.data["expires"]; 209 } 210 211 var comparator; 212 switch (this._dataGrid.sortColumnIdentifier) { 213 case "value": comparator = localeCompare.bind(this, "value"); break; 214 case "domain": comparator = localeCompare.bind(this, "domain"); break; 215 case "path": comparator = localeCompare.bind(this, "path"); break; 216 case "expires": comparator = expiresCompare; break; 217 case "size": comparator = numberCompare.bind(this, "size"); break; 218 case "http": comparator = localeCompare.bind(this, "http"); break; 219 case "secure": comparator = localeCompare.bind(this, "secure"); break; 220 case "name": 221 default: comparator = localeCompare.bind(this, "name"); break; 222 } 223 224 console.assert(comparator); 225 this._dataGrid.sortNodes(comparator); 226 }, 227 228 _deleteCallback: function(node) 229 { 230 if (!node || !node.cookie) 231 return; 232 233 var cookie = node.cookie; 234 var cookieURL = (cookie.secure ? "https://" : "http://") + cookie.domain + cookie.path; 235 236 // COMPATIBILITY (iOS 6): PageAgent.deleteCookie used to take 'domain', now takes 'url'. Send both. 237 PageAgent.deleteCookie.invoke({cookieName: cookie.name, domain: cookie.domain, url: cookieURL}); 238 239 this.update(); 240 } 241}; 242 243WebInspector.CookieStorageContentView.prototype.__proto__ = WebInspector.ContentView.prototype; 244 245WebInspector.cookieMatchesResourceURL = function(cookie, resourceURL) 246{ 247 var parsedURL = parseURL(resourceURL); 248 if (!parsedURL || !WebInspector.cookieDomainMatchesResourceDomain(cookie.domain, parsedURL.host)) 249 return false; 250 251 return (parsedURL.path.startsWith(cookie.path) 252 && (!cookie.port || parsedURL.port == cookie.port) 253 && (!cookie.secure || parsedURL.scheme === "https")); 254} 255 256WebInspector.cookieDomainMatchesResourceDomain = function(cookieDomain, resourceDomain) 257{ 258 if (cookieDomain.charAt(0) !== ".") 259 return resourceDomain === cookieDomain; 260 return !!resourceDomain.match(new RegExp("^([^\\.]+\\.)?" + cookieDomain.substring(1).escapeForRegExp() + "$"), "i"); 261} 262 263WebInspector.CookieType = { 264 Request: 0, 265 Response: 1 266}; 267