Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2896034
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
29 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/docs/flavortext/soon_static_resources.diviner b/src/docs/flavortext/soon_static_resources.diviner
index 7b8139bc73..1a663fea26 100644
--- a/src/docs/flavortext/soon_static_resources.diviner
+++ b/src/docs/flavortext/soon_static_resources.diviner
@@ -1,123 +1,126 @@
@title Things You Should Do Soon: Static Resources
@group flavortext
-
Over time, you'll write more JS and CSS and eventually need to put systems in
place to manage it.
+This is part of @{article:Things You Should Do Soon}, which describes
+architectural problems in web applications which you should begin to consider
+before you encounter them.
+
= Manage Dependencies Automatically =
The naive way to add static resources to a page is to include them at the top
of the page, before rendering begins, by enumerating filenames. Facebook used to
work like that:
COUNTEREXAMPLE
<?php
require_js('js/base.js');
require_js('js/utils.js');
require_js('js/ajax.js');
require_js('js/dialog.js');
// ...
This was okay for a while but had become unmanageable by 2007. Because
dependencies were managed completely manually and you had to explicitly list
every file you needed in the right order, everyone copy-pasted a giant block
of this stuff into every page. The major problem this created was that each page
pulled in way too much JS, which slowed down frontend performance.
We moved to a system (called //Haste//) which declared JS dependencies in the
files using a docblock-like header:
/**
* @provides dialog
* @requires utils ajax base
*/
We annotated files manually, although theoretically you could use static
analysis instead (we couldn't realistically do that, our JS was pretty
unstructured). This allowed us to pull in the entire dependency chain of
component with one call:
require_static('dialog');
...instead of copy-pasting every dependency.
= Include When Used =
The other part of this problem was that all the resources were required at the
top of the page instead of when they were actually used. This meant two things:
- you needed to include every resource that //could ever// appear on a page;
- if you were adding something new to 2+ pages, you had a strong incentive to
put it in base.js.
So every page pulled in a bunch of silly stuff like the CAPTCHA code (because
there was one obscure workflow involving unverified users which could
theoretically show any user a CAPTCHA on any page) and every random thing anyone
had stuck in base.js.
We moved to a system where JS and CSS tags were output **after** page rendering
had run instead (they still appeared at the top of the page, they were just
prepended rather than appended before being output to the browser -- there are
some complexities here, but they are beyond the immediate scope), so
require_static() could appear anywhere in the code. Then we moved all the
require_static() calls to be proximate to their use sites (so dialog rendering
code would pull in dialog-related CSS and JS, for example, not any page which
might need a dialog), and split base.js into a bunch of smaller files.
= Packaging =
The biggest frontend performance killer in most cases is the raw number of HTTP
requests, and the biggest hammer for addressing it is to package related JS
and CSS into larger files, so you send down all the core JS code in one big file
instead of a lot of smaller ones. Once the other groundwork is in place, this is
a relatively easy change. We started with manual package definitions and
eventually moved to automatic generation based on production data.
= Caches and Serving Content =
In the simplest implementation of static resources, you write out a raw JS tag
with something like ##src="/js/base.js"##. This will break disastrously as you
scale, because clients will be running with stale versions of resources. There
are bunch of subtle problems (especially once you have a CDN), but the big one
is that if a user is browsing your site as you push/deploy, their client will
not make requests for the resources they already have in cache, so even if your
servers respond correctly to If-None-Match (ETags) and If-Modified-Since
(Expires) the site will appear completely broken to everyone who was using it
when you push a breaking change to static resources.
The best way to solve this problem is to version your resources in the URI,
so each version of a resource has a unique URI:
rsrc/af04d14/js/base.js
When you push, users will receive pages which reference the new URI so their
browsers will retrieve it.
**But**, there's a big problem, once you have a bunch of web frontends:
While you're pushing, a user may make a request which is handled by a server
running the new version of the code, which delivers a page with a new resource
URI. Their browser then makes a request for the new resource, but that request
is routed to a server which has not been pushed yet, which delivers an old
version of the resource. They now have a poisoned cache: old resource data for
a new resource URI.
You can do a lot of clever things to solve this, but the solution we chose at
Facebook was to serve resources out of a database instead of off disk. Before a
push begins, new resources are written to the database so that every server is
able to satisfy both old and new resource requests.
This also made it relatively easy to do processing steps (like stripping
comments and whitespace) in one place, and just insert a minified/processed
version of CSS and JS into the database.
= Reference Implementation: Celerity =
Some of the ideas discussed here are implemented in Phabricator's //Celerity//
system, which is essentially a simplified version of the //Haste// system used
by Facebook.
diff --git a/src/docs/technical/celerity.diviner b/src/docs/technical/celerity.diviner
new file mode 100644
index 0000000000..9c707565ce
--- /dev/null
+++ b/src/docs/technical/celerity.diviner
@@ -0,0 +1,65 @@
+@title Celerity Technical Documentation
+@group celerity
+
+Technical overview of the Celerity system.
+
+= Overview =
+
+Celerity is a static resource (CSS and JS) management system, which handles:
+
+ - Keeping track of which resources a page needs.
+ - Generating URIs for the browser to access resources.
+ - Managing dependencies between resources.
+ - Packaging resources into fewer HTTP requests for performance.
+ - Preprocessing resources (e.g., stripping comments and whitespace).
+ - Delivering resources and managing resource cache lifetimes.
+ - Interfacing with the client to manage resources.
+
+Celerity is an outgrowth of the //Haste// system at Facebook. You can find more
+information about Celerity here:
+
+ - @{article:Things You Should Do Soon: Static Resources} describes the history
+ and context of the system and the problems it solves.
+ - @{article:Adding New CSS and JS} provides a developer guide to using
+ Celerity.
+
+= Class Relationships =
+
+Celerity's primary API is @{function:require_celerity_resource}, which marks a
+resource for inclusion when a response is rendered (e.g., when the HTML page is
+generated, or when the response to an Ajax request is built). For instance, if
+you use a CSS class like "widget-view", you must ensure the appropriate CSS is
+included by calling ##require_celerity_resource('widget-view-css')## (or
+similar), at your use site.
+
+This function uses @{class:CelerityAPI} to access the active
+@{class:CelerityStaticResourceResponse} and tells it that it needs to include
+the resource later, when the response actually gets built. (This layer of
+indirection provides future-proofing against certain complex situations Facebook
+eventually encountered).
+
+When the time comes to render the response, the page renderer uses
+@{class:CelerityAPI} to access the active
+@{class:CelerityStaticResourceResponse} and requests that it render out
+appropriate references to CSS and JS resources. It uses
+@{class:CelerityResourceMap} to determine the dependencies for the requested
+resources (so you only have to explicitly include what you're actually using,
+and not all of its dependencies) and any packaging rules (so it may be able to
+generate fewer resource requests, improving performance). It then generates
+##<script />## and ##<link />## references to these resources.
+
+These references point at ##/res/## URIs, which are handled by
+@{class:CelerityResourceController}. It responds to these requests and delivers
+the relevant resources and packages, managing cache lifetimes and handling any
+neessary preprocessing. It uses @{class:CelerityResourceMap} to locate resources
+and read packaging rules.
+
+The dependency and packaging maps are generated by
+##scripts/celerity_mapper.php##, which updates
+##src/__celerity_resource_map__.php##. This file is automatically included and
+just calls @{function:celerity_register_resource_map} with a large blob of
+static data to populate @{class:CelerityResourceMap}.
+
+@{class:CelerityStaticResourceResponse} also manages some Javelin information,
+and @{function:celerity_generate_unique_node_id} uses this metadata to provide
+a better uniqueness guarantee when generating unique node IDs.
diff --git a/src/infrastructure/celerity/api/CelerityAPI.php b/src/infrastructure/celerity/api/CelerityAPI.php
index bf9064f44b..16f7b640ba 100644
--- a/src/infrastructure/celerity/api/CelerityAPI.php
+++ b/src/infrastructure/celerity/api/CelerityAPI.php
@@ -1,44 +1,36 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/**
+ * Indirection layer which provisions for a terrifying future where we need to
+ * build multiple resource responses per page.
+ *
+ * @group celerity
+ */
final class CelerityAPI {
private static $response;
public static function getStaticResourceResponse() {
if (empty(self::$response)) {
self::$response = new CelerityStaticResourceResponse();
}
return self::$response;
}
-}
-
-function require_celerity_resource($symbol) {
- $response = CelerityAPI::getStaticResourceResponse();
- $response->requireResource($symbol);
-}
-
-function celerity_generate_unique_node_id() {
- static $uniq = 0;
- $response = CelerityAPI::getStaticResourceResponse();
- $block = $response->getMetadataBlock();
-
- return 'UQ'.$block.'_'.($uniq++);
-}
-
+}
\ No newline at end of file
diff --git a/src/infrastructure/celerity/api/__init__.php b/src/infrastructure/celerity/api/__init__.php
index 762576517c..144af866b1 100644
--- a/src/infrastructure/celerity/api/__init__.php
+++ b/src/infrastructure/celerity/api/__init__.php
@@ -1,12 +1,13 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'infrastructure/celerity/response');
phutil_require_source('CelerityAPI.php');
+phutil_require_source('utils.php');
diff --git a/src/infrastructure/celerity/api/utils.php b/src/infrastructure/celerity/api/utils.php
new file mode 100644
index 0000000000..ed82dbc9ce
--- /dev/null
+++ b/src/infrastructure/celerity/api/utils.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * Copyright 2011 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Include a CSS or JS static resource by name. This function records a
+ * dependency for the current page, so when a response is generated it can be
+ * included. You can call this method from any context, and it is recommended
+ * you invoke it as close to the actual dependency as possible so that page
+ * dependencies are minimized.
+ *
+ * For more information, see @{article:Adding New CSS and JS}.
+ *
+ * @param string Name of the celerity module to include. This is whatever you
+ * annotated as "@provides" in the file.
+ * @return void
+ *
+ * @group celerity
+ */
+function require_celerity_resource($symbol) {
+ $response = CelerityAPI::getStaticResourceResponse();
+ $response->requireResource($symbol);
+}
+
+
+/**
+ * Generate a node ID which is guaranteed to be unique for the current page,
+ * even across Ajax requests. You should use this method to generate IDs for
+ * nodes which require a uniqueness guarantee.
+ *
+ * @return string A string appropriate for use as an 'id' attribute on a DOM
+ * node. It is guaranteed to be unique for the current page, even
+ * if the current request is a subsequent Ajax request.
+ *
+ * @group celerity
+ */
+function celerity_generate_unique_node_id() {
+ static $uniq = 0;
+ $response = CelerityAPI::getStaticResourceResponse();
+ $block = $response->getMetadataBlock();
+
+ return 'UQ'.$block.'_'.($uniq++);
+}
+
diff --git a/src/infrastructure/celerity/controller/CelerityResourceController.php b/src/infrastructure/celerity/controller/CelerityResourceController.php
index 2b09c7899e..0fe43c64ee 100644
--- a/src/infrastructure/celerity/controller/CelerityResourceController.php
+++ b/src/infrastructure/celerity/controller/CelerityResourceController.php
@@ -1,97 +1,104 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/**
+ * Delivers CSS and JS resources to the browser. This controller handles all
+ * ##/res/## requests, and manages caching, package construction, and resource
+ * preprocessing.
+ *
+ * @group celerity
+ */
class CelerityResourceController extends AphrontController {
private $path;
private $hash;
private $package;
public function willProcessRequest(array $data) {
$this->path = $data['path'];
$this->hash = $data['hash'];
$this->package = !empty($data['package']);
}
public function processRequest() {
$path = $this->path;
// Sanity checking to keep this from exposing anything sensitive.
$path = preg_replace('@(//|\\.\\.)@', '', $path);
$matches = null;
if (!preg_match('/\.(css|js)$/', $path, $matches)) {
throw new Exception("Only CSS and JS resources may be served.");
}
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
!PhabricatorEnv::getEnvConfig('celerity.force-disk-reads')) {
// Return a "304 Not Modified". We don't care about the value of this
// field since we never change what resource is served by a given URI.
return $this->makeResponseCacheable(new Aphront304Response());
}
$type = $matches[1];
$root = dirname(phutil_get_library_root('phabricator'));
if ($this->package) {
$map = CelerityResourceMap::getInstance();
$paths = $map->resolvePackage($this->hash);
if (!$paths) {
return new Aphront404Response();
}
try {
$data = array();
foreach ($paths as $path) {
$data[] = Filesystem::readFile($root.'/webroot/'.$path);
}
$data = implode("\n\n", $data);
} catch (Exception $ex) {
return new Aphront404Response();
}
} else {
try {
$data = Filesystem::readFile($root.'/webroot/'.$path);
} catch (Exception $ex) {
return new Aphront404Response();
}
}
$response = new AphrontFileResponse();
$response->setContent($data);
switch ($type) {
case 'css':
$response->setMimeType("text/css; charset=utf-8");
break;
case 'js':
$response->setMimeType("text/javascript; charset=utf-8");
break;
}
return $this->makeResponseCacheable($response);
}
private function makeResponseCacheable(AphrontResponse $response) {
$response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
$response->setLastModified(time());
return $response;
}
}
diff --git a/src/infrastructure/celerity/map/CelerityResourceMap.php b/src/infrastructure/celerity/map/CelerityResourceMap.php
index 2dc0be4465..59fff93f34 100644
--- a/src/infrastructure/celerity/map/CelerityResourceMap.php
+++ b/src/infrastructure/celerity/map/CelerityResourceMap.php
@@ -1,132 +1,134 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/**
+ * Interface to the static resource map, which is a graph of available
+ * resources, resource dependencies, and packaging information. You generally do
+ * not need to invoke it directly; instead, you call higher-level Celerity APIs
+ * and it uses the resource map to satisfy your requests.
+ *
+ * @group celerity
+ */
final class CelerityResourceMap {
private static $instance;
private $resourceMap;
private $packageMap;
private $reverseMap;
public static function getInstance() {
if (empty(self::$instance)) {
self::$instance = new CelerityResourceMap();
$root = phutil_get_library_root('phabricator');
$ok = include_once $root.'/__celerity_resource_map__.php';
if (!$ok) {
throw new Exception("Failed to load Celerity resource map!");
}
}
return self::$instance;
}
public function setResourceMap($resource_map) {
$this->resourceMap = $resource_map;
return $this;
}
public function resolveResources(array $symbols) {
$map = array();
foreach ($symbols as $symbol) {
if (!empty($map[$symbol])) {
continue;
}
$this->resolveResource($map, $symbol);
}
return $map;
}
private function resolveResource(array &$map, $symbol) {
if (empty($this->resourceMap[$symbol])) {
throw new Exception(
"Attempting to resolve unknown resource, '{$symbol}'.");
}
$info = $this->resourceMap[$symbol];
foreach ($info['requires'] as $requires) {
if (!empty($map[$requires])) {
continue;
}
$this->resolveResource($map, $requires);
}
$map[$symbol] = $info;
}
public function setPackageMap($package_map) {
$this->packageMap = $package_map;
}
public function packageResources(array $resolved_map) {
$packaged = array();
$handled = array();
foreach ($resolved_map as $symbol => $info) {
if (isset($handled[$symbol])) {
continue;
}
if (empty($this->packageMap['reverse'][$symbol])) {
$packaged[$symbol] = $info;
} else {
$package = $this->packageMap['reverse'][$symbol];
$package_info = $this->packageMap['packages'][$package];
$packaged[$package_info['name']] = $package_info;
foreach ($package_info['symbols'] as $symbol) {
$handled[$symbol] = true;
}
}
}
return $packaged;
}
public function resolvePackage($package_hash) {
$package = idx($this->packageMap['packages'], $package_hash);
if (!$package) {
return null;
}
$paths = array();
foreach ($package['symbols'] as $symbol) {
$paths[] = $this->resourceMap[$symbol]['disk'];
}
return $paths;
}
public function lookupSymbolInformation($symbol) {
return idx($this->resourceMap, $symbol);
}
public function lookupFileInformation($path) {
if (empty($this->reverseMap)) {
$this->reverseMap = array();
foreach ($this->resourceMap as $symbol => $data) {
$data['provides'] = $symbol;
$this->reverseMap[$data['disk']] = $data;
}
}
return idx($this->reverseMap, $path);
}
}
-
-function celerity_register_resource_map(array $map, array $package_map) {
- $instance = CelerityResourceMap::getInstance();
- $instance->setResourceMap($map);
- $instance->setPackageMap($package_map);
-}
diff --git a/src/infrastructure/celerity/map/__init__.php b/src/infrastructure/celerity/map/__init__.php
index d34da6d1be..889b007a93 100644
--- a/src/infrastructure/celerity/map/__init__.php
+++ b/src/infrastructure/celerity/map/__init__.php
@@ -1,13 +1,14 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phutil', 'moduleutils');
phutil_require_module('phutil', 'utils');
phutil_require_source('CelerityResourceMap.php');
+phutil_require_source('utils.php');
diff --git a/src/infrastructure/celerity/api/CelerityAPI.php b/src/infrastructure/celerity/map/utils.php
similarity index 50%
copy from src/infrastructure/celerity/api/CelerityAPI.php
copy to src/infrastructure/celerity/map/utils.php
index bf9064f44b..93c5e05653 100644
--- a/src/infrastructure/celerity/api/CelerityAPI.php
+++ b/src/infrastructure/celerity/map/utils.php
@@ -1,44 +1,29 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-final class CelerityAPI {
-
- private static $response;
-
- public static function getStaticResourceResponse() {
- if (empty(self::$response)) {
- self::$response = new CelerityStaticResourceResponse();
- }
- return self::$response;
- }
-
-}
-
-function require_celerity_resource($symbol) {
- $response = CelerityAPI::getStaticResourceResponse();
- $response->requireResource($symbol);
-}
-
-function celerity_generate_unique_node_id() {
- static $uniq = 0;
- $response = CelerityAPI::getStaticResourceResponse();
- $block = $response->getMetadataBlock();
-
- return 'UQ'.$block.'_'.($uniq++);
+/**
+ * Registers a resource map for Celerity. This is glue code between the Celerity
+ * mapper script and @{class:CelerityResourceMap}.
+ *
+ * @group celerity
+ */
+function celerity_register_resource_map(array $map, array $package_map) {
+ $instance = CelerityResourceMap::getInstance();
+ $instance->setResourceMap($map);
+ $instance->setPackageMap($package_map);
}
-
diff --git a/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php b/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php
index 8ef6be945c..4331df11cc 100644
--- a/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php
+++ b/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php
@@ -1,183 +1,190 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/**
+ * Tracks and resolves dependencies the page declares with
+ * @{function:require_celerity_resource}, and then builds appropriate HTML or
+ * Ajax responses.
+ *
+ * @group celerity
+ */
final class CelerityStaticResourceResponse {
private $symbols = array();
private $needsResolve = true;
private $resolved;
private $packaged;
private $metadata = array();
private $metadataBlock = 0;
private $behaviors = array();
private $hasRendered = array();
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;
}
/**
* 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.
*/
public function initBehavior($behavior, array $config = array()) {
$this->requireResource('javelin-behavior-'.$behavior);
if (empty($this->behaviors[$behavior])) {
$this->behaviors[$behavior] = array();
}
if ($config) {
$this->behaviors[$behavior][] = $config;
}
return $this;
}
public function requireResource($symbol) {
$this->symbols[$symbol] = true;
$this->needsResolve = true;
return $this;
}
private function resolveResources() {
if ($this->needsResolve) {
$map = CelerityResourceMap::getInstance();
$this->resolved = $map->resolveResources(array_keys($this->symbols));
$this->packaged = $map->packageResources($this->resolved);
$this->needsResolve = false;
}
return $this;
}
public function renderSingleResource($symbol) {
$map = CelerityResourceMap::getInstance();
$resolved = $map->resolveResources(array($symbol));
$packaged = $map->packageResources($resolved);
return $this->renderPackagedResources($packaged);
}
public function renderResourcesOfType($type) {
$this->resolveResources();
$resources = array();
foreach ($this->packaged as $resource) {
if ($resource['type'] == $type) {
$resources[] = $resource;
}
}
return $this->renderPackagedResources($resources);
}
private function renderPackagedResources(array $resources) {
$output = array();
foreach ($resources as $resource) {
if (isset($this->hasRendered[$resource['uri']])) {
continue;
}
$this->hasRendered[$resource['uri']] = true;
$output[] = $this->renderResource($resource);
}
return implode("\n", $output);
}
private function renderResource(array $resource) {
switch ($resource['type']) {
case 'css':
$path = phutil_escape_html($resource['uri']);
return '<link rel="stylesheet" type="text/css" href="'.$path.'" />';
case 'js':
$path = phutil_escape_html($resource['uri']);
return '<script type="text/javascript" src="'.$path.'">'.
'</script>';
}
throw new Exception("Unable to render resource.");
}
public function renderHTMLFooter() {
$data = array();
if ($this->metadata) {
$json_metadata = json_encode($this->metadata);
$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) {
$behavior = json_encode($this->behaviors);
$onload[] = 'JX.initBehaviors('.$behavior.')';
$this->behaviors = array();
}
if ($onload) {
foreach ($onload as $func) {
$data[] = 'JX.onload(function(){'.$func.'});';
}
}
if ($data) {
$data = implode("\n", $data);
return '<script type="text/javascript">//<![CDATA['."\n".
$data.'//]]></script>';
} else {
return '';
}
}
public function renderAjaxResponse($payload, $error = null) {
$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();
}
$response = 'for (;;);'.json_encode($response);
return $response;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Jan 19 2025, 22:27 (6 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1129390
Default Alt Text
(29 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment