Skip to content

Commit

Permalink
LibWeb: Make BrowsingContext GC-allocated
Browse files Browse the repository at this point in the history
(And BrowsingContextGroup had to come along for the ride as well.)
This solves a number of nasty reference cycles between browsing
contexts, history items, and their documents.
  • Loading branch information
awesomekling committed Oct 20, 2022
1 parent 2898701 commit 83c5ff5
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 44 deletions.
12 changes: 7 additions & 5 deletions Userland/Libraries/LibWeb/DOM/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
namespace Web::DOM {

// https://html.spec.whatwg.org/multipage/origin.html#obtain-browsing-context-navigation
static NonnullRefPtr<HTML::BrowsingContext> obtain_a_browsing_context_to_use_for_a_navigation_response(
static JS::NonnullGCPtr<HTML::BrowsingContext> obtain_a_browsing_context_to_use_for_a_navigation_response(
HTML::BrowsingContext& browsing_context,
HTML::SandboxingFlagSet sandbox_flags,
HTML::CrossOriginOpenerPolicy navigation_coop,
Expand Down Expand Up @@ -130,7 +130,7 @@ JS::NonnullGCPtr<Document> Document::create_and_initialize(Type type, String con
// given navigationParams's browsing context, navigationParams's final sandboxing flag set,
// navigationParams's cross-origin opener policy, and navigationParams's COOP enforcement result.
auto browsing_context = obtain_a_browsing_context_to_use_for_a_navigation_response(
navigation_params.browsing_context,
*navigation_params.browsing_context,
navigation_params.final_sandboxing_flag_set,
navigation_params.cross_origin_opener_policy,
navigation_params.coop_enforcement_result);
Expand Down Expand Up @@ -330,6 +330,8 @@ void Document::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_pending_parsing_blocking_script.ptr());
visitor.visit(m_history.ptr());

visitor.visit(m_browsing_context);

visitor.visit(m_applets);
visitor.visit(m_anchors);
visitor.visit(m_images);
Expand Down Expand Up @@ -2051,10 +2053,10 @@ HTML::PolicyContainer Document::policy_container() const
}

// https://html.spec.whatwg.org/multipage/browsers.html#list-of-the-descendant-browsing-contexts
Vector<NonnullRefPtr<HTML::BrowsingContext>> Document::list_of_descendant_browsing_contexts() const
Vector<JS::Handle<HTML::BrowsingContext>> Document::list_of_descendant_browsing_contexts() const
{
// 1. Let list be an empty list.
Vector<NonnullRefPtr<HTML::BrowsingContext>> list;
Vector<JS::Handle<HTML::BrowsingContext>> list;

// 2. For each browsing context container container,
// whose nested browsing context is non-null and whose shadow-including root is d, in shadow-including tree order:
Expand All @@ -2063,7 +2065,7 @@ Vector<NonnullRefPtr<HTML::BrowsingContext>> Document::list_of_descendant_browsi
// of this document's browsing context.
if (browsing_context()) {
browsing_context()->for_each_in_subtree([&](auto& context) {
list.append(context);
list.append(JS::make_handle(const_cast<HTML::BrowsingContext&>(context)));
return IterationDecision::Continue;
});
}
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/DOM/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ class Document
HTML::PolicyContainer policy_container() const;

// https://html.spec.whatwg.org/multipage/browsers.html#list-of-the-descendant-browsing-contexts
Vector<NonnullRefPtr<HTML::BrowsingContext>> list_of_descendant_browsing_contexts() const;
Vector<JS::Handle<HTML::BrowsingContext>> list_of_descendant_browsing_contexts() const;

// https://html.spec.whatwg.org/multipage/window-object.html#discard-a-document
void discard();
Expand Down
92 changes: 80 additions & 12 deletions Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,21 @@ HTML::Origin determine_the_origin(BrowsingContext const& browsing_context, Optio
}

// https://html.spec.whatwg.org/multipage/browsers.html#creating-a-new-top-level-browsing-context
NonnullRefPtr<BrowsingContext> BrowsingContext::create_a_new_top_level_browsing_context(Web::Page& page)
JS::NonnullGCPtr<BrowsingContext> BrowsingContext::create_a_new_top_level_browsing_context(Web::Page& page)
{
// 1. Let group be the result of creating a new browsing context group.
auto group = BrowsingContextGroup::create_a_new_browsing_context_group(page);

// 2. Return group's browsing context set[0].
return *group->browsing_context_set().begin();
return *(*group->browsing_context_set().begin());
}

// https://html.spec.whatwg.org/multipage/browsers.html#creating-a-new-browsing-context
NonnullRefPtr<BrowsingContext> BrowsingContext::create_a_new_browsing_context(Page& page, JS::GCPtr<DOM::Document> creator, JS::GCPtr<DOM::Element> embedder, BrowsingContextGroup&)
JS::NonnullGCPtr<BrowsingContext> BrowsingContext::create_a_new_browsing_context(Page& page, JS::GCPtr<DOM::Document> creator, JS::GCPtr<DOM::Element> embedder, BrowsingContextGroup&)
{
// 1. Let browsingContext be a new browsing context.
BrowsingContextContainer* container = (embedder && is<BrowsingContextContainer>(*embedder)) ? static_cast<BrowsingContextContainer*>(embedder.ptr()) : nullptr;
auto browsing_context = adopt_ref(*new BrowsingContext(page, container));
auto browsing_context = Bindings::main_thread_vm().heap().allocate_without_realm<BrowsingContext>(page, container);

// 2. Let unsafeContextCreationTime be the unsafe shared current time.
[[maybe_unused]] auto unsafe_context_creation_time = HighResolutionTime::unsafe_shared_current_time();
Expand All @@ -125,7 +125,7 @@ NonnullRefPtr<BrowsingContext> BrowsingContext::create_a_new_browsing_context(Pa
SandboxingFlagSet sandbox_flags;

// 5. Let origin be the result of determining the origin given browsingContext, about:blank, sandboxFlags, and browsingContext's creator origin.
auto origin = determine_the_origin(browsing_context, AK::URL("about:blank"), sandbox_flags, browsing_context->m_creator_origin);
auto origin = determine_the_origin(*browsing_context, AK::URL("about:blank"), sandbox_flags, browsing_context->m_creator_origin);

// FIXME: 6. Let permissionsPolicy be the result of creating a permissions policy given browsingContext and origin. [PERMISSIONSPOLICY]

Expand Down Expand Up @@ -240,7 +240,7 @@ NonnullRefPtr<BrowsingContext> BrowsingContext::create_a_new_browsing_context(Pa
document->completely_finish_loading();

// 24. Return browsingContext.
return browsing_context;
return *browsing_context;
}

BrowsingContext::BrowsingContext(Page& page, HTML::BrowsingContextContainer* container)
Expand All @@ -261,6 +261,22 @@ BrowsingContext::BrowsingContext(Page& page, HTML::BrowsingContextContainer* con

BrowsingContext::~BrowsingContext() = default;

void BrowsingContext::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);

for (auto& entry : m_session_history)
visitor.visit(entry.document);
visitor.visit(m_container);
visitor.visit(m_window_proxy);
visitor.visit(m_group);
visitor.visit(m_parent);
visitor.visit(m_first_child);
visitor.visit(m_last_child);
visitor.visit(m_next_sibling);
visitor.visit(m_previous_sibling);
}

void BrowsingContext::did_edit(Badge<EditEventHandler>)
{
reset_cursor_blink_cycle();
Expand Down Expand Up @@ -446,7 +462,7 @@ Gfx::IntRect BrowsingContext::to_top_level_rect(Gfx::IntRect const& a_rect)
Gfx::IntPoint BrowsingContext::to_top_level_position(Gfx::IntPoint const& a_position)
{
auto position = a_position;
for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
for (auto ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
if (ancestor->is_top_level())
break;
if (!ancestor->container())
Expand Down Expand Up @@ -657,7 +673,7 @@ BrowsingContext* BrowsingContext::choose_a_browsing_context(StringView name, boo
// name, a browsing context current, and a boolean noopener are as follows:

// 1. Let chosen be null.
BrowsingContext* chosen = nullptr;
JS::GCPtr<BrowsingContext> chosen = nullptr;

// FIXME: 2. Let windowType be "existing or none".

Expand All @@ -672,7 +688,7 @@ BrowsingContext* BrowsingContext::choose_a_browsing_context(StringView name, boo
// set chosen to current's parent browsing context, if any, and current
// otherwise.
if (name.equals_ignoring_case("_parent"sv)) {
if (auto* parent = this->parent())
if (auto parent = this->parent())
chosen = parent;
else
chosen = this;
Expand Down Expand Up @@ -872,13 +888,13 @@ void BrowsingContext::remove()
VERIFY(group());

// 2. Let group be browsingContext's group.
NonnullRefPtr<BrowsingContextGroup> group = *this->group();
JS::NonnullGCPtr<BrowsingContextGroup> group = *this->group();

// 3. Set browsingContext's group to null.
set_group(nullptr);

// 4. Remove browsingContext from group's browsing context set.
group->browsing_context_set().remove(*this);
group->browsing_context_set().remove(this);

// 5. If group's browsing context set is empty, then remove group from the user agent's browsing context group set.
// NOTE: This is done by ~BrowsingContextGroup() when the refcount reaches 0.
Expand Down Expand Up @@ -1281,7 +1297,7 @@ Vector<JS::Handle<DOM::Document>> BrowsingContext::document_family() const
for (auto& entry : m_session_history) {
if (!entry.document)
continue;
if (documents.set(entry.document.ptr()) == AK::HashSetResult::ReplacedExistingEntry)
if (documents.set(const_cast<DOM::Document*>(entry.document.ptr())) == AK::HashSetResult::ReplacedExistingEntry)
continue;
for (auto& context : entry.document->list_of_descendant_browsing_contexts()) {
for (auto& document : context->document_family()) {
Expand Down Expand Up @@ -1367,4 +1383,56 @@ void BrowsingContext::close()
discard();
}

void BrowsingContext::append_child(JS::NonnullGCPtr<BrowsingContext> child)
{
VERIFY(!child->m_parent);

if (m_last_child)
m_last_child->m_next_sibling = child;
child->m_previous_sibling = m_last_child;
child->m_parent = this;
m_last_child = child;
if (!m_first_child)
m_first_child = m_last_child;
}

void BrowsingContext::remove_child(JS::NonnullGCPtr<BrowsingContext> child)
{
VERIFY(child->m_parent.ptr() == this);

if (m_first_child == child)
m_first_child = child->m_next_sibling;

if (m_last_child == child)
m_last_child = child->m_previous_sibling;

if (child->m_next_sibling)
child->m_next_sibling->m_previous_sibling = child->m_previous_sibling;

if (child->m_previous_sibling)
child->m_previous_sibling->m_next_sibling = child->m_next_sibling;

child->m_next_sibling = nullptr;
child->m_previous_sibling = nullptr;
child->m_parent = nullptr;
}

JS::GCPtr<BrowsingContext> BrowsingContext::first_child() const
{
return m_first_child;
}
JS::GCPtr<BrowsingContext> BrowsingContext::next_sibling() const
{
return m_next_sibling;
}

bool BrowsingContext::is_ancestor_of(BrowsingContext const& other) const
{
for (auto ancestor = other.parent(); ancestor; ancestor = ancestor->parent()) {
if (ancestor == this)
return true;
}
return false;
}

}
91 changes: 85 additions & 6 deletions Userland/Libraries/LibWeb/HTML/BrowsingContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <LibGfx/Bitmap.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
#include <LibJS/Heap/Cell.h>
#include <LibWeb/DOM/Position.h>
#include <LibWeb/HTML/BrowsingContextContainer.h>
#include <LibWeb/HTML/HistoryHandlingBehavior.h>
Expand All @@ -26,13 +27,83 @@

namespace Web::HTML {

class BrowsingContext : public TreeNode<BrowsingContext> {
class BrowsingContext final
: public JS::Cell
, public Weakable<BrowsingContext> {
JS_CELL(BrowsingContext, JS::Cell);

public:
static NonnullRefPtr<BrowsingContext> create_a_new_browsing_context(Page&, JS::GCPtr<DOM::Document> creator, JS::GCPtr<DOM::Element> embedder, BrowsingContextGroup&);
static NonnullRefPtr<BrowsingContext> create_a_new_top_level_browsing_context(Page&);
static JS::NonnullGCPtr<BrowsingContext> create_a_new_browsing_context(Page&, JS::GCPtr<DOM::Document> creator, JS::GCPtr<DOM::Element> embedder, BrowsingContextGroup&);
static JS::NonnullGCPtr<BrowsingContext> create_a_new_top_level_browsing_context(Page&);

~BrowsingContext();

JS::GCPtr<BrowsingContext> parent() const { return m_parent; }
void append_child(JS::NonnullGCPtr<BrowsingContext>);
void remove_child(JS::NonnullGCPtr<BrowsingContext>);
JS::GCPtr<BrowsingContext> first_child() const;
JS::GCPtr<BrowsingContext> next_sibling() const;

bool is_ancestor_of(BrowsingContext const&) const;

template<typename Callback>
IterationDecision for_each_in_inclusive_subtree(Callback callback) const
{
if (callback(*this) == IterationDecision::Break)
return IterationDecision::Break;
for (auto child = first_child(); child; child = child->next_sibling()) {
if (child->for_each_in_inclusive_subtree(callback) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;
}

template<typename Callback>
IterationDecision for_each_in_inclusive_subtree(Callback callback)
{
if (callback(*this) == IterationDecision::Break)
return IterationDecision::Break;
for (auto child = first_child(); child; child = child->next_sibling()) {
if (child->for_each_in_inclusive_subtree(callback) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;
}

template<typename Callback>
void for_each_child(Callback callback) const
{
for (auto node = first_child(); node; node = node->next_sibling())
callback(*node);
}

template<typename Callback>
void for_each_child(Callback callback)
{
for (auto node = first_child(); node; node = node->next_sibling())
callback(*node);
}

template<typename Callback>
IterationDecision for_each_in_subtree(Callback callback) const
{
for (auto child = first_child(); child; child = child->next_sibling()) {
if (child->for_each_in_inclusive_subtree(callback) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;
}

template<typename Callback>
IterationDecision for_each_in_subtree(Callback callback)
{
for (auto child = first_child(); child; child = child->next_sibling()) {
if (child->for_each_in_inclusive_subtree(callback) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;
}

class ViewportClient {
public:
virtual ~ViewportClient() = default;
Expand Down Expand Up @@ -178,6 +249,8 @@ class BrowsingContext : public TreeNode<BrowsingContext> {
private:
explicit BrowsingContext(Page&, HTML::BrowsingContextContainer*);

virtual void visit_edges(Cell::Visitor&) override;

void reset_cursor_blink_cycle();

void scroll_offset_did_change();
Expand All @@ -204,12 +277,12 @@ class BrowsingContext : public TreeNode<BrowsingContext> {
// https://html.spec.whatwg.org/multipage/browsers.html#creator-origin
Optional<HTML::Origin> m_creator_origin;

WeakPtr<HTML::BrowsingContextContainer> m_container;
JS::GCPtr<HTML::BrowsingContextContainer> m_container;
Gfx::IntSize m_size;
Gfx::IntPoint m_viewport_scroll_offset;

// https://html.spec.whatwg.org/multipage/browsers.html#browsing-context
JS::Handle<HTML::WindowProxy> m_window_proxy;
JS::GCPtr<HTML::WindowProxy> m_window_proxy;

DOM::Position m_cursor_position;
RefPtr<Platform::Timer> m_cursor_blink_timer;
Expand All @@ -221,10 +294,16 @@ class BrowsingContext : public TreeNode<BrowsingContext> {
String m_name;

// https://html.spec.whatwg.org/multipage/browsers.html#tlbc-group
RefPtr<BrowsingContextGroup> m_group;
JS::GCPtr<BrowsingContextGroup> m_group;

// https://html.spec.whatwg.org/multipage/interaction.html#system-visibility-state
VisibilityState m_system_visibility_state { VisibilityState::Hidden };

JS::GCPtr<BrowsingContext> m_parent;
JS::GCPtr<BrowsingContext> m_first_child;
JS::GCPtr<BrowsingContext> m_last_child;
JS::GCPtr<BrowsingContext> m_next_sibling;
JS::GCPtr<BrowsingContext> m_previous_sibling;
};

HTML::Origin determine_the_origin(BrowsingContext const& browsing_context, Optional<AK::URL> url, SandboxingFlagSet sandbox_flags, Optional<HTML::Origin> invocation_origin);
Expand Down
8 changes: 7 additions & 1 deletion Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ BrowsingContextContainer::BrowsingContextContainer(DOM::Document& document, DOM:

BrowsingContextContainer::~BrowsingContextContainer() = default;

void BrowsingContextContainer::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_nested_browsing_context);
}

// https://html.spec.whatwg.org/multipage/browsers.html#creating-a-new-nested-browsing-context
void BrowsingContextContainer::create_new_nested_browsing_context()
{
Expand Down Expand Up @@ -142,7 +148,7 @@ void BrowsingContextContainer::shared_attribute_processing_steps_for_iframe_and_
// 3. If there exists an ancestor browsing context of element's nested browsing context
// whose active document's URL, ignoring fragments, is equal to url, then return.
if (m_nested_browsing_context) {
for (auto* ancestor = m_nested_browsing_context->parent(); ancestor; ancestor = ancestor->parent()) {
for (auto ancestor = m_nested_browsing_context->parent(); ancestor; ancestor = ancestor->parent()) {
VERIFY(ancestor->active_document());
if (ancestor->active_document()->url().equals(url, AK::URL::ExcludeFragment::Yes))
return;
Expand Down
Loading

0 comments on commit 83c5ff5

Please sign in to comment.