The Storage panel expects storesUpdate messages after watched storage changes. Summarize committed IndexedDB mutation logs into DevTools paths and forward them through WebContent so Firefox can refresh database, object-store, and record rows without polling. Firefox's behaviour is less than ideal here. A lot of things don't update automatically even inspecting a page in Firefox. Some things (like new databases) won't show up until you fully refresh the page. So that makes it a bit hard to know that we're doing things correctly. As far as I can tell, we are at least behaving as well as Firefox requires. We do have one workaround: Firefox doesn't display record updates without a manual refresh, and in fact any change messages for them show up as rows in the host's database table. So for now, we filter them out to avoid visual weirdness in the inspector.
3764 lines
170 KiB
C++
3764 lines
170 KiB
C++
/*
|
|
* Copyright (c) 2026, Ladybird contributors
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Atomic.h>
|
|
#include <AK/ByteBuffer.h>
|
|
#include <AK/JsonArray.h>
|
|
#include <AK/JsonObject.h>
|
|
#include <AK/JsonValue.h>
|
|
#include <AK/OwnPtr.h>
|
|
#include <AK/ScopeGuard.h>
|
|
#include <AK/Time.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibCore/Socket.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibDevTools/Connection.h>
|
|
#include <LibDevTools/DevToolsDelegate.h>
|
|
#include <LibDevTools/DevToolsServer.h>
|
|
#include <LibDevTools/IndexedDBSerialization.h>
|
|
#include <LibHTTP/Cookie/ParsedCookie.h>
|
|
#include <LibHTTP/Header.h>
|
|
#include <LibRequests/RequestTimingInfo.h>
|
|
#include <LibTest/TestCase.h>
|
|
#include <LibThreading/Thread.h>
|
|
#include <LibWeb/CSS/StyleSheetIdentifier.h>
|
|
#include <LibWebView/Attribute.h>
|
|
#include <LibWebView/ConsoleOutput.h>
|
|
#include <LibWebView/DOMNodeProperties.h>
|
|
#include <LibWebView/Mutation.h>
|
|
|
|
using namespace AK::TimeLiterals;
|
|
|
|
static void pump(Core::EventLoop& loop)
|
|
{
|
|
(void)loop.pump(Core::EventLoop::WaitMode::PollForEvents);
|
|
}
|
|
|
|
static void spin_until(Core::EventLoop& loop, Function<bool()> condition, AK::Duration timeout = 2000_ms)
|
|
{
|
|
for (i64 elapsed_ms = 0; elapsed_ms < timeout.to_milliseconds(); elapsed_ms += 5) {
|
|
pump(loop);
|
|
if (condition())
|
|
return;
|
|
MUST(Core::System::sleep_ms(5));
|
|
}
|
|
FAIL("Timed out waiting for condition");
|
|
}
|
|
|
|
static JsonObject make_node(Web::UniqueNodeID id, StringView type, StringView name)
|
|
{
|
|
JsonObject node;
|
|
node.set("id"sv, id.value());
|
|
node.set("type"sv, type);
|
|
node.set("name"sv, name);
|
|
return node;
|
|
}
|
|
|
|
static JsonObject make_dom_tree()
|
|
{
|
|
JsonObject target = make_node(4, "element"sv, "DIV"sv);
|
|
target.set("visible"sv, true);
|
|
target.set("scrollable"sv, true);
|
|
target.set("display"sv, "grid"sv);
|
|
|
|
JsonObject target_attributes;
|
|
target_attributes.set("id"sv, "target"sv);
|
|
target_attributes.set("class"sv, "fixture"sv);
|
|
target.set("attributes"sv, move(target_attributes));
|
|
|
|
JsonObject text = make_node(5, "text"sv, "#text"sv);
|
|
text.set("text"sv, "Hello"sv);
|
|
|
|
JsonObject comment = make_node(6, "comment"sv, "#comment"sv);
|
|
comment.set("data"sv, "A comment"sv);
|
|
|
|
JsonObject whitespace = make_node(7, "text"sv, "#text"sv);
|
|
whitespace.set("text"sv, " \n "sv);
|
|
|
|
JsonObject before = make_node(40, "pseudo-element"sv, "::before"sv);
|
|
before.set("parent-id"sv, 4);
|
|
before.set("pseudo-element"sv, to_underlying(Web::CSS::PseudoElement::Before));
|
|
|
|
JsonObject subgrid = make_node(9, "element"sv, "SECTION"sv);
|
|
subgrid.set("visible"sv, true);
|
|
subgrid.set("display"sv, "grid"sv);
|
|
|
|
JsonArray target_children;
|
|
target_children.must_append(move(text));
|
|
target_children.must_append(move(comment));
|
|
target_children.must_append(move(whitespace));
|
|
target_children.must_append(move(before));
|
|
target_children.must_append(move(subgrid));
|
|
target.set("children"sv, move(target_children));
|
|
|
|
JsonObject sibling = make_node(8, "element"sv, "SPAN"sv);
|
|
sibling.set("visible"sv, true);
|
|
|
|
JsonObject first_flex_item = make_node(11, "element"sv, "P"sv);
|
|
first_flex_item.set("visible"sv, true);
|
|
|
|
JsonObject second_flex_item = make_node(12, "element"sv, "NAV"sv);
|
|
second_flex_item.set("visible"sv, true);
|
|
|
|
JsonArray flex_children;
|
|
flex_children.must_append(move(first_flex_item));
|
|
flex_children.must_append(move(second_flex_item));
|
|
|
|
JsonObject flex = make_node(10, "element"sv, "ASIDE"sv);
|
|
flex.set("visible"sv, true);
|
|
flex.set("display"sv, "flex"sv);
|
|
flex.set("children"sv, move(flex_children));
|
|
|
|
JsonArray body_children;
|
|
body_children.must_append(move(sibling));
|
|
body_children.must_append(move(target));
|
|
body_children.must_append(move(flex));
|
|
|
|
JsonObject body = make_node(3, "element"sv, "BODY"sv);
|
|
body.set("visible"sv, true);
|
|
body.set("children"sv, move(body_children));
|
|
|
|
JsonArray html_children;
|
|
html_children.must_append(move(body));
|
|
|
|
JsonObject html = make_node(2, "element"sv, "HTML"sv);
|
|
html.set("visible"sv, true);
|
|
html.set("children"sv, move(html_children));
|
|
|
|
JsonArray document_children;
|
|
document_children.must_append(move(html));
|
|
|
|
JsonObject document = make_node(1, "document"sv, "#document"sv);
|
|
document.set("children"sv, move(document_children));
|
|
return document;
|
|
}
|
|
|
|
static JsonObject make_navigation_dom_tree()
|
|
{
|
|
JsonObject heading = make_node(105, "element"sv, "H1"sv);
|
|
heading.set("visible"sv, true);
|
|
|
|
JsonObject main = make_node(104, "element"sv, "MAIN"sv);
|
|
main.set("visible"sv, true);
|
|
|
|
JsonArray main_children;
|
|
main_children.must_append(move(heading));
|
|
main.set("children"sv, move(main_children));
|
|
|
|
JsonArray body_children;
|
|
body_children.must_append(move(main));
|
|
|
|
JsonObject body = make_node(103, "element"sv, "BODY"sv);
|
|
body.set("visible"sv, true);
|
|
body.set("children"sv, move(body_children));
|
|
|
|
JsonArray html_children;
|
|
html_children.must_append(move(body));
|
|
|
|
JsonObject html = make_node(102, "element"sv, "HTML"sv);
|
|
html.set("visible"sv, true);
|
|
html.set("children"sv, move(html_children));
|
|
|
|
JsonArray document_children;
|
|
document_children.must_append(move(html));
|
|
|
|
JsonObject document = make_node(101, "document"sv, "#document"sv);
|
|
document.set("children"sv, move(document_children));
|
|
return document;
|
|
}
|
|
|
|
static JsonObject make_accessibility_tree()
|
|
{
|
|
JsonObject button = make_node(4, "element"sv, "Target button"sv);
|
|
button.set("role"sv, "pushbutton"sv);
|
|
|
|
JsonArray children;
|
|
children.must_append(move(button));
|
|
|
|
JsonObject root = make_node(1, "document"sv, "Document"sv);
|
|
root.set("role"sv, "document"sv);
|
|
root.set("children"sv, move(children));
|
|
return root;
|
|
}
|
|
|
|
static WebView::DOMNodeProperties make_computed_style()
|
|
{
|
|
JsonObject properties;
|
|
properties.set("display"sv, "block"sv);
|
|
properties.set("color"sv, "rgb(1, 2, 3)"sv);
|
|
return { WebView::DOMNodeProperties::Type::ComputedStyle, move(properties) };
|
|
}
|
|
|
|
static JsonObject serialized_fixture_style_sheet();
|
|
static JsonObject serialized_fixture_user_agent_style_sheet();
|
|
|
|
static WebView::DOMNodeProperties make_applied_style_rules()
|
|
{
|
|
JsonArray entries;
|
|
|
|
JsonArray inline_declarations;
|
|
JsonObject inline_display;
|
|
inline_display.set("name"sv, "display"sv);
|
|
inline_display.set("value"sv, "grid"sv);
|
|
inline_display.set("priority"sv, ""sv);
|
|
inline_display.set("isCustomProperty"sv, false);
|
|
inline_display.set("isNameValid"sv, true);
|
|
inline_display.set("isValid"sv, true);
|
|
inline_display.set("inherits"sv, false);
|
|
inline_declarations.must_append(move(inline_display));
|
|
|
|
JsonObject inline_rule;
|
|
inline_rule.set("type"sv, 100);
|
|
inline_rule.set("className"sv, 100);
|
|
inline_rule.set("cssText"sv, "display: grid;"sv);
|
|
inline_rule.set("authoredText"sv, "display: grid;"sv);
|
|
inline_rule.set("declarations"sv, move(inline_declarations));
|
|
|
|
JsonObject inline_entry;
|
|
inline_entry.set("rule"sv, move(inline_rule));
|
|
inline_entry.set("isSystem"sv, false);
|
|
inline_entry.set("matchedSelectorIndexes"sv, JsonArray {});
|
|
inline_entry.set("inherited"sv, JsonValue {});
|
|
entries.must_append(move(inline_entry));
|
|
|
|
JsonArray rule_declarations;
|
|
JsonObject rule_color;
|
|
rule_color.set("name"sv, "color"sv);
|
|
rule_color.set("value"sv, "rgb(1, 2, 3)"sv);
|
|
rule_color.set("priority"sv, ""sv);
|
|
rule_color.set("isCustomProperty"sv, false);
|
|
rule_color.set("isNameValid"sv, true);
|
|
rule_color.set("isValid"sv, true);
|
|
rule_color.set("inherits"sv, true);
|
|
rule_declarations.must_append(move(rule_color));
|
|
|
|
JsonObject rule_invalid_color;
|
|
rule_invalid_color.set("name"sv, "color"sv);
|
|
rule_invalid_color.set("value"sv, "invalid-value"sv);
|
|
rule_invalid_color.set("priority"sv, ""sv);
|
|
rule_invalid_color.set("isCustomProperty"sv, false);
|
|
rule_invalid_color.set("isNameValid"sv, true);
|
|
rule_invalid_color.set("isValid"sv, false);
|
|
rule_invalid_color.set("inherits"sv, true);
|
|
rule_declarations.must_append(move(rule_invalid_color));
|
|
|
|
JsonArray selectors;
|
|
selectors.must_append("body div.fixture"sv);
|
|
|
|
JsonArray selector_specificities;
|
|
selector_specificities.must_append(0x101u);
|
|
|
|
JsonArray matched_selector_indexes;
|
|
matched_selector_indexes.must_append(0u);
|
|
|
|
JsonObject rule;
|
|
rule.set("type"sv, 1);
|
|
rule.set("className"sv, "CSSStyleRule"sv);
|
|
rule.set("selectors"sv, move(selectors));
|
|
rule.set("selectorsSpecificity"sv, move(selector_specificities));
|
|
rule.set("cssText"sv, "body div.fixture { color: rgb(1, 2, 3); }"sv);
|
|
rule.set("authoredText"sv, "color: rgb(1, 2, 3);"sv);
|
|
rule.set("declarations"sv, move(rule_declarations));
|
|
rule.set("line"sv, 4);
|
|
rule.set("column"sv, 9);
|
|
rule.set("styleSheet"sv, serialized_fixture_style_sheet());
|
|
|
|
JsonObject rule_entry;
|
|
rule_entry.set("rule"sv, move(rule));
|
|
rule_entry.set("isSystem"sv, false);
|
|
rule_entry.set("matchedSelectorIndexes"sv, move(matched_selector_indexes));
|
|
rule_entry.set("inheritedNodeId"sv, 1u);
|
|
entries.must_append(move(rule_entry));
|
|
|
|
JsonArray user_agent_declarations;
|
|
JsonObject user_agent_display;
|
|
user_agent_display.set("name"sv, "display"sv);
|
|
user_agent_display.set("value"sv, "block"sv);
|
|
user_agent_display.set("priority"sv, ""sv);
|
|
user_agent_display.set("isCustomProperty"sv, false);
|
|
user_agent_display.set("isNameValid"sv, true);
|
|
user_agent_display.set("isValid"sv, true);
|
|
user_agent_display.set("inherits"sv, false);
|
|
user_agent_declarations.must_append(move(user_agent_display));
|
|
|
|
JsonArray user_agent_selectors;
|
|
user_agent_selectors.must_append("div"sv);
|
|
|
|
JsonArray user_agent_selector_specificities;
|
|
user_agent_selector_specificities.must_append(0x1u);
|
|
|
|
JsonArray user_agent_matched_selector_indexes;
|
|
user_agent_matched_selector_indexes.must_append(0u);
|
|
|
|
JsonObject user_agent_rule;
|
|
user_agent_rule.set("type"sv, 1);
|
|
user_agent_rule.set("className"sv, "CSSStyleRule"sv);
|
|
user_agent_rule.set("selectors"sv, move(user_agent_selectors));
|
|
user_agent_rule.set("selectorsSpecificity"sv, move(user_agent_selector_specificities));
|
|
user_agent_rule.set("cssText"sv, "div { display: block; }"sv);
|
|
user_agent_rule.set("authoredText"sv, "display: block;"sv);
|
|
user_agent_rule.set("declarations"sv, move(user_agent_declarations));
|
|
user_agent_rule.set("isSystem"sv, true);
|
|
user_agent_rule.set("line"sv, 12);
|
|
user_agent_rule.set("column"sv, 5);
|
|
user_agent_rule.set("styleSheet"sv, serialized_fixture_user_agent_style_sheet());
|
|
|
|
JsonObject user_agent_rule_entry;
|
|
user_agent_rule_entry.set("rule"sv, move(user_agent_rule));
|
|
user_agent_rule_entry.set("isSystem"sv, true);
|
|
user_agent_rule_entry.set("matchedSelectorIndexes"sv, move(user_agent_matched_selector_indexes));
|
|
user_agent_rule_entry.set("inherited"sv, JsonValue {});
|
|
entries.must_append(move(user_agent_rule_entry));
|
|
|
|
return { WebView::DOMNodeProperties::Type::AppliedStyleRules, move(entries) };
|
|
}
|
|
|
|
static WebView::DOMNodeProperties make_layout()
|
|
{
|
|
JsonObject properties;
|
|
properties.set("width"sv, "100px"sv);
|
|
properties.set("height"sv, "50px"sv);
|
|
properties.set("border-top-width"sv, 1);
|
|
properties.set("border-right-width"sv, 2);
|
|
properties.set("border-bottom-width"sv, 3);
|
|
properties.set("border-left-width"sv, 4);
|
|
properties.set("margin-top"sv, 5);
|
|
properties.set("margin-right"sv, 6);
|
|
properties.set("margin-bottom"sv, 7);
|
|
properties.set("margin-left"sv, 8);
|
|
properties.set("padding-top"sv, 9);
|
|
properties.set("padding-right"sv, 10);
|
|
properties.set("padding-bottom"sv, 11);
|
|
properties.set("padding-left"sv, 12);
|
|
properties.set("box-sizing"sv, "border-box"sv);
|
|
properties.set("display"sv, "block"sv);
|
|
properties.set("float"sv, "none"sv);
|
|
properties.set("line-height"sv, "normal"sv);
|
|
properties.set("position"sv, "static"sv);
|
|
properties.set("z-index"sv, "auto"sv);
|
|
return { WebView::DOMNodeProperties::Type::Layout, move(properties) };
|
|
}
|
|
|
|
static WebView::DOMNodeProperties make_used_fonts()
|
|
{
|
|
JsonArray fonts;
|
|
JsonObject font;
|
|
font.set("name"sv, "Test Sans"sv);
|
|
font.set("weight"sv, 400);
|
|
fonts.must_append(move(font));
|
|
return { WebView::DOMNodeProperties::Type::UsedFonts, move(fonts) };
|
|
}
|
|
|
|
static Web::CSS::StyleSheetIdentifier fixture_style_sheet()
|
|
{
|
|
return { .type = Web::CSS::StyleSheetIdentifier::Type::StyleElement,
|
|
.dom_element_unique_id = 9,
|
|
.url = "https://example.test/style.css"_string,
|
|
.rule_count = 2 };
|
|
}
|
|
|
|
static JsonObject serialized_fixture_style_sheet()
|
|
{
|
|
JsonObject style_sheet;
|
|
style_sheet.set("type"sv, Web::CSS::style_sheet_identifier_type_to_string(Web::CSS::StyleSheetIdentifier::Type::StyleElement));
|
|
style_sheet.set("domElementUniqueId"sv, 9);
|
|
style_sheet.set("url"sv, "https://example.test/style.css"sv);
|
|
style_sheet.set("ruleCount"sv, 2);
|
|
return style_sheet;
|
|
}
|
|
|
|
static Web::CSS::StyleSheetIdentifier fixture_user_agent_style_sheet()
|
|
{
|
|
return { .type = Web::CSS::StyleSheetIdentifier::Type::UserAgent,
|
|
.url = "CSS/Default.css"_string,
|
|
.rule_count = 4 };
|
|
}
|
|
|
|
static JsonObject serialized_fixture_user_agent_style_sheet()
|
|
{
|
|
JsonObject style_sheet;
|
|
style_sheet.set("type"sv, Web::CSS::style_sheet_identifier_type_to_string(Web::CSS::StyleSheetIdentifier::Type::UserAgent));
|
|
style_sheet.set("url"sv, "CSS/Default.css"sv);
|
|
style_sheet.set("ruleCount"sv, 4);
|
|
return style_sheet;
|
|
}
|
|
|
|
static JsonObject make_grid_line(double start, i32 number, i32 negative_number)
|
|
{
|
|
JsonObject line;
|
|
line.set("breadth"sv, 0);
|
|
line.set("names"sv, JsonArray {});
|
|
line.set("negativeNumber"sv, negative_number);
|
|
line.set("number"sv, number);
|
|
line.set("start"sv, start);
|
|
line.set("type"sv, "explicit"sv);
|
|
return line;
|
|
}
|
|
|
|
static JsonObject make_grid_track(double start, double breadth)
|
|
{
|
|
JsonObject track;
|
|
track.set("breadth"sv, breadth);
|
|
track.set("start"sv, start);
|
|
track.set("state"sv, "static"sv);
|
|
track.set("type"sv, "explicit"sv);
|
|
return track;
|
|
}
|
|
|
|
static JsonObject make_grid_dimension()
|
|
{
|
|
JsonArray lines;
|
|
lines.must_append(make_grid_line(0, 1, -2));
|
|
lines.must_append(make_grid_line(100, 2, -1));
|
|
|
|
JsonArray tracks;
|
|
tracks.must_append(make_grid_track(0, 100));
|
|
|
|
JsonObject dimension;
|
|
dimension.set("lines"sv, move(lines));
|
|
dimension.set("tracks"sv, move(tracks));
|
|
return dimension;
|
|
}
|
|
|
|
static JsonObject make_grid_layout(Web::UniqueNodeID container_node_id, StringView area_name, bool is_subgrid = false)
|
|
{
|
|
JsonObject area;
|
|
area.set("columnEnd"sv, 2);
|
|
area.set("columnStart"sv, 1);
|
|
area.set("name"sv, area_name);
|
|
area.set("rowEnd"sv, 2);
|
|
area.set("rowStart"sv, 1);
|
|
area.set("type"sv, "explicit"sv);
|
|
|
|
JsonArray areas;
|
|
areas.must_append(move(area));
|
|
|
|
JsonObject fragment;
|
|
fragment.set("areas"sv, move(areas));
|
|
fragment.set("cols"sv, make_grid_dimension());
|
|
fragment.set("rows"sv, make_grid_dimension());
|
|
|
|
JsonArray fragments;
|
|
fragments.must_append(move(fragment));
|
|
|
|
JsonObject layout;
|
|
layout.set("containerNodeId"sv, container_node_id.value());
|
|
layout.set("direction"sv, "ltr"sv);
|
|
layout.set("gridFragments"sv, move(fragments));
|
|
layout.set("isSubgrid"sv, is_subgrid);
|
|
layout.set("writingMode"sv, "horizontal-tb"sv);
|
|
return layout;
|
|
}
|
|
|
|
static JsonArray make_grid_layouts()
|
|
{
|
|
JsonArray grids;
|
|
grids.must_append(make_grid_layout(Web::UniqueNodeID { 4 }, "content"sv));
|
|
grids.must_append(make_grid_layout(Web::UniqueNodeID { 9 }, "subgrid"sv, true));
|
|
grids.must_append(make_grid_layout(Web::UniqueNodeID { 8 }, "subframe"sv));
|
|
return grids;
|
|
}
|
|
|
|
static JsonObject make_flex_item(Web::UniqueNodeID node_id, double main_base_size, double main_delta_size, StringView line_growth_state, StringView clamp_state)
|
|
{
|
|
JsonObject sizing;
|
|
sizing.set("clampState"sv, clamp_state);
|
|
sizing.set("crossAxisDirection"sv, "vertical-tb"sv);
|
|
sizing.set("crossMaxSize"sv, 20);
|
|
sizing.set("crossMinSize"sv, 0);
|
|
sizing.set("lineGrowthState"sv, line_growth_state);
|
|
sizing.set("mainAxisDirection"sv, "horizontal-lr"sv);
|
|
sizing.set("mainBaseSize"sv, main_base_size);
|
|
sizing.set("mainDeltaSize"sv, main_delta_size);
|
|
sizing.set("mainMaxSize"sv, 100);
|
|
sizing.set("mainMinSize"sv, 10);
|
|
|
|
JsonObject properties;
|
|
properties.set("flex-basis"sv, "auto"sv);
|
|
properties.set("flex-grow"sv, 1);
|
|
properties.set("flex-shrink"sv, 1);
|
|
properties.set("width"sv, "auto"sv);
|
|
properties.set("min-width"sv, "auto"sv);
|
|
properties.set("max-width"sv, "none"sv);
|
|
|
|
JsonObject computed_style;
|
|
computed_style.set("flexGrow"sv, 1);
|
|
computed_style.set("flexShrink"sv, 1);
|
|
|
|
JsonObject item;
|
|
item.set("nodeId"sv, node_id.value());
|
|
item.set("flexItemSizing"sv, move(sizing));
|
|
item.set("properties"sv, move(properties));
|
|
item.set("computedStyle"sv, move(computed_style));
|
|
return item;
|
|
}
|
|
|
|
static JsonObject make_flexbox_layout()
|
|
{
|
|
JsonArray items;
|
|
items.must_append(make_flex_item(Web::UniqueNodeID { 11 }, 40, 20, "growing"sv, "unclamped"sv));
|
|
items.must_append(make_flex_item(Web::UniqueNodeID { 12 }, 80, -10, "shrinking"sv, "clamped_to_min"sv));
|
|
|
|
JsonObject properties;
|
|
properties.set("align-content"sv, "normal"sv);
|
|
properties.set("align-items"sv, "stretch"sv);
|
|
properties.set("flex-direction"sv, "row"sv);
|
|
properties.set("flex-wrap"sv, "nowrap"sv);
|
|
properties.set("justify-content"sv, "flex-start"sv);
|
|
|
|
JsonObject layout;
|
|
layout.set("containerNodeId"sv, Web::UniqueNodeID { 10 }.value());
|
|
layout.set("properties"sv, move(properties));
|
|
layout.set("items"sv, move(items));
|
|
return layout;
|
|
}
|
|
|
|
static HTTP::Cookie::Cookie make_cookie(String name, String value, String domain, String path)
|
|
{
|
|
HTTP::Cookie::Cookie cookie;
|
|
cookie.name = move(name);
|
|
cookie.value = move(value);
|
|
cookie.domain = move(domain);
|
|
cookie.path = move(path);
|
|
cookie.creation_time = UnixDateTime::from_seconds_since_epoch(10);
|
|
cookie.last_access_time = UnixDateTime::from_seconds_since_epoch(20);
|
|
return cookie;
|
|
}
|
|
|
|
static String cookie_unique_key(StringView name, StringView domain, StringView path)
|
|
{
|
|
return MUST(String::formatted("{}{}{}{}{}{}",
|
|
name,
|
|
"{9d414cc5-8319-0a04-0586-c0a6ae01670a}"sv,
|
|
domain,
|
|
"{9d414cc5-8319-0a04-0586-c0a6ae01670a}"sv,
|
|
path,
|
|
"{9d414cc5-8319-0a04-0586-c0a6ae01670a}"sv));
|
|
}
|
|
|
|
static bool cookie_matches(HTTP::Cookie::Cookie const& cookie, StringView name, StringView domain, StringView path)
|
|
{
|
|
return cookie.name == name
|
|
&& cookie.domain == domain
|
|
&& cookie.path == path;
|
|
}
|
|
|
|
static String indexed_database_path(StringView database, Optional<StringView> object_store = {})
|
|
{
|
|
JsonArray path;
|
|
path.must_append(database);
|
|
if (object_store.has_value())
|
|
path.must_append(*object_store);
|
|
return path.serialized();
|
|
}
|
|
|
|
static JsonObject make_indexed_database_storage_hosts()
|
|
{
|
|
JsonArray names;
|
|
names.must_append(indexed_database_path("fixtures (default)"sv, "people"sv));
|
|
names.must_append(indexed_database_path("empty (default)"sv));
|
|
|
|
JsonObject hosts;
|
|
hosts.set("https://example.test"sv, move(names));
|
|
return hosts;
|
|
}
|
|
|
|
static JsonObject make_indexed_database_store_objects(Optional<JsonArray> const& names)
|
|
{
|
|
JsonArray data;
|
|
|
|
bool should_return_databases = !names.has_value() || names->is_empty();
|
|
Vector<JsonArray> paths;
|
|
if (names.has_value()) {
|
|
for (auto const& name : names->values()) {
|
|
if (!name.is_string())
|
|
continue;
|
|
auto parsed = JsonValue::from_string(name.as_string());
|
|
if (parsed.is_error() || !parsed.value().is_array())
|
|
continue;
|
|
auto path = parsed.release_value().as_array();
|
|
if (path.is_empty())
|
|
continue;
|
|
paths.append(move(path));
|
|
}
|
|
if (paths.is_empty())
|
|
should_return_databases = true;
|
|
}
|
|
|
|
if (should_return_databases) {
|
|
JsonObject fixtures;
|
|
fixtures.set("uniqueKey"sv, "fixtures (default)"sv);
|
|
fixtures.set("db"sv, "fixtures"sv);
|
|
fixtures.set("storage"sv, "default"sv);
|
|
fixtures.set("origin"sv, "https://example.test"sv);
|
|
fixtures.set("version"sv, 1);
|
|
fixtures.set("objectStores"sv, 1);
|
|
data.must_append(move(fixtures));
|
|
|
|
JsonObject empty;
|
|
empty.set("uniqueKey"sv, "empty (default)"sv);
|
|
empty.set("db"sv, "empty"sv);
|
|
empty.set("storage"sv, "default"sv);
|
|
empty.set("origin"sv, "https://example.test"sv);
|
|
empty.set("version"sv, 2);
|
|
empty.set("objectStores"sv, 0);
|
|
data.must_append(move(empty));
|
|
} else {
|
|
for (auto const& path : paths) {
|
|
if (path.size() == 1) {
|
|
JsonObject store;
|
|
store.set("objectStore"sv, "people"sv);
|
|
store.set("keyPath"sv, "id"sv);
|
|
store.set("autoIncrement"sv, true);
|
|
store.set("indexes"sv, "[]"_string);
|
|
data.must_append(move(store));
|
|
} else if (path.size() >= 2) {
|
|
JsonObject record;
|
|
record.set("name"sv, 1);
|
|
record.set("value"sv, "{\"name\":\"Ada\"}"sv);
|
|
data.must_append(move(record));
|
|
}
|
|
}
|
|
}
|
|
|
|
JsonObject response;
|
|
response.set("offset"sv, 0);
|
|
response.set("total"sv, data.size());
|
|
response.set("data"sv, move(data));
|
|
return response;
|
|
}
|
|
|
|
class TestDevToolsDelegate final : public DevTools::DevToolsDelegate {
|
|
public:
|
|
virtual Vector<DevTools::TabDescription> tab_list() const override
|
|
{
|
|
return { { .id = 1, .title = "Fixture page"_string, .url = tab_url } };
|
|
}
|
|
|
|
virtual Vector<DevTools::CSSProperty> css_property_list() const override
|
|
{
|
|
Vector<DevTools::CSSProperty> properties;
|
|
properties.append({ "display"_string, false });
|
|
properties.append({ "color"_string, true });
|
|
return properties;
|
|
}
|
|
|
|
virtual void reload_tab(DevTools::TabDescription const&, bool bypass_cache) const override
|
|
{
|
|
++reload_tab_call_count;
|
|
last_reload_bypass_cache = bypass_cache;
|
|
}
|
|
|
|
virtual void navigate_tab(DevTools::TabDescription const&, String const& url) const override
|
|
{
|
|
++navigate_tab_call_count;
|
|
last_navigated_url = url;
|
|
}
|
|
|
|
virtual void traverse_the_history_by_delta(DevTools::TabDescription const&, int delta) const override
|
|
{
|
|
++traverse_the_history_by_delta_call_count;
|
|
last_history_delta = delta;
|
|
}
|
|
|
|
virtual Vector<HTTP::Cookie::Cookie> cookies(DevTools::TabDescription const&) const override
|
|
{
|
|
++cookies_call_count;
|
|
return fixture_cookies;
|
|
}
|
|
|
|
virtual ErrorOr<void> set_cookie(DevTools::TabDescription const&, Optional<HTTP::Cookie::Cookie> old_cookie, HTTP::Cookie::Cookie cookie) const override
|
|
{
|
|
++set_cookie_call_count;
|
|
|
|
if (HTTP::Cookie::cookie_contains_invalid_control_character(cookie.name))
|
|
return Error::from_string_literal("Invalid cookie name");
|
|
|
|
auto key_matches_cookie = [](HTTP::Cookie::Cookie const& left, HTTP::Cookie::Cookie const& right) {
|
|
return left.name == right.name
|
|
&& left.domain == right.domain
|
|
&& left.path == right.path;
|
|
};
|
|
|
|
if (old_cookie.has_value()) {
|
|
for (auto i = fixture_cookies.size(); i > 0; --i) {
|
|
if (key_matches_cookie(fixture_cookies[i - 1], *old_cookie))
|
|
fixture_cookies.remove(i - 1);
|
|
}
|
|
}
|
|
|
|
for (auto i = fixture_cookies.size(); i > 0; --i) {
|
|
if (key_matches_cookie(fixture_cookies[i - 1], cookie))
|
|
fixture_cookies.remove(i - 1);
|
|
}
|
|
|
|
fixture_cookies.append(cookie);
|
|
|
|
if (on_host_cookie_change) {
|
|
Vector<HTTP::Cookie::Cookie> changed_cookies;
|
|
if (old_cookie.has_value())
|
|
changed_cookies.append(*old_cookie);
|
|
changed_cookies.append(move(cookie));
|
|
on_host_cookie_change(move(changed_cookies));
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
virtual void delete_cookies(DevTools::TabDescription const&, Vector<HTTP::Cookie::Cookie> cookies) const override
|
|
{
|
|
++delete_cookies_call_count;
|
|
|
|
auto key_matches_cookie = [](HTTP::Cookie::Cookie const& left, HTTP::Cookie::Cookie const& right) {
|
|
return left.name == right.name
|
|
&& left.domain == right.domain
|
|
&& left.path == right.path;
|
|
};
|
|
|
|
for (auto const& cookie_to_delete : cookies) {
|
|
for (auto i = fixture_cookies.size(); i > 0; --i) {
|
|
if (key_matches_cookie(fixture_cookies[i - 1], cookie_to_delete))
|
|
fixture_cookies.remove(i - 1);
|
|
}
|
|
}
|
|
|
|
if (on_host_cookie_change)
|
|
on_host_cookie_change(move(cookies));
|
|
}
|
|
|
|
virtual void listen_for_host_cookie_changes(DevTools::TabDescription const&, OnHostCookieChange callback) const override
|
|
{
|
|
++listen_for_cookie_changes_call_count;
|
|
on_host_cookie_change = move(callback);
|
|
}
|
|
|
|
virtual void stop_listening_for_host_cookie_changes(DevTools::TabDescription const&) const override
|
|
{
|
|
++stop_listening_for_cookie_changes_call_count;
|
|
on_host_cookie_change = nullptr;
|
|
}
|
|
|
|
virtual void inspect_storage(DevTools::TabDescription const&, Web::StorageAPI::StorageEndpointType storage_endpoint, OnStorageItemsReceived callback) const override
|
|
{
|
|
++inspect_storage_call_count;
|
|
callback(storage_items_for_endpoint(storage_endpoint));
|
|
}
|
|
|
|
virtual ErrorOr<Optional<String>> set_storage_item(DevTools::TabDescription const&, Web::StorageAPI::StorageEndpointType storage_endpoint, String const& storage_key, String const& key, String const& value) const override
|
|
{
|
|
++set_storage_item_call_count;
|
|
if (fail_set_storage_item)
|
|
return Error::from_string_literal("Unable to set storage item");
|
|
|
|
auto& storage_items = storage_items_for_endpoint(storage_endpoint);
|
|
for (auto& item : storage_items) {
|
|
if (item.name != key)
|
|
continue;
|
|
|
|
auto old_value = item.value;
|
|
item.value = value;
|
|
if (old_value != value)
|
|
emit_storage_change({ storage_endpoint, storage_key, DevTools::DevToolsDelegate::StorageChange::Type::Changed, key });
|
|
return old_value;
|
|
}
|
|
|
|
storage_items.append({ key, value });
|
|
emit_storage_change({ storage_endpoint, storage_key, DevTools::DevToolsDelegate::StorageChange::Type::Added, key });
|
|
return Optional<String> {};
|
|
}
|
|
|
|
virtual ErrorOr<Optional<String>> remove_storage_item(DevTools::TabDescription const&, Web::StorageAPI::StorageEndpointType storage_endpoint, String const& storage_key, String const& key) const override
|
|
{
|
|
++remove_storage_item_call_count;
|
|
if (fail_remove_storage_item)
|
|
return Error::from_string_literal("Unable to remove storage item");
|
|
|
|
auto& storage_items = storage_items_for_endpoint(storage_endpoint);
|
|
for (auto i = 0uz; i < storage_items.size(); ++i) {
|
|
if (storage_items[i].name != key)
|
|
continue;
|
|
|
|
auto old_value = storage_items[i].value;
|
|
storage_items.remove(i);
|
|
emit_storage_change({ storage_endpoint, storage_key, DevTools::DevToolsDelegate::StorageChange::Type::Deleted, key });
|
|
return old_value;
|
|
}
|
|
|
|
return Optional<String> {};
|
|
}
|
|
|
|
virtual ErrorOr<void> clear_storage(DevTools::TabDescription const&, Web::StorageAPI::StorageEndpointType storage_endpoint, String const& storage_key) const override
|
|
{
|
|
++clear_storage_call_count;
|
|
if (fail_clear_storage)
|
|
return Error::from_string_literal("Unable to clear storage");
|
|
|
|
auto& storage_items = storage_items_for_endpoint(storage_endpoint);
|
|
if (storage_items.is_empty())
|
|
return {};
|
|
|
|
storage_items.clear();
|
|
emit_storage_change({ storage_endpoint, storage_key, DevTools::DevToolsDelegate::StorageChange::Type::Cleared, {} });
|
|
return {};
|
|
}
|
|
|
|
virtual u64 add_storage_change_listener(DevTools::TabDescription const&, OnStorageChange callback) const override
|
|
{
|
|
auto listener_id = next_storage_change_listener_id++;
|
|
storage_change_listeners.set(listener_id, move(callback));
|
|
++add_storage_change_listener_call_count;
|
|
return listener_id;
|
|
}
|
|
|
|
virtual void remove_storage_change_listener(DevTools::TabDescription const&, u64 listener_id) const override
|
|
{
|
|
storage_change_listeners.remove(listener_id);
|
|
++remove_storage_change_listener_call_count;
|
|
}
|
|
|
|
virtual void inspect_indexed_database_storage(DevTools::TabDescription const&, OnIndexedDBInspectionComplete callback) const override
|
|
{
|
|
++inspect_indexed_database_storage_call_count;
|
|
callback(make_indexed_database_storage_hosts());
|
|
}
|
|
|
|
virtual void inspect_indexed_database_objects(DevTools::TabDescription const&, String const& host, Optional<JsonArray> names, JsonObject, OnIndexedDBInspectionComplete callback) const override
|
|
{
|
|
++inspect_indexed_database_objects_call_count;
|
|
last_indexed_database_host = host;
|
|
callback(make_indexed_database_store_objects(names));
|
|
}
|
|
|
|
virtual void delete_indexed_database(DevTools::TabDescription const&, String const& host, String const& name, OnIndexedDBInspectionComplete callback) const override
|
|
{
|
|
++delete_indexed_database_call_count;
|
|
last_indexed_database_host = host;
|
|
last_indexed_database_name = name;
|
|
callback(JsonObject {});
|
|
}
|
|
|
|
virtual void clear_indexed_database_object_store(DevTools::TabDescription const&, String const& host, String const& name, OnIndexedDBInspectionComplete callback) const override
|
|
{
|
|
++clear_indexed_database_object_store_call_count;
|
|
last_indexed_database_host = host;
|
|
last_indexed_database_name = name;
|
|
callback(JsonObject {});
|
|
}
|
|
|
|
virtual void delete_indexed_database_record(DevTools::TabDescription const&, String const& host, String const& name, OnIndexedDBInspectionComplete callback) const override
|
|
{
|
|
++delete_indexed_database_record_call_count;
|
|
last_indexed_database_host = host;
|
|
last_indexed_database_name = name;
|
|
callback(JsonObject {});
|
|
}
|
|
|
|
virtual u64 add_indexed_database_change_listener(DevTools::TabDescription const&, OnIndexedDatabaseChange callback) const override
|
|
{
|
|
auto listener_id = next_indexed_database_change_listener_id++;
|
|
indexed_database_change_listeners.set(listener_id, move(callback));
|
|
++add_indexed_database_change_listener_call_count;
|
|
return listener_id;
|
|
}
|
|
|
|
virtual void remove_indexed_database_change_listener(DevTools::TabDescription const&, u64 listener_id) const override
|
|
{
|
|
indexed_database_change_listeners.remove(listener_id);
|
|
++remove_indexed_database_change_listener_call_count;
|
|
}
|
|
|
|
virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete callback) const override
|
|
{
|
|
++inspect_tab_call_count;
|
|
callback(use_navigation_dom_tree ? make_navigation_dom_tree() : make_dom_tree());
|
|
}
|
|
|
|
virtual void inspect_accessibility_tree(DevTools::TabDescription const&, OnAccessibilityTreeInspectionComplete callback) const override
|
|
{
|
|
++inspect_accessibility_tree_call_count;
|
|
callback(make_accessibility_tree());
|
|
}
|
|
|
|
virtual void listen_for_dom_properties(DevTools::TabDescription const&, OnDOMNodePropertiesReceived callback) const override
|
|
{
|
|
++listen_for_dom_properties_call_count;
|
|
on_dom_node_properties = move(callback);
|
|
}
|
|
|
|
virtual void stop_listening_for_dom_properties(DevTools::TabDescription const&) const override
|
|
{
|
|
++stop_listening_for_dom_properties_call_count;
|
|
}
|
|
|
|
virtual void inspect_dom_node(DevTools::TabDescription const&, WebView::DOMNodeProperties::Type type, Web::UniqueNodeID node_id, Optional<Web::CSS::PseudoElement> pseudo_element, JsonObject options = {}) const override
|
|
{
|
|
++inspect_dom_node_call_count;
|
|
last_inspected_dom_node = node_id;
|
|
last_inspected_pseudo_element = pseudo_element;
|
|
last_inspected_dom_node_options = move(options);
|
|
|
|
Core::deferred_invoke([this, type] {
|
|
VERIFY(on_dom_node_properties);
|
|
if (type == WebView::DOMNodeProperties::Type::AppliedStyleRules)
|
|
on_dom_node_properties(make_applied_style_rules());
|
|
else if (type == WebView::DOMNodeProperties::Type::ComputedStyle)
|
|
on_dom_node_properties(make_computed_style());
|
|
else if (type == WebView::DOMNodeProperties::Type::Layout)
|
|
on_dom_node_properties(make_layout());
|
|
else
|
|
on_dom_node_properties(make_used_fonts());
|
|
});
|
|
}
|
|
|
|
virtual void clear_inspected_dom_node(DevTools::TabDescription const&) const override { ++clear_inspected_dom_node_call_count; }
|
|
virtual void clear_highlighted_dom_node(DevTools::TabDescription const&) const override { ++clear_highlighted_dom_node_call_count; }
|
|
|
|
virtual void start_node_picker(DevTools::TabDescription const&, OnNodePickerEvent callback) const override
|
|
{
|
|
++start_node_picker_call_count;
|
|
on_node_picker_event = move(callback);
|
|
}
|
|
|
|
virtual void stop_node_picker(DevTools::TabDescription const&) const override
|
|
{
|
|
++stop_node_picker_call_count;
|
|
}
|
|
|
|
virtual void clear_node_picker(DevTools::TabDescription const&) const override
|
|
{
|
|
++clear_node_picker_call_count;
|
|
}
|
|
|
|
virtual void inspect_grid_layouts(DevTools::TabDescription const&, Web::UniqueNodeID root_node_id, OnGridLayoutsReceived callback) const override
|
|
{
|
|
++inspect_grid_layouts_call_count;
|
|
last_grid_root_node = root_node_id;
|
|
callback(make_grid_layouts());
|
|
}
|
|
|
|
virtual void inspect_current_grid(DevTools::TabDescription const&, Web::UniqueNodeID node_id, OnCurrentGridReceived callback) const override
|
|
{
|
|
++inspect_current_grid_call_count;
|
|
last_current_grid_node = node_id;
|
|
|
|
if (node_id == Web::UniqueNodeID { 4 }) {
|
|
callback(make_grid_layout(node_id, "content"sv));
|
|
return;
|
|
}
|
|
|
|
callback({});
|
|
}
|
|
|
|
virtual void inspect_current_flexbox(DevTools::TabDescription const&, Web::UniqueNodeID node_id, bool only_look_at_parents, OnCurrentFlexboxReceived callback) const override
|
|
{
|
|
++inspect_current_flexbox_call_count;
|
|
last_current_flexbox_node = node_id;
|
|
last_current_flexbox_only_look_at_parents = only_look_at_parents;
|
|
|
|
if ((node_id == Web::UniqueNodeID { 10 } && !only_look_at_parents)
|
|
|| node_id == Web::UniqueNodeID { 11 }
|
|
|| node_id == Web::UniqueNodeID { 12 }) {
|
|
callback(make_flexbox_layout());
|
|
return;
|
|
}
|
|
|
|
callback({});
|
|
}
|
|
|
|
virtual void highlight_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID node_id, Optional<Web::CSS::PseudoElement> pseudo_element) const override
|
|
{
|
|
++highlight_dom_node_call_count;
|
|
last_highlighted_dom_node = node_id;
|
|
last_highlighted_pseudo_element = pseudo_element;
|
|
}
|
|
|
|
virtual void highlight_flexbox(DevTools::TabDescription const&, Web::UniqueNodeID node_id, JsonValue options) const override
|
|
{
|
|
++highlight_flexbox_call_count;
|
|
last_highlighted_flexbox_node = node_id;
|
|
last_flexbox_highlight_options = move(options);
|
|
}
|
|
|
|
virtual void clear_flexbox_highlight(DevTools::TabDescription const&, Web::UniqueNodeID node_id) const override
|
|
{
|
|
++clear_flexbox_highlight_call_count;
|
|
last_cleared_flexbox_node = node_id;
|
|
}
|
|
|
|
virtual void highlight_grid(DevTools::TabDescription const&, Web::UniqueNodeID node_id, JsonValue options) const override
|
|
{
|
|
++highlight_grid_call_count;
|
|
last_highlighted_grid_node = node_id;
|
|
last_grid_highlight_options = move(options);
|
|
}
|
|
|
|
virtual void clear_grid_highlight(DevTools::TabDescription const&, Web::UniqueNodeID node_id) const override
|
|
{
|
|
++clear_grid_highlight_call_count;
|
|
last_cleared_grid_node = node_id;
|
|
}
|
|
|
|
virtual void listen_for_dom_mutations(DevTools::TabDescription const&, OnDOMMutationReceived callback) const override
|
|
{
|
|
++listen_for_dom_mutations_call_count;
|
|
on_dom_mutation = move(callback);
|
|
}
|
|
|
|
virtual void stop_listening_for_dom_mutations(DevTools::TabDescription const&) const override { ++stop_listening_for_dom_mutations_call_count; }
|
|
|
|
virtual void get_dom_node_inner_html(DevTools::TabDescription const&, Web::UniqueNodeID node_id, OnDOMNodeHTMLReceived callback) const override
|
|
{
|
|
++get_dom_node_inner_html_call_count;
|
|
last_edited_node = node_id;
|
|
callback("<span>inner</span>"_string);
|
|
}
|
|
|
|
virtual void get_dom_node_outer_html(DevTools::TabDescription const&, Web::UniqueNodeID node_id, OnDOMNodeHTMLReceived callback) const override
|
|
{
|
|
++get_dom_node_outer_html_call_count;
|
|
last_edited_node = node_id;
|
|
callback("<div id=\"target\"></div>"_string);
|
|
}
|
|
|
|
virtual void set_dom_node_outer_html(DevTools::TabDescription const&, Web::UniqueNodeID node_id, String const& html, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++set_dom_node_outer_html_call_count;
|
|
last_edited_node = node_id;
|
|
last_html = html;
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void set_dom_node_text(DevTools::TabDescription const&, Web::UniqueNodeID node_id, String const& text, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++set_dom_node_text_call_count;
|
|
last_edited_node = node_id;
|
|
last_text = text;
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void set_dom_node_tag(DevTools::TabDescription const&, Web::UniqueNodeID node_id, String const& tag_name, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++set_dom_node_tag_call_count;
|
|
last_edited_node = node_id;
|
|
last_tag = tag_name;
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void add_dom_node_attributes(DevTools::TabDescription const&, Web::UniqueNodeID node_id, ReadonlySpan<WebView::Attribute> attributes, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++add_dom_node_attributes_call_count;
|
|
last_edited_node = node_id;
|
|
last_attribute_count = attributes.size();
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void replace_dom_node_attribute(DevTools::TabDescription const&, Web::UniqueNodeID node_id, String const& attribute, ReadonlySpan<WebView::Attribute> attributes, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++replace_dom_node_attribute_call_count;
|
|
last_edited_node = node_id;
|
|
last_attribute = attribute;
|
|
last_attribute_count = attributes.size();
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void create_child_element(DevTools::TabDescription const&, Web::UniqueNodeID node_id, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++create_child_element_call_count;
|
|
last_edited_node = node_id;
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void insert_dom_node_before(DevTools::TabDescription const&, Web::UniqueNodeID node_id, Web::UniqueNodeID parent_id, Optional<Web::UniqueNodeID> sibling_id, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++insert_dom_node_before_call_count;
|
|
last_edited_node = node_id;
|
|
last_parent_node = parent_id;
|
|
last_sibling_node = sibling_id;
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void clone_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID node_id, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++clone_dom_node_call_count;
|
|
last_edited_node = node_id;
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void remove_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID node_id, OnDOMNodeEditComplete callback) const override
|
|
{
|
|
++remove_dom_node_call_count;
|
|
last_edited_node = node_id;
|
|
callback(node_id);
|
|
}
|
|
|
|
virtual void retrieve_style_sheets(DevTools::TabDescription const&, OnStyleSheetsReceived callback) const override
|
|
{
|
|
++retrieve_style_sheets_call_count;
|
|
Vector<Web::CSS::StyleSheetIdentifier> style_sheets;
|
|
style_sheets.append(fixture_style_sheet());
|
|
style_sheets.append(fixture_user_agent_style_sheet());
|
|
callback(move(style_sheets));
|
|
}
|
|
|
|
virtual void retrieve_style_sheet_source(DevTools::TabDescription const&, Web::CSS::StyleSheetIdentifier const&) const override
|
|
{
|
|
++retrieve_style_sheet_source_call_count;
|
|
Core::deferred_invoke([this] {
|
|
VERIFY(on_style_sheet_source);
|
|
on_style_sheet_source(fixture_style_sheet(), "body { color: red; }"_string);
|
|
});
|
|
}
|
|
|
|
virtual void listen_for_style_sheet_sources(DevTools::TabDescription const&, OnStyleSheetSourceReceived callback) const override
|
|
{
|
|
++listen_for_style_sheet_sources_call_count;
|
|
on_style_sheet_source = move(callback);
|
|
}
|
|
|
|
virtual void stop_listening_for_style_sheet_sources(DevTools::TabDescription const&) const override { ++stop_listening_for_style_sheet_sources_call_count; }
|
|
|
|
virtual void listen_for_console_messages(DevTools::TabDescription const&, OnConsoleMessage callback) const override
|
|
{
|
|
++listen_for_console_messages_call_count;
|
|
on_console_message = move(callback);
|
|
}
|
|
|
|
virtual void stop_listening_for_console_messages(DevTools::TabDescription const&) const override { ++stop_listening_for_console_messages_call_count; }
|
|
|
|
virtual void listen_for_network_events(DevTools::TabDescription const&, OnNetworkRequestStarted on_request_started, OnNetworkResponseHeadersReceived on_headers_received, OnNetworkResponseBodyReceived on_body_received, OnNetworkRequestFinished on_request_finished) const override
|
|
{
|
|
++listen_for_network_events_call_count;
|
|
on_network_request_started = move(on_request_started);
|
|
on_network_response_headers_received = move(on_headers_received);
|
|
on_network_response_body_received = move(on_body_received);
|
|
on_network_request_finished = move(on_request_finished);
|
|
}
|
|
|
|
virtual void stop_listening_for_network_events(DevTools::TabDescription const&) const override { ++stop_listening_for_network_events_call_count; }
|
|
|
|
virtual void listen_for_navigation_events(DevTools::TabDescription const&, OnNavigationStarted on_started, OnNavigationFinished on_finished) const override
|
|
{
|
|
++listen_for_navigation_events_call_count;
|
|
navigation_listeners.append({ move(on_started), move(on_finished) });
|
|
}
|
|
|
|
virtual void stop_listening_for_navigation_events(DevTools::TabDescription const&) const override
|
|
{
|
|
++stop_listening_for_navigation_events_call_count;
|
|
if (!navigation_listeners.is_empty())
|
|
navigation_listeners.take_first();
|
|
}
|
|
virtual void did_connect_devtools_client(DevTools::TabDescription const&) const override { ++did_connect_devtools_client_call_count; }
|
|
virtual void did_disconnect_devtools_client(DevTools::TabDescription const&) const override { ++did_disconnect_devtools_client_call_count; }
|
|
|
|
void emit_mutation(WebView::Mutation mutation) const
|
|
{
|
|
VERIFY(on_dom_mutation);
|
|
on_dom_mutation(move(mutation));
|
|
}
|
|
|
|
void emit_console_log(JS::Console::LogLevel level, Vector<JsonValue> arguments) const
|
|
{
|
|
VERIFY(on_console_message);
|
|
on_console_message({ UnixDateTime::from_seconds_since_epoch(10), WebView::ConsoleLog { level, move(arguments) } });
|
|
}
|
|
|
|
void emit_console_trace() const
|
|
{
|
|
VERIFY(on_console_message);
|
|
Vector<WebView::StackFrame> stack;
|
|
stack.append({ "traceFunction"_string, "trace.js"_string, 12, 34 });
|
|
on_console_message({ UnixDateTime::from_seconds_since_epoch(11), WebView::ConsoleTrace { "trace label"_string, move(stack) } });
|
|
}
|
|
|
|
void emit_console_error() const
|
|
{
|
|
VERIFY(on_console_message);
|
|
Vector<WebView::StackFrame> stack;
|
|
stack.append({ "boom"_string, "error.js"_string, 56, 78 });
|
|
on_console_message({ UnixDateTime::from_seconds_since_epoch(12), WebView::ConsoleError { "TypeError"_string, "bad things"_string, move(stack), true } });
|
|
}
|
|
|
|
void emit_network_lifecycle() const
|
|
{
|
|
VERIFY(on_network_request_started);
|
|
VERIFY(on_network_response_headers_received);
|
|
VERIFY(on_network_response_body_received);
|
|
VERIFY(on_network_request_finished);
|
|
|
|
ByteBuffer request_body;
|
|
request_body.append("request", 7);
|
|
|
|
Vector<HTTP::Header> request_headers;
|
|
request_headers.append({ ByteString::formatted("Accept"), ByteString::formatted("*/*") });
|
|
|
|
on_network_request_started({ .request_id = 100,
|
|
.url = "https://example.test/data.json"_string,
|
|
.method = "POST"_string,
|
|
.start_time = UnixDateTime::from_seconds_since_epoch(20),
|
|
.request_headers = move(request_headers),
|
|
.request_body = move(request_body),
|
|
.initiator_type = "fetch"_string });
|
|
|
|
Vector<HTTP::Header> response_headers;
|
|
response_headers.append({ ByteString::formatted("Content-Type"), ByteString::formatted("application/json") });
|
|
on_network_response_headers_received({ .request_id = 100,
|
|
.status_code = 200,
|
|
.reason_phrase = "OK"_string,
|
|
.response_headers = move(response_headers) });
|
|
|
|
ByteBuffer response_body;
|
|
response_body.append("{\"ok\":true}", 11);
|
|
on_network_response_body_received(100, move(response_body));
|
|
|
|
Requests::RequestTimingInfo timing_info;
|
|
timing_info.request_start_microseconds = 1000;
|
|
timing_info.domain_lookup_start_microseconds = 1000;
|
|
timing_info.domain_lookup_end_microseconds = 2000;
|
|
timing_info.connect_start_microseconds = 2000;
|
|
timing_info.connect_end_microseconds = 4000;
|
|
timing_info.response_start_microseconds = 5000;
|
|
timing_info.response_end_microseconds = 9000;
|
|
on_network_request_finished({ .request_id = 100, .body_size = 11, .timing_info = timing_info, .network_error = {} });
|
|
}
|
|
|
|
void emit_navigation() const
|
|
{
|
|
emit_navigation_start();
|
|
emit_navigation_finish();
|
|
}
|
|
|
|
void emit_navigation_start() const
|
|
{
|
|
VERIFY(!navigation_listeners.is_empty());
|
|
auto listener_count = navigation_listeners.size();
|
|
for (size_t i = 0; i < listener_count; ++i)
|
|
navigation_listeners[i].on_navigation_started("https://example.test/next"_string);
|
|
}
|
|
|
|
void emit_navigation_finish() const
|
|
{
|
|
VERIFY(!navigation_listeners.is_empty());
|
|
auto listener_count = navigation_listeners.size();
|
|
for (size_t i = 0; i < listener_count; ++i)
|
|
navigation_listeners[i].on_navigation_finished("https://example.test/next"_string, "Next page"_string);
|
|
}
|
|
|
|
size_t navigation_listener_count() const
|
|
{
|
|
return navigation_listeners.size();
|
|
}
|
|
|
|
void switch_to_navigation_dom_tree() const
|
|
{
|
|
use_navigation_dom_tree = true;
|
|
}
|
|
|
|
void emit_node_picker_event(DevToolsDelegate::NodePickerEvent event) const
|
|
{
|
|
VERIFY(on_node_picker_event);
|
|
on_node_picker_event(move(event));
|
|
}
|
|
|
|
void emit_cookie_change(Vector<HTTP::Cookie::Cookie> cookies) const
|
|
{
|
|
VERIFY(on_host_cookie_change);
|
|
on_host_cookie_change(move(cookies));
|
|
}
|
|
|
|
void emit_storage_change(DevToolsDelegate::StorageChange change) const
|
|
{
|
|
for (auto& listener : storage_change_listeners)
|
|
listener.value(change);
|
|
}
|
|
|
|
Vector<DevTools::DevToolsDelegate::StorageItem>& storage_items_for_endpoint(Web::StorageAPI::StorageEndpointType storage_endpoint) const
|
|
{
|
|
if (storage_endpoint == Web::StorageAPI::StorageEndpointType::LocalStorage)
|
|
return fixture_local_storage_items;
|
|
|
|
VERIFY(storage_endpoint == Web::StorageAPI::StorageEndpointType::SessionStorage);
|
|
return fixture_session_storage_items;
|
|
}
|
|
|
|
void emit_indexed_database_change(JsonObject update) const
|
|
{
|
|
VERIFY(!indexed_database_change_listeners.is_empty());
|
|
for (auto& listener : indexed_database_change_listeners)
|
|
listener.value(update);
|
|
}
|
|
|
|
mutable Function<void(WebView::DOMNodeProperties)> on_dom_node_properties;
|
|
mutable Function<void(WebView::Mutation)> on_dom_mutation;
|
|
mutable Function<void(Web::CSS::StyleSheetIdentifier const&, String)> on_style_sheet_source;
|
|
mutable Function<void(WebView::ConsoleOutput)> on_console_message;
|
|
mutable Function<void(DevToolsDelegate::NetworkRequestData)> on_network_request_started;
|
|
mutable Function<void(DevToolsDelegate::NetworkResponseData)> on_network_response_headers_received;
|
|
mutable Function<void(u64, ByteBuffer)> on_network_response_body_received;
|
|
mutable Function<void(DevToolsDelegate::NetworkRequestCompleteData)> on_network_request_finished;
|
|
mutable Function<void(DevToolsDelegate::NodePickerEvent)> on_node_picker_event;
|
|
mutable Function<void(Vector<HTTP::Cookie::Cookie>)> on_host_cookie_change;
|
|
mutable HashMap<u64, Function<void(DevToolsDelegate::StorageChange)>> storage_change_listeners;
|
|
String tab_url { "https://example.test/"_string };
|
|
mutable HashMap<u64, Function<void(JsonObject)>> indexed_database_change_listeners;
|
|
|
|
struct NavigationListener {
|
|
Function<void(String)> on_navigation_started;
|
|
Function<void(String, String)> on_navigation_finished;
|
|
};
|
|
mutable Vector<NavigationListener> navigation_listeners;
|
|
|
|
mutable bool use_navigation_dom_tree { false };
|
|
mutable Vector<HTTP::Cookie::Cookie> fixture_cookies;
|
|
mutable Vector<DevTools::DevToolsDelegate::StorageItem> fixture_local_storage_items;
|
|
mutable Vector<DevTools::DevToolsDelegate::StorageItem> fixture_session_storage_items;
|
|
|
|
mutable size_t inspect_tab_call_count { 0 };
|
|
mutable size_t cookies_call_count { 0 };
|
|
mutable size_t set_cookie_call_count { 0 };
|
|
mutable size_t delete_cookies_call_count { 0 };
|
|
mutable size_t listen_for_cookie_changes_call_count { 0 };
|
|
mutable size_t stop_listening_for_cookie_changes_call_count { 0 };
|
|
mutable size_t inspect_storage_call_count { 0 };
|
|
mutable size_t set_storage_item_call_count { 0 };
|
|
mutable size_t remove_storage_item_call_count { 0 };
|
|
mutable size_t clear_storage_call_count { 0 };
|
|
mutable size_t add_storage_change_listener_call_count { 0 };
|
|
mutable size_t remove_storage_change_listener_call_count { 0 };
|
|
mutable u64 next_storage_change_listener_id { 1 };
|
|
mutable bool fail_set_storage_item { false };
|
|
mutable bool fail_remove_storage_item { false };
|
|
mutable bool fail_clear_storage { false };
|
|
mutable size_t inspect_indexed_database_storage_call_count { 0 };
|
|
mutable size_t inspect_indexed_database_objects_call_count { 0 };
|
|
mutable size_t delete_indexed_database_call_count { 0 };
|
|
mutable size_t clear_indexed_database_object_store_call_count { 0 };
|
|
mutable size_t delete_indexed_database_record_call_count { 0 };
|
|
mutable size_t add_indexed_database_change_listener_call_count { 0 };
|
|
mutable size_t remove_indexed_database_change_listener_call_count { 0 };
|
|
mutable u64 next_indexed_database_change_listener_id { 1 };
|
|
mutable size_t inspect_accessibility_tree_call_count { 0 };
|
|
mutable size_t listen_for_dom_properties_call_count { 0 };
|
|
mutable size_t stop_listening_for_dom_properties_call_count { 0 };
|
|
mutable size_t inspect_dom_node_call_count { 0 };
|
|
mutable size_t clear_inspected_dom_node_call_count { 0 };
|
|
mutable size_t start_node_picker_call_count { 0 };
|
|
mutable size_t stop_node_picker_call_count { 0 };
|
|
mutable size_t clear_node_picker_call_count { 0 };
|
|
mutable size_t inspect_grid_layouts_call_count { 0 };
|
|
mutable size_t inspect_current_grid_call_count { 0 };
|
|
mutable size_t inspect_current_flexbox_call_count { 0 };
|
|
mutable size_t highlight_dom_node_call_count { 0 };
|
|
mutable size_t clear_highlighted_dom_node_call_count { 0 };
|
|
mutable size_t highlight_flexbox_call_count { 0 };
|
|
mutable size_t clear_flexbox_highlight_call_count { 0 };
|
|
mutable size_t highlight_grid_call_count { 0 };
|
|
mutable size_t clear_grid_highlight_call_count { 0 };
|
|
mutable size_t listen_for_dom_mutations_call_count { 0 };
|
|
mutable size_t stop_listening_for_dom_mutations_call_count { 0 };
|
|
mutable size_t get_dom_node_inner_html_call_count { 0 };
|
|
mutable size_t get_dom_node_outer_html_call_count { 0 };
|
|
mutable size_t set_dom_node_outer_html_call_count { 0 };
|
|
mutable size_t set_dom_node_text_call_count { 0 };
|
|
mutable size_t set_dom_node_tag_call_count { 0 };
|
|
mutable size_t add_dom_node_attributes_call_count { 0 };
|
|
mutable size_t replace_dom_node_attribute_call_count { 0 };
|
|
mutable size_t create_child_element_call_count { 0 };
|
|
mutable size_t insert_dom_node_before_call_count { 0 };
|
|
mutable size_t clone_dom_node_call_count { 0 };
|
|
mutable size_t remove_dom_node_call_count { 0 };
|
|
mutable size_t retrieve_style_sheets_call_count { 0 };
|
|
mutable size_t retrieve_style_sheet_source_call_count { 0 };
|
|
mutable size_t listen_for_style_sheet_sources_call_count { 0 };
|
|
mutable size_t stop_listening_for_style_sheet_sources_call_count { 0 };
|
|
mutable size_t listen_for_console_messages_call_count { 0 };
|
|
mutable size_t stop_listening_for_console_messages_call_count { 0 };
|
|
mutable size_t listen_for_network_events_call_count { 0 };
|
|
mutable size_t stop_listening_for_network_events_call_count { 0 };
|
|
mutable size_t listen_for_navigation_events_call_count { 0 };
|
|
mutable size_t stop_listening_for_navigation_events_call_count { 0 };
|
|
mutable size_t did_connect_devtools_client_call_count { 0 };
|
|
mutable size_t did_disconnect_devtools_client_call_count { 0 };
|
|
mutable size_t navigate_tab_call_count { 0 };
|
|
mutable size_t reload_tab_call_count { 0 };
|
|
mutable size_t traverse_the_history_by_delta_call_count { 0 };
|
|
|
|
mutable Optional<Web::UniqueNodeID> last_highlighted_dom_node;
|
|
mutable Optional<Web::CSS::PseudoElement> last_highlighted_pseudo_element;
|
|
mutable Optional<Web::UniqueNodeID> last_inspected_dom_node;
|
|
mutable Optional<Web::CSS::PseudoElement> last_inspected_pseudo_element;
|
|
mutable JsonObject last_inspected_dom_node_options;
|
|
mutable Optional<Web::UniqueNodeID> last_grid_root_node;
|
|
mutable Optional<Web::UniqueNodeID> last_current_grid_node;
|
|
mutable Optional<Web::UniqueNodeID> last_current_flexbox_node;
|
|
mutable bool last_current_flexbox_only_look_at_parents { false };
|
|
mutable Optional<Web::UniqueNodeID> last_highlighted_flexbox_node;
|
|
mutable Optional<Web::UniqueNodeID> last_cleared_flexbox_node;
|
|
mutable JsonValue last_flexbox_highlight_options;
|
|
mutable Optional<Web::UniqueNodeID> last_highlighted_grid_node;
|
|
mutable Optional<Web::UniqueNodeID> last_cleared_grid_node;
|
|
mutable JsonValue last_grid_highlight_options;
|
|
mutable Optional<Web::UniqueNodeID> last_edited_node;
|
|
mutable Optional<Web::UniqueNodeID> last_parent_node;
|
|
mutable Optional<Web::UniqueNodeID> last_sibling_node;
|
|
mutable Optional<String> last_html;
|
|
mutable Optional<String> last_text;
|
|
mutable Optional<String> last_tag;
|
|
mutable Optional<String> last_attribute;
|
|
mutable size_t last_attribute_count { 0 };
|
|
mutable Optional<String> last_navigated_url;
|
|
mutable Optional<bool> last_reload_bypass_cache;
|
|
mutable Optional<int> last_history_delta;
|
|
mutable Optional<String> last_indexed_database_host;
|
|
};
|
|
|
|
class ProtocolClient {
|
|
public:
|
|
static NonnullOwnPtr<ProtocolClient> connect(Core::EventLoop& loop, DevTools::DevToolsServer& server)
|
|
{
|
|
auto port = server.local_port();
|
|
VERIFY(port.has_value());
|
|
auto socket = MUST(Core::TCPSocket::connect("127.0.0.1", *port));
|
|
MUST(socket->set_blocking(false));
|
|
|
|
auto client = adopt_own(*new ProtocolClient(loop, move(socket)));
|
|
spin_until(loop, [&] { return !!server.connection(); });
|
|
return client;
|
|
}
|
|
|
|
JsonObject read_message()
|
|
{
|
|
if (!m_pending_messages.is_empty()) {
|
|
auto message = move(m_pending_messages.first());
|
|
m_pending_messages.remove(0);
|
|
return message;
|
|
}
|
|
return read_message_from_socket();
|
|
}
|
|
|
|
void send(JsonObject message)
|
|
{
|
|
auto serialized = message.serialized();
|
|
auto packet = MUST(String::formatted("{}:{}", serialized.byte_count(), serialized));
|
|
send_packet_bytes(packet.bytes());
|
|
}
|
|
|
|
NonnullRefPtr<Threading::Thread> send_in_two_fragments(JsonObject message, size_t first_fragment_size, Atomic<bool>& may_send_second_fragment, Atomic<bool>& sent_second_fragment)
|
|
{
|
|
auto serialized = message.serialized();
|
|
auto packet = MUST(String::formatted("{}:{}", serialized.byte_count(), serialized));
|
|
VERIFY(first_fragment_size < packet.byte_count());
|
|
|
|
send_packet_bytes(packet.bytes().slice(0, first_fragment_size));
|
|
|
|
return Threading::Thread::construct("DevToolsFragmentSender"sv, [this, packet = move(packet), first_fragment_size, &may_send_second_fragment, &sent_second_fragment]() -> intptr_t {
|
|
for (auto i = 0; i < 5000 && !may_send_second_fragment; ++i)
|
|
MUST(Core::System::sleep_ms(1));
|
|
|
|
sent_second_fragment = true;
|
|
send_packet_bytes(packet.bytes().slice(first_fragment_size));
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
JsonObject request(StringView to, StringView type)
|
|
{
|
|
JsonObject message;
|
|
message.set("to"sv, to);
|
|
message.set("type"sv, type);
|
|
return request(move(message), to);
|
|
}
|
|
|
|
JsonObject request(JsonObject message)
|
|
{
|
|
auto to = message.get_string("to"sv).value_or("root"_string);
|
|
return request(move(message), to);
|
|
}
|
|
|
|
private:
|
|
void send_packet_bytes(ReadonlyBytes bytes)
|
|
{
|
|
MUST(m_socket->set_blocking(true));
|
|
auto restore_nonblocking = ScopeGuard([&] { MUST(m_socket->set_blocking(false)); });
|
|
MUST(m_socket->write_until_depleted(bytes));
|
|
}
|
|
|
|
ProtocolClient(Core::EventLoop& loop, NonnullOwnPtr<Core::TCPSocket> socket)
|
|
: m_loop(loop)
|
|
, m_socket(move(socket))
|
|
{
|
|
}
|
|
|
|
static bool is_event_packet(JsonObject const& packet)
|
|
{
|
|
auto type = packet.get_string("type"sv);
|
|
if (!type.has_value())
|
|
return false;
|
|
return *type == "frameUpdate"sv
|
|
|| *type == "resources-available-array"sv
|
|
|| *type == "resources-updated-array"sv
|
|
|| *type == "newMutations"sv
|
|
|| *type == "pickerNodeCanceled"sv
|
|
|| *type == "pickerNodeHovered"sv
|
|
|| *type == "pickerNodePicked"sv
|
|
|| *type == "pickerNodePreviewed"sv
|
|
|| *type == "tabListChanged"sv
|
|
|| *type == "storesCleared"sv
|
|
|| *type == "storesUpdate"sv
|
|
|| *type == "target-available-form"sv
|
|
|| *type == "target-destroyed-form"sv;
|
|
}
|
|
|
|
JsonObject request(JsonObject message, StringView response_actor)
|
|
{
|
|
send(move(message));
|
|
while (true) {
|
|
auto response = read_message_from_socket();
|
|
if (response.get_string("from"sv).value_or({}) == response_actor && !is_event_packet(response))
|
|
return response;
|
|
m_pending_messages.append(move(response));
|
|
}
|
|
}
|
|
|
|
JsonObject read_message_from_socket()
|
|
{
|
|
Optional<JsonObject> message;
|
|
spin_until(m_loop, [&] {
|
|
pump(m_loop);
|
|
if (!MUST(m_socket->can_read_without_blocking()))
|
|
return false;
|
|
message = read_message_now();
|
|
return true;
|
|
});
|
|
return message.release_value();
|
|
}
|
|
|
|
JsonObject read_message_now()
|
|
{
|
|
MUST(m_socket->set_blocking(true));
|
|
auto restore_nonblocking = ScopeGuard([&] { MUST(m_socket->set_blocking(false)); });
|
|
|
|
ByteBuffer length_buffer;
|
|
while (true) {
|
|
auto byte = MUST(m_socket->read_value<u8>());
|
|
if (byte == ':')
|
|
break;
|
|
length_buffer.append(byte);
|
|
}
|
|
|
|
auto length = StringView { length_buffer }.to_number<size_t>();
|
|
VERIFY(length.has_value());
|
|
|
|
ByteBuffer message_buffer;
|
|
message_buffer.resize(*length);
|
|
MUST(m_socket->read_until_filled(message_buffer));
|
|
|
|
auto value = JsonValue::from_string(message_buffer);
|
|
VERIFY(!value.is_error());
|
|
VERIFY(value.value().is_object());
|
|
return value.release_value().as_object();
|
|
}
|
|
|
|
Core::EventLoop& m_loop;
|
|
NonnullOwnPtr<Core::TCPSocket> m_socket;
|
|
Vector<JsonObject> m_pending_messages;
|
|
};
|
|
|
|
struct TestSession {
|
|
Core::EventLoop loop;
|
|
TestDevToolsDelegate delegate;
|
|
OwnPtr<DevTools::DevToolsServer> server;
|
|
OwnPtr<ProtocolClient> client;
|
|
};
|
|
|
|
static NonnullOwnPtr<TestSession> create_session(StringView tab_url = "https://example.test/"sv)
|
|
{
|
|
auto session = make<TestSession>();
|
|
session->delegate.tab_url = MUST(String::from_utf8(tab_url));
|
|
session->server = MUST(DevTools::DevToolsServer::create(session->delegate, 0));
|
|
session->client = ProtocolClient::connect(session->loop, *session->server);
|
|
return session;
|
|
}
|
|
|
|
static String actor_from(JsonObject const& object, StringView key)
|
|
{
|
|
return object.get_string(key).release_value();
|
|
}
|
|
|
|
static JsonObject get_tab(ProtocolClient& client)
|
|
{
|
|
auto tabs = client.request("root"sv, "listTabs"sv).get_array("tabs"sv).release_value();
|
|
VERIFY(tabs.size() == 1u);
|
|
return tabs.at(0).as_object();
|
|
}
|
|
|
|
static size_t style_rule_actor_count(DevTools::DevToolsServer const& server)
|
|
{
|
|
size_t count = 0;
|
|
for (auto const& actor : server.actor_registry()) {
|
|
if (actor.key.bytes_as_string_view().contains("-style-rule"sv))
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static JsonObject get_frame_target(ProtocolClient& client, StringView tab_actor)
|
|
{
|
|
auto watcher_actor = actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv);
|
|
|
|
JsonObject request;
|
|
request.set("to"sv, watcher_actor);
|
|
request.set("type"sv, "watchTargets"sv);
|
|
request.set("targetType"sv, "frame"sv);
|
|
|
|
EXPECT_EQ(client.request(move(request)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
while (true) {
|
|
auto message = client.read_message();
|
|
if (message.get_string("type"sv).value_or({}) == "target-available-form"sv)
|
|
return message.get_object("target"sv).release_value();
|
|
}
|
|
}
|
|
|
|
static JsonObject get_walker(ProtocolClient& client, StringView inspector_actor)
|
|
{
|
|
return client.request(inspector_actor, "getWalker"sv).get_object("walker"sv).release_value();
|
|
}
|
|
|
|
static String query_selector(ProtocolClient& client, StringView walker_actor, StringView root_node, StringView selector)
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, walker_actor);
|
|
request.set("type"sv, "querySelector"sv);
|
|
request.set("node"sv, root_node);
|
|
request.set("selector"sv, selector);
|
|
return client.request(move(request)).get_object("node"sv)->get_string("actor"sv).release_value();
|
|
}
|
|
|
|
static JsonObject read_resource(ProtocolClient& client, StringView resource_type, StringView packet_type = "resources-available-array"sv)
|
|
{
|
|
while (true) {
|
|
auto message = client.read_message();
|
|
if (message.get_string("type"sv).value_or({}) != packet_type)
|
|
continue;
|
|
|
|
auto array = message.get_array("array"sv);
|
|
if (!array.has_value())
|
|
continue;
|
|
|
|
for (auto const& entry : array->values()) {
|
|
if (!entry.is_array() || entry.as_array().size() < 2)
|
|
continue;
|
|
if (!entry.as_array().at(0).is_string() || entry.as_array().at(0).as_string() != resource_type)
|
|
continue;
|
|
auto const& resources = entry.as_array().at(1).as_array();
|
|
VERIFY(!resources.is_empty());
|
|
return resources.at(0).as_object();
|
|
}
|
|
}
|
|
}
|
|
|
|
static JsonObject read_packet_with_type(ProtocolClient& client, StringView packet_type)
|
|
{
|
|
while (true) {
|
|
auto message = client.read_message();
|
|
if (message.get_string("type"sv).value_or({}) == packet_type)
|
|
return message;
|
|
}
|
|
}
|
|
|
|
static String get_cookies_actor(ProtocolClient& client)
|
|
{
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
auto watcher_actor = actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv);
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append("cookies"sv);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
auto cookie_resource = read_resource(client, "cookies"sv);
|
|
return actor_from(cookie_resource, "actor"sv);
|
|
}
|
|
|
|
static JsonObject get_cookie_store_objects(ProtocolClient& client, StringView cookies_actor, StringView host = "https://example.test"sv)
|
|
{
|
|
JsonObject get_store_objects;
|
|
get_store_objects.set("to"sv, cookies_actor);
|
|
get_store_objects.set("type"sv, "getStoreObjects"sv);
|
|
get_store_objects.set("host"sv, host);
|
|
get_store_objects.set("names"sv, JsonValue {});
|
|
JsonObject options;
|
|
options.set("sessionString"sv, "Session"sv);
|
|
get_store_objects.set("options"sv, move(options));
|
|
return client.request(move(get_store_objects));
|
|
}
|
|
|
|
static String get_storage_actor(ProtocolClient& client, StringView resource_type)
|
|
{
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
auto watcher_actor = actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv);
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append(resource_type);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
return actor_from(read_resource(client, resource_type), "actor"sv);
|
|
}
|
|
|
|
static JsonObject get_storage_store_objects(ProtocolClient& client, StringView storage_actor, StringView host = "https://example.test"sv, Optional<JsonArray> names = {})
|
|
{
|
|
JsonObject get_store_objects;
|
|
get_store_objects.set("to"sv, storage_actor);
|
|
get_store_objects.set("type"sv, "getStoreObjects"sv);
|
|
get_store_objects.set("host"sv, host);
|
|
if (names.has_value())
|
|
get_store_objects.set("names"sv, names.release_value());
|
|
else
|
|
get_store_objects.set("names"sv, JsonValue {});
|
|
get_store_objects.set("options"sv, JsonObject {});
|
|
return client.request(move(get_store_objects));
|
|
}
|
|
|
|
static JsonArray get_cookie_update_keys(JsonObject const& stores_update, StringView update_type, StringView host = "https://example.test"sv)
|
|
{
|
|
return stores_update.get_object("data"sv)
|
|
->get_object(update_type)
|
|
->get_object("cookies"sv)
|
|
->get_array(host)
|
|
.release_value();
|
|
}
|
|
|
|
static JsonArray get_storage_update_keys(JsonObject const& stores_update, StringView update_type, StringView resource_key, StringView host = "https://example.test"sv)
|
|
{
|
|
return stores_update.get_object("data"sv)
|
|
->get_object(update_type)
|
|
->get_object(resource_key)
|
|
->get_array(host)
|
|
.release_value();
|
|
}
|
|
|
|
static JsonObject make_storage_edit_items(StringView name, StringView value)
|
|
{
|
|
JsonObject items;
|
|
items.set("name"sv, name);
|
|
items.set("value"sv, value);
|
|
return items;
|
|
}
|
|
|
|
static JsonObject edit_storage_item(ProtocolClient& client, StringView storage_actor, StringView field, JsonValue old_value, StringView name, StringView value)
|
|
{
|
|
JsonObject data;
|
|
data.set("host"sv, "https://example.test"sv);
|
|
data.set("field"sv, field);
|
|
data.set("oldValue"sv, move(old_value));
|
|
data.set("items"sv, make_storage_edit_items(name, value));
|
|
|
|
JsonObject request;
|
|
request.set("to"sv, storage_actor);
|
|
request.set("type"sv, "editItem"sv);
|
|
request.set("data"sv, move(data));
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static JsonObject add_storage_item(ProtocolClient& client, StringView storage_actor, StringView name)
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, storage_actor);
|
|
request.set("type"sv, "addItem"sv);
|
|
request.set("guid"sv, name);
|
|
request.set("host"sv, "https://example.test"sv);
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static JsonObject remove_storage_item(ProtocolClient& client, StringView storage_actor, StringView name)
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, storage_actor);
|
|
request.set("type"sv, "removeItem"sv);
|
|
request.set("host"sv, "https://example.test"sv);
|
|
request.set("name"sv, name);
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static JsonObject remove_all_storage_items(ProtocolClient& client, StringView storage_actor)
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, storage_actor);
|
|
request.set("type"sv, "removeAll"sv);
|
|
request.set("host"sv, "https://example.test"sv);
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static String get_indexed_database_actor(ProtocolClient& client)
|
|
{
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
auto watcher_actor = actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv);
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append("indexed-db"sv);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
auto indexed_database_resource = read_resource(client, "indexed-db"sv);
|
|
return actor_from(indexed_database_resource, "actor"sv);
|
|
}
|
|
|
|
static JsonObject get_indexed_database_store_objects(ProtocolClient& client, StringView indexed_database_actor, Optional<String> name = {})
|
|
{
|
|
JsonObject get_store_objects;
|
|
get_store_objects.set("to"sv, indexed_database_actor);
|
|
get_store_objects.set("type"sv, "getStoreObjects"sv);
|
|
get_store_objects.set("host"sv, "https://example.test"sv);
|
|
if (name.has_value()) {
|
|
JsonArray names;
|
|
names.must_append(*name);
|
|
get_store_objects.set("names"sv, move(names));
|
|
} else {
|
|
get_store_objects.set("names"sv, JsonValue {});
|
|
}
|
|
get_store_objects.set("options"sv, JsonObject {});
|
|
return client.request(move(get_store_objects));
|
|
}
|
|
|
|
static JsonObject get_indexed_database_store_objects(ProtocolClient& client, StringView indexed_database_actor, JsonArray names)
|
|
{
|
|
JsonObject get_store_objects;
|
|
get_store_objects.set("to"sv, indexed_database_actor);
|
|
get_store_objects.set("type"sv, "getStoreObjects"sv);
|
|
get_store_objects.set("host"sv, "https://example.test"sv);
|
|
get_store_objects.set("names"sv, move(names));
|
|
get_store_objects.set("options"sv, JsonObject {});
|
|
return client.request(move(get_store_objects));
|
|
}
|
|
|
|
static JsonArray get_indexed_database_update_paths(JsonObject const& stores_update, StringView update_type, StringView host = "https://example.test"sv)
|
|
{
|
|
return stores_update.get_object("data"sv)
|
|
->get_object(update_type)
|
|
->get_object("indexedDB"sv)
|
|
->get_array(host)
|
|
.release_value();
|
|
}
|
|
|
|
static JsonArray get_indexed_database_cleared_paths(JsonObject const& stores_cleared, StringView host = "https://example.test"sv)
|
|
{
|
|
return stores_cleared.get_object("data"sv)
|
|
->get_object("clearedHostsOrPaths"sv)
|
|
->get_array(host)
|
|
.release_value();
|
|
}
|
|
|
|
static JsonObject make_cookie_edit_items(HTTP::Cookie::Cookie const& cookie)
|
|
{
|
|
JsonObject items;
|
|
items.set("uniqueKey"sv, cookie_unique_key(cookie.name, cookie.domain, cookie.path));
|
|
items.set("name"sv, cookie.name);
|
|
items.set("value"sv, cookie.value);
|
|
items.set("host"sv, cookie.domain);
|
|
items.set("path"sv, cookie.path);
|
|
items.set("expires"sv, cookie.persistent ? MUST(cookie.expiry_time.to_string("%a, %d %b %Y %T GMT"sv, UnixDateTime::LocalTime::No)) : "Session"_string);
|
|
items.set("isSecure"sv, cookie.secure);
|
|
items.set("isHttpOnly"sv, cookie.http_only);
|
|
return items;
|
|
}
|
|
|
|
static JsonObject edit_cookie(ProtocolClient& client, StringView cookies_actor, HTTP::Cookie::Cookie const& cookie, StringView field, JsonValue old_value, JsonValue new_value)
|
|
{
|
|
JsonObject data;
|
|
data.set("host"sv, "https://example.test"sv);
|
|
data.set("field"sv, field);
|
|
data.set("oldValue"sv, move(old_value));
|
|
data.set("newValue"sv, move(new_value));
|
|
data.set("items"sv, make_cookie_edit_items(cookie));
|
|
|
|
JsonObject request;
|
|
request.set("to"sv, cookies_actor);
|
|
request.set("type"sv, "editItem"sv);
|
|
request.set("data"sv, move(data));
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static JsonObject add_cookie(ProtocolClient& client, StringView cookies_actor, StringView name)
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, cookies_actor);
|
|
request.set("type"sv, "addItem"sv);
|
|
request.set("guid"sv, name);
|
|
request.set("host"sv, "https://example.test"sv);
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static JsonObject remove_cookie(ProtocolClient& client, StringView cookies_actor, StringView name, StringView domain, StringView path)
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, cookies_actor);
|
|
request.set("type"sv, "removeItem"sv);
|
|
request.set("host"sv, "https://example.test"sv);
|
|
request.set("name"sv, cookie_unique_key(name, domain, path));
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static JsonObject remove_all_cookies(ProtocolClient& client, StringView cookies_actor, Optional<StringView> domain = {})
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, cookies_actor);
|
|
request.set("type"sv, "removeAll"sv);
|
|
request.set("host"sv, "https://example.test"sv);
|
|
if (domain.has_value())
|
|
request.set("domain"sv, *domain);
|
|
else
|
|
request.set("domain"sv, JsonValue {});
|
|
return client.request(move(request));
|
|
}
|
|
|
|
static JsonObject remove_all_session_cookies(ProtocolClient& client, StringView cookies_actor, Optional<StringView> domain = {})
|
|
{
|
|
JsonObject request;
|
|
request.set("to"sv, cookies_actor);
|
|
request.set("type"sv, "removeAllSessionCookies"sv);
|
|
request.set("host"sv, "https://example.test"sv);
|
|
if (domain.has_value())
|
|
request.set("domain"sv, *domain);
|
|
else
|
|
request.set("domain"sv, JsonValue {});
|
|
return client.request(move(request));
|
|
}
|
|
|
|
TEST_CASE(root_actor_and_connection_errors)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
|
|
auto greeting = client.read_message();
|
|
EXPECT_EQ(greeting.get_string("applicationType"sv).value(), "browser"sv);
|
|
auto traits = greeting.get_object("traits"sv).release_value();
|
|
EXPECT(traits.get_bool("highlightable"sv).value());
|
|
EXPECT(traits.get_bool("customHighlighters"sv).value());
|
|
EXPECT(traits.get_bool("networkMonitor"sv).value());
|
|
|
|
EXPECT_EQ(client.request("root"sv, "connect"sv).get_string("from"sv).value(), "root"sv);
|
|
auto root = client.request("root"sv, "getRoot"sv);
|
|
EXPECT(root.has_string("deviceActor"sv));
|
|
EXPECT(root.has_string("parentAccessibilityActor"sv));
|
|
EXPECT(root.has_string("preferenceActor"sv));
|
|
|
|
EXPECT_EQ(client.request("root"sv, "listProcesses"sv).get_array("processes"sv)->size(), 1u);
|
|
EXPECT(client.request("root"sv, "listAddons"sv).get_array("addons"sv)->is_empty());
|
|
EXPECT(client.request("root"sv, "listWorkers"sv).get_array("workers"sv)->is_empty());
|
|
EXPECT(client.request("root"sv, "listServiceWorkerRegistrations"sv).get_array("registrations"sv)->is_empty());
|
|
|
|
auto tab = get_tab(client);
|
|
auto tab_actor = actor_from(tab, "actor"sv);
|
|
EXPECT_EQ(tab.get_string("title"sv).value(), "Fixture page"sv);
|
|
EXPECT_EQ(tab.get_integer<u64>("browserId"sv).value(), 1u);
|
|
auto tab_traits = tab.get_object("traits"sv).release_value();
|
|
EXPECT(tab_traits.get_bool("supportsReloadDescriptor"sv).value());
|
|
EXPECT(tab_traits.get_bool("supportsNavigation"sv).value());
|
|
|
|
JsonObject get_tab_request;
|
|
get_tab_request.set("to"sv, "root"sv);
|
|
get_tab_request.set("type"sv, "getTab"sv);
|
|
get_tab_request.set("browserId"sv, 1);
|
|
EXPECT_EQ(client.request(move(get_tab_request)).get_object("tab"sv)->get_string("actor"sv).value(), tab_actor);
|
|
|
|
session->server->refresh_tab_list();
|
|
EXPECT_EQ(client.read_message().get_string("type"sv).value(), "tabListChanged"sv);
|
|
session->server->refresh_tab_list();
|
|
pump(session->loop);
|
|
|
|
JsonObject missing_to;
|
|
missing_to.set("type"sv, "connect"sv);
|
|
client.send(move(missing_to));
|
|
EXPECT_EQ(client.read_message().get_string("error"sv).value(), "missingParameter"sv);
|
|
|
|
JsonObject missing_type;
|
|
missing_type.set("to"sv, "root"sv);
|
|
client.send(move(missing_type));
|
|
EXPECT_EQ(client.read_message().get_string("error"sv).value(), "missingParameter"sv);
|
|
|
|
JsonObject unknown_actor;
|
|
unknown_actor.set("to"sv, "missing"sv);
|
|
unknown_actor.set("type"sv, "connect"sv);
|
|
client.send(move(unknown_actor));
|
|
EXPECT_EQ(client.read_message().get_string("error"sv).value(), "unknownActor"sv);
|
|
EXPECT_EQ(client.request("root"sv, "notARealPacket"sv).get_string("error"sv).value(), "unrecognizedPacketType"sv);
|
|
|
|
JsonObject first;
|
|
first.set("to"sv, "root"sv);
|
|
first.set("type"sv, "listWorkers"sv);
|
|
JsonObject second;
|
|
second.set("to"sv, "root"sv);
|
|
second.set("type"sv, "listAddons"sv);
|
|
client.send(move(first));
|
|
client.send(move(second));
|
|
EXPECT(client.read_message().has_array("workers"sv));
|
|
EXPECT(client.read_message().has_array("addons"sv));
|
|
}
|
|
|
|
TEST_CASE(connection_accepts_fragmented_packets)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
|
|
(void)client.read_message();
|
|
EXPECT_EQ(client.request("root"sv, "connect"sv).get_string("from"sv).value(), "root"sv);
|
|
|
|
JsonObject request;
|
|
request.set("to"sv, "root"sv);
|
|
request.set("type"sv, "getRoot"sv);
|
|
|
|
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<bool> may_send_second_fragment { false };
|
|
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<bool> sent_second_fragment { false };
|
|
auto thread = client.send_in_two_fragments(move(request), 2, may_send_second_fragment, sent_second_fragment);
|
|
thread->start();
|
|
|
|
pump(session->loop);
|
|
EXPECT(!sent_second_fragment);
|
|
|
|
may_send_second_fragment = true;
|
|
MUST(thread->join());
|
|
|
|
auto root = client.read_message();
|
|
EXPECT_EQ(root.get_string("from"sv).value(), "root"sv);
|
|
EXPECT(root.has_string("deviceActor"sv));
|
|
}
|
|
|
|
TEST_CASE(history_navigation_requests)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
|
|
JsonObject navigate_to;
|
|
navigate_to.set("to"sv, tab_actor);
|
|
navigate_to.set("type"sv, "navigateTo"sv);
|
|
navigate_to.set("url"sv, "https://example.test/from-devtools"sv);
|
|
navigate_to.set("waitForLoad"sv, false);
|
|
EXPECT_EQ(client.request(move(navigate_to)).get_string("from"sv).value(), tab_actor);
|
|
EXPECT_EQ(session->delegate.navigate_tab_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_navigated_url.value(), "https://example.test/from-devtools"sv);
|
|
|
|
EXPECT_EQ(client.request(tab_actor, "goBack"sv).get_string("from"sv).value(), tab_actor);
|
|
EXPECT_EQ(session->delegate.traverse_the_history_by_delta_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_history_delta.value(), -1);
|
|
|
|
EXPECT_EQ(client.request(tab_actor, "goForward"sv).get_string("from"sv).value(), tab_actor);
|
|
EXPECT_EQ(session->delegate.traverse_the_history_by_delta_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.last_history_delta.value(), 1);
|
|
|
|
auto target = get_frame_target(client, tab_actor);
|
|
auto target_actor = actor_from(target, "actor"sv);
|
|
|
|
EXPECT_EQ(client.request(target_actor, "goBack"sv).get_string("from"sv).value(), target_actor);
|
|
EXPECT_EQ(session->delegate.traverse_the_history_by_delta_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.last_history_delta.value(), -1);
|
|
|
|
EXPECT_EQ(client.request(target_actor, "goForward"sv).get_string("from"sv).value(), target_actor);
|
|
EXPECT_EQ(session->delegate.traverse_the_history_by_delta_call_count, 4u);
|
|
EXPECT_EQ(session->delegate.last_history_delta.value(), 1);
|
|
}
|
|
|
|
TEST_CASE(target_bootstrap_and_lifetime)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
EXPECT(client.request(tab_actor, "getFavicon"sv).get("favicon"sv).value().is_null());
|
|
|
|
auto watcher_actor = actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv);
|
|
EXPECT_EQ(actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv), watcher_actor);
|
|
EXPECT(client.request(watcher_actor, "getTargetConfigurationActor"sv).has_object("configuration"sv));
|
|
EXPECT(client.request(watcher_actor, "getThreadConfigurationActor"sv).has_object("configuration"sv));
|
|
EXPECT(client.request(watcher_actor, "getNetworkParentActor"sv).has_string("network"sv));
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append("console-message"sv);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
auto target = get_frame_target(client, tab_actor);
|
|
EXPECT_EQ(target.get_string("targetType"sv).value(), "frame"sv);
|
|
EXPECT(target.has_string("consoleActor"sv));
|
|
EXPECT(target.has_string("inspectorActor"sv));
|
|
EXPECT(target.has_string("styleSheetsActor"sv));
|
|
EXPECT(target.has_string("threadActor"sv));
|
|
EXPECT(target.has_string("accessibilityActor"sv));
|
|
EXPECT_EQ(session->delegate.did_connect_devtools_client_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.listen_for_console_messages_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.listen_for_network_events_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.retrieve_style_sheets_call_count, 1u);
|
|
|
|
EXPECT_EQ(client.request(actor_from(target, "actor"sv), "detach"sv).get_string("from"sv).value(), actor_from(target, "actor"sv));
|
|
EXPECT_EQ(session->delegate.stop_listening_for_console_messages_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.stop_listening_for_dom_mutations_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.clear_highlighted_dom_node_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.clear_inspected_dom_node_call_count, 1u);
|
|
}
|
|
|
|
TEST_CASE(storage_cookie_resource)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
auto watcher_response = client.request(tab_actor, "getWatcher"sv);
|
|
auto watcher_actor = actor_from(watcher_response, "actor"sv);
|
|
auto resources = watcher_response.get_object("traits"sv)->get_object("resources"sv).release_value();
|
|
EXPECT(resources.get_bool("cookies"sv).value());
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append("cookies"sv);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
auto cookie_resource = read_resource(client, "cookies"sv);
|
|
EXPECT_EQ(cookie_resource.get_string("resourceKey"sv).value(), "cookies"sv);
|
|
EXPECT_EQ(cookie_resource.get_integer<u64>("browsingContextID"sv).value(), 1u);
|
|
EXPECT_EQ(cookie_resource.get_integer<u64>("innerWindowId"sv).value(), 1u);
|
|
EXPECT_EQ(cookie_resource.get_string("resourceId"sv).value(), "cookies-1"sv);
|
|
|
|
auto hosts = cookie_resource.get_object("hosts"sv).release_value();
|
|
EXPECT(hosts.has_array("https://example.test"sv));
|
|
auto traits = cookie_resource.get_object("traits"sv).release_value();
|
|
EXPECT(traits.get_bool("supportsAddItem"sv).value());
|
|
EXPECT(traits.get_bool("supportsRemoveAll"sv).value());
|
|
EXPECT(traits.get_bool("supportsRemoveAllSessionCookies"sv).value());
|
|
EXPECT(traits.get_bool("supportsRemoveItem"sv).value());
|
|
|
|
auto cookies_actor = actor_from(cookie_resource, "actor"sv);
|
|
auto fields = client.request(cookies_actor, "getFields"sv).get_array("value"sv).release_value();
|
|
EXPECT_EQ(fields.at(0).as_object().get_string("name"sv).value(), "uniqueKey"sv);
|
|
EXPECT(fields.at(0).as_object().get_bool("private"sv).value());
|
|
EXPECT_EQ(fields.at(1).as_object().get_string("name"sv).value(), "name"sv);
|
|
EXPECT(fields.at(1).as_object().get_bool("editable"sv).value());
|
|
|
|
JsonObject get_store_objects;
|
|
get_store_objects.set("to"sv, cookies_actor);
|
|
get_store_objects.set("type"sv, "getStoreObjects"sv);
|
|
get_store_objects.set("host"sv, "https://example.test"sv);
|
|
get_store_objects.set("names"sv, JsonValue {});
|
|
JsonObject options;
|
|
options.set("sessionString"sv, "Session"sv);
|
|
get_store_objects.set("options"sv, move(options));
|
|
auto objects = client.request(move(get_store_objects));
|
|
EXPECT_EQ(objects.get_integer<size_t>("offset"sv).value(), 0u);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 0u);
|
|
EXPECT(objects.get_array("data"sv)->is_empty());
|
|
}
|
|
|
|
TEST_CASE(storage_web_storage_resources)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
auto watcher_response = client.request(tab_actor, "getWatcher"sv);
|
|
auto watcher_actor = actor_from(watcher_response, "actor"sv);
|
|
auto resources = watcher_response.get_object("traits"sv)->get_object("resources"sv).release_value();
|
|
EXPECT(resources.get_bool("local-storage"sv).value());
|
|
EXPECT(resources.get_bool("session-storage"sv).value());
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append("local-storage"sv);
|
|
resource_types.must_append("session-storage"sv);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
auto local_storage_resource = read_resource(client, "local-storage"sv);
|
|
EXPECT_EQ(local_storage_resource.get_string("resourceKey"sv).value(), "localStorage"sv);
|
|
EXPECT_EQ(local_storage_resource.get_integer<u64>("browsingContextID"sv).value(), 1u);
|
|
EXPECT_EQ(local_storage_resource.get_integer<u64>("innerWindowId"sv).value(), 1u);
|
|
EXPECT_EQ(local_storage_resource.get_string("resourceId"sv).value(), "localStorage-1"sv);
|
|
EXPECT(local_storage_resource.get_object("hosts"sv)->has_array("https://example.test"sv));
|
|
auto local_traits = local_storage_resource.get_object("traits"sv).release_value();
|
|
EXPECT(local_traits.get_bool("supportsAddItem"sv).value());
|
|
EXPECT(local_traits.get_bool("supportsRemoveAll"sv).value());
|
|
EXPECT(!local_traits.get_bool("supportsRemoveAllSessionCookies"sv).value());
|
|
EXPECT(local_traits.get_bool("supportsRemoveItem"sv).value());
|
|
|
|
auto local_storage_actor = actor_from(local_storage_resource, "actor"sv);
|
|
auto fields = client.request(local_storage_actor, "getFields"sv).get_array("value"sv).release_value();
|
|
EXPECT_EQ(fields.size(), 2u);
|
|
EXPECT_EQ(fields.at(0).as_object().get_string("name"sv).value(), "name"sv);
|
|
EXPECT(fields.at(0).as_object().get_bool("editable"sv).value());
|
|
EXPECT_EQ(fields.at(1).as_object().get_string("name"sv).value(), "value"sv);
|
|
EXPECT(fields.at(1).as_object().get_bool("editable"sv).value());
|
|
|
|
auto session_storage_resource = read_resource(client, "session-storage"sv);
|
|
EXPECT_EQ(session_storage_resource.get_string("resourceKey"sv).value(), "sessionStorage"sv);
|
|
EXPECT_EQ(session_storage_resource.get_string("resourceId"sv).value(), "sessionStorage-1"sv);
|
|
EXPECT(session_storage_resource.get_object("hosts"sv)->has_array("https://example.test"sv));
|
|
}
|
|
|
|
TEST_CASE(storage_web_storage_store_objects)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
session->delegate.fixture_local_storage_items.append({ "beta"_string, "two"_string });
|
|
session->delegate.fixture_local_storage_items.append({ "alpha"_string, "one"_string });
|
|
session->delegate.fixture_session_storage_items.append({ "session-key"_string, "session-value"_string });
|
|
|
|
auto local_storage_actor = get_storage_actor(client, "local-storage"sv);
|
|
auto objects = get_storage_store_objects(client, local_storage_actor);
|
|
EXPECT_EQ(session->delegate.inspect_storage_call_count, 1u);
|
|
EXPECT_EQ(objects.get_integer<size_t>("offset"sv).value(), 0u);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 2u);
|
|
|
|
auto data = objects.get_array("data"sv).release_value();
|
|
EXPECT_EQ(data.size(), 2u);
|
|
EXPECT_EQ(data.at(0).as_object().get_string("name"sv).value(), "alpha"sv);
|
|
EXPECT_EQ(data.at(0).as_object().get_string("value"sv).value(), "one"sv);
|
|
EXPECT_EQ(data.at(1).as_object().get_string("name"sv).value(), "beta"sv);
|
|
EXPECT_EQ(data.at(1).as_object().get_string("value"sv).value(), "two"sv);
|
|
|
|
JsonArray names;
|
|
names.must_append("beta"sv);
|
|
auto filtered_objects = get_storage_store_objects(client, local_storage_actor, "https://example.test"sv, move(names));
|
|
EXPECT_EQ(filtered_objects.get_integer<size_t>("total"sv).value(), 1u);
|
|
auto filtered_data = filtered_objects.get_array("data"sv).release_value();
|
|
EXPECT_EQ(filtered_data.size(), 1u);
|
|
EXPECT_EQ(filtered_data.at(0).as_object().get_string("name"sv).value(), "beta"sv);
|
|
|
|
JsonObject paginated_request;
|
|
paginated_request.set("to"sv, local_storage_actor);
|
|
paginated_request.set("type"sv, "getStoreObjects"sv);
|
|
paginated_request.set("host"sv, "https://example.test"sv);
|
|
paginated_request.set("names"sv, JsonValue {});
|
|
JsonObject options;
|
|
options.set("offset"sv, 1);
|
|
options.set("size"sv, 1);
|
|
paginated_request.set("options"sv, move(options));
|
|
auto paginated_objects = client.request(move(paginated_request));
|
|
EXPECT_EQ(paginated_objects.get_integer<size_t>("offset"sv).value(), 1u);
|
|
EXPECT_EQ(paginated_objects.get_integer<size_t>("total"sv).value(), 2u);
|
|
auto paginated_data = paginated_objects.get_array("data"sv).release_value();
|
|
EXPECT_EQ(paginated_data.size(), 1u);
|
|
EXPECT_EQ(paginated_data.at(0).as_object().get_string("name"sv).value(), "beta"sv);
|
|
|
|
auto session_storage_actor = get_storage_actor(client, "session-storage"sv);
|
|
objects = get_storage_store_objects(client, session_storage_actor);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 1u);
|
|
data = objects.get_array("data"sv).release_value();
|
|
EXPECT_EQ(data.at(0).as_object().get_string("name"sv).value(), "session-key"sv);
|
|
EXPECT_EQ(data.at(0).as_object().get_string("value"sv).value(), "session-value"sv);
|
|
|
|
objects = get_storage_store_objects(client, session_storage_actor, "https://other.test"sv);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 0u);
|
|
EXPECT(objects.get_array("data"sv)->is_empty());
|
|
}
|
|
|
|
TEST_CASE(storage_web_storage_change_events)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto local_storage_actor = get_storage_actor(client, "local-storage"sv);
|
|
EXPECT_EQ(session->delegate.add_storage_change_listener_call_count, 1u);
|
|
|
|
session->delegate.emit_storage_change({
|
|
.storage_endpoint = Web::StorageAPI::StorageEndpointType::LocalStorage,
|
|
.host = "https://example.test"_string,
|
|
.type = DevTools::DevToolsDelegate::StorageChange::Type::Added,
|
|
.key = "alpha"_string,
|
|
});
|
|
auto stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
EXPECT_EQ(stores_update.get_string("from"sv).value(), local_storage_actor);
|
|
auto added_keys = get_storage_update_keys(stores_update, "added"sv, "localStorage"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), "alpha"sv);
|
|
|
|
session->delegate.emit_storage_change({
|
|
.storage_endpoint = Web::StorageAPI::StorageEndpointType::LocalStorage,
|
|
.host = "https://example.test"_string,
|
|
.type = DevTools::DevToolsDelegate::StorageChange::Type::Changed,
|
|
.key = "alpha"_string,
|
|
});
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto changed_keys = get_storage_update_keys(stores_update, "changed"sv, "localStorage"sv);
|
|
EXPECT_EQ(changed_keys.size(), 1u);
|
|
EXPECT_EQ(changed_keys.at(0).as_string(), "alpha"sv);
|
|
|
|
session->delegate.emit_storage_change({
|
|
.storage_endpoint = Web::StorageAPI::StorageEndpointType::LocalStorage,
|
|
.host = "https://example.test"_string,
|
|
.type = DevTools::DevToolsDelegate::StorageChange::Type::Deleted,
|
|
.key = "alpha"_string,
|
|
});
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto deleted_keys = get_storage_update_keys(stores_update, "deleted"sv, "localStorage"sv);
|
|
EXPECT_EQ(deleted_keys.size(), 1u);
|
|
EXPECT_EQ(deleted_keys.at(0).as_string(), "alpha"sv);
|
|
|
|
session->delegate.emit_storage_change({
|
|
.storage_endpoint = Web::StorageAPI::StorageEndpointType::LocalStorage,
|
|
.host = "https://example.test"_string,
|
|
.type = DevTools::DevToolsDelegate::StorageChange::Type::Cleared,
|
|
.key = {},
|
|
});
|
|
auto stores_cleared = read_packet_with_type(client, "storesCleared"sv);
|
|
EXPECT_EQ(stores_cleared.get_string("from"sv).value(), local_storage_actor);
|
|
auto cleared_hosts = stores_cleared.get_object("data"sv)->get_array("clearedHostsOrPaths"sv).release_value();
|
|
EXPECT_EQ(cleared_hosts.size(), 1u);
|
|
EXPECT_EQ(cleared_hosts.at(0).as_string(), "https://example.test"sv);
|
|
|
|
auto session_storage_actor = get_storage_actor(client, "session-storage"sv);
|
|
EXPECT_EQ(session->delegate.add_storage_change_listener_call_count, 2u);
|
|
session->delegate.emit_storage_change({
|
|
.storage_endpoint = Web::StorageAPI::StorageEndpointType::SessionStorage,
|
|
.host = "https://example.test"_string,
|
|
.type = DevTools::DevToolsDelegate::StorageChange::Type::Added,
|
|
.key = "session-key"_string,
|
|
});
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
EXPECT_EQ(stores_update.get_string("from"sv).value(), session_storage_actor);
|
|
added_keys = get_storage_update_keys(stores_update, "added"sv, "sessionStorage"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), "session-key"sv);
|
|
}
|
|
|
|
TEST_CASE(storage_web_storage_change_events_for_file_urls)
|
|
{
|
|
auto session = create_session("file:///tmp/devtools-storage.html"sv);
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto local_storage_actor = get_storage_actor(client, "local-storage"sv);
|
|
|
|
session->delegate.emit_storage_change({
|
|
.storage_endpoint = Web::StorageAPI::StorageEndpointType::LocalStorage,
|
|
.host = "file:///tmp/other.html"_string,
|
|
.type = DevTools::DevToolsDelegate::StorageChange::Type::Added,
|
|
.key = "other"_string,
|
|
});
|
|
session->delegate.emit_storage_change({
|
|
.storage_endpoint = Web::StorageAPI::StorageEndpointType::LocalStorage,
|
|
.host = "file:///tmp/devtools-storage.html"_string,
|
|
.type = DevTools::DevToolsDelegate::StorageChange::Type::Added,
|
|
.key = "alpha"_string,
|
|
});
|
|
|
|
auto stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
EXPECT_EQ(stores_update.get_string("from"sv).value(), local_storage_actor);
|
|
auto added_keys = get_storage_update_keys(stores_update, "added"sv, "localStorage"sv, "file:///tmp/devtools-storage.html"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), "alpha"sv);
|
|
}
|
|
|
|
TEST_CASE(storage_web_storage_add_edit_and_remove_items)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto local_storage_actor = get_storage_actor(client, "local-storage"sv);
|
|
|
|
auto response = add_storage_item(client, local_storage_actor, "devtools-key"sv);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_storage_item_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items.size(), 1u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items[0].name, "devtools-key"sv);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items[0].value, "value"sv);
|
|
|
|
auto stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto added_keys = get_storage_update_keys(stores_update, "added"sv, "localStorage"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), "devtools-key"sv);
|
|
|
|
response = edit_storage_item(client, local_storage_actor, "value"sv, "value"_string, "devtools-key"sv, "updated-value"sv);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_storage_item_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items.size(), 1u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items[0].name, "devtools-key"sv);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items[0].value, "updated-value"sv);
|
|
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto changed_keys = get_storage_update_keys(stores_update, "changed"sv, "localStorage"sv);
|
|
EXPECT_EQ(changed_keys.size(), 1u);
|
|
EXPECT_EQ(changed_keys.at(0).as_string(), "devtools-key"sv);
|
|
|
|
response = edit_storage_item(client, local_storage_actor, "name"sv, "devtools-key"_string, "renamed-key"sv, "updated-value"sv);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.remove_storage_item_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.set_storage_item_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items.size(), 1u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items[0].name, "renamed-key"sv);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items[0].value, "updated-value"sv);
|
|
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto deleted_keys = get_storage_update_keys(stores_update, "deleted"sv, "localStorage"sv);
|
|
EXPECT_EQ(deleted_keys.size(), 1u);
|
|
EXPECT_EQ(deleted_keys.at(0).as_string(), "devtools-key"sv);
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
added_keys = get_storage_update_keys(stores_update, "added"sv, "localStorage"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), "renamed-key"sv);
|
|
|
|
response = remove_storage_item(client, local_storage_actor, "renamed-key"sv);
|
|
EXPECT_EQ(response.get_string("from"sv).value(), local_storage_actor);
|
|
EXPECT_EQ(session->delegate.remove_storage_item_call_count, 2u);
|
|
EXPECT(session->delegate.fixture_local_storage_items.is_empty());
|
|
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
deleted_keys = get_storage_update_keys(stores_update, "deleted"sv, "localStorage"sv);
|
|
EXPECT_EQ(deleted_keys.size(), 1u);
|
|
EXPECT_EQ(deleted_keys.at(0).as_string(), "renamed-key"sv);
|
|
|
|
session->delegate.fixture_local_storage_items.append({ "first"_string, "one"_string });
|
|
session->delegate.fixture_local_storage_items.append({ "second"_string, "two"_string });
|
|
response = remove_all_storage_items(client, local_storage_actor);
|
|
EXPECT_EQ(response.get_string("from"sv).value(), local_storage_actor);
|
|
EXPECT_EQ(session->delegate.clear_storage_call_count, 1u);
|
|
EXPECT(session->delegate.fixture_local_storage_items.is_empty());
|
|
|
|
auto stores_cleared = read_packet_with_type(client, "storesCleared"sv);
|
|
EXPECT_EQ(stores_cleared.get_string("from"sv).value(), local_storage_actor);
|
|
auto cleared_hosts = stores_cleared.get_object("data"sv)->get_array("clearedHostsOrPaths"sv).release_value();
|
|
EXPECT_EQ(cleared_hosts.size(), 1u);
|
|
EXPECT_EQ(cleared_hosts.at(0).as_string(), "https://example.test"sv);
|
|
|
|
auto session_storage_actor = get_storage_actor(client, "session-storage"sv);
|
|
response = add_storage_item(client, session_storage_actor, "session-key"sv);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.fixture_session_storage_items.size(), 1u);
|
|
EXPECT_EQ(session->delegate.fixture_session_storage_items[0].name, "session-key"sv);
|
|
EXPECT_EQ(session->delegate.fixture_session_storage_items[0].value, "value"sv);
|
|
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
added_keys = get_storage_update_keys(stores_update, "added"sv, "sessionStorage"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), "session-key"sv);
|
|
}
|
|
|
|
TEST_CASE(storage_web_storage_mutation_errors)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto local_storage_actor = get_storage_actor(client, "local-storage"sv);
|
|
session->delegate.fixture_local_storage_items.append({ "devtools-key"_string, "value"_string });
|
|
|
|
session->delegate.fail_remove_storage_item = true;
|
|
auto response = edit_storage_item(client, local_storage_actor, "name"sv, "devtools-key"_string, "renamed-key"sv, "value"sv);
|
|
EXPECT_EQ(response.get_string("errorString"sv).value(), "Unable to remove storage item"sv);
|
|
EXPECT_EQ(session->delegate.remove_storage_item_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.set_storage_item_call_count, 0u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items.size(), 1u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items[0].name, "devtools-key"sv);
|
|
|
|
response = remove_storage_item(client, local_storage_actor, "devtools-key"sv);
|
|
EXPECT_EQ(response.get_string("errorString"sv).value(), "Unable to remove storage item"sv);
|
|
EXPECT_EQ(session->delegate.remove_storage_item_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items.size(), 1u);
|
|
|
|
session->delegate.fail_remove_storage_item = false;
|
|
session->delegate.fail_clear_storage = true;
|
|
response = remove_all_storage_items(client, local_storage_actor);
|
|
EXPECT_EQ(response.get_string("errorString"sv).value(), "Unable to clear storage"sv);
|
|
EXPECT_EQ(session->delegate.clear_storage_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.fixture_local_storage_items.size(), 1u);
|
|
}
|
|
|
|
TEST_CASE(storage_indexed_database_resource)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
auto watcher_response = client.request(tab_actor, "getWatcher"sv);
|
|
auto watcher_actor = actor_from(watcher_response, "actor"sv);
|
|
auto resources = watcher_response.get_object("traits"sv)->get_object("resources"sv).release_value();
|
|
EXPECT(resources.get_bool("indexed-db"sv).value());
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append("indexed-db"sv);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
auto indexed_database_resource = read_resource(client, "indexed-db"sv);
|
|
EXPECT_EQ(indexed_database_resource.get_string("resourceKey"sv).value(), "indexedDB"sv);
|
|
EXPECT_EQ(indexed_database_resource.get_integer<u64>("browsingContextID"sv).value(), 1u);
|
|
EXPECT_EQ(indexed_database_resource.get_integer<u64>("innerWindowId"sv).value(), 1u);
|
|
EXPECT_EQ(indexed_database_resource.get_string("resourceId"sv).value(), "indexed-db-1"sv);
|
|
|
|
auto hosts = indexed_database_resource.get_object("hosts"sv).release_value();
|
|
auto names = hosts.get_array("https://example.test"sv).release_value();
|
|
EXPECT_EQ(names.size(), 2u);
|
|
EXPECT_EQ(names.at(0).as_string(), "[\"fixtures (default)\",\"people\"]"sv);
|
|
EXPECT_EQ(names.at(1).as_string(), "[\"empty (default)\"]"sv);
|
|
|
|
auto traits = indexed_database_resource.get_object("traits"sv).release_value();
|
|
EXPECT(!traits.get_bool("supportsAddItem"sv).value());
|
|
EXPECT(traits.get_bool("supportsRemoveAll"sv).value());
|
|
EXPECT(!traits.get_bool("supportsRemoveAllSessionCookies"sv).value());
|
|
EXPECT(traits.get_bool("supportsRemoveItem"sv).value());
|
|
|
|
auto indexed_database_actor = actor_from(indexed_database_resource, "actor"sv);
|
|
auto fields = client.request(indexed_database_actor, "getFields"sv).get_array("value"sv).release_value();
|
|
EXPECT_EQ(fields.at(0).as_object().get_string("name"sv).value(), "uniqueKey"sv);
|
|
EXPECT(fields.at(0).as_object().get_bool("private"sv).value());
|
|
EXPECT_EQ(fields.at(1).as_object().get_string("name"sv).value(), "db"sv);
|
|
|
|
JsonObject database_fields_request;
|
|
database_fields_request.set("to"sv, indexed_database_actor);
|
|
database_fields_request.set("type"sv, "getFields"sv);
|
|
database_fields_request.set("subType"sv, "database"sv);
|
|
auto database_fields = client.request(move(database_fields_request)).get_array("value"sv).release_value();
|
|
EXPECT_EQ(database_fields.at(0).as_object().get_string("name"sv).value(), "objectStore"sv);
|
|
|
|
JsonObject object_store_fields_request;
|
|
object_store_fields_request.set("to"sv, indexed_database_actor);
|
|
object_store_fields_request.set("type"sv, "getFields"sv);
|
|
object_store_fields_request.set("subType"sv, "object store"sv);
|
|
auto object_store_fields = client.request(move(object_store_fields_request)).get_array("value"sv).release_value();
|
|
EXPECT_EQ(object_store_fields.at(0).as_object().get_string("name"sv).value(), "name"sv);
|
|
EXPECT_EQ(object_store_fields.at(1).as_object().get_string("name"sv).value(), "value"sv);
|
|
|
|
EXPECT_EQ(session->delegate.inspect_indexed_database_storage_call_count, 1u);
|
|
}
|
|
|
|
TEST_CASE(storage_indexed_database_store_objects)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto indexed_database_actor = get_indexed_database_actor(client);
|
|
|
|
auto databases = get_indexed_database_store_objects(client, indexed_database_actor);
|
|
EXPECT_EQ(session->delegate.inspect_indexed_database_objects_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_indexed_database_host.value(), "https://example.test"sv);
|
|
EXPECT_EQ(databases.get_integer<size_t>("offset"sv).value(), 0u);
|
|
EXPECT_EQ(databases.get_integer<size_t>("total"sv).value(), 2u);
|
|
|
|
auto database_rows = databases.get_array("data"sv).release_value();
|
|
EXPECT_EQ(database_rows.size(), 2u);
|
|
auto fixtures_database = database_rows.at(0).as_object();
|
|
EXPECT_EQ(fixtures_database.get_string("uniqueKey"sv).value(), "fixtures (default)"sv);
|
|
EXPECT_EQ(fixtures_database.get_string("db"sv).value(), "fixtures"sv);
|
|
EXPECT_EQ(fixtures_database.get_string("storage"sv).value(), "default"sv);
|
|
EXPECT_EQ(fixtures_database.get_string("origin"sv).value(), "https://example.test"sv);
|
|
EXPECT_EQ(fixtures_database.get_integer<int>("version"sv).value(), 1);
|
|
EXPECT_EQ(fixtures_database.get_integer<int>("objectStores"sv).value(), 1);
|
|
|
|
auto databases_from_empty_names = get_indexed_database_store_objects(client, indexed_database_actor, JsonArray {});
|
|
EXPECT_EQ(databases_from_empty_names.get_integer<size_t>("total"sv).value(), 2u);
|
|
|
|
JsonArray empty_path_names;
|
|
empty_path_names.must_append("[]"sv);
|
|
auto databases_from_empty_path = get_indexed_database_store_objects(client, indexed_database_actor, move(empty_path_names));
|
|
EXPECT_EQ(databases_from_empty_path.get_integer<size_t>("total"sv).value(), 2u);
|
|
|
|
auto object_stores = get_indexed_database_store_objects(client, indexed_database_actor, indexed_database_path("fixtures (default)"sv));
|
|
EXPECT_EQ(object_stores.get_integer<size_t>("total"sv).value(), 1u);
|
|
auto object_store_rows = object_stores.get_array("data"sv).release_value();
|
|
auto people_store = object_store_rows.at(0).as_object();
|
|
EXPECT_EQ(people_store.get_string("objectStore"sv).value(), "people"sv);
|
|
EXPECT_EQ(people_store.get_string("keyPath"sv).value(), "id"sv);
|
|
EXPECT(people_store.get_bool("autoIncrement"sv).value());
|
|
EXPECT_EQ(people_store.get_string("indexes"sv).value(), "[]"sv);
|
|
|
|
auto records = get_indexed_database_store_objects(client, indexed_database_actor, indexed_database_path("fixtures (default)"sv, "people"sv));
|
|
EXPECT_EQ(records.get_integer<size_t>("total"sv).value(), 1u);
|
|
auto record_rows = records.get_array("data"sv).release_value();
|
|
auto record = record_rows.at(0).as_object();
|
|
EXPECT_EQ(record.get_integer<int>("name"sv).value(), 1);
|
|
EXPECT_EQ(record.get_string("value"sv).value(), "{\"name\":\"Ada\"}"sv);
|
|
}
|
|
|
|
TEST_CASE(storage_indexed_database_change_events)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto indexed_database_actor = get_indexed_database_actor(client);
|
|
EXPECT_EQ(session->delegate.add_indexed_database_change_listener_call_count, 1u);
|
|
|
|
JsonArray added;
|
|
added.must_append(indexed_database_path("fixtures (default)"sv, "people"sv));
|
|
|
|
JsonArray record_path;
|
|
record_path.must_append("fixtures (default)"sv);
|
|
record_path.must_append("people"sv);
|
|
record_path.must_append(1);
|
|
JsonArray changed;
|
|
changed.must_append(record_path.serialized());
|
|
|
|
JsonArray deleted;
|
|
deleted.must_append(indexed_database_path("empty (default)"sv));
|
|
|
|
JsonObject added_hosts;
|
|
added_hosts.set("https://example.test"sv, move(added));
|
|
JsonObject added_types;
|
|
added_types.set("indexedDB"sv, move(added_hosts));
|
|
|
|
JsonObject changed_hosts;
|
|
changed_hosts.set("https://example.test"sv, move(changed));
|
|
JsonObject changed_types;
|
|
changed_types.set("indexedDB"sv, move(changed_hosts));
|
|
|
|
JsonObject deleted_hosts;
|
|
deleted_hosts.set("https://example.test"sv, move(deleted));
|
|
JsonObject deleted_types;
|
|
deleted_types.set("indexedDB"sv, move(deleted_hosts));
|
|
|
|
JsonObject update;
|
|
update.set("added"sv, move(added_types));
|
|
update.set("changed"sv, move(changed_types));
|
|
update.set("deleted"sv, move(deleted_types));
|
|
session->delegate.emit_indexed_database_change(move(update));
|
|
|
|
auto stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
EXPECT_EQ(stores_update.get_string("from"sv).value(), indexed_database_actor);
|
|
|
|
auto added_paths = get_indexed_database_update_paths(stores_update, "added"sv);
|
|
EXPECT_EQ(added_paths.size(), 1u);
|
|
EXPECT_EQ(added_paths.at(0).as_string(), indexed_database_path("fixtures (default)"sv, "people"sv));
|
|
|
|
auto changed_paths = get_indexed_database_update_paths(stores_update, "changed"sv);
|
|
EXPECT_EQ(changed_paths.size(), 1u);
|
|
EXPECT_EQ(changed_paths.at(0).as_string(), record_path.serialized());
|
|
|
|
auto deleted_paths = get_indexed_database_update_paths(stores_update, "deleted"sv);
|
|
EXPECT_EQ(deleted_paths.size(), 1u);
|
|
EXPECT_EQ(deleted_paths.at(0).as_string(), indexed_database_path("empty (default)"sv));
|
|
}
|
|
|
|
TEST_CASE(storage_indexed_database_serializes_live_tree_updates)
|
|
{
|
|
Web::IndexedDB::TransactionChanges changes;
|
|
changes.added.append({ "fixtures"_string, "people"_string });
|
|
changes.added.append({ "fixtures"_string, "people"_string, JsonValue { 1 } });
|
|
changes.changed.append({ "fixtures"_string, "people"_string, JsonValue { 2 } });
|
|
changes.deleted.append({ "fixtures"_string, "people"_string, JsonValue { 3 } });
|
|
|
|
auto update = DevTools::IndexedDB::serialize_update("https://example.test/page"_string, changes);
|
|
|
|
auto added_paths = update.get_object("added"sv)
|
|
->get_object("indexedDB"sv)
|
|
->get_array("https://example.test"sv)
|
|
.release_value();
|
|
EXPECT_EQ(added_paths.size(), 1u);
|
|
EXPECT_EQ(added_paths.at(0).as_string(), indexed_database_path("fixtures (default)"sv, "people"sv));
|
|
EXPECT(!update.get_object("changed"sv).has_value());
|
|
EXPECT(!update.get_object("deleted"sv).has_value());
|
|
}
|
|
|
|
TEST_CASE(storage_cookie_store_objects)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto first = make_cookie("alpha"_string, "one"_string, "example.test"_string, "/"_string);
|
|
first.host_only = true;
|
|
first.http_only = true;
|
|
first.secure = true;
|
|
first.persistent = true;
|
|
first.expiry_time = UnixDateTime::from_seconds_since_epoch(1000);
|
|
first.same_site = HTTP::Cookie::SameSite::Lax;
|
|
session->delegate.fixture_cookies.append(move(first));
|
|
|
|
auto second = make_cookie("alpha"_string, "two"_string, "example.test"_string, "/nested"_string);
|
|
second.persistent = true;
|
|
second.expiry_time = UnixDateTime::from_seconds_since_epoch(2000);
|
|
second.same_site = HTTP::Cookie::SameSite::Strict;
|
|
session->delegate.fixture_cookies.append(move(second));
|
|
|
|
auto third = make_cookie("beta"_string, "session"_string, "example.test"_string, "/"_string);
|
|
third.same_site = HTTP::Cookie::SameSite::Default;
|
|
session->delegate.fixture_cookies.append(move(third));
|
|
|
|
session->delegate.fixture_cookies.append(
|
|
make_cookie("ignored"_string, "nope"_string, "other.test"_string, "/"_string));
|
|
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
auto watcher_actor = actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv);
|
|
|
|
JsonObject watch_resources;
|
|
watch_resources.set("to"sv, watcher_actor);
|
|
watch_resources.set("type"sv, "watchResources"sv);
|
|
JsonArray resource_types;
|
|
resource_types.must_append("cookies"sv);
|
|
watch_resources.set("resourceTypes"sv, move(resource_types));
|
|
EXPECT_EQ(client.request(move(watch_resources)).get_string("from"sv).value(), watcher_actor);
|
|
|
|
auto cookie_resource = read_resource(client, "cookies"sv);
|
|
auto cookies_actor = actor_from(cookie_resource, "actor"sv);
|
|
|
|
JsonObject get_store_objects;
|
|
get_store_objects.set("to"sv, cookies_actor);
|
|
get_store_objects.set("type"sv, "getStoreObjects"sv);
|
|
get_store_objects.set("host"sv, "https://example.test"sv);
|
|
get_store_objects.set("names"sv, JsonValue {});
|
|
JsonObject options;
|
|
options.set("sessionString"sv, "Session"sv);
|
|
get_store_objects.set("options"sv, move(options));
|
|
auto objects = client.request(move(get_store_objects));
|
|
EXPECT_EQ(session->delegate.cookies_call_count, 1u);
|
|
EXPECT_EQ(objects.get_integer<size_t>("offset"sv).value(), 0u);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 3u);
|
|
|
|
auto data = objects.get_array("data"sv).release_value();
|
|
EXPECT_EQ(data.size(), 3u);
|
|
|
|
auto first_row = data.at(0).as_object();
|
|
EXPECT_EQ(first_row.get_string("uniqueKey"sv).value(), cookie_unique_key("alpha"sv, "example.test"sv, "/"sv));
|
|
EXPECT_EQ(first_row.get_string("name"sv).value(), "alpha"sv);
|
|
EXPECT_EQ(first_row.get_string("value"sv).value(), "one"sv);
|
|
EXPECT_EQ(first_row.get_string("host"sv).value(), "example.test"sv);
|
|
EXPECT_EQ(first_row.get_string("path"sv).value(), "/"sv);
|
|
EXPECT_EQ(first_row.get_integer<i64>("expires"sv).value(), 1'000'000);
|
|
EXPECT_EQ(first_row.get_integer<size_t>("size"sv).value(), 8u);
|
|
EXPECT(first_row.get_bool("isHttpOnly"sv).value());
|
|
EXPECT(first_row.get_bool("isSecure"sv).value());
|
|
EXPECT(first_row.get_bool("hostOnly"sv).value());
|
|
EXPECT_EQ(first_row.get_string("sameSite"sv).value(), "Lax"sv);
|
|
EXPECT_EQ(first_row.get_integer<i64>("creationTime"sv).value(), 10'000);
|
|
EXPECT_EQ(first_row.get_integer<i64>("lastAccessed"sv).value(), 20'000);
|
|
EXPECT_EQ(first_row.get_integer<i64>("updateTime"sv).value(), 10'000);
|
|
EXPECT_EQ(first_row.get_string("partitionKey"sv).value(), ""sv);
|
|
|
|
auto third_row = data.at(2).as_object();
|
|
EXPECT_EQ(third_row.get_string("name"sv).value(), "beta"sv);
|
|
EXPECT_EQ(third_row.get_string("value"sv).value(), "session"sv);
|
|
EXPECT_EQ(third_row.get_integer<int>("expires"sv).value(), 0);
|
|
EXPECT_EQ(third_row.get_string("sameSite"sv).value(), ""sv);
|
|
|
|
JsonObject paginated_request;
|
|
paginated_request.set("to"sv, cookies_actor);
|
|
paginated_request.set("type"sv, "getStoreObjects"sv);
|
|
paginated_request.set("host"sv, "https://example.test"sv);
|
|
paginated_request.set("names"sv, JsonValue {});
|
|
JsonObject paginated_options;
|
|
paginated_options.set("offset"sv, 1);
|
|
paginated_options.set("size"sv, 1);
|
|
paginated_request.set("options"sv, move(paginated_options));
|
|
auto paginated_objects = client.request(move(paginated_request));
|
|
EXPECT_EQ(paginated_objects.get_integer<size_t>("offset"sv).value(), 1u);
|
|
EXPECT_EQ(paginated_objects.get_integer<size_t>("total"sv).value(), 3u);
|
|
auto paginated_data = paginated_objects.get_array("data"sv).release_value();
|
|
EXPECT_EQ(paginated_data.size(), 1u);
|
|
EXPECT_EQ(paginated_data.at(0).as_object().get_string("path"sv).value(), "/nested"sv);
|
|
|
|
JsonObject filtered_request;
|
|
filtered_request.set("to"sv, cookies_actor);
|
|
filtered_request.set("type"sv, "getStoreObjects"sv);
|
|
filtered_request.set("host"sv, "https://example.test"sv);
|
|
JsonArray names;
|
|
names.must_append(cookie_unique_key("beta"sv, "example.test"sv, "/"sv));
|
|
filtered_request.set("names"sv, move(names));
|
|
filtered_request.set("options"sv, JsonObject {});
|
|
auto filtered_objects = client.request(move(filtered_request));
|
|
EXPECT_EQ(filtered_objects.get_integer<size_t>("total"sv).value(), 1u);
|
|
auto filtered_data = filtered_objects.get_array("data"sv).release_value();
|
|
EXPECT_EQ(filtered_data.size(), 1u);
|
|
EXPECT_EQ(filtered_data.at(0).as_object().get_string("name"sv).value(), "beta"sv);
|
|
}
|
|
|
|
TEST_CASE(storage_cookie_change_events)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto initial_cookie = make_cookie("alpha"_string, "one"_string, "example.test"_string, "/"_string);
|
|
session->delegate.fixture_cookies.append(initial_cookie);
|
|
|
|
auto cookies_actor = get_cookies_actor(client);
|
|
EXPECT_EQ(session->delegate.listen_for_cookie_changes_call_count, 1u);
|
|
|
|
auto objects = get_cookie_store_objects(client, cookies_actor);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 1u);
|
|
|
|
auto added_cookie = make_cookie("beta"_string, "two"_string, "example.test"_string, "/"_string);
|
|
session->delegate.fixture_cookies.append(added_cookie);
|
|
session->delegate.emit_cookie_change({ added_cookie });
|
|
auto stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
EXPECT_EQ(stores_update.get_string("from"sv).value(), cookies_actor);
|
|
auto added_keys = get_cookie_update_keys(stores_update, "added"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), cookie_unique_key("beta"sv, "example.test"sv, "/"sv));
|
|
|
|
auto changed_cookie = initial_cookie;
|
|
changed_cookie.value = "updated"_string;
|
|
session->delegate.fixture_cookies[0] = changed_cookie;
|
|
session->delegate.emit_cookie_change({ changed_cookie });
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto changed_keys = get_cookie_update_keys(stores_update, "changed"sv);
|
|
EXPECT_EQ(changed_keys.size(), 1u);
|
|
EXPECT_EQ(changed_keys.at(0).as_string(), cookie_unique_key("alpha"sv, "example.test"sv, "/"sv));
|
|
|
|
auto deleted_cookie = changed_cookie;
|
|
session->delegate.fixture_cookies.remove(0);
|
|
session->delegate.emit_cookie_change({ deleted_cookie });
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto deleted_keys = get_cookie_update_keys(stores_update, "deleted"sv);
|
|
EXPECT_EQ(deleted_keys.size(), 1u);
|
|
EXPECT_EQ(deleted_keys.at(0).as_string(), cookie_unique_key("alpha"sv, "example.test"sv, "/"sv));
|
|
}
|
|
|
|
TEST_CASE(storage_cookie_edit_item)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto initial_cookie = make_cookie("alpha"_string, "one"_string, "example.test"_string, "/"_string);
|
|
session->delegate.fixture_cookies.append(initial_cookie);
|
|
|
|
auto cookies_actor = get_cookies_actor(client);
|
|
auto objects = get_cookie_store_objects(client, cookies_actor);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 1u);
|
|
|
|
auto response = edit_cookie(client, cookies_actor, initial_cookie, "value"sv, "one"_string, "two"_string);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies.size(), 1u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies[0].value, "two"sv);
|
|
|
|
auto secure_cookie = session->delegate.fixture_cookies[0];
|
|
response = edit_cookie(client, cookies_actor, secure_cookie, "isSecure"sv, false, true);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 2u);
|
|
EXPECT(session->delegate.fixture_cookies[0].secure);
|
|
|
|
auto persistent_cookie = session->delegate.fixture_cookies[0];
|
|
response = edit_cookie(client, cookies_actor, persistent_cookie, "expires"sv, 0, 3'000'000);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 3u);
|
|
EXPECT(session->delegate.fixture_cookies[0].persistent);
|
|
EXPECT_EQ(session->delegate.fixture_cookies[0].expiry_time.milliseconds_since_epoch(), 3'000'000);
|
|
|
|
auto invalid_expiry_cookie = session->delegate.fixture_cookies[0];
|
|
response = edit_cookie(client, cookies_actor, invalid_expiry_cookie, "expires"sv, 3'000'000, "not a date"_string);
|
|
EXPECT_EQ(response.get_string("errorString"sv).value(), "Cookie expiry date is invalid"sv);
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies[0].expiry_time.milliseconds_since_epoch(), 3'000'000);
|
|
|
|
auto session_cookie = session->delegate.fixture_cookies[0];
|
|
response = edit_cookie(client, cookies_actor, session_cookie, "expires"sv, 3'000'000, "Session"_string);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 4u);
|
|
EXPECT(!session->delegate.fixture_cookies[0].persistent);
|
|
|
|
auto renamed_cookie = session->delegate.fixture_cookies[0];
|
|
response = edit_cookie(client, cookies_actor, renamed_cookie, "name"sv, "alpha"_string, "gamma"_string);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 5u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies.size(), 1u);
|
|
EXPECT(cookie_matches(session->delegate.fixture_cookies[0], "gamma"sv, "example.test"sv, "/"sv));
|
|
|
|
auto invalid_cookie = session->delegate.fixture_cookies[0];
|
|
response = edit_cookie(client, cookies_actor, invalid_cookie, "name"sv, "gamma"_string, "bad\nname"_string);
|
|
EXPECT_EQ(response.get_string("errorString"sv).value(), "Invalid cookie name"sv);
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 6u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies.size(), 1u);
|
|
EXPECT(cookie_matches(session->delegate.fixture_cookies[0], "gamma"sv, "example.test"sv, "/"sv));
|
|
}
|
|
|
|
TEST_CASE(storage_cookie_add_and_remove_items)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto cookies_actor = get_cookies_actor(client);
|
|
auto objects = get_cookie_store_objects(client, cookies_actor);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 0u);
|
|
|
|
auto response = add_cookie(client, cookies_actor, "devtools-cookie"sv);
|
|
EXPECT(response.get("errorString"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.set_cookie_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies.size(), 1u);
|
|
EXPECT(cookie_matches(session->delegate.fixture_cookies[0], "devtools-cookie"sv, "example.test"sv, "/"sv));
|
|
EXPECT_EQ(session->delegate.fixture_cookies[0].value, "value"sv);
|
|
EXPECT(!session->delegate.fixture_cookies[0].persistent);
|
|
EXPECT(session->delegate.fixture_cookies[0].secure);
|
|
EXPECT_EQ(session->delegate.fixture_cookies[0].same_site, HTTP::Cookie::SameSite::Lax);
|
|
|
|
auto stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto added_keys = get_cookie_update_keys(stores_update, "added"sv);
|
|
EXPECT_EQ(added_keys.size(), 1u);
|
|
EXPECT_EQ(added_keys.at(0).as_string(), cookie_unique_key("devtools-cookie"sv, "example.test"sv, "/"sv));
|
|
|
|
response = remove_cookie(client, cookies_actor, "devtools-cookie"sv, "example.test"sv, "/"sv);
|
|
EXPECT_EQ(response.get_string("from"sv).value(), cookies_actor);
|
|
EXPECT_EQ(session->delegate.delete_cookies_call_count, 1u);
|
|
EXPECT(session->delegate.fixture_cookies.is_empty());
|
|
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
auto deleted_keys = get_cookie_update_keys(stores_update, "deleted"sv);
|
|
EXPECT_EQ(deleted_keys.size(), 1u);
|
|
EXPECT_EQ(deleted_keys.at(0).as_string(), cookie_unique_key("devtools-cookie"sv, "example.test"sv, "/"sv));
|
|
|
|
auto session_cookie = make_cookie("session-cookie"_string, "one"_string, "example.test"_string, "/"_string);
|
|
session_cookie.persistent = false;
|
|
session->delegate.fixture_cookies.append(session_cookie);
|
|
|
|
auto persistent_cookie = make_cookie("persistent-cookie"_string, "two"_string, "example.test"_string, "/"_string);
|
|
persistent_cookie.persistent = true;
|
|
persistent_cookie.expiry_time = UnixDateTime::from_seconds_since_epoch(4000);
|
|
session->delegate.fixture_cookies.append(persistent_cookie);
|
|
|
|
session->delegate.fixture_cookies.append(
|
|
make_cookie("ignored-cookie"_string, "three"_string, "other.test"_string, "/"_string));
|
|
|
|
objects = get_cookie_store_objects(client, cookies_actor);
|
|
EXPECT_EQ(objects.get_integer<size_t>("total"sv).value(), 2u);
|
|
|
|
response = remove_all_session_cookies(client, cookies_actor);
|
|
EXPECT_EQ(response.get_string("from"sv).value(), cookies_actor);
|
|
EXPECT_EQ(session->delegate.delete_cookies_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies.size(), 2u);
|
|
EXPECT(cookie_matches(session->delegate.fixture_cookies[0], "persistent-cookie"sv, "example.test"sv, "/"sv));
|
|
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
deleted_keys = get_cookie_update_keys(stores_update, "deleted"sv);
|
|
EXPECT_EQ(deleted_keys.size(), 1u);
|
|
EXPECT_EQ(deleted_keys.at(0).as_string(), cookie_unique_key("session-cookie"sv, "example.test"sv, "/"sv));
|
|
|
|
response = remove_all_cookies(client, cookies_actor, "example.test"sv);
|
|
EXPECT_EQ(response.get_string("from"sv).value(), cookies_actor);
|
|
EXPECT_EQ(session->delegate.delete_cookies_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.fixture_cookies.size(), 1u);
|
|
EXPECT(cookie_matches(session->delegate.fixture_cookies[0], "ignored-cookie"sv, "other.test"sv, "/"sv));
|
|
|
|
stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
|
deleted_keys = get_cookie_update_keys(stores_update, "deleted"sv);
|
|
EXPECT_EQ(deleted_keys.size(), 1u);
|
|
EXPECT_EQ(deleted_keys.at(0).as_string(), cookie_unique_key("persistent-cookie"sv, "example.test"sv, "/"sv));
|
|
}
|
|
|
|
TEST_CASE(walker_node_picker)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto target = get_frame_target(client, actor_from(get_tab(client), "actor"sv));
|
|
auto inspector_actor = actor_from(target, "inspectorActor"sv);
|
|
auto walker = get_walker(client, inspector_actor);
|
|
auto walker_actor = actor_from(walker, "actor"sv);
|
|
auto root_node_actor = walker.get_object("root"sv)->get_string("actor"sv).release_value();
|
|
|
|
EXPECT_EQ(client.request(walker_actor, "pick"sv).get_string("from"sv).value(), walker_actor);
|
|
EXPECT_EQ(session->delegate.start_node_picker_call_count, 1u);
|
|
|
|
session->delegate.emit_node_picker_event({
|
|
.type = DevTools::DevToolsDelegate::NodePickerEvent::Type::Hovered,
|
|
.node_id = Web::UniqueNodeID { 5 },
|
|
});
|
|
|
|
auto hover_event = read_packet_with_type(client, "pickerNodeHovered"sv);
|
|
EXPECT_EQ(hover_event.get_string("type"sv).value(), "pickerNodeHovered"sv);
|
|
EXPECT_EQ(hover_event.get_object("node"sv)->get_object("node"sv)->get_string("nodeName"sv).value(), "DIV"sv);
|
|
auto const& hover_new_parents = *hover_event.get_object("node"sv)->get_array("newParents"sv);
|
|
EXPECT_EQ(hover_new_parents.size(), 2u);
|
|
EXPECT_EQ(hover_new_parents.at(0).as_object().get_string("nodeName"sv).value(), "BODY"sv);
|
|
EXPECT_EQ(hover_new_parents.at(1).as_object().get_string("nodeName"sv).value(), "HTML"sv);
|
|
EXPECT_EQ(session->delegate.highlight_dom_node_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_dom_node.value(), Web::UniqueNodeID { 4 });
|
|
|
|
session->delegate.emit_node_picker_event({
|
|
.type = DevTools::DevToolsDelegate::NodePickerEvent::Type::Hovered,
|
|
.node_id = Web::UniqueNodeID { 4 },
|
|
});
|
|
EXPECT_EQ(session->delegate.highlight_dom_node_call_count, 1u);
|
|
|
|
session->delegate.emit_node_picker_event({
|
|
.type = DevTools::DevToolsDelegate::NodePickerEvent::Type::Previewed,
|
|
.node_id = Web::UniqueNodeID { 8 },
|
|
});
|
|
|
|
auto preview_event = read_packet_with_type(client, "pickerNodePreviewed"sv);
|
|
EXPECT_EQ(preview_event.get_string("type"sv).value(), "pickerNodePreviewed"sv);
|
|
EXPECT_EQ(preview_event.get_object("node"sv)->get_object("node"sv)->get_string("nodeName"sv).value(), "SPAN"sv);
|
|
EXPECT_EQ(preview_event.get_object("node"sv)->get_array("newParents"sv)->size(), 2u);
|
|
EXPECT_EQ(session->delegate.stop_node_picker_call_count, 0u);
|
|
|
|
session->delegate.emit_node_picker_event({
|
|
.type = DevTools::DevToolsDelegate::NodePickerEvent::Type::Picked,
|
|
.node_id = Web::UniqueNodeID { 5 },
|
|
});
|
|
|
|
auto picked_event = read_packet_with_type(client, "pickerNodePicked"sv);
|
|
EXPECT_EQ(picked_event.get_string("type"sv).value(), "pickerNodePicked"sv);
|
|
EXPECT_EQ(picked_event.get_object("node"sv)->get_object("node"sv)->get_string("nodeName"sv).value(), "DIV"sv);
|
|
EXPECT_EQ(picked_event.get_object("node"sv)->get_array("newParents"sv)->size(), 2u);
|
|
EXPECT_EQ(session->delegate.stop_node_picker_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.clear_node_picker_call_count, 1u);
|
|
|
|
EXPECT_EQ(client.request(walker_actor, "pick"sv).get_string("from"sv).value(), walker_actor);
|
|
EXPECT_EQ(session->delegate.start_node_picker_call_count, 2u);
|
|
|
|
session->delegate.emit_node_picker_event({
|
|
.type = DevTools::DevToolsDelegate::NodePickerEvent::Type::Canceled,
|
|
.node_id = {},
|
|
});
|
|
|
|
auto canceled_event = read_packet_with_type(client, "pickerNodeCanceled"sv);
|
|
EXPECT_EQ(canceled_event.get_string("type"sv).value(), "pickerNodeCanceled"sv);
|
|
EXPECT_EQ(session->delegate.stop_node_picker_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.clear_node_picker_call_count, 2u);
|
|
|
|
EXPECT_EQ(client.request(walker_actor, "pick"sv).get_string("from"sv).value(), walker_actor);
|
|
EXPECT_EQ(client.request(walker_actor, "clearPicker"sv).get_string("from"sv).value(), walker_actor);
|
|
EXPECT_EQ(session->delegate.clear_node_picker_call_count, 3u);
|
|
EXPECT_EQ(client.request(walker_actor, "cancelPick"sv).get_string("from"sv).value(), walker_actor);
|
|
EXPECT_EQ(session->delegate.stop_node_picker_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.clear_node_picker_call_count, 4u);
|
|
|
|
JsonObject children;
|
|
children.set("to"sv, walker_actor);
|
|
children.set("type"sv, "children"sv);
|
|
children.set("node"sv, root_node_actor);
|
|
EXPECT_EQ(client.request(move(children)).get_array("nodes"sv)->size(), 1u);
|
|
}
|
|
|
|
TEST_CASE(inspector_walker_navigation_reloads_root)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto tab = get_tab(client);
|
|
auto tab_actor = actor_from(tab, "actor"sv);
|
|
auto target = get_frame_target(client, tab_actor);
|
|
auto inspector_actor = actor_from(target, "inspectorActor"sv);
|
|
auto walker = get_walker(client, inspector_actor);
|
|
auto walker_actor = actor_from(walker, "actor"sv);
|
|
auto root_node = walker.get_object("root"sv).release_value();
|
|
auto root_node_actor = actor_from(root_node, "actor"sv);
|
|
EXPECT_EQ(session->delegate.navigation_listener_count(), 1u);
|
|
|
|
auto div_actor = query_selector(client, walker_actor, root_node_actor, "div"sv);
|
|
|
|
JsonObject watch_root;
|
|
watch_root.set("to"sv, walker_actor);
|
|
watch_root.set("type"sv, "watchRootNode"sv);
|
|
EXPECT_EQ(client.request(move(watch_root)).get_string("type"sv).value(), "root-available"sv);
|
|
|
|
session->delegate.emit_navigation_start();
|
|
|
|
auto root_destroyed = read_packet_with_type(client, "root-destroyed"sv);
|
|
auto destroyed_node = root_destroyed.get_object("node"sv).release_value();
|
|
EXPECT_EQ(destroyed_node.get_string("actor"sv).value(), root_node_actor);
|
|
EXPECT(destroyed_node.get_bool("isTopLevelDocument"sv).value());
|
|
|
|
JsonObject is_in_dom_tree;
|
|
is_in_dom_tree.set("to"sv, walker_actor);
|
|
is_in_dom_tree.set("type"sv, "isInDOMTree"sv);
|
|
is_in_dom_tree.set("node"sv, div_actor);
|
|
EXPECT(!client.request(move(is_in_dom_tree)).get_bool("attached"sv).value());
|
|
|
|
EXPECT_EQ(session->delegate.clear_highlighted_dom_node_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.clear_inspected_dom_node_call_count, 1u);
|
|
|
|
auto will_navigate = read_resource(client, "document-event"sv);
|
|
EXPECT_EQ(will_navigate.get_string("name"sv).value(), "will-navigate"sv);
|
|
EXPECT_EQ(will_navigate.get_string("newURI"sv).value(), "https://example.test/next"sv);
|
|
EXPECT(!will_navigate.has_string("url"sv));
|
|
|
|
session->delegate.switch_to_navigation_dom_tree();
|
|
session->delegate.emit_navigation_finish();
|
|
|
|
auto root_available = read_packet_with_type(client, "root-available"sv);
|
|
auto new_root_node = root_available.get_object("node"sv).release_value();
|
|
auto new_root_node_actor = actor_from(new_root_node, "actor"sv);
|
|
auto main_actor = query_selector(client, walker_actor, new_root_node_actor, "main"sv);
|
|
|
|
JsonObject main_is_in_dom_tree;
|
|
main_is_in_dom_tree.set("to"sv, walker_actor);
|
|
main_is_in_dom_tree.set("type"sv, "isInDOMTree"sv);
|
|
main_is_in_dom_tree.set("node"sv, main_actor);
|
|
EXPECT(client.request(move(main_is_in_dom_tree)).get_bool("attached"sv).value());
|
|
|
|
EXPECT_EQ(session->delegate.inspect_tab_call_count, 2u);
|
|
|
|
auto target_destroyed = read_packet_with_type(client, "target-destroyed-form"sv);
|
|
auto destroyed_target = target_destroyed.get_object("target"sv).release_value();
|
|
EXPECT_EQ(actor_from(destroyed_target, "actor"sv), actor_from(target, "actor"sv));
|
|
auto options = target_destroyed.get_object("options"sv).release_value();
|
|
EXPECT(options.get_bool("isTargetSwitching"sv).value());
|
|
EXPECT(options.get_bool("shouldDestroyTargetFront"sv).value());
|
|
|
|
auto new_target_available = read_packet_with_type(client, "target-available-form"sv);
|
|
auto new_target = new_target_available.get_object("target"sv).release_value();
|
|
EXPECT_NE(actor_from(new_target, "actor"sv), actor_from(target, "actor"sv));
|
|
EXPECT_EQ(new_target.get_string("url"sv).value(), "https://example.test/next"sv);
|
|
EXPECT_NE(
|
|
new_target.get_integer<u64>("innerWindowId"sv).value(),
|
|
target.get_integer<u64>("innerWindowId"sv).value());
|
|
|
|
EXPECT_EQ(client.request(actor_from(new_target, "actor"sv), "listFrames"sv).get_string("from"sv).value(), actor_from(new_target, "actor"sv));
|
|
|
|
auto dom_loading = read_resource(client, "document-event"sv);
|
|
EXPECT_EQ(dom_loading.get_string("name"sv).value(), "dom-loading"sv);
|
|
EXPECT_EQ(dom_loading.get_string("url"sv).value(), "https://example.test/next"sv);
|
|
EXPECT(!dom_loading.has_string("title"sv));
|
|
|
|
auto dom_interactive = read_resource(client, "document-event"sv);
|
|
EXPECT_EQ(dom_interactive.get_string("name"sv).value(), "dom-interactive"sv);
|
|
EXPECT_EQ(dom_interactive.get_string("url"sv).value(), "https://example.test/next"sv);
|
|
EXPECT_EQ(dom_interactive.get_string("title"sv).value(), "Next page"sv);
|
|
|
|
auto dom_complete = read_resource(client, "document-event"sv);
|
|
EXPECT_EQ(dom_complete.get_string("name"sv).value(), "dom-complete"sv);
|
|
EXPECT(!dom_complete.has_string("url"sv));
|
|
EXPECT(!dom_complete.has_string("title"sv));
|
|
|
|
EXPECT_EQ(session->delegate.navigation_listener_count(), 1u);
|
|
EXPECT_EQ(session->delegate.listen_for_navigation_events_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.stop_listening_for_navigation_events_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.did_connect_devtools_client_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.did_disconnect_devtools_client_call_count, 0u);
|
|
|
|
auto new_walker = get_walker(client, actor_from(new_target, "inspectorActor"sv));
|
|
auto new_walker_root = new_walker.get_object("root"sv).release_value();
|
|
auto new_walker_root_actor = actor_from(new_walker_root, "actor"sv);
|
|
auto heading_actor = query_selector(client, actor_from(new_walker, "actor"sv), new_walker_root_actor, "h1"sv);
|
|
EXPECT(!heading_actor.is_empty());
|
|
|
|
JsonObject reload_descriptor;
|
|
reload_descriptor.set("to"sv, tab_actor);
|
|
reload_descriptor.set("type"sv, "reloadDescriptor"sv);
|
|
reload_descriptor.set("bypassCache"sv, true);
|
|
EXPECT_EQ(client.request(move(reload_descriptor)).get_string("from"sv).value(), tab_actor);
|
|
EXPECT_EQ(session->delegate.reload_tab_call_count, 1u);
|
|
EXPECT(session->delegate.last_reload_bypass_cache.value());
|
|
|
|
session->delegate.emit_navigation_start();
|
|
(void)read_packet_with_type(client, "root-destroyed"sv);
|
|
|
|
session->delegate.emit_navigation_finish();
|
|
(void)read_packet_with_type(client, "root-available"sv);
|
|
|
|
auto refreshed_target_destroyed = read_packet_with_type(client, "target-destroyed-form"sv);
|
|
auto refreshed_destroyed_target = refreshed_target_destroyed.get_object("target"sv).release_value();
|
|
EXPECT_EQ(actor_from(refreshed_destroyed_target, "actor"sv), actor_from(new_target, "actor"sv));
|
|
|
|
auto refreshed_target_available = read_packet_with_type(client, "target-available-form"sv);
|
|
auto refreshed_target = refreshed_target_available.get_object("target"sv).release_value();
|
|
EXPECT_NE(actor_from(refreshed_target, "actor"sv), actor_from(new_target, "actor"sv));
|
|
EXPECT_NE(
|
|
refreshed_target.get_integer<u64>("innerWindowId"sv).value(),
|
|
new_target.get_integer<u64>("innerWindowId"sv).value());
|
|
EXPECT_EQ(session->delegate.navigation_listener_count(), 1u);
|
|
EXPECT_EQ(session->delegate.listen_for_navigation_events_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.stop_listening_for_navigation_events_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.did_connect_devtools_client_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.did_disconnect_devtools_client_call_count, 0u);
|
|
}
|
|
|
|
TEST_CASE(inspector_walker_highlighter_layout_and_editing)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto target = get_frame_target(client, actor_from(get_tab(client), "actor"sv));
|
|
auto inspector_actor = actor_from(target, "inspectorActor"sv);
|
|
auto walker = get_walker(client, inspector_actor);
|
|
auto walker_actor = actor_from(walker, "actor"sv);
|
|
auto root_node = walker.get_object("root"sv).release_value();
|
|
auto root_node_actor = actor_from(root_node, "actor"sv);
|
|
|
|
EXPECT_EQ(session->delegate.inspect_tab_call_count, 1u);
|
|
EXPECT(root_node.get_bool("isTopLevelDocument"sv).value());
|
|
EXPECT_EQ(root_node.get_string("displayType"sv).value(), "block"sv);
|
|
|
|
JsonObject children;
|
|
children.set("to"sv, walker_actor);
|
|
children.set("type"sv, "children"sv);
|
|
children.set("node"sv, root_node_actor);
|
|
auto children_response = client.request(move(children));
|
|
EXPECT(children_response.get_bool("hasFirst"sv).value());
|
|
EXPECT_EQ(children_response.get_array("nodes"sv)->size(), 1u);
|
|
|
|
auto div_actor = query_selector(client, walker_actor, root_node_actor, "div"sv);
|
|
auto section_actor = query_selector(client, walker_actor, root_node_actor, "section"sv);
|
|
auto span_actor = query_selector(client, walker_actor, root_node_actor, "span"sv);
|
|
auto flex_actor = query_selector(client, walker_actor, root_node_actor, "aside"sv);
|
|
auto first_flex_item_actor = query_selector(client, walker_actor, root_node_actor, "p"sv);
|
|
JsonObject previous_sibling;
|
|
previous_sibling.set("to"sv, walker_actor);
|
|
previous_sibling.set("type"sv, "previousSibling"sv);
|
|
previous_sibling.set("node"sv, div_actor);
|
|
EXPECT_EQ(client.request(move(previous_sibling)).get_object("node"sv)->get_string("displayName"sv).value(), "span"sv);
|
|
|
|
JsonObject is_in_dom_tree;
|
|
is_in_dom_tree.set("to"sv, walker_actor);
|
|
is_in_dom_tree.set("type"sv, "isInDOMTree"sv);
|
|
is_in_dom_tree.set("node"sv, div_actor);
|
|
EXPECT(client.request(move(is_in_dom_tree)).get_bool("attached"sv).value());
|
|
|
|
JsonObject watch_root;
|
|
watch_root.set("to"sv, walker_actor);
|
|
watch_root.set("type"sv, "watchRootNode"sv);
|
|
EXPECT_EQ(client.request(move(watch_root)).get_string("type"sv).value(), "root-available"sv);
|
|
while (true) {
|
|
auto message = client.read_message();
|
|
if (message.get_string("from"sv).value_or({}) == walker_actor && !message.has_string("type"sv))
|
|
break;
|
|
}
|
|
|
|
EXPECT(client.request(walker_actor, "retainNode"sv).has_string("from"sv));
|
|
EXPECT(client.request(walker_actor, "getOffsetParent"sv).get("node"sv).value().is_null());
|
|
|
|
JsonObject highlighter_request;
|
|
highlighter_request.set("to"sv, inspector_actor);
|
|
highlighter_request.set("type"sv, "getHighlighterByType"sv);
|
|
highlighter_request.set("typeName"sv, "BoxModelHighlighter"sv);
|
|
auto highlighter_actor = client.request(move(highlighter_request)).get_object("highlighter"sv)->get_string("actor"sv).release_value();
|
|
|
|
JsonObject second_highlighter_request;
|
|
second_highlighter_request.set("to"sv, inspector_actor);
|
|
second_highlighter_request.set("type"sv, "getHighlighterByType"sv);
|
|
second_highlighter_request.set("typeName"sv, "BoxModelHighlighter"sv);
|
|
auto second_highlighter_response = client.request(move(second_highlighter_request));
|
|
VERIFY(second_highlighter_response.has_object("highlighter"sv));
|
|
EXPECT_NE(
|
|
second_highlighter_response.get_object("highlighter"sv)->get_string("actor"sv).value(),
|
|
highlighter_actor);
|
|
|
|
JsonObject show_highlighter;
|
|
show_highlighter.set("to"sv, highlighter_actor);
|
|
show_highlighter.set("type"sv, "show"sv);
|
|
show_highlighter.set("node"sv, div_actor);
|
|
EXPECT(client.request(move(show_highlighter)).get_bool("value"sv).value());
|
|
EXPECT_EQ(session->delegate.highlight_dom_node_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_dom_node.value(), 4u);
|
|
|
|
JsonObject show_unknown;
|
|
show_unknown.set("to"sv, highlighter_actor);
|
|
show_unknown.set("type"sv, "show"sv);
|
|
show_unknown.set("node"sv, "missing-actor"sv);
|
|
EXPECT(!client.request(move(show_unknown)).get_bool("value"sv).value());
|
|
EXPECT_EQ(client.request(highlighter_actor, "hide"sv).get_string("from"sv).value(), highlighter_actor);
|
|
|
|
JsonObject grid_highlighter_request;
|
|
grid_highlighter_request.set("to"sv, inspector_actor);
|
|
grid_highlighter_request.set("type"sv, "getHighlighterByType"sv);
|
|
grid_highlighter_request.set("typeName"sv, "CssGridHighlighter"sv);
|
|
auto grid_highlighter_actor = client.request(move(grid_highlighter_request)).get_object("highlighter"sv)->get_string("actor"sv).release_value();
|
|
|
|
JsonObject second_grid_highlighter_request;
|
|
second_grid_highlighter_request.set("to"sv, inspector_actor);
|
|
second_grid_highlighter_request.set("type"sv, "getHighlighterByType"sv);
|
|
second_grid_highlighter_request.set("typeName"sv, "CssGridHighlighter"sv);
|
|
auto second_grid_highlighter_actor = client.request(move(second_grid_highlighter_request))
|
|
.get_object("highlighter"sv)
|
|
->get_string("actor"sv)
|
|
.release_value();
|
|
EXPECT_NE(second_grid_highlighter_actor, grid_highlighter_actor);
|
|
|
|
JsonObject grid_options;
|
|
grid_options.set("showGridArea"sv, true);
|
|
|
|
JsonObject show_grid_highlighter;
|
|
show_grid_highlighter.set("to"sv, grid_highlighter_actor);
|
|
show_grid_highlighter.set("type"sv, "show"sv);
|
|
show_grid_highlighter.set("node"sv, div_actor);
|
|
show_grid_highlighter.set("options"sv, move(grid_options));
|
|
EXPECT(client.request(move(show_grid_highlighter)).get_bool("value"sv).value());
|
|
EXPECT_EQ(session->delegate.highlight_grid_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_grid_node.value(), 4u);
|
|
EXPECT(session->delegate.last_grid_highlight_options.as_object().get_bool("showGridArea"sv).value());
|
|
EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 0u);
|
|
|
|
JsonObject show_second_grid_highlighter;
|
|
show_second_grid_highlighter.set("to"sv, second_grid_highlighter_actor);
|
|
show_second_grid_highlighter.set("type"sv, "show"sv);
|
|
show_second_grid_highlighter.set("node"sv, span_actor);
|
|
EXPECT(client.request(move(show_second_grid_highlighter)).get_bool("value"sv).value());
|
|
EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 0u);
|
|
EXPECT_EQ(session->delegate.highlight_grid_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_grid_node.value(), 8u);
|
|
|
|
EXPECT_EQ(client.request(grid_highlighter_actor, "hide"sv).get_string("from"sv).value(), grid_highlighter_actor);
|
|
EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_cleared_grid_node.value(), 4u);
|
|
|
|
JsonObject retarget_second_grid_highlighter;
|
|
retarget_second_grid_highlighter.set("to"sv, second_grid_highlighter_actor);
|
|
retarget_second_grid_highlighter.set("type"sv, "show"sv);
|
|
retarget_second_grid_highlighter.set("node"sv, div_actor);
|
|
EXPECT(client.request(move(retarget_second_grid_highlighter)).get_bool("value"sv).value());
|
|
EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.last_cleared_grid_node.value(), 8u);
|
|
EXPECT_EQ(session->delegate.highlight_grid_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_grid_node.value(), 4u);
|
|
|
|
EXPECT_EQ(client.request(second_grid_highlighter_actor, "release"sv).get_string("from"sv).value(), second_grid_highlighter_actor);
|
|
EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.last_cleared_grid_node.value(), 4u);
|
|
|
|
JsonObject flexbox_highlighter_request;
|
|
flexbox_highlighter_request.set("to"sv, inspector_actor);
|
|
flexbox_highlighter_request.set("type"sv, "getHighlighterByType"sv);
|
|
flexbox_highlighter_request.set("typeName"sv, "FlexboxHighlighter"sv);
|
|
auto flexbox_highlighter_actor = client.request(move(flexbox_highlighter_request)).get_object("highlighter"sv)->get_string("actor"sv).release_value();
|
|
|
|
JsonObject second_flexbox_highlighter_request;
|
|
second_flexbox_highlighter_request.set("to"sv, inspector_actor);
|
|
second_flexbox_highlighter_request.set("type"sv, "getHighlighterByType"sv);
|
|
second_flexbox_highlighter_request.set("typeName"sv, "FlexboxHighlighter"sv);
|
|
auto second_flexbox_highlighter_actor = client.request(move(second_flexbox_highlighter_request))
|
|
.get_object("highlighter"sv)
|
|
->get_string("actor"sv)
|
|
.release_value();
|
|
EXPECT_NE(second_flexbox_highlighter_actor, flexbox_highlighter_actor);
|
|
|
|
JsonObject flexbox_options;
|
|
flexbox_options.set("color"sv, "#9400ff"sv);
|
|
|
|
JsonObject show_flexbox_highlighter;
|
|
show_flexbox_highlighter.set("to"sv, flexbox_highlighter_actor);
|
|
show_flexbox_highlighter.set("type"sv, "show"sv);
|
|
show_flexbox_highlighter.set("node"sv, flex_actor);
|
|
show_flexbox_highlighter.set("options"sv, move(flexbox_options));
|
|
EXPECT(client.request(move(show_flexbox_highlighter)).get_bool("value"sv).value());
|
|
EXPECT_EQ(session->delegate.highlight_flexbox_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_flexbox_node.value(), 10u);
|
|
EXPECT_EQ(session->delegate.last_flexbox_highlight_options.as_object().get_string("color"sv).value(), "#9400ff"sv);
|
|
EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 0u);
|
|
|
|
JsonObject show_second_flexbox_highlighter;
|
|
show_second_flexbox_highlighter.set("to"sv, second_flexbox_highlighter_actor);
|
|
show_second_flexbox_highlighter.set("type"sv, "show"sv);
|
|
show_second_flexbox_highlighter.set("node"sv, first_flex_item_actor);
|
|
EXPECT(client.request(move(show_second_flexbox_highlighter)).get_bool("value"sv).value());
|
|
EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 0u);
|
|
EXPECT_EQ(session->delegate.highlight_flexbox_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_flexbox_node.value(), 11u);
|
|
|
|
EXPECT_EQ(client.request(flexbox_highlighter_actor, "hide"sv).get_string("from"sv).value(), flexbox_highlighter_actor);
|
|
EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_cleared_flexbox_node.value(), 10u);
|
|
|
|
JsonObject retarget_second_flexbox_highlighter;
|
|
retarget_second_flexbox_highlighter.set("to"sv, second_flexbox_highlighter_actor);
|
|
retarget_second_flexbox_highlighter.set("type"sv, "show"sv);
|
|
retarget_second_flexbox_highlighter.set("node"sv, flex_actor);
|
|
EXPECT(client.request(move(retarget_second_flexbox_highlighter)).get_bool("value"sv).value());
|
|
EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.last_cleared_flexbox_node.value(), 11u);
|
|
EXPECT_EQ(session->delegate.highlight_flexbox_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.last_highlighted_flexbox_node.value(), 10u);
|
|
|
|
EXPECT_EQ(client.request(second_flexbox_highlighter_actor, "release"sv).get_string("from"sv).value(), second_flexbox_highlighter_actor);
|
|
EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 3u);
|
|
EXPECT_EQ(session->delegate.last_cleared_flexbox_node.value(), 10u);
|
|
|
|
auto layout_actor = client.request(walker_actor, "getLayoutInspector"sv).get_object("actor"sv)->get_string("actor"sv).release_value();
|
|
|
|
JsonObject get_grids;
|
|
get_grids.set("to"sv, layout_actor);
|
|
get_grids.set("type"sv, "getGrids"sv);
|
|
get_grids.set("rootNode"sv, root_node_actor);
|
|
auto grids = client.request(move(get_grids)).get_array("grids"sv).release_value();
|
|
EXPECT_EQ(session->delegate.inspect_grid_layouts_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_grid_root_node.value(), 1u);
|
|
EXPECT_EQ(grids.size(), 3u);
|
|
|
|
auto const& content_grid = grids.at(0).as_object();
|
|
EXPECT(!content_grid.has("containerNodeId"sv));
|
|
EXPECT(content_grid.has_string("actor"sv));
|
|
EXPECT_EQ(content_grid.get_string("containerNodeActorID"sv).value(), div_actor);
|
|
EXPECT_EQ(content_grid.get_string("direction"sv).value(), "ltr"sv);
|
|
EXPECT_EQ(content_grid.get_string("writingMode"sv).value(), "horizontal-tb"sv);
|
|
EXPECT_EQ(content_grid.get_bool("isSubgrid"sv).value(), false);
|
|
auto const& content_grid_fragment = content_grid.get_array("gridFragments"sv)->at(0).as_object();
|
|
EXPECT_EQ(content_grid_fragment.get_array("areas"sv)->at(0).as_object().get_string("name"sv).value(), "content"sv);
|
|
EXPECT_EQ(content_grid_fragment.get_object("cols"sv)->get_array("lines"sv)->at(0).as_object().get_integer<i32>("negativeNumber"sv).value(), -2);
|
|
|
|
auto const& subgrid = grids.at(1).as_object();
|
|
EXPECT_EQ(subgrid.get_string("containerNodeActorID"sv).value(), section_actor);
|
|
EXPECT_EQ(subgrid.get_bool("isSubgrid"sv).value(), true);
|
|
EXPECT_EQ(subgrid.get_array("gridFragments"sv)->at(0).as_object().get_array("areas"sv)->at(0).as_object().get_string("name"sv).value(), "subgrid"sv);
|
|
|
|
JsonObject get_parent_grid_node;
|
|
get_parent_grid_node.set("to"sv, walker_actor);
|
|
get_parent_grid_node.set("type"sv, "getParentGridNode"sv);
|
|
get_parent_grid_node.set("node"sv, section_actor);
|
|
EXPECT_EQ(client.request(move(get_parent_grid_node)).get_object("node"sv)->get_string("actor"sv).value(), div_actor);
|
|
|
|
JsonObject get_missing_parent_grid_node;
|
|
get_missing_parent_grid_node.set("to"sv, walker_actor);
|
|
get_missing_parent_grid_node.set("type"sv, "getParentGridNode"sv);
|
|
get_missing_parent_grid_node.set("node"sv, div_actor);
|
|
EXPECT(client.request(move(get_missing_parent_grid_node)).get("node"sv).value().is_null());
|
|
|
|
auto const& subframe_grid = grids.at(2).as_object();
|
|
EXPECT_EQ(subframe_grid.get_string("containerNodeActorID"sv).value(), span_actor);
|
|
EXPECT_EQ(subframe_grid.get_array("gridFragments"sv)->at(0).as_object().get_array("areas"sv)->at(0).as_object().get_string("name"sv).value(), "subframe"sv);
|
|
|
|
JsonObject get_current_grid;
|
|
get_current_grid.set("to"sv, layout_actor);
|
|
get_current_grid.set("type"sv, "getCurrentGrid"sv);
|
|
get_current_grid.set("node"sv, div_actor);
|
|
auto current_grid = client.request(move(get_current_grid)).get_object("grid"sv).release_value();
|
|
EXPECT_EQ(session->delegate.inspect_current_grid_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_current_grid_node.value(), 4u);
|
|
EXPECT_EQ(current_grid.get_string("actor"sv).value(), content_grid.get_string("actor"sv).value());
|
|
EXPECT_EQ(current_grid.get_string("containerNodeActorID"sv).value(), div_actor);
|
|
|
|
JsonObject get_missing_flexbox;
|
|
get_missing_flexbox.set("to"sv, layout_actor);
|
|
get_missing_flexbox.set("type"sv, "getCurrentFlexbox"sv);
|
|
get_missing_flexbox.set("node"sv, root_node_actor);
|
|
EXPECT(client.request(move(get_missing_flexbox)).get("flexbox"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.inspect_current_flexbox_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_current_flexbox_node.value(), 1u);
|
|
EXPECT(!session->delegate.last_current_flexbox_only_look_at_parents);
|
|
|
|
JsonObject get_current_flexbox;
|
|
get_current_flexbox.set("to"sv, layout_actor);
|
|
get_current_flexbox.set("type"sv, "getCurrentFlexbox"sv);
|
|
get_current_flexbox.set("node"sv, flex_actor);
|
|
auto current_flexbox = client.request(move(get_current_flexbox)).get_object("flexbox"sv).release_value();
|
|
EXPECT_EQ(session->delegate.inspect_current_flexbox_call_count, 2u);
|
|
EXPECT_EQ(session->delegate.last_current_flexbox_node.value(), 10u);
|
|
EXPECT_EQ(current_flexbox.get_string("containerNodeActorID"sv).value(), flex_actor);
|
|
EXPECT(!current_flexbox.has("containerNodeId"sv));
|
|
EXPECT(!current_flexbox.has("items"sv));
|
|
EXPECT_EQ(current_flexbox.get_object("properties"sv)->get_string("flex-direction"sv).value(), "row"sv);
|
|
auto flexbox_actor = current_flexbox.get_string("actor"sv).release_value();
|
|
|
|
JsonObject get_parent_flexbox;
|
|
get_parent_flexbox.set("to"sv, layout_actor);
|
|
get_parent_flexbox.set("type"sv, "getCurrentFlexbox"sv);
|
|
get_parent_flexbox.set("node"sv, flex_actor);
|
|
get_parent_flexbox.set("onlyLookAtParents"sv, true);
|
|
EXPECT(client.request(move(get_parent_flexbox)).get("flexbox"sv).value().is_null());
|
|
EXPECT_EQ(session->delegate.inspect_current_flexbox_call_count, 3u);
|
|
EXPECT(session->delegate.last_current_flexbox_only_look_at_parents);
|
|
|
|
JsonObject get_flex_item_parent_flexbox;
|
|
get_flex_item_parent_flexbox.set("to"sv, layout_actor);
|
|
get_flex_item_parent_flexbox.set("type"sv, "getCurrentFlexbox"sv);
|
|
get_flex_item_parent_flexbox.set("node"sv, first_flex_item_actor);
|
|
get_flex_item_parent_flexbox.set("onlyLookAtParents"sv, true);
|
|
auto parent_flexbox = client.request(move(get_flex_item_parent_flexbox)).get_object("flexbox"sv).release_value();
|
|
EXPECT_EQ(session->delegate.inspect_current_flexbox_call_count, 4u);
|
|
EXPECT_EQ(session->delegate.last_current_flexbox_node.value(), 11u);
|
|
EXPECT_EQ(parent_flexbox.get_string("actor"sv).value(), flexbox_actor);
|
|
|
|
JsonObject get_flex_items;
|
|
get_flex_items.set("to"sv, flexbox_actor);
|
|
get_flex_items.set("type"sv, "getFlexItems"sv);
|
|
auto flex_items = client.request(move(get_flex_items)).get_array("flexitems"sv).release_value();
|
|
EXPECT_EQ(flex_items.size(), 2u);
|
|
auto const& first_flex_item = flex_items.at(0).as_object();
|
|
EXPECT(first_flex_item.has_string("actor"sv));
|
|
EXPECT(!first_flex_item.has("nodeId"sv));
|
|
EXPECT_EQ(first_flex_item.get_string("nodeActorID"sv).value(), first_flex_item_actor);
|
|
EXPECT_EQ(first_flex_item.get_object("flexItemSizing"sv)->get_string("lineGrowthState"sv).value(), "growing"sv);
|
|
EXPECT_EQ(first_flex_item.get_object("flexItemSizing"sv)->get_string("clampState"sv).value(), "unclamped"sv);
|
|
EXPECT_EQ(first_flex_item.get_object("properties"sv)->get_string("flex-basis"sv).value(), "auto"sv);
|
|
EXPECT_EQ(first_flex_item.get_object("computedStyle"sv)->get_integer<int>("flexGrow"sv).value(), 1);
|
|
|
|
auto const& second_flex_item = flex_items.at(1).as_object();
|
|
EXPECT_EQ(second_flex_item.get_object("flexItemSizing"sv)->get_string("lineGrowthState"sv).value(), "shrinking"sv);
|
|
EXPECT_EQ(second_flex_item.get_object("flexItemSizing"sv)->get_string("clampState"sv).value(), "clamped_to_min"sv);
|
|
|
|
JsonObject node_from_flexbox;
|
|
node_from_flexbox.set("to"sv, walker_actor);
|
|
node_from_flexbox.set("type"sv, "getNodeFromActor"sv);
|
|
node_from_flexbox.set("actorID"sv, flexbox_actor);
|
|
JsonArray flexbox_path;
|
|
flexbox_path.must_append("containerEl"sv);
|
|
node_from_flexbox.set("path"sv, move(flexbox_path));
|
|
EXPECT_EQ(client.request(move(node_from_flexbox)).get_object("node"sv)->get_object("node"sv)->get_string("actor"sv).value(), flex_actor);
|
|
|
|
JsonObject node_from_flex_item;
|
|
node_from_flex_item.set("to"sv, walker_actor);
|
|
node_from_flex_item.set("type"sv, "getNodeFromActor"sv);
|
|
node_from_flex_item.set("actorID"sv, first_flex_item.get_string("actor"sv).value());
|
|
JsonArray flex_item_path;
|
|
flex_item_path.must_append("element"sv);
|
|
node_from_flex_item.set("path"sv, move(flex_item_path));
|
|
EXPECT_EQ(client.request(move(node_from_flex_item)).get_object("node"sv)->get_object("node"sv)->get_string("actor"sv).value(), first_flex_item_actor);
|
|
|
|
JsonObject set_outer_html;
|
|
set_outer_html.set("to"sv, walker_actor);
|
|
set_outer_html.set("type"sv, "setOuterHTML"sv);
|
|
set_outer_html.set("node"sv, div_actor);
|
|
set_outer_html.set("value"sv, "<section></section>"sv);
|
|
EXPECT(client.request(move(set_outer_html)).has_string("from"sv));
|
|
EXPECT_EQ(session->delegate.set_dom_node_outer_html_call_count, 1u);
|
|
EXPECT_EQ(session->delegate.last_html.value(), "<section></section>"sv);
|
|
|
|
JsonObject edit_tag;
|
|
edit_tag.set("to"sv, walker_actor);
|
|
edit_tag.set("type"sv, "editTagName"sv);
|
|
edit_tag.set("node"sv, div_actor);
|
|
edit_tag.set("tagName"sv, "section"sv);
|
|
EXPECT(client.request(move(edit_tag)).has_string("from"sv));
|
|
EXPECT_EQ(session->delegate.set_dom_node_tag_call_count, 1u);
|
|
|
|
JsonObject duplicate;
|
|
duplicate.set("to"sv, walker_actor);
|
|
duplicate.set("type"sv, "duplicateNode"sv);
|
|
duplicate.set("node"sv, div_actor);
|
|
EXPECT(client.request(move(duplicate)).has_string("from"sv));
|
|
EXPECT_EQ(session->delegate.clone_dom_node_call_count, 1u);
|
|
|
|
JsonObject mutation_target = make_node(4, "element"sv, "DIV"sv);
|
|
JsonObject attributes;
|
|
attributes.set("id"sv, "target"sv);
|
|
attributes.set("class"sv, "updated"sv);
|
|
mutation_target.set("attributes"sv, move(attributes));
|
|
|
|
WebView::Mutation mutation { "attributes"_string, 4, mutation_target.serialized(), WebView::AttributeMutation { "class"_string, "updated"_string } };
|
|
session->delegate.emit_mutation(move(mutation));
|
|
EXPECT_EQ(client.read_message().get_string("type"sv).value(), "newMutations"sv);
|
|
|
|
auto mutations = client.request(walker_actor, "getMutations"sv).get_array("mutations"sv).release_value();
|
|
VERIFY(mutations.size() == 1u);
|
|
EXPECT_EQ(mutations.at(0).as_object().get_string("type"sv).value(), "attributes"sv);
|
|
EXPECT_EQ(mutations.at(0).as_object().get_string("newValue"sv).value(), "updated"sv);
|
|
}
|
|
|
|
TEST_CASE(styles_and_stylesheets)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto target = get_frame_target(client, actor_from(get_tab(client), "actor"sv));
|
|
auto inspector_actor = actor_from(target, "inspectorActor"sv);
|
|
auto style_sheets_actor = actor_from(target, "styleSheetsActor"sv);
|
|
|
|
auto page_style = client.request(inspector_actor, "getPageStyle"sv).get_object("pageStyle"sv).release_value();
|
|
auto page_style_actor = actor_from(page_style, "actor"sv);
|
|
EXPECT(page_style.get_object("traits"sv)->get_bool("fontVariations"sv).value());
|
|
|
|
auto walker = get_walker(client, inspector_actor);
|
|
auto walker_actor = actor_from(walker, "actor"sv);
|
|
auto root_actor = walker.get_object("root"sv)->get_string("actor"sv).release_value();
|
|
auto div_actor = query_selector(client, walker_actor, root_actor, "div"sv);
|
|
|
|
auto get_applied = [&] {
|
|
JsonObject applied;
|
|
applied.set("to"sv, page_style_actor);
|
|
applied.set("type"sv, "getApplied"sv);
|
|
applied.set("node"sv, div_actor);
|
|
applied.set("inherited"sv, true);
|
|
applied.set("matchedSelectors"sv, true);
|
|
return client.request(move(applied)).get_array("entries"sv).release_value();
|
|
};
|
|
|
|
auto applied_entries = get_applied();
|
|
EXPECT(session->delegate.last_inspected_dom_node_options.get_bool("inherited"sv).value());
|
|
EXPECT(session->delegate.last_inspected_dom_node_options.get_bool("matchedSelectors"sv).value());
|
|
VERIFY(applied_entries.size() == 3u);
|
|
EXPECT_EQ(style_rule_actor_count(*session->server), applied_entries.size());
|
|
|
|
auto inline_rule = applied_entries.at(0).as_object().get_object("rule"sv).release_value();
|
|
auto inline_rule_actor = actor_from(inline_rule, "actor"sv);
|
|
EXPECT_EQ(inline_rule.get_integer<int>("type"sv).value(), 100);
|
|
EXPECT(inline_rule.get_array("ancestorData"sv)->is_empty());
|
|
EXPECT(!inline_rule.get_object("traits"sv)->get_bool("canSetRuleText"sv).value());
|
|
EXPECT_EQ(inline_rule.get_array("declarations"sv)->at(0).as_object().get_string("name"sv).value(), "display"sv);
|
|
EXPECT(inline_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isNameValid"sv).value());
|
|
EXPECT(inline_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isValid"sv).value());
|
|
EXPECT_EQ(client.request(inline_rule_actor, "getRuleText"sv).get_string("text"sv).value(), "display: grid;"sv);
|
|
|
|
auto inherited_rule_entry = applied_entries.at(1).as_object();
|
|
auto inherited_rule = inherited_rule_entry.get_object("rule"sv).release_value();
|
|
EXPECT_EQ(inherited_rule_entry.get_string("inherited"sv).value(), root_actor);
|
|
EXPECT_EQ(inherited_rule.get_array("selectors"sv)->at(0).as_string(), "body div.fixture"sv);
|
|
EXPECT_EQ(inherited_rule.get_array("declarations"sv)->at(0).as_object().get_string("name"sv).value(), "color"sv);
|
|
EXPECT(inherited_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isNameValid"sv).value());
|
|
EXPECT(inherited_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isValid"sv).value());
|
|
EXPECT(inherited_rule.get_array("declarations"sv)->at(1).as_object().get_bool("isNameValid"sv).value());
|
|
EXPECT(!inherited_rule.get_array("declarations"sv)->at(1).as_object().get_bool("isValid"sv).value());
|
|
EXPECT_EQ(inherited_rule.get_string("parentStyleSheet"sv).value(), MUST(String::formatted("{}-stylesheet:0", style_sheets_actor)));
|
|
EXPECT_EQ(inherited_rule.get_integer<int>("line"sv).value(), 4);
|
|
EXPECT_EQ(inherited_rule.get_integer<int>("column"sv).value(), 9);
|
|
EXPECT(!inherited_rule.has("styleSheet"sv));
|
|
EXPECT_EQ(inherited_rule_entry.get_array("matchedSelectorIndexes"sv)->at(0).as_integer<int>(), 0);
|
|
|
|
auto user_agent_rule_entry = applied_entries.at(2).as_object();
|
|
auto user_agent_rule = user_agent_rule_entry.get_object("rule"sv).release_value();
|
|
EXPECT(user_agent_rule_entry.get_bool("isSystem"sv).value());
|
|
EXPECT_EQ(user_agent_rule.get_array("selectors"sv)->at(0).as_string(), "div"sv);
|
|
EXPECT_EQ(user_agent_rule.get_string("parentStyleSheet"sv).value(), MUST(String::formatted("{}-stylesheet:1", style_sheets_actor)));
|
|
EXPECT_EQ(user_agent_rule.get_integer<int>("line"sv).value(), 12);
|
|
EXPECT_EQ(user_agent_rule.get_integer<int>("column"sv).value(), 5);
|
|
EXPECT(!user_agent_rule.has("styleSheet"sv));
|
|
EXPECT(!client.request(page_style_actor, "isPositionEditable"sv).get_bool("value"sv).value());
|
|
|
|
auto second_applied_entries = get_applied();
|
|
VERIFY(second_applied_entries.size() == applied_entries.size());
|
|
spin_until(session->loop, [&] {
|
|
return style_rule_actor_count(*session->server) == second_applied_entries.size()
|
|
&& !session->server->actor_registry().contains(inline_rule_actor);
|
|
});
|
|
|
|
JsonObject computed_request;
|
|
computed_request.set("to"sv, page_style_actor);
|
|
computed_request.set("type"sv, "getComputed"sv);
|
|
computed_request.set("node"sv, div_actor);
|
|
auto computed = client.request(move(computed_request)).get_object("computed"sv).release_value();
|
|
EXPECT_EQ(computed.get_object("display"sv)->get_string("value"sv).value(), "block"sv);
|
|
|
|
JsonObject layout_request;
|
|
layout_request.set("to"sv, page_style_actor);
|
|
layout_request.set("type"sv, "getLayout"sv);
|
|
layout_request.set("node"sv, div_actor);
|
|
auto layout = client.request(move(layout_request));
|
|
EXPECT_EQ(layout.get_string("width"sv).value(), "100px"sv);
|
|
EXPECT_EQ(layout.get_string("margin-top"sv).value(), "5px"sv);
|
|
|
|
JsonObject font_request;
|
|
font_request.set("to"sv, page_style_actor);
|
|
font_request.set("type"sv, "getUsedFontFaces"sv);
|
|
font_request.set("node"sv, div_actor);
|
|
auto fonts = client.request(move(font_request)).get_array("fontFaces"sv).release_value();
|
|
VERIFY(fonts.size() == 1u);
|
|
EXPECT_EQ(fonts.at(0).as_object().get_string("name"sv).value(), "Test Sans"sv);
|
|
|
|
auto style_resources = client.read_message();
|
|
while (style_resources.get_string("type"sv).value_or({}) != "resources-available-array"sv)
|
|
style_resources = client.read_message();
|
|
auto sheets = style_resources.get_array("array"sv)->at(0).as_array().at(1).as_array();
|
|
VERIFY(sheets.size() == 2u);
|
|
auto resource_id = sheets.at(0).as_object().get_string("resourceId"sv).release_value();
|
|
|
|
JsonObject get_text;
|
|
get_text.set("to"sv, style_sheets_actor);
|
|
get_text.set("type"sv, "getText"sv);
|
|
get_text.set("resourceId"sv, resource_id);
|
|
EXPECT_EQ(client.request(move(get_text)).get_string("text"sv).value(), "body { color: red; }"sv);
|
|
|
|
JsonObject bad_get_text;
|
|
bad_get_text.set("to"sv, style_sheets_actor);
|
|
bad_get_text.set("type"sv, "getText"sv);
|
|
bad_get_text.set("resourceId"sv, "missing:99"sv);
|
|
EXPECT_EQ(client.request(move(bad_get_text)).get_string("error"sv).value(), "unknownActor"sv);
|
|
}
|
|
|
|
TEST_CASE(devtools_server_teardown_with_pending_actor_cleanup)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto tab_actor = actor_from(get_tab(client), "actor"sv);
|
|
session->server->unregister_actor(tab_actor);
|
|
session->server->connection()->on_connection_closed();
|
|
session->server.clear();
|
|
pump(session->loop);
|
|
}
|
|
|
|
TEST_CASE(console_network_navigation_and_accessibility)
|
|
{
|
|
auto session = create_session();
|
|
auto& client = *session->client;
|
|
(void)client.read_message();
|
|
|
|
auto target = get_frame_target(client, actor_from(get_tab(client), "actor"sv));
|
|
auto accessibility_actor = actor_from(target, "accessibilityActor"sv);
|
|
|
|
session->delegate.emit_console_log(JS::Console::LogLevel::Warn, { "careful"_string });
|
|
auto warning = read_resource(client, "console-message"sv);
|
|
EXPECT_EQ(warning.get_string("level"sv).value(), "warn"sv);
|
|
|
|
session->delegate.emit_console_trace();
|
|
auto trace = read_resource(client, "console-message"sv);
|
|
EXPECT_EQ(trace.get_string("level"sv).value(), "trace"sv);
|
|
EXPECT_EQ(trace.get_array("stacktrace"sv)->at(0).as_object().get_string("functionName"sv).value(), "traceFunction"sv);
|
|
|
|
session->delegate.emit_console_error();
|
|
auto page_error = read_resource(client, "error-message"sv).get_object("pageError"sv).release_value();
|
|
EXPECT(page_error.get_bool("isPromiseRejection"sv).value());
|
|
|
|
session->delegate.emit_network_lifecycle();
|
|
auto network_event = read_resource(client, "network-event"sv);
|
|
EXPECT_EQ(network_event.get_string("method"sv).value(), "POST"sv);
|
|
EXPECT(network_event.get_bool("isXHR"sv).value());
|
|
auto network_actor = network_event.get_string("actor"sv).release_value();
|
|
(void)read_resource(client, "network-event"sv, "resources-updated-array"sv);
|
|
(void)read_resource(client, "network-event"sv, "resources-updated-array"sv);
|
|
|
|
JsonObject content_request;
|
|
content_request.set("to"sv, network_actor);
|
|
content_request.set("type"sv, "getResponseContent"sv);
|
|
auto content = client.request(move(content_request)).get_object("content"sv).release_value();
|
|
EXPECT_EQ(content.get_string("text"sv).value(), "{\"ok\":true}"sv);
|
|
EXPECT_EQ(content.get_string("mimeType"sv).value(), "application/json"sv);
|
|
|
|
session->delegate.emit_navigation();
|
|
EXPECT_EQ(read_resource(client, "document-event"sv).get_string("name"sv).value(), "will-navigate"sv);
|
|
|
|
(void)read_packet_with_type(client, "target-destroyed-form"sv);
|
|
auto new_target_available = read_packet_with_type(client, "target-available-form"sv);
|
|
target = new_target_available.get_object("target"sv).release_value();
|
|
accessibility_actor = actor_from(target, "accessibilityActor"sv);
|
|
|
|
EXPECT_EQ(client.request(actor_from(target, "actor"sv), "listFrames"sv).get_string("from"sv).value(), actor_from(target, "actor"sv));
|
|
|
|
EXPECT_EQ(read_resource(client, "document-event"sv).get_string("name"sv).value(), "dom-loading"sv);
|
|
EXPECT_EQ(read_resource(client, "document-event"sv).get_string("name"sv).value(), "dom-interactive"sv);
|
|
EXPECT_EQ(read_resource(client, "document-event"sv).get_string("name"sv).value(), "dom-complete"sv);
|
|
|
|
EXPECT(client.request(accessibility_actor, "bootstrap"sv).has_object("state"sv));
|
|
EXPECT(client.request(accessibility_actor, "getTraits"sv).get_object("traits"sv)->get_bool("tabbingOrder"sv).value());
|
|
EXPECT(client.request(accessibility_actor, "getSimulator"sv).get("simulator"sv).value().is_null());
|
|
|
|
auto accessibility_walker = client.request(accessibility_actor, "getWalker"sv).get_object("walker"sv)->get_string("actor"sv).release_value();
|
|
auto accessibility_root = client.request(accessibility_walker, "children"sv).get_array("children"sv)->at(0).as_object().get_string("actor"sv).release_value();
|
|
auto accessibility_button = client.request(accessibility_root, "children"sv).get_array("children"sv)->at(0).as_object().get_string("actor"sv).release_value();
|
|
|
|
auto walker = get_walker(client, actor_from(target, "inspectorActor"sv));
|
|
JsonObject node_from_accessibility;
|
|
node_from_accessibility.set("to"sv, actor_from(walker, "actor"sv));
|
|
node_from_accessibility.set("type"sv, "getNodeFromActor"sv);
|
|
node_from_accessibility.set("actorID"sv, accessibility_button);
|
|
JsonArray path;
|
|
path.must_append("rawAccessible"sv);
|
|
path.must_append("DOMNode"sv);
|
|
node_from_accessibility.set("path"sv, move(path));
|
|
auto node_response = client.request(move(node_from_accessibility));
|
|
EXPECT_EQ(node_response.get_object("node"sv)->get_object("node"sv)->get_string("displayName"sv).value(), "div"sv);
|
|
}
|