2011-01-25 18:59:31 +01:00
|
|
|
<?php
|
|
|
|
|
2011-07-04 20:22:42 +02:00
|
|
|
/**
|
|
|
|
* Tracks and resolves dependencies the page declares with
|
|
|
|
* @{function:require_celerity_resource}, and then builds appropriate HTML or
|
|
|
|
* Ajax responses.
|
|
|
|
*
|
|
|
|
* @group celerity
|
|
|
|
*/
|
2011-01-25 18:59:31 +01:00
|
|
|
final class CelerityStaticResourceResponse {
|
|
|
|
|
|
|
|
private $symbols = array();
|
|
|
|
private $needsResolve = true;
|
|
|
|
private $resolved;
|
2011-01-30 01:10:05 +01:00
|
|
|
private $packaged;
|
2011-01-25 20:57:47 +01:00
|
|
|
private $metadata = array();
|
|
|
|
private $metadataBlock = 0;
|
|
|
|
private $behaviors = array();
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
private $hasRendered = array();
|
2011-01-25 20:57:47 +01:00
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
if (isset($_REQUEST['__metablock__'])) {
|
|
|
|
$this->metadataBlock = (int)$_REQUEST['__metablock__'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function addMetadata($metadata) {
|
|
|
|
$id = count($this->metadata);
|
|
|
|
$this->metadata[$id] = $metadata;
|
|
|
|
return $this->metadataBlock.'_'.$id;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getMetadataBlock() {
|
|
|
|
return $this->metadataBlock;
|
|
|
|
}
|
|
|
|
|
2011-05-31 19:23:31 +02:00
|
|
|
/**
|
|
|
|
* Register a behavior for initialization. NOTE: if $config is empty,
|
|
|
|
* a behavior will execute only once even if it is initialized multiple times.
|
|
|
|
* If $config is nonempty, the behavior will be invoked once for each config.
|
|
|
|
*/
|
2011-01-25 20:57:47 +01:00
|
|
|
public function initBehavior($behavior, array $config = array()) {
|
|
|
|
$this->requireResource('javelin-behavior-'.$behavior);
|
2011-05-31 19:23:31 +02:00
|
|
|
|
|
|
|
if (empty($this->behaviors[$behavior])) {
|
|
|
|
$this->behaviors[$behavior] = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($config) {
|
|
|
|
$this->behaviors[$behavior][] = $config;
|
|
|
|
}
|
|
|
|
|
2011-01-25 20:57:47 +01:00
|
|
|
return $this;
|
|
|
|
}
|
2011-01-25 18:59:31 +01:00
|
|
|
|
|
|
|
public function requireResource($symbol) {
|
|
|
|
$this->symbols[$symbol] = true;
|
|
|
|
$this->needsResolve = true;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function resolveResources() {
|
|
|
|
if ($this->needsResolve) {
|
2014-01-01 16:46:18 +01:00
|
|
|
$map = CelerityResourceMap::getNamedInstance('phabricator');
|
2014-01-01 03:03:24 +01:00
|
|
|
|
|
|
|
$symbols = array_keys($this->symbols);
|
|
|
|
$this->packaged = $map->getPackagedNamesForSymbols($symbols);
|
|
|
|
|
2011-01-25 18:59:31 +01:00
|
|
|
$this->needsResolve = false;
|
|
|
|
}
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-01-01 16:46:18 +01:00
|
|
|
public function renderSingleResource($symbol, $source_name) {
|
|
|
|
$map = CelerityResourceMap::getNamedInstance($source_name);
|
2014-01-01 03:03:24 +01:00
|
|
|
$packaged = $map->getPackagedNamesForSymbols(array($symbol));
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
return $this->renderPackagedResources($packaged);
|
|
|
|
}
|
|
|
|
|
2011-01-25 18:59:31 +01:00
|
|
|
public function renderResourcesOfType($type) {
|
|
|
|
$this->resolveResources();
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
|
|
|
|
$resources = array();
|
2014-01-01 03:03:24 +01:00
|
|
|
foreach ($this->packaged as $name) {
|
|
|
|
$resource_type = CelerityResourceTransformer::getResourceType($name);
|
|
|
|
if ($resource_type == $type) {
|
|
|
|
$resources[] = $name;
|
2011-01-25 18:59:31 +01:00
|
|
|
}
|
|
|
|
}
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
|
|
|
|
return $this->renderPackagedResources($resources);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function renderPackagedResources(array $resources) {
|
|
|
|
$output = array();
|
2014-01-01 03:03:24 +01:00
|
|
|
foreach ($resources as $name) {
|
|
|
|
if (isset($this->hasRendered[$name])) {
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
continue;
|
|
|
|
}
|
2014-01-01 03:03:24 +01:00
|
|
|
$this->hasRendered[$name] = true;
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
|
2014-01-01 03:03:24 +01:00
|
|
|
$output[] = $this->renderResource($name);
|
2013-02-13 23:50:15 +01:00
|
|
|
$output[] = "\n";
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
}
|
2013-02-13 23:50:15 +01:00
|
|
|
return phutil_implode_html('', $output);
|
2011-01-25 18:59:31 +01:00
|
|
|
}
|
|
|
|
|
2014-01-01 03:03:24 +01:00
|
|
|
private function renderResource($name) {
|
|
|
|
$uri = $this->getURI($name);
|
|
|
|
$type = CelerityResourceTransformer::getResourceType($name);
|
|
|
|
switch ($type) {
|
2011-01-25 18:59:31 +01:00
|
|
|
case 'css':
|
2013-01-18 03:39:02 +01:00
|
|
|
return phutil_tag(
|
2012-10-16 18:44:53 +02:00
|
|
|
'link',
|
|
|
|
array(
|
|
|
|
'rel' => 'stylesheet',
|
|
|
|
'type' => 'text/css',
|
|
|
|
'href' => $uri,
|
|
|
|
));
|
2011-01-25 18:59:31 +01:00
|
|
|
case 'js':
|
2013-01-18 03:57:09 +01:00
|
|
|
return phutil_tag(
|
2012-10-16 18:44:53 +02:00
|
|
|
'script',
|
|
|
|
array(
|
|
|
|
'type' => 'text/javascript',
|
|
|
|
'src' => $uri,
|
|
|
|
),
|
|
|
|
'');
|
2011-01-25 18:59:31 +01:00
|
|
|
}
|
|
|
|
throw new Exception("Unable to render resource.");
|
|
|
|
}
|
|
|
|
|
2011-01-25 20:57:47 +01:00
|
|
|
public function renderHTMLFooter() {
|
|
|
|
$data = array();
|
|
|
|
if ($this->metadata) {
|
2013-01-29 03:11:27 +01:00
|
|
|
$json_metadata = AphrontResponse::encodeJSONForHTTPResponse(
|
|
|
|
$this->metadata);
|
2011-01-25 20:57:47 +01:00
|
|
|
$this->metadata = array();
|
|
|
|
} else {
|
|
|
|
$json_metadata = '{}';
|
|
|
|
}
|
|
|
|
// Even if there is no metadata on the page, Javelin uses the mergeData()
|
|
|
|
// call to start dispatching the event queue.
|
|
|
|
$data[] = 'JX.Stratcom.mergeData('.$this->metadataBlock.', '.
|
|
|
|
$json_metadata.');';
|
|
|
|
|
|
|
|
$onload = array();
|
|
|
|
if ($this->behaviors) {
|
2011-12-07 22:49:56 +01:00
|
|
|
$behaviors = $this->behaviors;
|
2011-01-25 20:57:47 +01:00
|
|
|
$this->behaviors = array();
|
2011-12-07 22:49:56 +01:00
|
|
|
|
|
|
|
$higher_priority_names = array(
|
|
|
|
'refresh-csrf',
|
2011-12-23 02:59:00 +01:00
|
|
|
'aphront-basic-tokenizer',
|
2013-02-09 22:29:47 +01:00
|
|
|
'dark-console',
|
2013-03-01 03:58:00 +01:00
|
|
|
'history-install',
|
2011-12-07 22:49:56 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$higher_priority_behaviors = array_select_keys(
|
|
|
|
$behaviors,
|
|
|
|
$higher_priority_names);
|
|
|
|
|
|
|
|
foreach ($higher_priority_names as $name) {
|
|
|
|
unset($behaviors[$name]);
|
|
|
|
}
|
|
|
|
|
|
|
|
$behavior_groups = array(
|
|
|
|
$higher_priority_behaviors,
|
|
|
|
$behaviors);
|
|
|
|
|
|
|
|
foreach ($behavior_groups as $group) {
|
|
|
|
if (!$group) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-01-29 03:11:27 +01:00
|
|
|
$group_json = AphrontResponse::encodeJSONForHTTPResponse(
|
|
|
|
$group);
|
|
|
|
$onload[] = 'JX.initBehaviors('.$group_json.')';
|
2011-12-07 22:49:56 +01:00
|
|
|
}
|
2011-01-25 20:57:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($onload) {
|
|
|
|
foreach ($onload as $func) {
|
|
|
|
$data[] = 'JX.onload(function(){'.$func.'});';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($data) {
|
|
|
|
$data = implode("\n", $data);
|
2013-04-21 02:55:47 +02:00
|
|
|
return self::renderInlineScript($data);
|
2011-01-25 20:57:47 +01:00
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-21 02:55:47 +02:00
|
|
|
public static function renderInlineScript($data) {
|
|
|
|
if (stripos($data, '</script>') !== false) {
|
|
|
|
throw new Exception(
|
|
|
|
'Literal </script> is not allowed inside inline script.');
|
|
|
|
}
|
2013-10-16 18:28:37 +02:00
|
|
|
if (strpos($data, '<!') !== false) {
|
|
|
|
throw new Exception('Literal <! is not allowed inside inline script.');
|
|
|
|
}
|
2013-11-09 19:48:19 +01:00
|
|
|
// We don't use <![CDATA[ ]]> because it is ignored by HTML parsers. We
|
|
|
|
// would need to send the document with XHTML content type.
|
|
|
|
return phutil_tag(
|
|
|
|
'script',
|
|
|
|
array('type' => 'text/javascript'),
|
2013-04-21 02:55:47 +02:00
|
|
|
phutil_safe_html($data));
|
|
|
|
}
|
|
|
|
|
2012-02-14 23:51:51 +01:00
|
|
|
public function buildAjaxResponse($payload, $error = null) {
|
2011-01-25 20:57:47 +01:00
|
|
|
$response = array(
|
|
|
|
'error' => $error,
|
|
|
|
'payload' => $payload,
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($this->metadata) {
|
|
|
|
$response['javelin_metadata'] = $this->metadata;
|
|
|
|
$this->metadata = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->behaviors) {
|
|
|
|
$response['javelin_behaviors'] = $this->behaviors;
|
|
|
|
$this->behaviors = array();
|
|
|
|
}
|
|
|
|
|
2012-11-21 17:37:01 +01:00
|
|
|
$this->resolveResources();
|
|
|
|
$resources = array();
|
|
|
|
foreach ($this->packaged as $resource) {
|
Add timestamps to development-mode static resource URIs
Summary:
When a developer changes CSS, it is normally sufficient to reload the page to get changes to show up, because browsers revalidate resources on reload.
However, if you reload the page and then an Ajax request adds new CSS to the page, this CSS does not trigger revalidation. The developer must currently clear their cache or re-run `scripts/celerity_mapper.php webroot`, to get this request to skip cache. We rarely use CSS over Ajax right now, so this hasn't cropped up much, but Conpherence does use this and clearing the resource is a big pain.
This seems to work fine normally, but I'm worried it might break some of the extra-celerity-resources stuff Facebook is doing.
Test Plan: In development mode, changed `conpherence/message-pane.css` and saw changes reflected on reload. Verified normal page loads do not cause additional HTTP requests. This change has no effect in production mode.
Reviewers: edward, vrana, btrahan
Reviewed By: vrana
CC: aran
Maniphest Tasks: T2428
Differential Revision: https://secure.phabricator.com/D4902
2013-02-11 20:06:41 +01:00
|
|
|
$resources[] = $this->getURI($resource);
|
2012-11-21 17:37:01 +01:00
|
|
|
}
|
|
|
|
if ($resources) {
|
|
|
|
$response['javelin_resources'] = $resources;
|
|
|
|
}
|
|
|
|
|
2011-01-25 20:57:47 +01:00
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
2014-01-01 03:03:24 +01:00
|
|
|
private function getURI($name) {
|
2014-01-01 16:46:18 +01:00
|
|
|
$map = CelerityResourceMap::getNamedInstance('phabricator');
|
2014-01-01 03:03:24 +01:00
|
|
|
$uri = $map->getURIForName($name);
|
Add timestamps to development-mode static resource URIs
Summary:
When a developer changes CSS, it is normally sufficient to reload the page to get changes to show up, because browsers revalidate resources on reload.
However, if you reload the page and then an Ajax request adds new CSS to the page, this CSS does not trigger revalidation. The developer must currently clear their cache or re-run `scripts/celerity_mapper.php webroot`, to get this request to skip cache. We rarely use CSS over Ajax right now, so this hasn't cropped up much, but Conpherence does use this and clearing the resource is a big pain.
This seems to work fine normally, but I'm worried it might break some of the extra-celerity-resources stuff Facebook is doing.
Test Plan: In development mode, changed `conpherence/message-pane.css` and saw changes reflected on reload. Verified normal page loads do not cause additional HTTP requests. This change has no effect in production mode.
Reviewers: edward, vrana, btrahan
Reviewed By: vrana
CC: aran
Maniphest Tasks: T2428
Differential Revision: https://secure.phabricator.com/D4902
2013-02-11 20:06:41 +01:00
|
|
|
|
|
|
|
// In developer mode, we dump file modification times into the URI. When a
|
|
|
|
// page is reloaded in the browser, any resources brought in by Ajax calls
|
|
|
|
// do not trigger revalidation, so without this it's very difficult to get
|
|
|
|
// changes to Ajaxed-in CSS to work (you must clear your cache or rerun
|
|
|
|
// the map script). In production, we can assume the map script gets run
|
|
|
|
// after changes, and safely skip this.
|
|
|
|
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
|
2014-01-01 03:03:24 +01:00
|
|
|
$mtime = $map->getModifiedTimeForName($name);
|
Add timestamps to development-mode static resource URIs
Summary:
When a developer changes CSS, it is normally sufficient to reload the page to get changes to show up, because browsers revalidate resources on reload.
However, if you reload the page and then an Ajax request adds new CSS to the page, this CSS does not trigger revalidation. The developer must currently clear their cache or re-run `scripts/celerity_mapper.php webroot`, to get this request to skip cache. We rarely use CSS over Ajax right now, so this hasn't cropped up much, but Conpherence does use this and clearing the resource is a big pain.
This seems to work fine normally, but I'm worried it might break some of the extra-celerity-resources stuff Facebook is doing.
Test Plan: In development mode, changed `conpherence/message-pane.css` and saw changes reflected on reload. Verified normal page loads do not cause additional HTTP requests. This change has no effect in production mode.
Reviewers: edward, vrana, btrahan
Reviewed By: vrana
CC: aran
Maniphest Tasks: T2428
Differential Revision: https://secure.phabricator.com/D4902
2013-02-11 20:06:41 +01:00
|
|
|
$uri = preg_replace('@^/res/@', '/res/'.$mtime.'T/', $uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
return PhabricatorEnv::getCDNURI($uri);
|
|
|
|
}
|
|
|
|
|
2011-01-25 18:59:31 +01:00
|
|
|
}
|