Commit ee7b1d65 authored by Ulan Degenbaev's avatar Ulan Degenbaev Committed by Commit Bot

[bindings] Show detached DOM nodes in DevTools heap snapshot.

The patch uses Node::isConnected() and Document::IsContextDestroyed()
predicates to compute whether a Node is detached or not. This information
is then propagated to all ScriptWrappables.

DevTools shows detached nodes with the "Detached" prefix. For example,
"Detached HTMLDocument" or "Detached Window".

Bug: 811925
Change-Id: Ib7987f578cc513b6bdfcfe084c885cbff84a4dba
Reviewed-on: https://chromium-review.googlesource.com/961401
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: default avatarMichael Lippautz <mlippautz@chromium.org>
Reviewed-by: default avatarAlexei Filippov <alph@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543382}
parent d867246f
...@@ -36,8 +36,11 @@ crbug.com/704360 [ Linux ] shapedetection/detection-ImageData.html [ Timeout Pas ...@@ -36,8 +36,11 @@ crbug.com/704360 [ Linux ] shapedetection/detection-ImageData.html [ Timeout Pas
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-samples-in-snapshot.js [ Timeout ] crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-samples-in-snapshot.js [ Timeout ]
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-merged-nodes.js [ Timeout ] crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-merged-nodes.js [ Timeout ]
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-active-dom-object.js [ Timeout ] crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-active-dom-object.js [ Timeout ]
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-detached-dom-tree.js [ Timeout ]
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-detached-iframe.js [ Timeout ]
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-event-listener.js [ Timeout ] crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-event-listener.js [ Timeout ]
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers.js [ Timeout ] crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers.js [ Timeout ]
crbug.com/462190 [ Linux ] inspector-protocol/heap-profiler/heap-snapshot-with-no-detached-iframe.js [ Timeout ]
crbug.com/667560 [ Linux ] http/tests/devtools/startup/console/console-format-startup.js [ Timeout Pass ] crbug.com/667560 [ Linux ] http/tests/devtools/startup/console/console-format-startup.js [ Timeout Pass ]
crbug.com/751906 [ Linux ] http/tests/devtools/console/console-correct-suggestions.js [ Timeout Pass ] crbug.com/751906 [ Linux ] http/tests/devtools/console/console-correct-suggestions.js [ Timeout Pass ]
......
...@@ -2,5 +2,5 @@ Test that DOM node and its JS wrapper appear as a single node. ...@@ -2,5 +2,5 @@ Test that DOM node and its JS wrapper appear as a single node.
Took heap snapshot Took heap snapshot
Parsed snapshot Parsed snapshot
SUCCESS: found leaking SUCCESS: found leaking
SUCCESS: retaining path = [EventListener, InternalNode, HTMLDivElement, Window / file://, ] SUCCESS: retaining path = [Detached EventListener, Detached InternalNode, Detached HTMLDivElement, Window / file://, ]
Test that all nodes from the detached DOM tree will be marked as detached.
Took heap snapshot
Parsed snapshot
SUCCESS: found 3 detached DIV elements
(async function(testRunner) {
var {page, session, dp} = await testRunner.startBlank(
`Test that all nodes from the detached DOM tree will be marked as detached.`);
await session.evaluate(`
window.retaining_wrapper = document.createElement('div');
var t = document.createElement('div');
retaining_wrapper.appendChild(t);
t.appendChild(document.createElement('div'));
`);
var Helper = await testRunner.loadScript('resources/heap-snapshot-common.js');
var helper = await Helper(testRunner, session);
var snapshot = await helper.takeHeapSnapshot();
var divCount = 0;
for (var it = snapshot._allNodes(); it.hasNext(); it.next()) {
if (it.node.name() === 'Detached HTMLDivElement')
++divCount;
}
if (divCount === 3)
testRunner.log('SUCCESS: found 3 detached DIV elements');
else
return testRunner.fail('unexpected detached DIV count: ' + divCount);
testRunner.completeTest();
})
\ No newline at end of file
Test that a detached iframe will be marked as detached.
Took heap snapshot
Parsed snapshot
SUCCESS: found Leak
SUCCESS: detached iframe in retaining path
(async function(testRunner) {
var {page, session, dp} = await testRunner.startBlank(
`Test that a detached iframe will be marked as detached.`);
dp.Page.enable();
session.evaluate(`
var frame = document.createElement('iframe');
frame.src = 'data:text/html,<script>class Leak{}; var x = new Leak();<'+
'/script>';
document.body.appendChild(frame);
frame.addEventListener("load", function() {
var iframeWindow = this.contentWindow;
function retainingListener() {
// This is leaking the iframe.
console.log(iframeWindow);
}
document.body.addEventListener('click', retainingListener, true);
document.body.removeChild(frame);
});
`);
await dp.Page.onceFrameStoppedLoading();
var Helper = await testRunner.loadScript('resources/heap-snapshot-common.js');
var helper = await Helper(testRunner, session);
var snapshot = await helper.takeHeapSnapshot();
var node;
for (var it = snapshot._allNodes(); it.hasNext(); it.next()) {
if (it.node.className() === 'Leak') {
node = it.node;
break;
}
}
if (node)
testRunner.log('SUCCESS: found ' + node.name());
else
return testRunner.fail('cannot find leaking node');
var retainers = helper.firstRetainingPath(node).map(node => node.name());
if (retainers.includes('Detached Window'))
testRunner.log('SUCCESS: detached iframe in retaining path');
else
return testRunner.fail('no detached iframe in retaining path');
testRunner.completeTest();
})
\ No newline at end of file
Test that an attached iframe is not marked as detached.
Took heap snapshot
Parsed snapshot
SUCCESS: found Leak
SUCCESS: no detached iframe in retaining path
(async function(testRunner) {
var {page, session, dp} = await testRunner.startBlank(
`Test that an attached iframe is not marked as detached.`);
dp.Page.enable();
session.evaluate(`
var frame = document.createElement('iframe');
frame.src = 'data:text/html,<script>class Leak{}; var x = new Leak();<'+
'/script>';
document.body.appendChild(frame);
frame.addEventListener("load", function() {
var iframeWindow = this.contentWindow;
function retainingListener() {
// This is leaking the iframe.
console.log(iframeWindow);
}
document.body.addEventListener('click', retainingListener, true);
});
`);
await dp.Page.onceFrameStoppedLoading();
var Helper = await testRunner.loadScript('resources/heap-snapshot-common.js');
var helper = await Helper(testRunner, session);
var snapshot = await helper.takeHeapSnapshot();
var node;
for (var it = snapshot._allNodes(); it.hasNext(); it.next()) {
if (it.node.className() === 'Leak') {
node = it.node;
break;
}
}
if (node)
testRunner.log('SUCCESS: found ' + node.name());
else
return testRunner.fail('cannot find leaking node');
var retainers = helper.firstRetainingPath(node).map(node => node.name());
if (!retainers.includes('Detached Window'))
testRunner.log('SUCCESS: no detached iframe in retaining path');
else
return testRunner.fail('unexpected detached iframe in retaining path');
testRunner.completeTest();
})
\ No newline at end of file
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
#include "bindings/core/v8/V8EmbedderGraphBuilder.h" #include "bindings/core/v8/V8EmbedderGraphBuilder.h"
#include "bindings/core/v8/ActiveScriptWrappable.h" #include "bindings/core/v8/ActiveScriptWrappable.h"
#include "bindings/core/v8/V8GCController.h"
#include "bindings/core/v8/V8Node.h" #include "bindings/core/v8/V8Node.h"
#include "core/dom/Document.h"
#include "platform/bindings/DOMWrapperMap.h" #include "platform/bindings/DOMWrapperMap.h"
#include "platform/bindings/ScriptWrappable.h" #include "platform/bindings/ScriptWrappable.h"
#include "platform/bindings/ScriptWrappableVisitor.h" #include "platform/bindings/ScriptWrappableVisitor.h"
...@@ -26,8 +28,70 @@ void V8EmbedderGraphBuilder::BuildEmbedderGraphCallback( ...@@ -26,8 +28,70 @@ void V8EmbedderGraphBuilder::BuildEmbedderGraphCallback(
void V8EmbedderGraphBuilder::BuildEmbedderGraph() { void V8EmbedderGraphBuilder::BuildEmbedderGraph() {
isolate_->VisitHandlesWithClassIds(this); isolate_->VisitHandlesWithClassIds(this);
// At this point we collected ScriptWrappables in three groups:
// attached, detached, and unknown.
#if DCHECK_IS_ON()
for (const WorklistItem& item : worklist_) {
DCHECK_EQ(DomTreeState::kAttached, item.node->GetDomTreeState());
}
for (const WorklistItem& item : detached_worklist_) {
DCHECK_EQ(DomTreeState::kDetached, item.node->GetDomTreeState());
}
for (const WorklistItem& item : unknown_worklist_) {
DCHECK_EQ(DomTreeState::kUnknown, item.node->GetDomTreeState());
}
#endif
// We need to propagate attached/detached information to ScriptWrappables
// with the unknown state. The information propagates from a parent to
// a child as follows:
// - if the parent is attached, then the child is considered attached.
// - if the parent is detached and the child is unknown, then the child is
// considered detached.
// - if the parent is unknown, then the state of the child does not change.
//
// We need to organize DOM traversal in three stages to ensure correct
// propagation:
// 1) Traverse from the attached nodes. All nodes discovered in this stage
// will be marked as kAttached.
// 2) Traverse from the detached nodes. All nodes discovered in this stage
// will be marked as kDetached if they are not already marked as kAttached.
// 3) Traverse from the unknown nodes. This is needed only for edge recording.
// Stage 1: find transitive closure of the attached nodes.
VisitTransitiveClosure();
// Stage 2: find transitive closure of the detached nodes.
while (!detached_worklist_.empty()) {
auto item = detached_worklist_.back();
detached_worklist_.pop_back();
PushToWorklist(item);
}
VisitTransitiveClosure();
// Stage 3: find transitive closure of the unknown nodes.
// Nodes reachable only via pending activities are treated as unknown.
VisitPendingActivities(); VisitPendingActivities();
while (!unknown_worklist_.empty()) {
auto item = unknown_worklist_.back();
unknown_worklist_.pop_back();
PushToWorklist(item);
}
VisitTransitiveClosure(); VisitTransitiveClosure();
DCHECK(worklist_.empty());
DCHECK(detached_worklist_.empty());
DCHECK(unknown_worklist_.empty());
}
V8EmbedderGraphBuilder::DomTreeState
V8EmbedderGraphBuilder::DomTreeStateFromWrapper(
uint16_t class_id,
v8::Local<v8::Object> v8_value) {
if (class_id != WrapperTypeInfo::kNodeClassId)
return DomTreeState::kUnknown;
Node* node = V8Node::ToImpl(v8_value);
Node* root = V8GCController::OpaqueRootForGC(isolate_, node);
if (root->isConnected() &&
!node->GetDocument().MasterDocument().IsContextDestroyed()) {
return DomTreeState::kAttached;
}
return DomTreeState::kDetached;
} }
void V8EmbedderGraphBuilder::VisitPersistentHandle( void V8EmbedderGraphBuilder::VisitPersistentHandle(
...@@ -39,13 +103,25 @@ void V8EmbedderGraphBuilder::VisitPersistentHandle( ...@@ -39,13 +103,25 @@ void V8EmbedderGraphBuilder::VisitPersistentHandle(
v8::Local<v8::Object> v8_value = v8::Local<v8::Object>::New( v8::Local<v8::Object> v8_value = v8::Local<v8::Object>::New(
isolate_, v8::Persistent<v8::Object>::Cast(*value)); isolate_, v8::Persistent<v8::Object>::Cast(*value));
ScriptWrappable* traceable = ToScriptWrappable(v8_value); ScriptWrappable* traceable = ToScriptWrappable(v8_value);
if (traceable) { if (!traceable)
Graph::Node* wrapper = GraphNode(v8_value); return;
Graph::Node* graph_node = Graph::Node* wrapper = GraphNode(v8_value);
GraphNode(traceable, traceable->NameInHeapSnapshot(), wrapper); DomTreeState dom_tree_state = DomTreeStateFromWrapper(class_id, v8_value);
// Visit traceable members. This will also add traceable => v8_value edge. EmbedderNode* graph_node = GraphNode(
ParentScope parent(this, graph_node); traceable, traceable->NameInHeapSnapshot(), wrapper, dom_tree_state);
traceable->TraceWrappers(this); const TraceWrapperDescriptor& wrapper_descriptor =
WrapperDescriptorFor<ScriptWrappable>(traceable);
WorklistItem item = ToWorklistItem(graph_node, wrapper_descriptor);
switch (graph_node->GetDomTreeState()) {
case DomTreeState::kAttached:
PushToWorklist(item);
break;
case DomTreeState::kDetached:
detached_worklist_.push_back(item);
break;
case DomTreeState::kUnknown:
unknown_worklist_.push_back(item);
break;
} }
} }
...@@ -64,13 +140,11 @@ void V8EmbedderGraphBuilder::Visit( ...@@ -64,13 +140,11 @@ void V8EmbedderGraphBuilder::Visit(
// Add an edge from the current parent to this object. // Add an edge from the current parent to this object.
// Also push the object to the worklist in order to process its members. // Also push the object to the worklist in order to process its members.
const void* traceable = wrapper_descriptor.base_object_payload; const void* traceable = wrapper_descriptor.base_object_payload;
Graph::Node* graph_node = GraphNode( EmbedderNode* graph_node =
traceable, wrapper_descriptor.name_callback(traceable), nullptr); GraphNode(traceable, wrapper_descriptor.name_callback(traceable), nullptr,
current_parent_->GetDomTreeState());
graph_->AddEdge(current_parent_, graph_node); graph_->AddEdge(current_parent_, graph_node);
if (!visited_.Contains(traceable)) { PushToWorklist(ToWorklistItem(graph_node, wrapper_descriptor));
visited_.insert(traceable);
worklist_.push_back(ToWorklistItem(graph_node, wrapper_descriptor));
}
} }
void V8EmbedderGraphBuilder::Visit(DOMWrapperMap<ScriptWrappable>* wrapper_map, void V8EmbedderGraphBuilder::Visit(DOMWrapperMap<ScriptWrappable>* wrapper_map,
...@@ -87,36 +161,48 @@ v8::EmbedderGraph::Node* V8EmbedderGraphBuilder::GraphNode( ...@@ -87,36 +161,48 @@ v8::EmbedderGraph::Node* V8EmbedderGraphBuilder::GraphNode(
return graph_->V8Node(value); return graph_->V8Node(value);
} }
v8::EmbedderGraph::Node* V8EmbedderGraphBuilder::GraphNode( V8EmbedderGraphBuilder::EmbedderNode* V8EmbedderGraphBuilder::GraphNode(
Traceable traceable, Traceable traceable,
const char* name, const char* name,
v8::EmbedderGraph::Node* wrapper) const { v8::EmbedderGraph::Node* wrapper,
DomTreeState dom_tree_state) const {
auto iter = graph_node_.find(traceable); auto iter = graph_node_.find(traceable);
if (iter != graph_node_.end()) if (iter != graph_node_.end()) {
iter->value->UpdateDomTreeState(dom_tree_state);
return iter->value; return iter->value;
}
// Ownership of the new node is transferred to the graph_. // Ownership of the new node is transferred to the graph_.
// graph_node_.at(tracable) is valid for all BuildEmbedderGraph execution. // graph_node_.at(tracable) is valid for all BuildEmbedderGraph execution.
auto node = graph_->AddNode( auto raw_node = new EmbedderNode(name, wrapper, dom_tree_state);
std::unique_ptr<Graph::Node>(new EmbedderNode(name, wrapper))); EmbedderNode* node = static_cast<EmbedderNode*>(
graph_->AddNode(std::unique_ptr<Graph::Node>(raw_node)));
graph_node_.insert(traceable, node); graph_node_.insert(traceable, node);
return node; return node;
} }
void V8EmbedderGraphBuilder::VisitPendingActivities() { void V8EmbedderGraphBuilder::VisitPendingActivities() {
// Ownership of the new node is transferred to the graph_. // Ownership of the new node is transferred to the graph_.
Graph::Node* root = graph_->AddNode( EmbedderNode* root =
std::unique_ptr<Graph::Node>(new EmbedderRootNode("Pending activities"))); static_cast<EmbedderNode*>(graph_->AddNode(std::unique_ptr<Graph::Node>(
new EmbedderRootNode("Pending activities"))));
ParentScope parent(this, root); ParentScope parent(this, root);
ActiveScriptWrappableBase::TraceActiveScriptWrappables(isolate_, this); ActiveScriptWrappableBase::TraceActiveScriptWrappables(isolate_, this);
} }
V8EmbedderGraphBuilder::WorklistItem V8EmbedderGraphBuilder::ToWorklistItem( V8EmbedderGraphBuilder::WorklistItem V8EmbedderGraphBuilder::ToWorklistItem(
Graph::Node* node, EmbedderNode* node,
const TraceWrapperDescriptor& wrapper_descriptor) const { const TraceWrapperDescriptor& wrapper_descriptor) const {
return {node, wrapper_descriptor.base_object_payload, return {node, wrapper_descriptor.base_object_payload,
wrapper_descriptor.trace_wrappers_callback}; wrapper_descriptor.trace_wrappers_callback};
} }
void V8EmbedderGraphBuilder::PushToWorklist(WorklistItem item) const {
if (!visited_.Contains(item.traceable)) {
visited_.insert(item.traceable);
worklist_.push_back(item);
}
}
void V8EmbedderGraphBuilder::VisitTransitiveClosure() { void V8EmbedderGraphBuilder::VisitTransitiveClosure() {
// Depth-first search. // Depth-first search.
while (!worklist_.empty()) { while (!worklist_.empty()) {
......
...@@ -34,24 +34,60 @@ class V8EmbedderGraphBuilder : public ScriptWrappableVisitor, ...@@ -34,24 +34,60 @@ class V8EmbedderGraphBuilder : public ScriptWrappableVisitor,
const ScriptWrappable*) const final; const ScriptWrappable*) const final;
private: private:
// Information about whether a node is attached to the main DOM tree
// or not. It is computed as follows:
// 1) A Document with IsContextDestroyed() = true is detached.
// 2) A Document with IsContextDestroyed() = false is attached.
// 3) A Node that is not connected to any Document is detached.
// 4) A Node that is connected to a detached Document is detached.
// 5) A Node that is connected to an attached Document is attached.
// 6) A ScriptWrappable that is reachable from an attached Node is
// attached.
// 7) A ScriptWrappable that is reachable from a detached Node is
// detached.
// 8) A ScriptWrappable that is not reachable from any Node is
// considered (conservatively) as attached.
// The unknown state applies to ScriptWrappables during graph
// traversal when we don't have reachability information yet.
enum class DomTreeState { kAttached, kDetached, kUnknown };
DomTreeState DomTreeStateFromWrapper(uint16_t class_id,
v8::Local<v8::Object> v8_value);
class EmbedderNode : public Graph::Node { class EmbedderNode : public Graph::Node {
public: public:
EmbedderNode(const char* name, Graph::Node* wrapper) EmbedderNode(const char* name,
: name_(name), wrapper_(wrapper) {} Graph::Node* wrapper,
DomTreeState dom_tree_state)
: name_(name), wrapper_(wrapper), dom_tree_state_(dom_tree_state) {}
DomTreeState GetDomTreeState() { return dom_tree_state_; }
void UpdateDomTreeState(DomTreeState parent_dom_tree_state) {
// If the child's state is unknown, then take the parent's state.
// If the parent is attached, then the child is also attached.
if (dom_tree_state_ == DomTreeState::kUnknown ||
parent_dom_tree_state == DomTreeState::kAttached) {
dom_tree_state_ = parent_dom_tree_state;
}
}
// Graph::Node overrides. // Graph::Node overrides.
const char* Name() override { return name_; } const char* Name() override { return name_; }
const char* NamePrefix() override {
return dom_tree_state_ == DomTreeState::kDetached ? "Detached" : nullptr;
}
size_t SizeInBytes() override { return 0; } size_t SizeInBytes() override { return 0; }
Graph::Node* WrapperNode() override { return wrapper_; } Graph::Node* WrapperNode() override { return wrapper_; }
private: private:
const char* name_; const char* name_;
Graph::Node* wrapper_; Graph::Node* wrapper_;
DomTreeState dom_tree_state_;
}; };
class EmbedderRootNode : public EmbedderNode { class EmbedderRootNode : public EmbedderNode {
public: public:
explicit EmbedderRootNode(const char* name) : EmbedderNode(name, nullptr) {} explicit EmbedderRootNode(const char* name)
: EmbedderNode(name, nullptr, DomTreeState::kUnknown) {}
// Graph::Node override. // Graph::Node override.
bool IsRootNode() { return true; } bool IsRootNode() { return true; }
}; };
...@@ -60,7 +96,7 @@ class V8EmbedderGraphBuilder : public ScriptWrappableVisitor, ...@@ -60,7 +96,7 @@ class V8EmbedderGraphBuilder : public ScriptWrappableVisitor,
STACK_ALLOCATED(); STACK_ALLOCATED();
public: public:
ParentScope(V8EmbedderGraphBuilder* visitor, Graph::Node* parent) ParentScope(V8EmbedderGraphBuilder* visitor, EmbedderNode* parent)
: visitor_(visitor) { : visitor_(visitor) {
DCHECK_EQ(visitor->current_parent_, nullptr); DCHECK_EQ(visitor->current_parent_, nullptr);
visitor->current_parent_ = parent; visitor->current_parent_ = parent;
...@@ -72,28 +108,40 @@ class V8EmbedderGraphBuilder : public ScriptWrappableVisitor, ...@@ -72,28 +108,40 @@ class V8EmbedderGraphBuilder : public ScriptWrappableVisitor,
}; };
struct WorklistItem { struct WorklistItem {
Graph::Node* node; EmbedderNode* node;
Traceable traceable; Traceable traceable;
TraceWrappersCallback trace_wrappers_callback; TraceWrappersCallback trace_wrappers_callback;
}; };
WorklistItem ToWorklistItem(Graph::Node*, WorklistItem ToWorklistItem(EmbedderNode*,
const TraceWrapperDescriptor&) const; const TraceWrapperDescriptor&) const;
Graph::Node* GraphNode(const v8::Local<v8::Value>&) const; Graph::Node* GraphNode(const v8::Local<v8::Value>&) const;
Graph::Node* GraphNode(Traceable, EmbedderNode* GraphNode(Traceable,
const char* name, const char* name,
Graph::Node* wrapper) const; Graph::Node* wrapper,
DomTreeState) const;
void VisitPendingActivities(); void VisitPendingActivities();
void VisitTransitiveClosure(); void VisitTransitiveClosure();
// Push the item to the default worklist if item.traceable was not
// already visited.
void PushToWorklist(WorklistItem) const;
v8::Isolate* isolate_; v8::Isolate* isolate_;
Graph::Node* current_parent_; EmbedderNode* current_parent_;
mutable Graph* graph_; mutable Graph* graph_;
mutable HashSet<Traceable> visited_; mutable HashSet<Traceable> visited_;
mutable HashMap<Traceable, Graph::Node*> graph_node_; mutable HashMap<Traceable, EmbedderNode*> graph_node_;
// The default worklist that is used to visit transitive closure.
mutable Deque<WorklistItem> worklist_; mutable Deque<WorklistItem> worklist_;
// The worklist that collects detached Nodes during persistent handle
// iteration.
mutable Deque<WorklistItem> detached_worklist_;
// The worklist that collects ScriptWrappables with unknown information
// about attached/detached state during persistent handle iteration.
mutable Deque<WorklistItem> unknown_worklist_;
}; };
} // namespace blink } // namespace blink
......
...@@ -7327,6 +7327,7 @@ void Document::TraceWrappers(const ScriptWrappableVisitor* visitor) const { ...@@ -7327,6 +7327,7 @@ void Document::TraceWrappers(const ScriptWrappableVisitor* visitor) const {
// node_lists_ are traced in their corresponding NodeListsNodeData, keeping // node_lists_ are traced in their corresponding NodeListsNodeData, keeping
// them only alive for live nodes. Otherwise we would keep lists of dead // them only alive for live nodes. Otherwise we would keep lists of dead
// nodes alive that have not yet been invalidated. // nodes alive that have not yet been invalidated.
visitor->TraceWrappers(dom_window_);
visitor->TraceWrappers(imports_controller_); visitor->TraceWrappers(imports_controller_);
visitor->TraceWrappers(parser_); visitor->TraceWrappers(parser_);
visitor->TraceWrappers(implementation_); visitor->TraceWrappers(implementation_);
......
...@@ -1566,7 +1566,7 @@ class CORE_EXPORT Document : public ContainerNode, ...@@ -1566,7 +1566,7 @@ class CORE_EXPORT Document : public ContainerNode,
PendingSheetLayout pending_sheet_layout_; PendingSheetLayout pending_sheet_layout_;
Member<LocalFrame> frame_; Member<LocalFrame> frame_;
Member<LocalDOMWindow> dom_window_; TraceWrapperMember<LocalDOMWindow> dom_window_;
TraceWrapperMember<HTMLImportsController> imports_controller_; TraceWrapperMember<HTMLImportsController> imports_controller_;
// The document of creator browsing context for frame-less documents such as // The document of creator browsing context for frame-less documents such as
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment