@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow dashboard panels to detect rendering cycles and arrest stack overflows

Summary:
Ref T4986. Ref T4983. Panels will soon be able to contain other panels, either via Remarkup (`{W1}`) or maybe through new types of meta-panels.

Allow panels to detect that they are being rendered very deeply and/or within themselves.

Test Plan: Faked some errors, got failed panel renders. Since panels can't //really// contain other panels yet, this doesn't really have an impact.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: chad, epriestley

Maniphest Tasks: T4983, T4986

Differential Revision: https://secure.phabricator.com/D9140

+95 -31
+26 -26
resources/celerity/map.php
··· 52 52 'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c', 53 53 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 54 54 'rsrc/css/application/countdown/timer.css' => '86b7b0a0', 55 - 'rsrc/css/application/dashboard/dashboard.css' => '5b532b7b', 55 + 'rsrc/css/application/dashboard/dashboard.css' => '2b41640b', 56 56 'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8', 57 57 'rsrc/css/application/differential/add-comment.css' => 'c478bcaa', 58 58 'rsrc/css/application/differential/changeset-view.css' => '1570a1ff', ··· 358 358 'rsrc/js/application/conpherence/behavior-pontificate.js' => '53f6f2dd', 359 359 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', 360 360 'rsrc/js/application/countdown/timer.js' => '889c96f3', 361 - 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '4398eabb', 362 - 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'aa3f313b', 361 + 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => 'fd965b41', 362 + 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'fa187a68', 363 363 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746', 364 364 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => '533a187b', 365 365 'rsrc/js/application/differential/behavior-comment-jump.js' => '71755c79', ··· 553 553 'javelin-behavior-conpherence-widget-pane' => '40b1ff90', 554 554 'javelin-behavior-countdown-timer' => '889c96f3', 555 555 'javelin-behavior-dark-console' => 'e9fdb5e5', 556 - 'javelin-behavior-dashboard-async-panel' => '4398eabb', 557 - 'javelin-behavior-dashboard-move-panels' => 'aa3f313b', 556 + 'javelin-behavior-dashboard-async-panel' => 'fd965b41', 557 + 'javelin-behavior-dashboard-move-panels' => 'fa187a68', 558 558 'javelin-behavior-device' => '03d6ed07', 559 559 'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b', 560 560 'javelin-behavior-differential-comment-jump' => '71755c79', ··· 701 701 'phabricator-core-css' => '40151074', 702 702 'phabricator-countdown-css' => '86b7b0a0', 703 703 'phabricator-crumbs-view-css' => '6a23399c', 704 - 'phabricator-dashboard-css' => '5b532b7b', 704 + 'phabricator-dashboard-css' => '2b41640b', 705 705 'phabricator-drag-and-drop-file-upload' => 'ae6abfba', 706 706 'phabricator-draggable-list' => '1681c4d4', 707 707 'phabricator-fatal-config-template-css' => '25d446d6', ··· 1133 1133 8 => 'phuix-action-list-view', 1134 1134 9 => 'phuix-action-view', 1135 1135 ), 1136 - '4398eabb' => 1137 - array( 1138 - 0 => 'javelin-behavior', 1139 - 1 => 'javelin-dom', 1140 - 2 => 'javelin-workflow', 1141 - ), 1142 1136 '441f2137' => 1143 1137 array( 1144 1138 0 => 'javelin-behavior', ··· 1270 1264 2 => 'javelin-util', 1271 1265 3 => 'phabricator-shaped-request', 1272 1266 ), 1273 - '7319e029' => 1274 - array( 1275 - 0 => 'javelin-behavior', 1276 - 1 => 'javelin-dom', 1277 - ), 1278 1267 '62e18640' => 1279 1268 array( 1280 1269 0 => 'javelin-install', ··· 1329 1318 1 => 'javelin-stratcom', 1330 1319 2 => 'javelin-dom', 1331 1320 ), 1321 + '7319e029' => 1322 + array( 1323 + 0 => 'javelin-behavior', 1324 + 1 => 'javelin-dom', 1325 + ), 1332 1326 '76f4ebed' => 1333 1327 array( 1334 1328 0 => 'javelin-install', ··· 1597 1591 0 => 'javelin-behavior', 1598 1592 1 => 'javelin-stratcom', 1599 1593 2 => 'javelin-dom', 1600 - ), 1601 - 'aa3f313b' => 1602 - array( 1603 - 0 => 'javelin-behavior', 1604 - 1 => 'javelin-dom', 1605 - 2 => 'javelin-util', 1606 - 3 => 'javelin-stratcom', 1607 - 4 => 'javelin-workflow', 1608 - 5 => 'phabricator-draggable-list', 1609 1594 ), 1610 1595 'ad7a69ca' => 1611 1596 array( ··· 2034 2019 4 => 'javelin-stratcom', 2035 2020 5 => 'phabricator-shaped-request', 2036 2021 ), 2022 + 'fa187a68' => 2023 + array( 2024 + 0 => 'javelin-behavior', 2025 + 1 => 'javelin-dom', 2026 + 2 => 'javelin-util', 2027 + 3 => 'javelin-stratcom', 2028 + 4 => 'javelin-workflow', 2029 + 5 => 'phabricator-draggable-list', 2030 + ), 2037 2031 'fbbce3bf' => 2038 2032 array( 2039 2033 0 => 'phabricator-busy', 2040 2034 1 => 'javelin-behavior', 2035 + ), 2036 + 'fd965b41' => 2037 + array( 2038 + 0 => 'javelin-behavior', 2039 + 1 => 'javelin-dom', 2040 + 2 => 'javelin-workflow', 2041 2041 ), 2042 2042 'fe2e0ba4' => 2043 2043 array(
+13
src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php
··· 21 21 return new Aphront404Response(); 22 22 } 23 23 24 + if ($request->isAjax()) { 25 + $parent_phids = $request->getStrList('parentPanelPHIDs', null); 26 + if ($parent_phids === null) { 27 + throw new Exception( 28 + pht( 29 + 'Required parameter `parentPanelPHIDs` is not present in '. 30 + 'request.')); 31 + } 32 + } else { 33 + $parent_phids = array(); 34 + } 35 + 24 36 $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) 25 37 ->setViewer($viewer) 26 38 ->setPanel($panel) 39 + ->setParentPanelPHIDs($parent_phids) 27 40 ->renderPanel(); 28 41 29 42 if ($request->isAjax()) {
+1
src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php
··· 41 41 $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) 42 42 ->setViewer($viewer) 43 43 ->setPanel($panel) 44 + ->setParentPanelPHIDs(array()) 44 45 ->renderPanel(); 45 46 46 47 return $this->buildApplicationPage(
+53 -5
src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
··· 5 5 private $panel; 6 6 private $viewer; 7 7 private $enableAsyncRendering; 8 + private $parentPanelPHIDs; 8 9 9 10 /** 10 11 * Allow the engine to render the panel via Ajax. 11 12 */ 12 13 public function setEnableAsyncRendering($enable) { 13 14 $this->enableAsyncRendering = $enable; 15 + return $this; 16 + } 17 + 18 + public function setParentPanelPHIDs(array $parents) { 19 + $this->parentPanelPHIDs = $parents; 14 20 return $this; 15 21 } 16 22 ··· 44 50 $panel->getPanelType())); 45 51 } 46 52 47 - if ($this->enableAsyncRendering) { 48 - if ($panel_type->shouldRenderAsync()) { 49 - return $this->renderAsyncPanel($panel); 53 + try { 54 + $this->detectRenderingCycle($panel); 55 + 56 + if ($this->enableAsyncRendering) { 57 + if ($panel_type->shouldRenderAsync()) { 58 + return $this->renderAsyncPanel($panel); 59 + } 50 60 } 51 - } 52 61 53 - try { 54 62 return $panel_type->renderPanel($viewer, $panel); 55 63 } catch (Exception $ex) { 56 64 return $this->renderErrorPanel( ··· 75 83 'dashboard-async-panel', 76 84 array( 77 85 'panelID' => $panel_id, 86 + 'parentPanelPHIDs' => $this->parentPanelPHIDs, 78 87 'uri' => '/dashboard/panel/render/'.$panel->getID().'/', 79 88 )); 80 89 ··· 86 95 ->setID($panel_id) 87 96 ->appendChild(pht('Loading...')); 88 97 } 98 + 99 + /** 100 + * Detect graph cycles in panels, and deeply nested panels. 101 + * 102 + * This method throws if the current rendering stack is too deep or contains 103 + * a cycle. This can happen if you embed layout panels inside each other, 104 + * build a big stack of panels, or embed a panel in remarkup inside another 105 + * panel. Generally, all of this stuff is ridiculous and we just want to 106 + * shut it down. 107 + * 108 + * @param PhabricatorDashboardPanel Panel being rendered. 109 + * @return void 110 + */ 111 + private function detectRenderingCycle(PhabricatorDashboardPanel $panel) { 112 + if ($this->parentPanelPHIDs === null) { 113 + throw new Exception( 114 + pht( 115 + 'You must call setParentPanelPHIDs() before rendering panels.')); 116 + } 117 + 118 + $max_depth = 4; 119 + if (count($this->parentPanelPHIDs) >= $max_depth) { 120 + throw new Exception( 121 + pht( 122 + 'To render more than %s levels of panels nested inside other '. 123 + 'panels, purchase a subscription to Phabricator Gold.', 124 + new PhutilNumber($max_depth))); 125 + } 126 + 127 + if (in_array($panel->getPHID(), $this->parentPanelPHIDs)) { 128 + throw new Exception( 129 + pht( 130 + 'You awake in a twisting maze of mirrors, all alike. '. 131 + 'You are likely to be eaten by a graph cycle. '. 132 + 'Should you escape alive, you resolve to be more careful about '. 133 + 'putting dashboard panels inside themselves.')); 134 + } 135 + } 136 + 89 137 90 138 }
+1
src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
··· 43 43 ->setViewer($viewer) 44 44 ->setPanel($panel) 45 45 ->setEnableAsyncRendering(true) 46 + ->setParentPanelPHIDs(array()) 46 47 ->renderPanel(); 47 48 } 48 49 $column_class = $layout_config->getColumnClass(
+1
webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js
··· 10 10 panel.style.opacity = '0.5'; 11 11 12 12 new JX.Workflow(config.uri) 13 + .setData({parentPanelPHIDs: config.parentPanelPHIDs.join(',')}) 13 14 .setHandler(function(r) { 14 15 JX.DOM.replace(panel, JX.$H(r.panelMarkup)); 15 16 })