@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.

Provide a global router for Ajax requests

Summary:
Fixes T430. Fixes T4834. Obsoletes D7641. Currently, we do some things less-well than we could:

- We just let the browser queue and prioritize requests, so if you load a revision with 50 changes and then click "Award Token", the action blocks until the changes load in most/all browsers. It would be better to prioritize this action and queue it immediately.
- Similarly, changes tend to load in order, even if the user has clicked to a specific file. When the user expresses a preference for a specific file, we should prioritize it.
- We show a spinning GIF when waiting on requests. This is appropriate for some types of reuqests, but distracting for others.

To fix this:

- Queue all (or, at least, most) requests into a new queue in JX.Router.
- JX.Router handles prioritizing the requests. Principally:
- You can submit a request with a specific priority (500 = general content loading, 1000 = default, 2000 = explicit user action) and JX.Router will get the higher stuff fired off sooner.
- You can name requests and then adjust their prorities later, if the user expresses an interest in specific results.
- Only use the spinner gif for "workflow" requests, which is bascially when the user clicked something and we're waiting on the server. I think it's useful and not-annoying in this case.
- Don't show any status for draft requests.
- For content requests, show a subtle hipster-style top loading bar.

Test Plan:
- Viewed a diff with 93 changes, and clicked award token.
- Prior to this patch, the action took many many seconds to resolve.
- After this patch, it resolves quickly.
- Viewed a diff with 93 changes and saw a pleasant subtle hipster-style loading bar.
- Viewed a diff with 93 changes and typed some draft text. Previews populated fairly quickly and there was no spinner.
- Viewed a diff with 93 changes and clicked something with workflow, saw a spinner after a moment.
- Viewed a diff with 93 changes and clicked a file in the table of contents near the end of the list.
- Prior to this patch, it took a long time to show up.
- After this patch, it loads directly.

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T430, T4834

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

+469 -104
+107 -86
resources/celerity/map.php
··· 7 7 return array( 8 8 'names' => 9 9 array( 10 - 'core.pkg.css' => '4279f4bd', 11 - 'core.pkg.js' => 'f6616bcf', 10 + 'core.pkg.css' => '989eee69', 11 + 'core.pkg.js' => 'b2ed04a2', 12 12 'darkconsole.pkg.js' => 'ca8671ce', 13 13 'differential.pkg.css' => '4b8686e3', 14 - 'differential.pkg.js' => 'a2f45b5f', 14 + 'differential.pkg.js' => '05ad02d3', 15 15 'diffusion.pkg.css' => '3783278d', 16 16 'diffusion.pkg.js' => '5b4010f4', 17 - 'javelin.pkg.js' => 'c57fd32c', 17 + 'javelin.pkg.js' => 'dbef0389', 18 18 'maniphest.pkg.css' => 'f1887d71', 19 19 'maniphest.pkg.js' => '2fe8af22', 20 20 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', ··· 105 105 'rsrc/css/application/subscriptions/subscribers-list.css' => '5bb30c78', 106 106 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 107 107 'rsrc/css/application/uiexample/example.css' => '528b19de', 108 - 'rsrc/css/core/core.css' => 'ef1b7892', 108 + 'rsrc/css/core/core.css' => '40151074', 109 109 'rsrc/css/core/remarkup.css' => '80c3a48c', 110 110 'rsrc/css/core/syntax.css' => '3c18c1cb', 111 111 'rsrc/css/core/z-index.css' => 'efb673ac', ··· 204 204 'rsrc/externals/javelin/lib/History.js' => 'c60f4327', 205 205 'rsrc/externals/javelin/lib/JSON.js' => '08e56a4e', 206 206 'rsrc/externals/javelin/lib/Mask.js' => 'b9f26029', 207 - 'rsrc/externals/javelin/lib/Request.js' => '23f9bb8d', 207 + 'rsrc/externals/javelin/lib/Request.js' => '7bad574b', 208 208 'rsrc/externals/javelin/lib/Resource.js' => '356de121', 209 + 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 210 + 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', 209 211 'rsrc/externals/javelin/lib/URI.js' => 'd9a9b862', 210 212 'rsrc/externals/javelin/lib/Vector.js' => '039fb90d', 211 - 'rsrc/externals/javelin/lib/Workflow.js' => 'ff8091f7', 213 + 'rsrc/externals/javelin/lib/Workflow.js' => '09b15cf1', 212 214 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 213 215 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 214 216 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '2295d074', ··· 349 351 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 350 352 'rsrc/js/application/aphlict/Aphlict.js' => '493665ee', 351 353 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a2dba85', 352 - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '845731b8', 354 + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '0a6c2de6', 353 355 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 354 356 'rsrc/js/application/config/behavior-reorder-fields.js' => '938aed89', 355 357 'rsrc/js/application/conpherence/behavior-menu.js' => '7ee23816', ··· 365 367 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '7f93ef26', 366 368 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '00861799', 367 369 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '173ce7e7', 368 - 'rsrc/js/application/differential/behavior-populate.js' => 'ce0c217a', 370 + 'rsrc/js/application/differential/behavior-populate.js' => 'dfdf9f34', 369 371 'rsrc/js/application/differential/behavior-show-all-comments.js' => '7c273581', 370 372 'rsrc/js/application/differential/behavior-show-field-details.js' => '441f2137', 371 373 'rsrc/js/application/differential/behavior-show-more.js' => 'dd7e8ef5', ··· 438 440 'rsrc/js/core/MultirowRowManager.js' => '50395a1b', 439 441 'rsrc/js/core/Notification.js' => '0c6946e7', 440 442 'rsrc/js/core/Prefab.js' => '0326e5d0', 441 - 'rsrc/js/core/ShapedRequest.js' => 'dfa181a4', 443 + 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 442 444 'rsrc/js/core/TextAreaUtils.js' => 'b3ec3cfc', 443 445 'rsrc/js/core/ToolTip.js' => '3915d490', 444 446 'rsrc/js/core/behavior-active-nav.js' => 'c81bc98f', ··· 467 469 'rsrc/js/core/behavior-oncopy.js' => 'c3e218fe', 468 470 'rsrc/js/core/behavior-phabricator-nav.js' => 'b5842a5e', 469 471 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'c021950a', 470 - 'rsrc/js/core/behavior-refresh-csrf.js' => 'c4b31646', 472 + 'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593', 471 473 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 472 474 'rsrc/js/core/behavior-reveal-content.js' => '8f24abfc', 473 475 'rsrc/js/core/behavior-search-typeahead.js' => 'd8469741', ··· 476 478 'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884', 477 479 'rsrc/js/core/behavior-tooltip.js' => '48db4145', 478 480 'rsrc/js/core/behavior-watch-anchor.js' => '06e05112', 479 - 'rsrc/js/core/behavior-workflow.js' => 'fee00761', 481 + 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 480 482 'rsrc/js/core/phtize.js' => 'd254d646', 481 483 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => 'a3e2244e', 482 484 'rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js' => '4d94d9c3', ··· 534 536 'javelin-aphlict' => '493665ee', 535 537 'javelin-behavior' => '8a3ed18b', 536 538 'javelin-behavior-aphlict-dropdown' => '2a2dba85', 537 - 'javelin-behavior-aphlict-listen' => '845731b8', 539 + 'javelin-behavior-aphlict-listen' => '0a6c2de6', 538 540 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 539 541 'javelin-behavior-aphront-crop' => 'b98fc918', 540 542 'javelin-behavior-aphront-drag-and-drop-textarea' => '4a11ea9c', ··· 558 560 'javelin-behavior-differential-edit-inline-comments' => '00861799', 559 561 'javelin-behavior-differential-feedback-preview' => '127f2018', 560 562 'javelin-behavior-differential-keyboard-navigation' => '173ce7e7', 561 - 'javelin-behavior-differential-populate' => 'ce0c217a', 563 + 'javelin-behavior-differential-populate' => 'dfdf9f34', 562 564 'javelin-behavior-differential-show-field-details' => '441f2137', 563 565 'javelin-behavior-differential-show-more' => 'dd7e8ef5', 564 566 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', ··· 623 625 'javelin-behavior-ponder-votebox' => '327dbe61', 624 626 'javelin-behavior-project-boards' => 'd8e135db', 625 627 'javelin-behavior-project-create' => '065227cc', 626 - 'javelin-behavior-refresh-csrf' => 'c4b31646', 628 + 'javelin-behavior-refresh-csrf' => '7814b593', 627 629 'javelin-behavior-releeph-preview-branch' => '9eb2cedb', 628 630 'javelin-behavior-releeph-request-state-change' => 'd259e7c9', 629 631 'javelin-behavior-releeph-request-typeahead' => 'cd9e7094', ··· 636 638 'javelin-behavior-test-payment-form' => 'b3e5ee60', 637 639 'javelin-behavior-toggle-class' => 'a82a7769', 638 640 'javelin-behavior-view-placeholder' => '2fa810fc', 639 - 'javelin-behavior-workflow' => 'fee00761', 641 + 'javelin-behavior-workflow' => '0a3f3021', 640 642 'javelin-color' => '7e41274a', 641 643 'javelin-cookie' => '6b3dcf44', 642 644 'javelin-dom' => '07d99a3d', ··· 652 654 'javelin-reactor-dom' => 'b6d401d6', 653 655 'javelin-reactor-node-calmer' => '76f4ebed', 654 656 'javelin-reactornode' => 'b4c30592', 655 - 'javelin-request' => '23f9bb8d', 657 + 'javelin-request' => '7bad574b', 656 658 'javelin-resource' => '356de121', 659 + 'javelin-routable' => 'b3e7d692', 660 + 'javelin-router' => '29274e2b', 657 661 'javelin-stratcom' => 'c293f7b9', 658 662 'javelin-tokenizer' => 'e7c21fb3', 659 663 'javelin-typeahead' => 'c54eeefb', ··· 671 675 'javelin-view-interpreter' => '0c33c1a0', 672 676 'javelin-view-renderer' => '6c2b09a2', 673 677 'javelin-view-visitor' => 'efe49472', 674 - 'javelin-workflow' => 'ff8091f7', 678 + 'javelin-workflow' => '09b15cf1', 675 679 'lightbox-attachment-css' => '7acac05d', 676 680 'maniphest-batch-editor' => '8f380ebc', 677 681 'maniphest-report-css' => '6fc16517', ··· 689 693 'phabricator-busy' => '6453c869', 690 694 'phabricator-chatlog-css' => '852140ff', 691 695 'phabricator-content-source-view-css' => '4b8b05d4', 692 - 'phabricator-core-css' => 'ef1b7892', 696 + 'phabricator-core-css' => '40151074', 693 697 'phabricator-countdown-css' => '86b7b0a0', 694 698 'phabricator-crumbs-view-css' => '0222cbe0', 695 699 'phabricator-drag-and-drop-file-upload' => 'ae6abfba', ··· 717 721 'phabricator-remarkup-css' => '80c3a48c', 718 722 'phabricator-search-results-css' => 'f240504c', 719 723 'phabricator-settings-css' => 'ea8f5915', 720 - 'phabricator-shaped-request' => 'dfa181a4', 724 + 'phabricator-shaped-request' => '7cbe244b', 721 725 'phabricator-side-menu-view-css' => '503699d0', 722 726 'phabricator-slowvote-css' => '266df6a1', 723 727 'phabricator-source-code-view-css' => '62a99814', ··· 879 883 array( 880 884 0 => 'javelin-install', 881 885 ), 886 + '09b15cf1' => 887 + array( 888 + 0 => 'javelin-stratcom', 889 + 1 => 'javelin-request', 890 + 2 => 'javelin-dom', 891 + 3 => 'javelin-vector', 892 + 4 => 'javelin-install', 893 + 5 => 'javelin-util', 894 + 6 => 'javelin-mask', 895 + 7 => 'javelin-uri', 896 + 8 => 'javelin-routable', 897 + ), 898 + '0a3f3021' => 899 + array( 900 + 0 => 'javelin-behavior', 901 + 1 => 'javelin-stratcom', 902 + 2 => 'javelin-workflow', 903 + 3 => 'javelin-dom', 904 + 4 => 'javelin-router', 905 + ), 906 + '0a6c2de6' => 907 + array( 908 + 0 => 'javelin-behavior', 909 + 1 => 'javelin-aphlict', 910 + 2 => 'javelin-stratcom', 911 + 3 => 'javelin-request', 912 + 4 => 'javelin-uri', 913 + 5 => 'javelin-dom', 914 + 6 => 'javelin-json', 915 + 7 => 'javelin-router', 916 + 8 => 'phabricator-notification', 917 + ), 882 918 '0c33c1a0' => 883 919 array( 884 920 0 => 'javelin-view', ··· 990 1026 5 => 'javelin-json', 991 1027 6 => 'phabricator-prefab', 992 1028 ), 993 - '23f9bb8d' => 994 - array( 995 - 0 => 'javelin-install', 996 - 1 => 'javelin-stratcom', 997 - 2 => 'javelin-util', 998 - 3 => 'javelin-behavior', 999 - 4 => 'javelin-json', 1000 - 5 => 'javelin-dom', 1001 - 6 => 'javelin-resource', 1002 - ), 1003 1029 '263aeb8c' => 1004 1030 array( 1005 1031 0 => 'javelin-behavior', ··· 1011 1037 6 => 'javelin-typeahead', 1012 1038 7 => 'javelin-typeahead-preloaded-source', 1013 1039 8 => 'javelin-json', 1040 + ), 1041 + '29274e2b' => 1042 + array( 1043 + 0 => 'javelin-install', 1044 + 1 => 'javelin-util', 1014 1045 ), 1015 1046 '2a2dba85' => 1016 1047 array( ··· 1223 1254 2 => 'javelin-util', 1224 1255 3 => 'phabricator-shaped-request', 1225 1256 ), 1257 + '7319e029' => 1258 + array( 1259 + 0 => 'javelin-behavior', 1260 + 1 => 'javelin-dom', 1261 + ), 1226 1262 '62e18640' => 1227 1263 array( 1228 1264 0 => 'javelin-install', ··· 1258 1294 1 => 'javelin-stratcom', 1259 1295 2 => 'javelin-dom', 1260 1296 ), 1261 - '7319e029' => 1262 - array( 1263 - 0 => 'javelin-behavior', 1264 - 1 => 'javelin-dom', 1265 - ), 1266 1297 '75903ee1' => 1267 1298 array( 1268 1299 0 => 'javelin-behavior', ··· 1281 1312 0 => 'javelin-install', 1282 1313 1 => 'javelin-util', 1283 1314 ), 1315 + '7814b593' => 1316 + array( 1317 + 0 => 'javelin-request', 1318 + 1 => 'javelin-behavior', 1319 + 2 => 'javelin-dom', 1320 + 3 => 'javelin-router', 1321 + 4 => 'javelin-util', 1322 + 5 => 'phabricator-busy', 1323 + ), 1284 1324 '79473b62' => 1285 1325 array( 1286 1326 0 => 'javelin-install', ··· 1295 1335 0 => 'javelin-install', 1296 1336 1 => 'javelin-dom', 1297 1337 2 => 'javelin-reactor-dom', 1338 + ), 1339 + '7bad574b' => 1340 + array( 1341 + 0 => 'javelin-install', 1342 + 1 => 'javelin-stratcom', 1343 + 2 => 'javelin-util', 1344 + 3 => 'javelin-behavior', 1345 + 4 => 'javelin-json', 1346 + 5 => 'javelin-dom', 1347 + 6 => 'javelin-resource', 1348 + 7 => 'javelin-routable', 1298 1349 ), 1299 1350 '7c273581' => 1300 1351 array( ··· 1302 1353 1 => 'javelin-stratcom', 1303 1354 2 => 'javelin-dom', 1304 1355 ), 1356 + '7cbe244b' => 1357 + array( 1358 + 0 => 'javelin-install', 1359 + 1 => 'javelin-util', 1360 + 2 => 'javelin-request', 1361 + 3 => 'javelin-router', 1362 + ), 1305 1363 '7e41274a' => 1306 1364 array( 1307 1365 0 => 'javelin-install', ··· 1344 1402 0 => 'javelin-install', 1345 1403 1 => 'javelin-dom', 1346 1404 2 => 'javelin-reactor-dom', 1347 - ), 1348 - '845731b8' => 1349 - array( 1350 - 0 => 'javelin-behavior', 1351 - 1 => 'javelin-aphlict', 1352 - 2 => 'javelin-stratcom', 1353 - 3 => 'javelin-request', 1354 - 4 => 'javelin-uri', 1355 - 5 => 'javelin-dom', 1356 - 6 => 'javelin-json', 1357 - 7 => 'phabricator-notification', 1358 1405 ), 1359 1406 '84845b5b' => 1360 1407 array( ··· 1555 1602 1 => 'javelin-dom', 1556 1603 2 => 'phortune-credit-card-form', 1557 1604 ), 1605 + 'b3e7d692' => 1606 + array( 1607 + 0 => 'javelin-install', 1608 + ), 1558 1609 'b3ec3cfc' => 1559 1610 array( 1560 1611 0 => 'javelin-install', ··· 1683 1734 0 => 'javelin-behavior', 1684 1735 1 => 'javelin-dom', 1685 1736 ), 1686 - 'c4b31646' => 1687 - array( 1688 - 0 => 'javelin-request', 1689 - 1 => 'javelin-behavior', 1690 - 2 => 'javelin-dom', 1691 - 3 => 'phabricator-busy', 1692 - ), 1693 1737 'c51a6616' => 1694 1738 array( 1695 1739 0 => 'phabricator-notification', ··· 1743 1787 array( 1744 1788 0 => 'javelin-install', 1745 1789 1 => 'javelin-typeahead-source', 1746 - ), 1747 - 'ce0c217a' => 1748 - array( 1749 - 0 => 'javelin-behavior', 1750 - 1 => 'javelin-workflow', 1751 - 2 => 'javelin-util', 1752 - 3 => 'javelin-dom', 1753 - 4 => 'javelin-stratcom', 1754 - 5 => 'javelin-behavior-device', 1755 - 6 => 'javelin-vector', 1756 - 7 => 'phabricator-tooltip', 1757 1790 ), 1758 1791 'cf656c84' => 1759 1792 array( ··· 1848 1881 1 => 'javelin-dom', 1849 1882 2 => 'phabricator-prefab', 1850 1883 ), 1851 - 'dfa181a4' => 1884 + 'dfdf9f34' => 1852 1885 array( 1853 - 0 => 'javelin-install', 1854 - 1 => 'javelin-util', 1855 - 2 => 'javelin-request', 1886 + 0 => 'javelin-behavior', 1887 + 1 => 'javelin-workflow', 1888 + 2 => 'javelin-util', 1889 + 3 => 'javelin-dom', 1890 + 4 => 'javelin-stratcom', 1891 + 5 => 'javelin-behavior-device', 1892 + 6 => 'javelin-vector', 1893 + 7 => 'javelin-router', 1894 + 8 => 'phabricator-tooltip', 1856 1895 ), 1857 1896 'e1ff79b1' => 1858 1897 array( ··· 1986 2025 3 => 'phabricator-prefab', 1987 2026 4 => 'multirow-row-manager', 1988 2027 5 => 'javelin-json', 1989 - ), 1990 - 'fee00761' => 1991 - array( 1992 - 0 => 'javelin-behavior', 1993 - 1 => 'javelin-stratcom', 1994 - 2 => 'javelin-workflow', 1995 - 3 => 'javelin-dom', 1996 - ), 1997 - 'ff8091f7' => 1998 - array( 1999 - 0 => 'javelin-stratcom', 2000 - 1 => 'javelin-request', 2001 - 2 => 'javelin-dom', 2002 - 3 => 'javelin-vector', 2003 - 4 => 'javelin-install', 2004 - 5 => 'javelin-util', 2005 - 6 => 'javelin-mask', 2006 - 7 => 'javelin-uri', 2007 2028 ), 2008 2029 28497740 => 2009 2030 array(
+18
webroot/rsrc/css/core/core.css
··· 159 159 background: #990066; 160 160 opacity: 0.25; 161 161 } 162 + 163 + .routing-bar { 164 + position: fixed; 165 + top: 0; 166 + width: 100%; 167 + height: 2px; 168 + background: {$darkbluetext}; 169 + z-index: 80; 170 + box-shadow: 0 2px 1px rgba(0, 128, 255, 0.25); 171 + } 172 + 173 + .routing-progress { 174 + position: fixed; 175 + top: 0; 176 + left: 0; 177 + height: 2px; 178 + background: {$sky}; 179 + }
+13
webroot/rsrc/externals/javelin/lib/Request.js
··· 6 6 * javelin-json 7 7 * javelin-dom 8 8 * javelin-resource 9 + * javelin-routable 9 10 * @provides javelin-request 10 11 * @javelin 11 12 */ ··· 67 68 this._getSameOriginTransport(); 68 69 } 69 70 return this._transport; 71 + }, 72 + 73 + getRoutable: function() { 74 + var routable = new JX.Routable(); 75 + routable.listen('start', JX.bind(this, function() { 76 + // Pass the event to allow other listeners to "start" to configure this 77 + // request before it fires. 78 + JX.Stratcom.pass(JX.Stratcom.context()); 79 + this.send(); 80 + })); 81 + this.listen('finally', JX.bind(routable, routable.done)); 82 + return routable; 70 83 }, 71 84 72 85 send : function() {
+41
webroot/rsrc/externals/javelin/lib/Routable.js
··· 1 + /** 2 + * @provides javelin-routable 3 + * @requires javelin-install 4 + * @javelin 5 + */ 6 + 7 + JX.install('Routable', { 8 + 9 + construct : function() { 10 + this._id = (JX.Routable._nextID++); 11 + }, 12 + 13 + properties: { 14 + key: null, 15 + priority: 1000, 16 + type: 'default' 17 + }, 18 + 19 + events: ['start', 'done'], 20 + 21 + members: { 22 + _id: null, 23 + 24 + getID: function() { 25 + return this._id; 26 + }, 27 + 28 + start: function() { 29 + this.invoke('start'); 30 + }, 31 + 32 + done: function() { 33 + this.invoke('done'); 34 + } 35 + }, 36 + 37 + statics: { 38 + _nextID: 0 39 + } 40 + 41 + });
+115
webroot/rsrc/externals/javelin/lib/Router.js
··· 1 + /** 2 + * @provides javelin-router 3 + * @requires javelin-install 4 + * javelin-util 5 + * @javelin 6 + */ 7 + 8 + /** 9 + * Route requests. Primarily, this class provides a quality-of-service 10 + * priority queue so large numbers of background loading tasks don't block 11 + * interactive requests. 12 + */ 13 + JX.install('Router', { 14 + 15 + construct: function() { 16 + this._queue = []; 17 + }, 18 + 19 + events: ['queue', 'start', 'done'], 20 + 21 + members: { 22 + _queue: null, 23 + _active: 0, 24 + _limit: 5, 25 + 26 + queue: function(routable) { 27 + this._queue.push(routable); 28 + 29 + this.invoke('queue', routable); 30 + this._update(); 31 + }, 32 + 33 + getRoutableByKey: function(key) { 34 + for (var ii = 0; ii < this._queue.length; ii++) { 35 + if (this._queue[ii].getKey() == key) { 36 + return this._queue[ii]; 37 + } 38 + } 39 + return null; 40 + }, 41 + 42 + /** 43 + * Start new requests if we have slots free for them. 44 + */ 45 + _update: function() { 46 + var active = this._active; 47 + var limit = this._limit; 48 + 49 + if (active >= limit) { 50 + // If we're already at the request limit, we can't add any more 51 + // requests. 52 + return; 53 + } 54 + 55 + // If we only have one free slot, we reserve it for a request with 56 + // at least priority 1000. 57 + var minimum; 58 + if ((active + 1) == limit) { 59 + minimum = 1000; 60 + } else { 61 + minimum = 0; 62 + } 63 + 64 + var idx = this._getNextRoutable(minimum); 65 + if (idx === null) { 66 + return; 67 + } 68 + 69 + var routable = this._queue[idx]; 70 + this._queue.splice(idx, 1); 71 + 72 + 73 + routable.listen('done', JX.bind(this, this._done, routable)); 74 + 75 + this._active++; 76 + routable.start(); 77 + this.invoke('start', routable); 78 + 79 + this._update(); 80 + }, 81 + 82 + _done: function(routable) { 83 + this._active--; 84 + this.invoke('done', routable); 85 + 86 + this._update(); 87 + }, 88 + 89 + _getNextRoutable: function(minimum) { 90 + var best = (minimum - 1); 91 + 92 + var routable = null; 93 + for (var ii = 0; ii < this._queue.length; ii++) { 94 + var priority = this._queue[ii].getPriority(); 95 + if (priority > best) { 96 + best = priority; 97 + routable = ii; 98 + } 99 + } 100 + 101 + return routable; 102 + } 103 + 104 + }, 105 + 106 + statics: { 107 + _instance: null, 108 + getInstance: function() { 109 + if (!JX.Router._instance) { 110 + JX.Router._instance = new JX.Router(); 111 + } 112 + return JX.Router._instance; 113 + } 114 + } 115 + });
+13
webroot/rsrc/externals/javelin/lib/Workflow.js
··· 7 7 * javelin-util 8 8 * javelin-mask 9 9 * javelin-uri 10 + * javelin-routable 10 11 * @provides javelin-workflow 11 12 * @javelin 12 13 */ ··· 257 258 // event instead. 258 259 })); 259 260 r.send(); 261 + }, 262 + 263 + getRoutable: function() { 264 + var routable = new JX.Routable(); 265 + routable.listen('start', JX.bind(this, function() { 266 + // Pass the event to allow other listeners to "start" to configure this 267 + // workflow before it fires. 268 + JX.Stratcom.pass(JX.Stratcom.context()); 269 + this.start(); 270 + })); 271 + this.listen('finally', JX.bind(routable, routable.done)); 272 + return routable; 260 273 }, 261 274 262 275 setData : function(dictionary) {
+9 -2
webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
··· 7 7 * javelin-uri 8 8 * javelin-dom 9 9 * javelin-json 10 + * javelin-router 10 11 * phabricator-notification 11 12 */ 12 13 ··· 24 25 // a request to Phabricator to get notification details. 25 26 function onaphlictmessage(type, message) { 26 27 if (type == 'receive') { 27 - var request = new JX.Request('/notification/individual/', onnotification) 28 + var routable = new JX.Request('/notification/individual/', onnotification) 28 29 .addData({key: message.key}) 29 - .send(); 30 + .getRoutable(); 31 + 32 + routable 33 + .setType('notification') 34 + .setPriority(250); 35 + 36 + JX.Router.getInstance().queue(routable); 30 37 } else if (__DEV__) { 31 38 if (config.debug) { 32 39 var details = message ? JX.JSON.stringify(message) : '';
+39 -10
webroot/rsrc/js/application/differential/behavior-populate.js
··· 7 7 * javelin-stratcom 8 8 * javelin-behavior-device 9 9 * javelin-vector 10 + * javelin-router 10 11 * phabricator-tooltip 11 12 */ 12 13 ··· 74 75 // TODO: Once 1up works better, figure out when to show it. 75 76 renderer = '2up'; 76 77 78 + var get_key = function(id) { 79 + return 'differential-populate.' + id; 80 + }; 81 + 82 + var load = function(id, data) { 83 + var routable = new JX.Workflow(config.uri, data) 84 + .setHandler(JX.bind(null, onresponse, id)) 85 + .getRoutable(); 86 + 87 + routable 88 + .setPriority(500) 89 + .setType('content') 90 + .setKey(get_key(id)); 91 + 92 + JX.Router.getInstance().queue(routable); 93 + 94 + return routable; 95 + }; 96 + 77 97 for (var k in config.registry) { 78 98 var data = { 79 99 ref : config.registry[k], ··· 81 101 renderer: renderer 82 102 }; 83 103 84 - new JX.Workflow(config.uri, data) 85 - .setHandler(JX.bind(null, onresponse, k)) 86 - .start(); 104 + load(k, data); 87 105 } 88 106 89 107 var highlighted = null; ··· 104 122 JX.DOM.setContent( 105 123 diff, 106 124 JX.$H('<div class="differential-loading">Loading...</div>')); 107 - var data = { 108 - ref : meta.ref, 109 - whitespace : config.whitespace 110 - }; 111 - new JX.Workflow(config.uri, data) 112 - .setHandler(JX.bind(null, onresponse, meta.id)) 113 - .start(); 125 + 126 + // When a user explicitly clicks "Load" (or clicks a link in the table 127 + // of contents) prioritize this request if it already exists. If it 128 + // doesn't, make a new high-priority request. 129 + 130 + var key = get_key(meta.id); 131 + var routable = JX.Router.getInstance().getRoutableByKey(key); 132 + 133 + if (!routable) { 134 + var data = { 135 + ref : meta.ref, 136 + whitespace : config.whitespace 137 + }; 138 + 139 + routable = load(meta.id, data); 140 + } 141 + 142 + routable.setPriority(2000); 114 143 } 115 144 if (meta.kill) { 116 145 e.kill();
+9 -1
webroot/rsrc/js/core/ShapedRequest.js
··· 2 2 * @requires javelin-install 3 3 * javelin-util 4 4 * javelin-request 5 + * javelin-router 5 6 * @provides phabricator-shaped-request 6 7 * @javelin 7 8 */ ··· 63 64 })); 64 65 this._request.setData(data); 65 66 this._request.setTimeout(this.getRequestTimeout()); 66 - this._request.send(); 67 + 68 + var routable = this._request.getRoutable(); 69 + 70 + routable 71 + .setType('draft') 72 + .setPriority(750); 73 + 74 + JX.Router.getInstance().queue(routable); 67 75 } else { 68 76 this._defer = setTimeout( 69 77 JX.bind(this, this.trigger),
+91 -3
webroot/rsrc/js/core/behavior-refresh-csrf.js
··· 3 3 * @requires javelin-request 4 4 * javelin-behavior 5 5 * javelin-dom 6 + * javelin-router 7 + * javelin-util 6 8 * phabricator-busy 7 9 */ 8 10 ··· 49 51 // Additionally, add the CSRF token as an HTTP header to every AJAX request. 50 52 JX.Request.listen('open', function(r) { 51 53 r.getTransport().setRequestHeader(config.header, current_token); 52 - JX.Busy.start(); 53 54 }); 54 55 55 - JX.Request.listen('finally', function(r) { 56 - JX.Busy.done(); 56 + // Does this type of routable show the "Busy" spinner? 57 + var is_busy_type = function(type) { 58 + switch (type) { 59 + case 'workflow': 60 + return true; 61 + } 62 + 63 + return false; 64 + }; 65 + 66 + // Does this type of routable show the "Loading" bar? 67 + var is_bar_type = function(type) { 68 + switch (type) { 69 + case 'content': 70 + return true; 71 + } 72 + 73 + return false; 74 + }; 75 + 76 + 77 + var queue = {}; 78 + var count = 0; 79 + var node; 80 + 81 + // Redraw the loading bar. 82 + var redraw_bar = function() { 83 + // If all requests have completed, hide the bar after a moment. 84 + if (!count) { 85 + if (node) { 86 + node.firstChild.style.width = '100%'; 87 + setTimeout(JX.bind(null, JX.DOM.remove, node), 500); 88 + } 89 + node = null; 90 + queue = {}; 91 + return; 92 + } 93 + 94 + // If we don't have the bar yet, draw it. 95 + if (!node) { 96 + node = JX.$N('div', {className: 'routing-bar'}); 97 + document.body.appendChild(node); 98 + node.appendChild(JX.$N('div', {className: 'routing-progress'})); 99 + } 100 + 101 + // Update the bar progress. 102 + var done = 0; 103 + var total = 0; 104 + for (var k in queue) { 105 + total++; 106 + if (queue[k]) { 107 + done++; 108 + } 109 + } 110 + 111 + node.firstChild.style.width = (100 * (done / total)) + '%'; 112 + }; 113 + 114 + 115 + // Listen for queued requests. 116 + JX.Router.listen('queue', function(r) { 117 + var type = r.getType(); 118 + 119 + if (is_bar_type(type)) { 120 + queue[r.getID()] = false; 121 + count++; 122 + redraw_bar(); 123 + } 124 + 125 + if (is_busy_type(r.getType())) { 126 + JX.Busy.start(); 127 + } 57 128 }); 129 + 130 + 131 + // Listen for completed requests. 132 + JX.Router.listen('done', function(r) { 133 + var type = r.getType(); 134 + 135 + if (is_bar_type(type)) { 136 + queue[r.getID()] = true; 137 + count--; 138 + redraw_bar(); 139 + } 140 + 141 + if (is_busy_type(r.getType())) { 142 + JX.Busy.done(); 143 + } 144 + }); 145 + 58 146 59 147 });
+14 -2
webroot/rsrc/js/core/behavior-workflow.js
··· 4 4 * javelin-stratcom 5 5 * javelin-workflow 6 6 * javelin-dom 7 + * javelin-router 7 8 */ 8 9 9 10 JX.behavior('workflow', function() { 10 11 12 + // Queue a workflow at elevated priority. The user just clicked or submitted 13 + // something, so service this before loading background content. 14 + var queue = function(workflow) { 15 + var routable = workflow.getRoutable() 16 + .setPriority(2000) 17 + .setType('workflow'); 18 + 19 + JX.Router.getInstance().queue(routable); 20 + }; 21 + 11 22 // If a user clicks an alternate submit button, make sure it gets marshalled 12 23 // into the workflow. 13 24 JX.Stratcom.listen( ··· 40 51 // not just the <form /> itself. 41 52 42 53 e.prevent(); 43 - JX.Workflow.newFromForm(e.getNode('tag:form'), extra).start(); 54 + 55 + queue(JX.Workflow.newFromForm(e.getNode('tag:form'), extra)); 44 56 }); 45 57 46 58 JX.Stratcom.listen( ··· 70 82 } 71 83 72 84 e.prevent(); 73 - JX.Workflow.newFromLink(e.getNode('tag:a')).start(); 85 + queue(JX.Workflow.newFromLink(e.getNode('tag:a'))); 74 86 }); 75 87 76 88 });