LibWebView: Add a storage tab to the Inspector to manage cookies

This adds a storage tab which contains just a cookie viewer for now. In
the future, storage like Local Storage and Indexed DB can be added here
as well.

In this patch, the cookie table is read-only.
This commit is contained in:
Timothy Flynn 2024-09-06 12:08:50 -04:00 committed by Andreas Kling
commit 3c5650f846
Notes: github-actions[bot] 2024-09-07 09:11:43 +00:00
5 changed files with 97 additions and 0 deletions

View file

@ -276,6 +276,9 @@ details > :not(:first-child) {
.property-table td { .property-table td {
padding: 4px; padding: 4px;
text-align: left; text-align: left;
overflow: hidden;
text-overflow: ellipsis;
} }
#fonts { #fonts {

View file

@ -16,6 +16,7 @@
<div class="tab-controls"> <div class="tab-controls">
<button id="dom-tree-button" onclick="selectTopTab(this, 'dom-tree')">DOM Tree</button> <button id="dom-tree-button" onclick="selectTopTab(this, 'dom-tree')">DOM Tree</button>
<button id="accessibility-tree-button" onclick="selectTopTab(this, 'accessibility-tree')">Accessibility Tree</button> <button id="accessibility-tree-button" onclick="selectTopTab(this, 'accessibility-tree')">Accessibility Tree</button>
<button id="storage-button" onclick="selectTopTab(this, 'storage')">Storage</button>
<button id="style-sheets-button" onclick="selectTopTab(this, 'style-sheets')">Style Sheets</button> <button id="style-sheets-button" onclick="selectTopTab(this, 'style-sheets')">Style Sheets</button>
</div> </div>
@ -27,6 +28,32 @@
<div id="dom-tree" class="tab-content html"></div> <div id="dom-tree" class="tab-content html"></div>
<div id="accessibility-tree" class="tab-content"></div> <div id="accessibility-tree" class="tab-content"></div>
<div id="storage" class="tab-content" style="padding: 0">
<div class="tab-header">
<select id="storage-picker">
<option value="cookies" selected>Cookies</option>
</select>
</div>
<div id="cookie-storage">
<table class="property-table">
<thead>
<tr>
<th style="width: 10%">Name</th>
<th style="width: 15%">Value</th>
<th style="width: 10%">Domain</th>
<th style="width: 5%">Path</th>
<th style="width: 20%">Created</th>
<th style="width: 20%">Last Accessed</th>
<th style="width: 20%">Expires</th>
</tr>
</thead>
<tbody id="cookie-table">
</tbody>
</table>
</div>
</div>
<div id="style-sheets" class="tab-content" style="padding: 0"> <div id="style-sheets" class="tab-content" style="padding: 0">
<div class="tab-header"> <div class="tab-header">
<select id="style-sheet-picker" disabled onchange="loadStyleSheet()"> <select id="style-sheet-picker" disabled onchange="loadStyleSheet()">

View file

@ -103,6 +103,9 @@ inspector.reset = () => {
let accessibilityTree = document.getElementById("accessibility-tree"); let accessibilityTree = document.getElementById("accessibility-tree");
accessibilityTree.innerHTML = ""; accessibilityTree.innerHTML = "";
let cookieTable = document.getElementById("cookie-table");
cookieTable.innerHTML = "";
let styleSheetPicker = document.getElementById("style-sheet-picker"); let styleSheetPicker = document.getElementById("style-sheet-picker");
styleSheetPicker.replaceChildren(); styleSheetPicker.replaceChildren();
@ -208,6 +211,35 @@ inspector.addAttributeToDOMNodeID = nodeID => {
pendingEditDOMNode = null; pendingEditDOMNode = null;
}; };
inspector.setCookies = cookies => {
let oldTable = document.getElementById("cookie-table");
let newTable = document.createElement("tbody");
newTable.setAttribute("id", oldTable.id);
const addColumn = (row, value) => {
let column = row.insertCell();
column.innerText = value;
column.title = value;
};
cookies
.sort((lhs, rhs) => lhs.name.localeCompare(rhs.name))
.forEach(cookie => {
let row = newTable.insertRow();
addColumn(row, cookie.name);
addColumn(row, cookie.value);
addColumn(row, cookie.domain);
addColumn(row, cookie.path);
addColumn(row, new Date(cookie.creationTime).toLocaleString());
addColumn(row, new Date(cookie.lastAccessTime).toLocaleString());
addColumn(row, new Date(cookie.expiryTime).toLocaleString());
});
oldTable.parentNode.replaceChild(newTable, oldTable);
};
inspector.setStyleSheets = styleSheets => { inspector.setStyleSheets = styleSheets => {
const styleSheetPicker = document.getElementById("style-sheet-picker"); const styleSheetPicker = document.getElementById("style-sheet-picker");
const styleSheetSource = document.getElementById("style-sheet-source"); const styleSheetSource = document.getElementById("style-sheet-source");

View file

@ -5,6 +5,7 @@
*/ */
#include <AK/Base64.h> #include <AK/Base64.h>
#include <AK/Enumerate.h>
#include <AK/JsonArray.h> #include <AK/JsonArray.h>
#include <AK/JsonObject.h> #include <AK/JsonObject.h>
#include <AK/LexicalPath.h> #include <AK/LexicalPath.h>
@ -16,6 +17,7 @@
#include <LibJS/MarkupGenerator.h> #include <LibJS/MarkupGenerator.h>
#include <LibWeb/Infra/Strings.h> #include <LibWeb/Infra/Strings.h>
#include <LibWebView/Application.h> #include <LibWebView/Application.h>
#include <LibWebView/CookieJar.h>
#include <LibWebView/InspectorClient.h> #include <LibWebView/InspectorClient.h>
#include <LibWebView/SourceHighlighter.h> #include <LibWebView/SourceHighlighter.h>
@ -294,6 +296,7 @@ void InspectorClient::inspect()
m_content_web_view.inspect_dom_tree(); m_content_web_view.inspect_dom_tree();
m_content_web_view.inspect_accessibility_tree(); m_content_web_view.inspect_accessibility_tree();
m_content_web_view.list_style_sheets(); m_content_web_view.list_style_sheets();
load_cookies();
} }
void InspectorClient::reset() void InspectorClient::reset()
@ -342,6 +345,34 @@ void InspectorClient::select_node(i32 node_id)
m_inspector_web_view.run_javascript(script); m_inspector_web_view.run_javascript(script);
} }
void InspectorClient::load_cookies()
{
m_cookies = Application::cookie_jar().get_all_cookies(m_content_web_view.url());
JsonArray json_cookies;
for (auto const& [index, cookie] : enumerate(m_cookies)) {
JsonObject json_cookie;
json_cookie.set("index"sv, JsonValue { index });
json_cookie.set("name"sv, JsonValue { cookie.name });
json_cookie.set("value"sv, JsonValue { cookie.value });
json_cookie.set("domain"sv, JsonValue { cookie.domain });
json_cookie.set("path"sv, JsonValue { cookie.path });
json_cookie.set("creationTime"sv, JsonValue { cookie.creation_time.milliseconds_since_epoch() });
json_cookie.set("lastAccessTime"sv, JsonValue { cookie.last_access_time.milliseconds_since_epoch() });
json_cookie.set("expiryTime"sv, JsonValue { cookie.expiry_time.milliseconds_since_epoch() });
MUST(json_cookies.append(move(json_cookie)));
}
StringBuilder builder;
builder.append("inspector.setCookies("sv);
json_cookies.serialize(builder);
builder.append(");"sv);
m_inspector_web_view.run_javascript(builder.string_view());
}
void InspectorClient::context_menu_edit_dom_node() void InspectorClient::context_menu_edit_dom_node()
{ {
VERIFY(m_context_menu_data.has_value()); VERIFY(m_context_menu_data.has_value());

View file

@ -51,6 +51,8 @@ private:
String generate_accessibility_tree(JsonObject const&); String generate_accessibility_tree(JsonObject const&);
void select_node(i32 node_id); void select_node(i32 node_id);
void load_cookies();
void request_console_messages(); void request_console_messages();
void handle_console_message(i32 message_index); void handle_console_message(i32 message_index);
void handle_console_messages(i32 start_index, ReadonlySpan<ByteString> message_types, ReadonlySpan<ByteString> messages); void handle_console_messages(i32 start_index, ReadonlySpan<ByteString> message_types, ReadonlySpan<ByteString> messages);
@ -82,6 +84,8 @@ private:
HashMap<int, Vector<Attribute>> m_dom_node_attributes; HashMap<int, Vector<Attribute>> m_dom_node_attributes;
Vector<Web::Cookie::Cookie> m_cookies;
i32 m_highest_notified_message_index { -1 }; i32 m_highest_notified_message_index { -1 };
i32 m_highest_received_message_index { -1 }; i32 m_highest_received_message_index { -1 };
bool m_waiting_for_messages { false }; bool m_waiting_for_messages { false };