web_memory_aggregator.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/performance_manager/v8_memory/web_memory_aggregator.h"
  5. #include <utility>
  6. #include <vector>
  7. #include "base/bind.h"
  8. #include "base/check.h"
  9. #include "base/containers/stack.h"
  10. #include "base/memory/raw_ptr.h"
  11. #include "components/performance_manager/public/graph/frame_node.h"
  12. #include "components/performance_manager/public/graph/page_node.h"
  13. #include "components/performance_manager/public/graph/worker_node.h"
  14. #include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
  15. #include "components/performance_manager/v8_memory/v8_context_tracker.h"
  16. #include "url/gurl.h"
  17. namespace performance_manager {
  18. namespace v8_memory {
  19. // A visitor that visits every node that can be aggregated into an aggregation
  20. // point.
  21. //
  22. // TODO(joenotcharles): If we ever need to aggregate different data for each
  23. // aggregation point, turn this into an interface and add a subclass for each
  24. // type of data to aggregate.
  25. class AggregationPointVisitor {
  26. public:
  27. // The given |main_origin| is the origin of the main web page, which is the
  28. // same as the origin of the top-level frames.
  29. AggregationPointVisitor(const url::Origin& requesting_origin,
  30. const ProcessNode* requesting_process_node,
  31. const url::Origin& main_origin);
  32. ~AggregationPointVisitor();
  33. AggregationPointVisitor(const AggregationPointVisitor& other) = delete;
  34. AggregationPointVisitor& operator=(const AggregationPointVisitor& other) =
  35. delete;
  36. mojom::WebMemoryMeasurementPtr TakeAggregationResult();
  37. // Called on first visiting |frame_node| in a depth-first traversal.
  38. void OnFrameEntered(const FrameNode* frame_node);
  39. // Called after visiting |frame_node| and all its children in a depth-first
  40. // traversal.
  41. void OnFrameExited(const FrameNode* frame_node);
  42. // Called on first visiting |worker_node| in a depth-first traversal.
  43. void OnWorkerEntered(const WorkerNode* worker_node);
  44. // Called after visiting |worker_node| and all its children in a depth-first
  45. // traversal.
  46. void OnWorkerExited(const WorkerNode* worker_node);
  47. // Called at the start of the depth-first traversal to set up the common
  48. // root node for all frame trees.
  49. void OnRootEntered();
  50. // Called at the end of the traversal.
  51. void OnRootExited();
  52. private:
  53. struct Enclosing {
  54. url::Origin origin;
  55. raw_ptr<mojom::WebMemoryBreakdownEntry> aggregation_point;
  56. };
  57. const url::Origin requesting_origin_;
  58. raw_ptr<const ProcessNode> requesting_process_node_;
  59. const url::Origin main_origin_;
  60. mojom::WebMemoryMeasurementPtr aggregation_result_ =
  61. mojom::WebMemoryMeasurement::New();
  62. mojom::WebMemoryBreakdownEntryPtr root_aggregation_point_;
  63. base::stack<Enclosing> enclosing_;
  64. };
  65. namespace {
  66. using AttributionScope = mojom::WebMemoryAttribution::Scope;
  67. // The various ways a node can be treated during the aggregation.
  68. enum class NodeAggregationType {
  69. // Node is same-origin to |requesting_node| and its iframe attributes are
  70. // visible;
  71. // will be a new aggregation point with a scope depending on the node type
  72. // (eg. "Window" or "DedicatedWorker").
  73. kSameOriginAggregationPoint,
  74. // Node is same-origin to |requesting_node| but its iframe attributes are not
  75. // visible;
  76. // will be a new aggregation point with a scope depending on the node type
  77. // (eg. "Window" or "DedicatedWorker").
  78. kSameOriginAggregationPointWithHiddenAttributes,
  79. // Node is cross-origin with |requesting_node| but its parent is not; will
  80. // be a new aggregation point with scope "cross-origin-aggregated".
  81. kCrossOriginAggregationPoint,
  82. // Node is cross-origin with |requesting_node| and so is its parent; will
  83. // be aggregated into its parent's aggregation point.
  84. kCrossOriginAggregated,
  85. };
  86. NodeAggregationType GetNodeAggregationType(const url::Origin& requesting_origin,
  87. const url::Origin& enclosing_origin,
  88. const url::Origin& node_origin) {
  89. bool same_origin_node = requesting_origin.IsSameOriginWith(node_origin);
  90. bool same_origin_parent =
  91. requesting_origin.IsSameOriginWith(enclosing_origin);
  92. if (same_origin_node) {
  93. return same_origin_parent
  94. ? NodeAggregationType::kSameOriginAggregationPoint
  95. : NodeAggregationType::
  96. kSameOriginAggregationPointWithHiddenAttributes;
  97. } else {
  98. return same_origin_parent
  99. ? NodeAggregationType::kCrossOriginAggregationPoint
  100. : NodeAggregationType::kCrossOriginAggregated;
  101. }
  102. }
  103. // Returns |frame_node|'s origin based on its current url.
  104. // An about:blank iframe inherits the origin of its parent. See:
  105. // https://html.spec.whatwg.org/multipage/browsers.html#determining-the-origin
  106. url::Origin GetOrigin(const FrameNode* frame_node) {
  107. if (frame_node->GetParentFrameNode()) {
  108. return url::Origin::Resolve(
  109. frame_node->GetURL(),
  110. url::Origin::Create(frame_node->GetParentFrameNode()->GetURL()));
  111. } else {
  112. return url::Origin::Create(frame_node->GetURL());
  113. }
  114. }
  115. #if DCHECK_IS_ON()
  116. // Returns |worker_node|'s origin based on its current url.
  117. url::Origin GetOrigin(const WorkerNode* worker_node) {
  118. return url::Origin::Create(worker_node->GetURL());
  119. }
  120. #endif
  121. // Returns a mutable pointer to the WebMemoryAttribution structure in the given
  122. // |breakdown|.
  123. mojom::WebMemoryAttribution* GetAttributionFromBreakdown(
  124. mojom::WebMemoryBreakdownEntry* breakdown) {
  125. // We only store a single attribution with each breakdown.
  126. DCHECK_EQ(breakdown->attribution.size(), 1U);
  127. mojom::WebMemoryAttribution* attribution =
  128. breakdown->attribution.front().get();
  129. DCHECK(attribution);
  130. return attribution;
  131. }
  132. // Returns a const pointer to the WebMemoryAttribution structure in the given
  133. // |breakdown|.
  134. const mojom::WebMemoryAttribution* GetAttributionFromBreakdown(
  135. const mojom::WebMemoryBreakdownEntry* breakdown) {
  136. auto* mutable_breakdown =
  137. const_cast<mojom::WebMemoryBreakdownEntry*>(breakdown);
  138. auto* mutable_attribution = GetAttributionFromBreakdown(mutable_breakdown);
  139. return const_cast<mojom::WebMemoryAttribution*>(mutable_attribution);
  140. }
  141. AttributionScope AttributionScopeFromWorkerType(
  142. WorkerNode::WorkerType worker_type) {
  143. switch (worker_type) {
  144. case WorkerNode::WorkerType::kDedicated:
  145. return AttributionScope::kDedicatedWorker;
  146. case WorkerNode::WorkerType::kShared:
  147. case WorkerNode::WorkerType::kService:
  148. // TODO(crbug.com/1169168): Support service and shared workers.
  149. NOTREACHED();
  150. return AttributionScope::kDedicatedWorker;
  151. }
  152. }
  153. void AddMemoryBytes(mojom::WebMemoryBreakdownEntry* aggregation_point,
  154. const V8DetailedMemoryExecutionContextData* data,
  155. bool is_same_process) {
  156. if (!data) {
  157. return;
  158. }
  159. if (!aggregation_point->memory) {
  160. aggregation_point->memory = mojom::WebMemoryUsage::New();
  161. }
  162. // Ensure this frame is actually in the same process as the requesting
  163. // frame. If not it should be considered to have 0 bytes.
  164. // (https://github.com/WICG/performance-measure-memory/issues/20).
  165. uint64_t bytes_used = is_same_process ? data->v8_bytes_used() : 0;
  166. aggregation_point->memory->bytes += bytes_used;
  167. // Add canvas memory similar to V8 memory above.
  168. if (data->canvas_bytes_used()) {
  169. uint64_t canvas_bytes_used =
  170. is_same_process ? *data->canvas_bytes_used() : 0;
  171. if (!aggregation_point->canvas_memory) {
  172. aggregation_point->canvas_memory = mojom::WebMemoryUsage::New();
  173. }
  174. aggregation_point->canvas_memory->bytes += canvas_bytes_used;
  175. }
  176. }
  177. const FrameNode* GetTopFrame(const FrameNode* frame) {
  178. DCHECK(frame);
  179. // Follow the parent to find the top-most frame.
  180. auto* current = frame;
  181. while (auto* parent = current->GetParentFrameNode()) {
  182. current = parent;
  183. }
  184. DCHECK(current);
  185. // Make sure we didn't break out of the browsing context group.
  186. DCHECK_EQ(current->GetBrowsingInstanceId(), frame->GetBrowsingInstanceId());
  187. return current;
  188. }
  189. // Returns the process node of the main frame that is in the same browsing
  190. // context group as the given frame.
  191. const ProcessNode* GetMainProcess(const FrameNode* frame) {
  192. // COOP guarantees that the top-most frame of the current frame tree
  193. // and the main frame of the page have the same origin and thus have
  194. // the same process node.
  195. return GetTopFrame(frame)->GetProcessNode();
  196. }
  197. } // anonymous namespace
  198. ////////////////////////////////////////////////////////////////////////////////
  199. // AggregationPointVisitor
  200. AggregationPointVisitor::AggregationPointVisitor(
  201. const url::Origin& requesting_origin,
  202. const ProcessNode* requesting_process_node,
  203. const url::Origin& main_origin)
  204. : requesting_origin_(requesting_origin),
  205. requesting_process_node_(requesting_process_node),
  206. main_origin_(main_origin) {}
  207. AggregationPointVisitor::~AggregationPointVisitor() {
  208. DCHECK(enclosing_.empty());
  209. }
  210. mojom::WebMemoryMeasurementPtr
  211. AggregationPointVisitor::TakeAggregationResult() {
  212. DCHECK(aggregation_result_);
  213. auto result = std::move(aggregation_result_);
  214. aggregation_result_ = nullptr;
  215. return result;
  216. }
  217. void AggregationPointVisitor::OnRootEntered() {
  218. DCHECK(enclosing_.empty());
  219. root_aggregation_point_ = mojom::WebMemoryBreakdownEntry::New();
  220. root_aggregation_point_->attribution.emplace_back(
  221. mojom::WebMemoryAttribution::New());
  222. enclosing_.push(Enclosing{main_origin_, root_aggregation_point_.get()});
  223. }
  224. void AggregationPointVisitor::OnRootExited() {
  225. if (root_aggregation_point_->memory) {
  226. aggregation_result_->breakdown.push_back(
  227. std::move(root_aggregation_point_));
  228. }
  229. enclosing_.pop();
  230. DCHECK(enclosing_.empty());
  231. }
  232. void AggregationPointVisitor::OnFrameEntered(const FrameNode* frame_node) {
  233. DCHECK(!enclosing_.empty());
  234. DCHECK(frame_node);
  235. url::Origin node_origin = GetOrigin(frame_node);
  236. NodeAggregationType aggregation_type = GetNodeAggregationType(
  237. requesting_origin_, enclosing_.top().origin, node_origin);
  238. mojom::WebMemoryBreakdownEntry* aggregation_point = nullptr;
  239. switch (aggregation_type) {
  240. case NodeAggregationType::kSameOriginAggregationPoint:
  241. aggregation_point = WebMemoryAggregator::CreateBreakdownEntry(
  242. AttributionScope::kWindow, frame_node->GetURL().spec(),
  243. aggregation_result_.get());
  244. WebMemoryAggregator::SetBreakdownAttributionFromFrame(frame_node,
  245. aggregation_point);
  246. break;
  247. case NodeAggregationType::kSameOriginAggregationPointWithHiddenAttributes:
  248. aggregation_point = WebMemoryAggregator::CreateBreakdownEntry(
  249. AttributionScope::kWindow, frame_node->GetURL().spec(),
  250. aggregation_result_.get());
  251. // Some grandparent node is the most recent aggregation point whose
  252. // attributes are visible to the start node, and
  253. // |enclosing_aggregation_point| includes those attributes. Copy the
  254. // id and src attributes from there.
  255. WebMemoryAggregator::CopyBreakdownAttribution(
  256. enclosing_.top().aggregation_point, aggregation_point);
  257. break;
  258. case NodeAggregationType::kCrossOriginAggregationPoint:
  259. // Create a new aggregation point with cross-origin-aggregated scope.
  260. // Since this node is NOT same-origin to the start node, the start node
  261. // CANNOT view its current url.
  262. aggregation_point = WebMemoryAggregator::CreateBreakdownEntry(
  263. AttributionScope::kCrossOriginAggregated, absl::nullopt,
  264. aggregation_result_.get());
  265. // This is cross-origin but not being aggregated into another
  266. // aggregation point, so its parent or opener must be same-origin to the
  267. // start node, which can therefore view its attributes. Add the id and
  268. // src recorded for the node in V8ContextTracker to the new breakdown
  269. // entry.
  270. WebMemoryAggregator::SetBreakdownAttributionFromFrame(frame_node,
  271. aggregation_point);
  272. break;
  273. case NodeAggregationType::kCrossOriginAggregated:
  274. // Update the enclosing aggregation point in-place.
  275. aggregation_point = enclosing_.top().aggregation_point;
  276. break;
  277. }
  278. // Now update the memory used in the chosen aggregation point.
  279. DCHECK(aggregation_point);
  280. AddMemoryBytes(aggregation_point,
  281. V8DetailedMemoryExecutionContextData::ForFrameNode(frame_node),
  282. frame_node->GetProcessNode() == requesting_process_node_);
  283. enclosing_.push(Enclosing{node_origin, aggregation_point});
  284. }
  285. void AggregationPointVisitor::OnFrameExited(const FrameNode* frame_node) {
  286. enclosing_.pop();
  287. DCHECK(!enclosing_.empty());
  288. }
  289. void AggregationPointVisitor::OnWorkerEntered(const WorkerNode* worker_node) {
  290. DCHECK(!enclosing_.empty());
  291. DCHECK(worker_node);
  292. // TODO(crbug.com/1169168): Support service and shared workers.
  293. DCHECK_EQ(worker_node->GetWorkerType(), WorkerNode::WorkerType::kDedicated);
  294. // A dedicated worker is guaranteed to have the same origin as its parent,
  295. // which means that a dedicated worker cannot be a cross-origin aggregation
  296. // point.
  297. // TODO(crbug.com/1169178): The URL of a worker node is currently not
  298. // available without PlzDedicatedWorker, which is disabled by default.
  299. // Until then we use the origin of the parent.
  300. url::Origin node_origin = enclosing_.top().origin;
  301. #if DCHECK_IS_ON()
  302. auto client_frames = worker_node->GetClientFrames();
  303. DCHECK(std::all_of(client_frames.begin(), client_frames.end(),
  304. [node_origin](const FrameNode* client) {
  305. return node_origin.IsSameOriginWith(GetOrigin(client));
  306. }));
  307. auto client_workers = worker_node->GetClientWorkers();
  308. DCHECK(std::all_of(client_workers.begin(), client_workers.end(),
  309. [node_origin](const WorkerNode* client) {
  310. // TODO(crbug.com/1169178): Remove the is_empty guard
  311. // once worker worker URLs are available.
  312. return client->GetURL().is_empty() ||
  313. node_origin.IsSameOriginWith(GetOrigin(client));
  314. }));
  315. #endif
  316. NodeAggregationType aggregation_type = GetNodeAggregationType(
  317. requesting_origin_, enclosing_.top().origin, node_origin);
  318. mojom::WebMemoryBreakdownEntry* aggregation_point = nullptr;
  319. switch (aggregation_type) {
  320. case NodeAggregationType::kSameOriginAggregationPoint:
  321. case NodeAggregationType::kSameOriginAggregationPointWithHiddenAttributes: {
  322. // Create a new aggregation point with window scope. Since this node is
  323. // same-origin to the start node, the start node can view its current
  324. // url.
  325. std::string url = worker_node->GetURL().spec();
  326. if (url.empty()) {
  327. // TODO(906991): Remove this once PlzDedicatedWorker ships. Until then
  328. // the browser does not know URLs of dedicated workers, so we pass them
  329. // together with the measurement result.
  330. const auto* data =
  331. V8DetailedMemoryExecutionContextData::ForWorkerNode(worker_node);
  332. if (data && data->url()) {
  333. url = *data->url();
  334. }
  335. }
  336. aggregation_point = WebMemoryAggregator::CreateBreakdownEntry(
  337. AttributionScopeFromWorkerType(worker_node->GetWorkerType()),
  338. std::move(url), aggregation_result_.get());
  339. WebMemoryAggregator::CopyBreakdownAttribution(
  340. enclosing_.top().aggregation_point, aggregation_point);
  341. break;
  342. }
  343. case NodeAggregationType::kCrossOriginAggregated:
  344. // Update the enclosing aggregation point in-place.
  345. aggregation_point = enclosing_.top().aggregation_point;
  346. break;
  347. case NodeAggregationType::kCrossOriginAggregationPoint:
  348. NOTREACHED();
  349. return;
  350. }
  351. // Now update the memory used in the chosen aggregation point.
  352. DCHECK(aggregation_point);
  353. AddMemoryBytes(
  354. aggregation_point,
  355. V8DetailedMemoryExecutionContextData::ForWorkerNode(worker_node),
  356. worker_node->GetProcessNode() == requesting_process_node_);
  357. enclosing_.push(Enclosing{node_origin, aggregation_point});
  358. }
  359. void AggregationPointVisitor::OnWorkerExited(const WorkerNode* worker_node) {
  360. enclosing_.pop();
  361. DCHECK(!enclosing_.empty());
  362. }
  363. ////////////////////////////////////////////////////////////////////////////////
  364. // WebMemoryAggregator
  365. WebMemoryAggregator::WebMemoryAggregator(const FrameNode* requesting_node)
  366. : requesting_origin_(GetOrigin(requesting_node)),
  367. requesting_process_node_(requesting_node->GetProcessNode()),
  368. main_process_node_(GetMainProcess(requesting_node)),
  369. browsing_instance_id_(requesting_node->GetBrowsingInstanceId()) {}
  370. WebMemoryAggregator::~WebMemoryAggregator() = default;
  371. namespace {
  372. // Returns v8_browsing_memory / v8_process_memory where
  373. // - v8_browsing_memory is the total V8 memory usage of all frames of the given
  374. // |browsing_instance_id| in the given |process_node|.
  375. // - v8_process_memory is the total V8 memory usage of all frames in the given
  376. // |process_node|.
  377. double GetBrowsingInstanceV8BytesFraction(
  378. const ProcessNode* process_node,
  379. content::BrowsingInstanceId browsing_instance_id) {
  380. uint64_t bytes_used = 0;
  381. uint64_t total_bytes_used = 0;
  382. process_node->VisitFrameNodes(base::BindRepeating(
  383. [](absl::optional<content::BrowsingInstanceId> browsing_instance_id,
  384. uint64_t* bytes_used, uint64_t* total_bytes_used,
  385. const FrameNode* frame_node) {
  386. const auto* data =
  387. V8DetailedMemoryExecutionContextData::ForFrameNode(frame_node);
  388. if (data) {
  389. if (frame_node->GetBrowsingInstanceId() == browsing_instance_id) {
  390. *bytes_used += data->v8_bytes_used();
  391. }
  392. *total_bytes_used += data->v8_bytes_used();
  393. }
  394. return true;
  395. },
  396. browsing_instance_id, &bytes_used, &total_bytes_used));
  397. DCHECK_LE(bytes_used, total_bytes_used);
  398. return total_bytes_used == 0
  399. ? 1
  400. : static_cast<double>(bytes_used) / total_bytes_used;
  401. }
  402. } // anonymous namespace
  403. mojom::WebMemoryMeasurementPtr
  404. WebMemoryAggregator::AggregateMeasureMemoryResult() {
  405. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  406. std::vector<const FrameNode*> top_frames;
  407. main_process_node_->VisitFrameNodes(base::BindRepeating(
  408. [](std::vector<const FrameNode*>* top_frames,
  409. content::BrowsingInstanceId browsing_instance_id,
  410. const FrameNode* node) {
  411. if (node->GetBrowsingInstanceId() == browsing_instance_id &&
  412. !node->GetParentFrameNode() && !GetOrigin(node).opaque()) {
  413. top_frames->push_back(node);
  414. }
  415. return true;
  416. },
  417. &top_frames, browsing_instance_id_));
  418. CHECK(!top_frames.empty());
  419. url::Origin main_origin = GetOrigin(top_frames[0]);
  420. DCHECK(std::all_of(top_frames.begin(), top_frames.end(),
  421. [&main_origin](const FrameNode* node) {
  422. return GetOrigin(node).IsSameOriginWith(main_origin);
  423. }));
  424. AggregationPointVisitor ap_visitor(requesting_origin_,
  425. requesting_process_node_, main_origin);
  426. ap_visitor.OnRootEntered();
  427. for (const FrameNode* node : top_frames) {
  428. VisitFrame(&ap_visitor, node);
  429. }
  430. ap_visitor.OnRootExited();
  431. mojom::WebMemoryMeasurementPtr aggregation_result =
  432. ap_visitor.TakeAggregationResult();
  433. auto* process_data =
  434. V8DetailedMemoryProcessData::ForProcessNode(requesting_process_node_);
  435. if (process_data) {
  436. // Shared memory is shared between browsing context groups in the process.
  437. // We cannot attribute it to a single browsing context group, so we report
  438. // it as is.
  439. aggregation_result->shared_memory = mojom::WebMemoryUsage::New();
  440. aggregation_result->shared_memory->bytes =
  441. process_data->shared_v8_bytes_used();
  442. // As we don't have precise attribution for detached and Blink memory,
  443. // we approximate it using V8 memory.
  444. double browsing_instance_factor = GetBrowsingInstanceV8BytesFraction(
  445. requesting_process_node_, browsing_instance_id_);
  446. aggregation_result->detached_memory = mojom::WebMemoryUsage::New();
  447. aggregation_result->detached_memory->bytes = static_cast<uint64_t>(
  448. process_data->detached_v8_bytes_used() * browsing_instance_factor);
  449. aggregation_result->blink_memory = mojom::WebMemoryUsage::New();
  450. aggregation_result->blink_memory->bytes = static_cast<uint64_t>(
  451. process_data->blink_bytes_used() * browsing_instance_factor);
  452. }
  453. return aggregation_result;
  454. }
  455. bool WebMemoryAggregator::VisitFrame(AggregationPointVisitor* ap_visitor,
  456. const FrameNode* frame_node) {
  457. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  458. DCHECK(frame_node);
  459. if (frame_node->GetBrowsingInstanceId() != browsing_instance_id_) {
  460. // Ignore frames from other browsing contexts.
  461. return true;
  462. }
  463. ap_visitor->OnFrameEntered(frame_node);
  464. frame_node->VisitChildDedicatedWorkers(base::BindRepeating(
  465. &WebMemoryAggregator::VisitWorker, base::Unretained(this), ap_visitor));
  466. frame_node->VisitChildFrameNodes(base::BindRepeating(
  467. &WebMemoryAggregator::VisitFrame, base::Unretained(this), ap_visitor));
  468. ap_visitor->OnFrameExited(frame_node);
  469. return true;
  470. }
  471. bool WebMemoryAggregator::VisitWorker(AggregationPointVisitor* ap_visitor,
  472. const WorkerNode* worker_node) {
  473. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  474. // TODO(crbug.com/1169168): Support service and shared workers.
  475. DCHECK_EQ(worker_node->GetWorkerType(), WorkerNode::WorkerType::kDedicated);
  476. ap_visitor->OnWorkerEntered(worker_node);
  477. worker_node->VisitChildDedicatedWorkers(base::BindRepeating(
  478. &WebMemoryAggregator::VisitWorker, base::Unretained(this), ap_visitor));
  479. ap_visitor->OnWorkerExited(worker_node);
  480. return true;
  481. }
  482. // static
  483. mojom::WebMemoryBreakdownEntry* WebMemoryAggregator::CreateBreakdownEntry(
  484. AttributionScope scope,
  485. absl::optional<std::string> url,
  486. mojom::WebMemoryMeasurement* measurement) {
  487. auto breakdown = mojom::WebMemoryBreakdownEntry::New();
  488. auto attribution = mojom::WebMemoryAttribution::New();
  489. attribution->scope = scope;
  490. attribution->url = std::move(url);
  491. breakdown->attribution.push_back(std::move(attribution));
  492. measurement->breakdown.push_back(std::move(breakdown));
  493. return measurement->breakdown.back().get();
  494. }
  495. // static
  496. void WebMemoryAggregator::SetBreakdownAttributionFromFrame(
  497. const FrameNode* frame_node,
  498. mojom::WebMemoryBreakdownEntry* breakdown) {
  499. DCHECK(breakdown);
  500. DCHECK(frame_node);
  501. auto* v8_context_tracker =
  502. V8ContextTracker::GetFromGraph(frame_node->GetGraph());
  503. DCHECK(v8_context_tracker);
  504. auto* ec_state =
  505. v8_context_tracker->GetExecutionContextState(frame_node->GetFrameToken());
  506. if (!ec_state)
  507. return;
  508. const mojom::IframeAttributionDataPtr& ec_attribution =
  509. ec_state->iframe_attribution_data;
  510. if (!ec_attribution)
  511. return;
  512. auto* attribution = GetAttributionFromBreakdown(breakdown);
  513. attribution->id = ec_attribution->id;
  514. attribution->src = ec_attribution->src;
  515. }
  516. // static
  517. void WebMemoryAggregator::CopyBreakdownAttribution(
  518. const mojom::WebMemoryBreakdownEntry* from,
  519. mojom::WebMemoryBreakdownEntry* to) {
  520. DCHECK(from);
  521. DCHECK(to);
  522. const auto* from_attribution = GetAttributionFromBreakdown(from);
  523. auto* to_attribution = GetAttributionFromBreakdown(to);
  524. to_attribution->id = from_attribution->id;
  525. to_attribution->src = from_attribution->src;
  526. }
  527. } // namespace v8_memory
  528. } // namespace performance_manager