diff --git a/composer.json b/composer.json
index 436dae9..b45baf8 100644
--- a/composer.json
+++ b/composer.json
@@ -21,7 +21,8 @@
     "symfony-cmf/routing": "1.1.*@alpha",
     "easyrdf/easyrdf": "0.8.*@beta",
     "phpunit/phpunit": "3.7.*",
-    "zendframework/zend-feed": "2.2.*"
+    "zendframework/zend-feed": "2.2.*",
+    "sdboyer/gliph": "0.1.*"
   },
   "autoload": {
     "psr-0": {
diff --git a/composer.lock b/composer.lock
index 5a70a6e..82c158d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -3,7 +3,7 @@
         "This file locks the dependencies of your project to a known state",
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
     ],
-    "hash": "4c727150110aeae10efe10a92ff256f5",
+    "hash": "5fad1bdb3b642274dcb00854fbd08808",
     "packages": [
         {
             "name": "doctrine/annotations",
@@ -647,21 +647,21 @@
         },
         {
             "name": "kriswallsmith/assetic",
-            "version": "v1.1.1",
+            "version": "v1.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/kriswallsmith/assetic.git",
-                "reference": "v1.1.1"
+                "reference": "735cffd3982c6e8cdebe292d5db39d077f65890f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/v1.1.1",
-                "reference": "v1.1.1",
+                "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/735cffd3982c6e8cdebe292d5db39d077f65890f",
+                "reference": "735cffd3982c6e8cdebe292d5db39d077f65890f",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.1",
-                "symfony/process": ">=2.1,<3.0"
+                "symfony/process": "~2.1"
             },
             "require-dev": {
                 "cssmin/cssmin": "*",
@@ -671,9 +671,9 @@
                 "leafo/scssphp": "*",
                 "leafo/scssphp-compass": "*",
                 "mrclay/minify": "*",
-                "phpunit/phpunit": ">=3.7,<4.0",
+                "phpunit/phpunit": "~3.7",
                 "ptachoire/cssembed": "*",
-                "twig/twig": ">=1.6,<2.0"
+                "twig/twig": "~1.6"
             },
             "suggest": {
                 "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler",
@@ -714,7 +714,7 @@
                 "compression",
                 "minification"
             ],
-            "time": "2013-06-01 22:13:43"
+            "time": "2013-07-19 00:03:27"
         },
         {
             "name": "phpunit/php-code-coverage",
@@ -1547,17 +1547,17 @@
         },
         {
             "name": "symfony/process",
-            "version": "v2.3.4",
+            "version": "v2.3.6",
             "target-dir": "Symfony/Component/Process",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/Process.git",
-                "reference": "1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b"
+                "reference": "81191e354fe9dad0451036ccf0fdf283649d3f1e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Process/zipball/1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b",
-                "reference": "1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b",
+                "url": "https://api.github.com/repos/symfony/Process/zipball/81191e354fe9dad0451036ccf0fdf283649d3f1e",
+                "reference": "81191e354fe9dad0451036ccf0fdf283649d3f1e",
                 "shasum": ""
             },
             "require": {
@@ -1590,7 +1590,7 @@
             ],
             "description": "Symfony Process Component",
             "homepage": "http://symfony.com",
-            "time": "2013-08-22 06:42:25"
+            "time": "2013-10-09 21:17:57"
         },
         {
             "name": "symfony/routing",
diff --git a/core/core.services.yml b/core/core.services.yml
index 608c439..9dd036c 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -631,3 +631,4 @@ services:
     class: Drupal\Core\Asset\JsCollectionGrouper
   asset.js.dumper:
     class: Drupal\Core\Asset\AssetDumper
+
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 77ea718..687eeb9 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1631,6 +1631,9 @@ function drupal_add_css($data = NULL, $options = NULL) {
   // Create an array of CSS files for each media type first, since each type needs to be served
   // to the browser differently.
   if (isset($data)) {
+    $options['type'] = isset($options['type']) ? $options['type'] : 'file';
+    drupal_collect_assets($data, $options, 'css');
+
     $options += array(
       'type' => 'file',
       'group' => CSS_AGGREGATE_DEFAULT,
@@ -1682,6 +1685,29 @@ function drupal_add_css($data = NULL, $options = NULL) {
   return $css;
 }
 
+function drupal_collect_assets($data, $options, $type = '') {
+  $collection = &drupal_static('global_asset_bag', FALSE);
+  $collector = &drupal_static('global_asset_collector', FALSE);
+
+  $collection = ($collection instanceof \Drupal\Core\Asset\Collection\AssetCollection) ? $collection : new \Drupal\Core\Asset\Collection\AssetCollection();
+  if (!$collector instanceof \Drupal\Core\Asset\Factory\AssetCollector) {
+    $collector = new \Drupal\Core\Asset\Factory\AssetCollector();
+    $collector->setCollection($collection);
+  }
+
+  if ($data instanceof \Drupal\Core\Asset\AssetInterface) {
+    $collector->add($data);
+    return;
+  }
+
+  if ($type == 'js-setting') {
+    // TODO handle js settings
+    return;
+  }
+
+  $collector->create($type, $options['type'], $data, $options);
+}
+
 /**
  * Returns a themed representation of all stylesheets to attach to the page.
  *
@@ -2212,6 +2238,11 @@ function drupal_add_js($data = NULL, $options = NULL) {
   }
   $options += drupal_js_defaults($data);
 
+  if (isset($data) && is_array($options)) {
+    $options['type'] = isset($options['type']) ? $options['type'] : 'file';
+    drupal_collect_assets($data, $options, $options['type'] == 'setting' ? 'js-setting' : 'js');
+  }
+
   // Preprocess can only be set if caching is enabled and no attributes are set.
   $options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE;
 
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/AssetAggregateInterface.php b/core/lib/Drupal/Core/Asset/Aggregate/AssetAggregateInterface.php
new file mode 100644
index 0000000..8d37137
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/AssetAggregateInterface.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Aggregate\AssetAggregateInterface.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+use Assetic\Asset\AssetCollectionInterface as AsseticAssetCollectionInterface;
+use Drupal\Core\Asset\Collection\AssetCollectionBasicInterface;
+use Drupal\Core\Asset\AssetInterface;
+
+/**
+ * Describes an aggregate asset: a logical asset composed of other assets.
+ *
+ * This interface extends to Assetic's AssetCollectionInterface, but is intended
+ * for a more narrow purpose than it. Whereas Assetic uses AssetCollections as
+ * both a container for assets (a collection in the conventional sense) *and* as
+ * a renderable unit, implementors of AssetAggregateInterface are considered to
+ * be solely the latter.
+ *
+ * This approach was taken because these two are discrete responsibilities, and
+ * while the conflation of the two is not problematic for most contexts in which
+ * Assetic is used, Drupal's complex asset declaration and rendering environment
+ * necessitates a clear differentiation between the two.
+ *
+ * In the end, aggregates are exactly what the interface composition looks like:
+ * a real, functioning asset, and a basic container for other assets.
+ *
+ * @see \Assetic\Asset\AssetCollectionInterface
+ * @see \Drupal\Core\Asset\Collection\AssetCollectionInterface
+ */
+interface AssetAggregateInterface extends AssetInterface, AssetCollectionBasicInterface, AsseticAssetCollectionInterface {
+
+  /**
+   * Replaces an existing asset in the aggregate with a new one.
+   *
+   * This maintains ordering of the assets within the aggregate; the new asset
+   * will occupy the same position as the old asset.
+   *
+   * @param AssetInterface|string $needle
+   *   Either an AssetInterface instance, or the string id of an asset.
+   * @param AssetInterface $replacement
+   *   The new asset to swap into place.
+   * @param bool $graceful
+   *   Whether failure should return FALSE or throw an exception.
+   *
+   * @return bool
+   *
+   * @throws \OutOfBoundsException
+   */
+  public function replace($needle, AssetInterface $replacement, $graceful = FALSE);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php
new file mode 100644
index 0000000..65928b7
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php
@@ -0,0 +1,355 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\BaseAggregateAsset.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+
+use Assetic\Filter\FilterCollection;
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\AsseticAdapterAsset;
+use Drupal\Core\Asset\AssetInterface;
+use Assetic\Asset\AssetInterface as AsseticAssetInterface;
+use Drupal\Core\Asset\Aggregate\AssetAggregateInterface;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Base class for representing aggregate assets.
+ *
+ */
+abstract class BaseAggregateAsset extends AsseticAdapterAsset implements \IteratorAggregate, AssetInterface, AssetAggregateInterface {
+
+  /**
+   * @var \Drupal\Core\Asset\Metadata\AssetMetadataBag
+   */
+  protected $metadata;
+
+  /**
+   * Container for all assets attached to this object.
+   *
+   * @var \SplObjectStorage
+   */
+  protected $assetStorage;
+
+  /**
+   * @var \SplObjectStorage
+   */
+  protected $nestedStorage;
+
+  /**
+   * A string identifier for this aggregate.
+   *
+   * This is calculated based on
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Maintains a map, keyed by id, of all assets.
+   *
+   * This map is also the canonical source for ordering information.
+   *
+   * @var array
+   */
+  protected $assetIdMap = array();
+
+  protected $content;
+
+  /**
+   * @param AssetMetadataBag $metadata
+   *   The metadata bag for this aggregate.
+   * @param array $assets
+   *   Assets to add to this aggregate.
+   * @param array $filters
+   *   Filters to apply to this aggregate.
+   */
+  public function __construct(AssetMetadataBag $metadata, $assets = array(), $filters = array()) {
+    parent::__construct($filters);
+
+    $this->metadata = $metadata;
+    $this->assetStorage = new \SplObjectStorage();
+    $this->nestedStorage = new \SplObjectStorage();
+
+    foreach ($assets as $asset) {
+      $this->add($asset);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    if (empty($this->id)) {
+      $this->calculateId();
+    }
+
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAssetType() {
+    return $this->metadata->getType();
+  }
+
+  /**
+   * Calculates and stores an id for this aggregate from the contained assets.
+   *
+   * @return void
+   */
+  protected function calculateId() {
+    $id = '';
+    foreach ($this->assetStorage as $asset) {
+      // Preserve a little id stability by not composing id from aggregates
+      if (!$asset instanceof AssetAggregateInterface) {
+        $id .= $asset->id();
+      }
+    }
+    // TODO come up with something stabler/more serialization friendly than object hash
+    $this->id = hash('sha256', $id ?: spl_object_hash($this));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadata() {
+    // TODO should this immutable? doable if we further granulate the interfaces
+    return $this->metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(AsseticAssetInterface $asset) {
+    if (!$asset instanceof AssetInterface) {
+      throw new UnsupportedAsseticBehaviorException('Vanilla Assetic asset provided; Drupal aggregates require Drupal-flavored assets.');
+    }
+    $this->ensureCorrectType($asset);
+
+    $this->assetStorage->attach($asset);
+    $this->assetIdMap[$asset->id()] = $asset;
+
+    if ($asset instanceof AssetAggregateInterface) {
+      $this->nestedStorage->attach($asset);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function contains(AssetInterface $asset) {
+    if ($this->assetStorage->contains($asset)) {
+      return TRUE;
+    }
+
+    foreach ($this->nestedStorage as $aggregate) {
+      if ($aggregate->contains($asset)) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getById($id, $graceful = TRUE) {
+    if (isset($this->assetIdMap[$id])) {
+      return $this->assetIdMap[$id];
+    }
+    else {
+      // Recursively search for the id
+      foreach ($this->nestedStorage as $aggregate) {
+        if ($found = $aggregate->getById($id)) {
+          return $found;
+        }
+      }
+    }
+
+    if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException(sprintf('This aggregate does not contain an asset with id %s.', $id));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function remove($needle, $graceful = TRUE) {
+    if (is_string($needle)) {
+      if (!$needle = $this->getById($needle, $graceful)) {
+        return FALSE;
+      }
+    }
+
+    return $this->removeLeaf($needle, $graceful);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeLeaf(AsseticAssetInterface $needle, $graceful = FALSE) {
+    if (!$needle instanceof AssetInterface) {
+      throw new UnsupportedAsseticBehaviorException('Vanilla Assetic asset provided; Drupal aggregates require Drupal-flavored assets.');
+    }
+    $this->ensureCorrectType($needle);
+
+    foreach ($this->assetIdMap as $id => $asset) {
+      if ($asset === $needle) {
+        unset($this->assetStorage[$asset], $this->assetIdMap[$id], $this->nestedStorage[$asset]);
+
+        return TRUE;
+      }
+
+      if ($asset instanceof AssetAggregateInterface && $asset->removeLeaf($needle, $graceful)) {
+        return TRUE;
+      }
+    }
+
+    if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException('Asset not found.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function replace($needle, AssetInterface $replacement, $graceful = TRUE) {
+    if (is_string($needle)) {
+      if (!$needle = $this->getById($needle, $graceful)) {
+        return FALSE;
+      }
+    }
+
+    return $this->replaceLeaf($needle, $replacement, $graceful);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function replaceLeaf(AsseticAssetInterface $needle, AsseticAssetInterface $replacement, $graceful = FALSE) {
+    if (!($needle instanceof AssetInterface && $replacement instanceof AssetInterface)) {
+      throw new UnsupportedAsseticBehaviorException('Vanilla Assetic asset(s) provided; Drupal aggregates require Drupal-flavored assets.');
+    }
+    $this->ensureCorrectType($needle);
+    $this->ensureCorrectType($replacement);
+
+    foreach ($this->assetIdMap as $id => $asset) {
+      if ($asset === $needle) {
+        unset($this->assetStorage[$asset], $this->nestedStorage[$asset]);
+
+        array_splice($this->assetIdMap, $i, 1, array($replacement->id() => $replacement));
+        $this->assetStorage->attach($replacement);
+        if ($replacement instanceof AssetAggregateInterface) {
+          $this->nestedStorage->attach($replacement);
+        }
+
+        return TRUE;
+      }
+
+      if ($asset instanceof AssetAggregateInterface && $asset->replaceLeaf($needle, $replacement, $graceful)) {
+        return TRUE;
+      }
+    }
+
+    if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException('Asset not found.');
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Aggregate assets are inherently eligible for preprocessing, so this is
+   * always true.
+   */
+  public function isPreprocessable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function all() {
+    return $this->assetIdMap;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(FilterInterface $additionalFilter = NULL) {
+    // loop through leaves and load each asset
+    $parts = array();
+    foreach ($this as $asset) {
+      $asset->load($additionalFilter);
+      $parts[] = $asset->getContent();
+    }
+
+    $this->content = implode("\n", $parts);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dump(FilterInterface $additionalFilter = NULL) {
+    // loop through leaves and dump each asset
+    $parts = array();
+    foreach ($this as $asset) {
+      $parts[] = $asset->dump($additionalFilter);
+    }
+
+    return implode("\n", $parts);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContent() {
+    return $this->content;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContent($content) {
+    $this->content = $content;
+  }
+
+  /**
+   * TODO Assetic uses their iterator to clone, then populate values and return here; is that a good model for us?
+   */
+  public function getIterator() {
+    // TODO this is totally junk
+    return new \ArrayIterator($this->assetIdMap);
+  }
+
+  /**
+   * Indicates whether this collection contains any assets.
+   *
+   * @return bool
+   *   TRUE if contained assets are present, FALSE otherwise.
+   */
+  public function isEmpty() {
+    return $this->assetStorage->count() === 0;
+  }
+
+  /**
+   * Ensures that the asset is of the correct subtype (e.g., css vs. js).
+   *
+   * @param AssetInterface $asset
+   *
+   * @throws \Drupal\Core\Asset\Exception\AssetTypeMismatchException
+   */
+  abstract protected function ensureCorrectType(AssetInterface $asset);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php
new file mode 100644
index 0000000..934317a
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssAggregateAsset.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Exception\AssetTypeMismatchException;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+
+/**
+ * A CSS asset that is an aggregate of multiple other CSS assets.
+ */
+class CssAggregateAsset extends BaseAggregateAsset {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \Drupal\Core\Asset\Exception\AssetTypeMismatchException
+   */
+  public function __construct(AssetMetadataBag $metadata, $assets = array(), $filters = array(), $sourceRoot = array()) {
+    if (!$metadata instanceof CssMetadataBag) {
+      throw new AssetTypeMismatchException('CSS aggregates require CSS metadata bags.');
+    }
+
+    parent::__construct($metadata, $assets, $filters, $sourceRoot);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function ensureCorrectType(AssetInterface $asset) {
+    if ($asset->getAssetType() !== 'css') {
+      throw new AssetTypeMismatchException('CSS aggregates can only work with CSS assets.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php
new file mode 100644
index 0000000..37f8a2e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Aggregate\JsAggregateAsset.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Exception\AssetTypeMismatchException;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+
+/**
+ * A Javascript asset that aggregates together multiple other Javascript assets.
+ */
+class JsAggregateAsset extends BaseAggregateAsset {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \Drupal\Core\Asset\Exception\AssetTypeMismatchException
+   */
+  public function __construct(AssetMetadataBag $metadata, $assets = array(), $filters = array(), $sourceRoot = array()) {
+    if (!$metadata instanceof JsMetadataBag) {
+      throw new AssetTypeMismatchException('JS aggregates require JS metadata bags.');
+    }
+
+    parent::__construct($metadata, $assets, $filters, $sourceRoot);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function ensureCorrectType(AssetInterface $asset) {
+   if ($asset->getAssetType() !== 'js') {
+      throw new AssetTypeMismatchException('JS aggregates can only work with JS assets.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/AssetCollectionAggregatorInterface.php b/core/lib/Drupal/Core/Asset/AssetCollectionAggregatorInterface.php
new file mode 100644
index 0000000..17b7191
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetCollectionAggregatorInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetCollectionAggregatorInterface.
+ */
+
+namespace Drupal\Core\Asset;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+
+/**
+ * Interface for a service that groups assets into logical aggregates.
+ */
+interface AssetCollectionAggregatorInterface {
+
+  /**
+   * Groups a collection of assets into logical aggregates.
+   *
+   * @param AssetCollectionInterface $collection
+   *   The AssetCollectionInterface to aggregate.
+   *
+   * @return AssetCollectionInterface
+   *   A new AssetCollectionInterface containing the aggregated assets. The
+   *   collection is populated by objects implementing at least AssetInterface,
+   *   and possibly also AssetAggregateInterface.
+   */
+  public function aggregate(AssetCollectionInterface $collection);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerNouveauxInterface.php b/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerNouveauxInterface.php
new file mode 100644
index 0000000..59ab396
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerNouveauxInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetCollectionOptimizerNouveauxInterface.
+ */
+
+namespace Drupal\Core\Asset;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+
+/**
+ * Interface for a service that optimizes an asset collection.
+ */
+interface AssetCollectionOptimizerNouveauxInterface {
+
+  /**
+   * Optimizes a collection of assets.
+   *
+   * "Asset collection" means an object implementing AssetCollectionInterface.
+   * Optimization encompasses both aggregating assets together into a smaller
+   * set, and performing operations such as minification.
+   *
+   * @param AssetCollectionInterface $collection
+   *   The AssetCollectionInterface to optimize.
+   *
+   * @return AssetCollectionInterface
+   *   An AssetCollectionInterface containing fully optimized AssetInterface
+   *   objects.
+   */
+  public function optimize(AssetCollectionInterface $collection);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/AssetGraph.php b/core/lib/Drupal/Core/Asset/AssetGraph.php
new file mode 100644
index 0000000..ee94b08
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetGraph.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetGraph.
+ */
+
+namespace Drupal\Core\Asset;
+use Gliph\Exception\InvalidVertexTypeException;
+use Gliph\Graph\DirectedAdjacencyList;
+
+/**
+ * An extension of the DirectedAdjacencyGraph concept designed specifically for
+ * Drupal's asset management use case.
+ *
+ * Drupal allows for two types of sequencing declarations:
+ *
+ *   - Dependencies, which guarantee that dependent asset must be present and
+ *     that it must precede the asset declaring it as a dependency.
+ *   - Ordering, which can guarantee that asset A will be either preceded or
+ *     succeeded by asset B, but does NOT guarantee that B will be present.
+ *
+ * The impact of a dependency can be calculated myopically (without knowledge of
+ * the full set), as a dependency inherently guarantees the presence of the
+ * other vertex in the set.
+ *
+ * For ordering, however, the full set must be inspected to determine whether or
+ * not the other asset is already present. If it is, a directed edge can be
+ * declared; if it is not.
+ *
+ * This class eases the process of determining what to do with ordering
+ * declarations by implementing a more sophisticated addVertex() mechanism,
+ * which incrementally sets up (and triggers) watches for any ordering
+ * declarations that have not yet been realized.
+ *
+ * TODO add stuff that tracks data about unresolved successors/predecessors
+ */
+class AssetGraph extends DirectedAdjacencyList {
+
+  protected $before = array();
+  protected $after = array();
+  protected $verticesById = array();
+  protected $process;
+
+  /**
+   * Creates a new AssetGraph object.
+   *
+   * AssetGraphs are a specialization of DirectedAdjacencyGraph that is tailored
+   * to handling the sequencing information carried by AssetOrderingInterface
+   * instances.
+   *
+   * @param bool $process
+   *   Whether or not to automatically process sequencing as vertices are added.
+   *   This should be left as TRUE in most every user-facing case; its primary
+   *   audience is for the creation of a graph transpose.
+   */
+  public function __construct($process = TRUE) {
+    parent::__construct();
+    $this->process = $process;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addVertex($vertex) {
+    if (!$vertex instanceof AssetInterface) {
+      throw new InvalidVertexTypeException('AssetGraph requires vertices to implement AssetInterface.');
+    }
+
+    if (!$this->hasVertex($vertex)) {
+      $this->vertices[$vertex] = new \SplObjectStorage();
+      $this->verticesById[$vertex->id()] = $vertex;
+
+      if ($this->process) {
+        $this->processNewVertex($vertex);
+      }
+    }
+  }
+
+  /**
+   * Processes all sequencing information for a given vertex.
+   *
+   * @param AssetInterface $vertex
+   */
+  protected function processNewVertex(AssetInterface $vertex) {
+    $id = $vertex->id();
+    // First, check if anything has a watch out for this vertex.
+    if (isset($this->before[$id])) {
+      foreach ($this->before[$id] as $predecessor) {
+        $this->addDirectedEdge($predecessor, $vertex);
+      }
+      unset($this->before[$id]);
+    }
+
+    if (isset($this->after[$id])) {
+      foreach ($this->after[$id] as $successor) {
+        $this->addDirectedEdge($vertex, $successor);
+      }
+      unset($this->after[$id]);
+    }
+
+    // Add watches for this vertex, if it implements the interface.
+    if ($vertex instanceof AssetOrderingInterface) {
+      // TODO this logic assumes collections enforce uniqueness - ensure that's the case.
+      foreach ($vertex->getPredecessors() as $predecessor) {
+        // Normalize to id string.
+        $predecessor = is_string($predecessor) ? $predecessor : $predecessor->id();
+
+        if (isset($this->verticesById[$predecessor])) {
+          $this->addDirectedEdge($vertex, $this->verticesById[$predecessor]);
+        }
+        else {
+          if (!isset($this->before[$predecessor])) {
+            $this->before[$predecessor] = array();
+          }
+          $this->before[$predecessor][] = $vertex;
+        }
+      }
+
+      foreach ($vertex->getSuccessors() as $successor) {
+        // Normalize to id string.
+        $successor = is_string($successor) ? $successor : $successor->id();
+
+        if (isset($this->verticesById[$successor])) {
+          $this->addDirectedEdge($this->verticesById[$successor], $vertex);
+        }
+        else {
+          if (!isset($this->before[$successor])) {
+            $this->after[$successor] = array();
+          }
+          $this->after[$successor][] = $vertex;
+        }
+      }
+    }
+  }
+
+  /**
+   * Remove a vertex from the graph. Unsupported in AssetGraph.
+   *
+   * Vertex removals are unsupported because it would necessitate permanent
+   * bookkeeping on sequencing data. With forty or fifty assets, each having
+   * only a few dependencies, there would be a fair bit of pointless iterating.
+   *
+   * @throws \LogicException
+   *   This exception will always be thrown.
+   */
+  public function removeVertex($vertex) {
+    throw new \LogicException('AssetGraph does not support vertex removals.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transpose() {
+    $graph = new self(FALSE);
+    $this->eachEdge(function($edge) use (&$graph) {
+        $graph->addDirectedEdge($edge[1], $edge[0]);
+    });
+
+    return $graph;
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetInterface.php b/core/lib/Drupal/Core/Asset/AssetInterface.php
new file mode 100644
index 0000000..1d21dac
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetInterface.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetInterface.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Asset\AssetInterface as AsseticAssetInterface;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Represents a CSS or Javascript asset.
+ *
+ * This interface extends the AssetInterface provided by Assetic to facilitate
+ * different behaviors by individual assets.
+ */
+interface AssetInterface extends AsseticAssetInterface {
+
+  /**
+   * Returns the metadata bag for this asset.
+   *
+   * @return AssetMetadataBag
+   */
+  public function getMetadata();
+
+  /**
+   * Indicates whether or not this asset is eligible for preprocessing.
+   *
+   * Assets that are marked as not preprocessable will always be passed directly
+   * to the browser without aggregation or minification. Assets that are marked
+   * as eligible for preprocessing will be included in any broader aggregation
+   * logic that has been configured.
+   *
+   * @return bool
+   */
+  public function isPreprocessable();
+
+  /**
+   * Returns a unique string identifier that uniquely identifies this asset.
+   *
+   * Note that this id IS subject to change, if certain internal object
+   * properties change.
+   *
+   * // TODO if it's subject to change, 'id' is misleading
+   *
+   * @return string
+   *   The asset id.
+   */
+  public function id();
+
+  /**
+   * Returns a string identifying the type of asset - i.e., 'css' or 'js'.
+   *
+   * @return string
+   */
+  public function getAssetType();
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetOrderingInterface.php b/core/lib/Drupal/Core/Asset/AssetOrderingInterface.php
new file mode 100644
index 0000000..7fe5835
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetOrderingInterface.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Core\Asset\AssetOrderingInterface.
+ */
+
+namespace Drupal\Core\Asset;
+
+/**
+ * Describes an asset or asset-like object that can declare dependencies.
+ */
+interface AssetOrderingInterface {
+
+  /**
+   * Indicates whether this asset has one or more library dependencies.
+   *
+   * @return boolean
+   */
+  public function hasDependencies();
+
+  /**
+   * Retrieve this asset's dependencies.
+   *
+   * @return mixed
+   *   An array of dependencies if they exist,
+   */
+  public function getDependencyInfo();
+
+  /**
+   * Add a dependency on a library for this asset.
+   *
+   * @param string $module
+   *   The name of the module declaring the library.
+   * @param string $name
+   *   The name of the library.
+   *
+   * @return void
+   */
+  public function addDependency($module, $name);
+
+  /**
+   * Clears (removes) all library dependencies for this asset.
+   *
+   * This does not affect ordering data.
+   *
+   * @return void
+   */
+  public function clearDependencies();
+
+  /**
+   * Declare that an asset should, if present, succeed this asset on output.
+   *
+   * Either the string identifier for the other asset, or the asset object
+   * itself, should be provided.
+   *
+   * @param string|AssetInterface $asset
+   *   The asset to succeed the current asset.
+   *
+   * @return void
+   */
+  public function before($asset);
+
+  /**
+   * Declare that an asset should, if present, precede this asset on output.
+   *
+   * Either the string identifier for the other asset, or the asset object
+   * itself, should be provided.
+   *
+   * @param string|AssetInterface $asset
+   *   The asset to precede the current asset.
+   *
+   * @return void
+   */
+  public function after($asset);
+
+  /**
+   * Returns ordering info declared by after().
+   *
+   * @return array
+   *   An array of strings or AssetInterface instances that must precede this
+   *   object on final output.
+   */
+  public function getPredecessors();
+
+  /**
+   * Returns ordering info declared by before().
+   *
+   * @return array
+   *   An array of strings or AssetInterface instances that must succeed this
+   *   object on final output.
+   */
+  public function getSuccessors();
+
+  /**
+   * Clears (removes) all ordering info declared by before() for this asset.
+   *
+   * This does not affect dependency data.
+   *
+   * @return void
+   */
+  public function clearSuccessors();
+
+  /**
+   * Clears (removes) all ordering info declared by after() for this asset.
+   *
+   * This does not affect dependency data.
+   *
+   * @return void
+   */
+  public function clearPredecessors();
+}
diff --git a/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php b/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php
new file mode 100644
index 0000000..de2980c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AsseticAdapterAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Asset\AssetInterface;
+use Assetic\Asset\BaseAsset as AsseticBaseAsset;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+
+/**
+ * A class that reduces boilerplate code by centrally disabling the Assetic
+ * properties and methods Drupal does not support.
+ */
+abstract class AsseticAdapterAsset extends AsseticBaseAsset implements AssetInterface {
+
+  /**
+   * @throws \Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException
+   */
+  public final function getVars() {
+    throw new UnsupportedAsseticBehaviorException("Drupal does not use or support Assetic's 'vars' concept.");
+  }
+
+  /**
+   * @throws \Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException
+   */
+  public final function setValues(array $values) {
+    throw new UnsupportedAsseticBehaviorException("Drupal does not use or support Assetic's 'values' concept.");
+  }
+
+  /**
+   * @throws \Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException
+   */
+  public final function getValues() {
+    throw new UnsupportedAsseticBehaviorException("Drupal does not use or support Assetic's 'values' concept.");
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/BaseAsset.php b/core/lib/Drupal/Core/Asset/BaseAsset.php
new file mode 100644
index 0000000..d42f9a2
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/BaseAsset.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\BaseAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * A base abstract asset.
+ *
+ * This is an amalgam of Assetic\Asset\BaseAsset (copied directly) with
+ * implementations of the additional methods specified by Drupal's own
+ * Drupal\Core\Asset\AssetInterface.
+ *
+ * The methods load() and getLastModified() are left undefined, although a
+ * reusable doLoad() method is available to child classes.
+ */
+abstract class BaseAsset extends AsseticAdapterAsset implements AssetInterface, AssetOrderingInterface {
+
+  /**
+   * @var AssetMetadataBag
+   */
+  protected $metadata;
+
+  protected $dependencies = array();
+
+  protected $successors = array();
+
+  protected $predecessors = array();
+
+  public function __construct(AssetMetadataBag $metadata, $filters = array(), $sourceRoot = NULL, $sourcePath = NULL) {
+    $this->metadata = $metadata;
+    parent::__construct($filters, $sourceRoot, $sourcePath);
+  }
+
+  public function __clone() {
+    parent::__clone();
+    $this->metadata = clone $this->metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadata() {
+    return $this->metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAssetType() {
+    return $this->metadata->getType();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isPreprocessable() {
+    return (bool) $this->metadata->get('preprocess');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasDependencies() {
+    return !empty($this->dependencies);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addDependency($module, $name) {
+    if (!(is_string($module) && is_string($name))) {
+      throw new \InvalidArgumentException('Dependencies must be expressed as 2-tuple with the first element being owner/module, and the second being name.');
+    }
+
+    $this->dependencies[] = array($module, $name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearDependencies() {
+    $this->dependencies = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDependencyInfo() {
+    return $this->dependencies;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function before($asset) {
+    if (!($asset instanceof AssetInterface || is_string($asset))) {
+      throw new \InvalidArgumentException('Ordering information must be declared using either an asset string id or the full AssetInterface object.');
+    }
+
+    $this->successors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function after($asset) {
+    if (!($asset instanceof AssetInterface || is_string($asset))) {
+      throw new \InvalidArgumentException('Ordering information must be declared using either an asset string id or the full AssetInterface object.');
+    }
+
+    $this->predecessors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPredecessors() {
+    return $this->predecessors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSuccessors() {
+    return $this->successors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearSuccessors() {
+    $this->successors = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearPredecessors() {
+    $this->predecessors = array();
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollection.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollection.php
new file mode 100644
index 0000000..713ba1e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollection.php
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\AssetCollection.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Collection\Iterator\AssetSubtypeFilterIterator;
+
+/**
+ * A container for assets.
+ *
+ * @see CssCollection
+ * @see JsCollection
+ *
+ * TODO allow direct adding of libraries
+ */
+class AssetCollection implements \IteratorAggregate, AssetCollectionInterface {
+
+  protected $assetStorage;
+
+  protected $assetIdMap = array();
+
+  protected $frozen = FALSE;
+
+  public function __construct() {
+    $this->assetStorage = new \SplObjectStorage();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(AssetInterface $asset) {
+    $this->attemptWrite();
+
+    if (!$this->contains($asset)) {
+      $this->assetStorage->attach($asset);
+      $this->assetIdMap[$asset->id()] = $asset;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function contains(AssetInterface $asset) {
+    // TODO decide whether to do this by id or object instance
+    return $this->assetStorage->contains($asset);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getById($id, $graceful = TRUE) {
+    if (isset($this->assetIdMap[$id])) {
+      return $this->assetIdMap[$id];
+    }
+    else if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException(sprintf('This collection does not contain an asset with id %s.', $id));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function remove($needle, $graceful = TRUE) {
+    // TODO fix horrible complexity of conditionals, exceptions, and returns.
+    $this->attemptWrite();
+
+    // Validate and normalize type to AssetInterface
+    if (is_string($needle)) {
+      if (!$needle = $this->getById($needle, $graceful)) {
+        // Asset couldn't be found but we're in graceful mode - return FALSE.
+        return FALSE;
+      }
+    }
+    else if (!$needle instanceof AssetInterface) {
+      throw new \InvalidArgumentException('Invalid type provided to AssetCollection::remove(); must provide either a string asset id or AssetInterface instance.');
+    }
+
+    // Check for membership
+    if ($this->contains($needle)) {
+      unset($this->assetIdMap[$needle->id()], $this->assetStorage[$needle]);
+      return TRUE;
+    }
+    else if (!$graceful) {
+      throw new \OutOfBoundsException(sprintf('This collection does not contain an asset with id %s.', $needle->id()));
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function all() {
+    return $this->assetIdMap;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeCollection(AssetCollectionInterface $collection, $freeze = TRUE) {
+    $this->attemptWrite();
+
+    foreach ($collection as $asset) {
+      if (!$this->contains($asset)) {
+        $this->add($asset);
+      }
+    }
+
+    if ($freeze) {
+      $collection->freeze();
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function freeze() {
+    $this->frozen = TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFrozen() {
+    return $this->frozen;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->assetIdMap);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return empty($this->assetIdMap);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCss() {
+    // TODO evaluate potential performance impact if this is done a lot...
+    $collection = new self();
+    foreach (new AssetSubtypeFilterIterator($this->getIterator(), 'css') as $asset) {
+      $collection->add($asset);
+    }
+
+    return $collection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getJs() {
+    $collection = new self();
+    foreach (new AssetSubtypeFilterIterator($this->getIterator(), 'js') as $asset) {
+      $collection->add($asset);
+    }
+
+    return $collection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function sort($callback) {
+    uksort($this->assetIdMap, $callback);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function ksort() {
+    ksort($this->assetIdMap);
+  }
+
+  /**
+   * Checks if the asset library is frozen, throws an exception if it is.
+   */
+  protected function attemptWrite() {
+    if ($this->isFrozen()) {
+      throw new \LogicException('Cannot write to a frozen AssetCollection.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php
new file mode 100644
index 0000000..e4cebc2
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\AssetCollectionBasicInterface.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\AssetInterface;
+use Assetic\Asset\AssetInterface as AsseticAssetInterface;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+
+/**
+ * Describes an asset collection: a container for assets.
+ *
+ * Asset collections are nothing more than a mechanism for holding and easily
+ * moving a set of a specific type of asset around.
+ *
+ * This interface contains the subset of methods that feasible for
+ * AssetAggregateInterface to share; because certain internal sequencing and
+ * state is important to aggregates, they cannot behave like a full collection.
+ *
+ * @see \Drupal\Core\Asset\Aggregate\AssetAggregateInterface
+ * @see \Drupal\Core\Asset\Collection\AssetCollectionInterface
+ */
+interface AssetCollectionBasicInterface extends \Traversable {
+
+  /**
+   * Removes an asset from the aggregate.
+   *
+   * Wraps Assetic's AssetCollection::removeLeaf() to ease removal of keys.
+   *
+   * @param AssetInterface|string $needle
+   *   Either an AssetInterface instance, or the string id of an asset.
+   * @param bool $graceful
+   *   Whether failure should return FALSE or throw an exception.
+   *
+   * @return bool
+   *
+   * @throws \OutOfBoundsException
+   */
+  public function remove($needle, $graceful = FALSE);
+
+  /**
+   * Indicates whether this collection contains the provided asset.
+  *
+   * @param AssetInterface $asset
+   *   Either an AssetInterface instance, or the string id of an asset.
+   *
+   * @return bool
+   */
+  public function contains(AssetInterface $asset);
+
+  /**
+   * Retrieves a contained asset by its string identifier.
+   *
+   * Call this with $graceful = TRUE as an equivalent to contains() if all you
+   * have is a string id.
+   *
+   * @param string $id
+   *   The id of the asset to retrieve.
+   * @param bool $graceful
+   *   Whether failure should return FALSE or throw an exception.
+   *
+   * @return AssetInterface|bool
+   *   FALSE if no asset could be found with that id, or an AssetInterface.
+   *
+   * @throws \OutOfBoundsException
+   */
+  public function getById($id, $graceful = TRUE);
+
+  /**
+   * Indicates whether this collection contains any assets.
+   *
+   * @return bool
+   *   TRUE if contained assets are present, FALSE otherwise.
+   */
+  public function isEmpty();
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php
new file mode 100644
index 0000000..555c74e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\AssetCollectionInterface.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\AssetLibraryRepository;
+
+/**
+ * Describes an asset collection.
+ *
+ * TODO we need a few more methods here to deal with asset type disambiguation and library resolution
+ *
+ * @see \Drupal\Core\Asset\Collection\AssetCollectionBasicInterface
+ */
+interface AssetCollectionInterface extends AssetCollectionBasicInterface {
+
+  /**
+   * Returns all assets contained in this collection.
+   *
+   * @return AssetInterface[]
+   */
+  public function all();
+
+  /**
+   * Adds an asset to the collection.
+   *
+   * @param \Drupal\Core\Asset\AssetInterface $asset
+   *   The asset to add.
+   *
+   * @return bool
+   *   TRUE if the asset was already added, FALSE if it was already present in
+   *   the collection.
+   */
+  public function add(AssetInterface $asset);
+
+  /**
+   * Merges another asset collection into this one.
+   *
+   * If an asset is present in both collections, as identified by
+   * AssetInterface::id(), the asset from the passed collection will
+   * supercede the asset in this collection.
+   *
+   * @param AssetCollectionInterface $collection
+   *   The collection to merge.
+   *
+   * @param bool $freeze
+   *   Whether to freeze the provided collection after merging. Defaults to TRUE.
+   *
+   * @return void
+   */
+  public function mergeCollection(AssetCollectionInterface $collection, $freeze = TRUE);
+
+  /**
+   * Freeze this asset collection, preventing asset additions or removals.
+   *
+   * This does not prevent modification of assets already contained within the
+   * collection.
+   *
+   * TODO put this on the basic interface so aggregates have it, too?
+   *
+   * @return void
+   */
+  public function freeze();
+
+  /**
+   * Indicates whether or not this collection is frozen.
+   *
+   * @return bool
+   */
+  public function isFrozen();
+
+  /**
+   * Returns all contained CSS assets in a traversable form.
+   *
+   * @return \Traversable
+   */
+  public function getCss();
+
+  /**
+   * Returns all contained JS assets in a traversable form.
+   *
+   * @return \Traversable
+   */
+  public function getJs();
+
+  /**
+   * Sorts contained assets by id by passing the provided callback to uksort().
+   *
+   * @param $callback
+   *
+   * @return void
+   */
+  public function sort($callback);
+
+  /**
+   * Sorts contained assets via ksort() on their ids.
+   *
+   * @return void
+   */
+  public function ksort();
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetLibrary.php b/core/lib/Drupal/Core/Asset/Collection/AssetLibrary.php
new file mode 100644
index 0000000..ce40bfc
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/AssetLibrary.php
@@ -0,0 +1,211 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetLibrary.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+
+use Drupal\Core\Asset\AssetOrderingInterface;
+use Drupal\Core\Asset\Collection\AssetCollection;
+
+class AssetLibrary extends AssetCollection implements AssetOrderingInterface {
+
+  /**
+   * The asset library's title.
+   *
+   * @var string
+   */
+  protected $title = '';
+
+  /**
+   * The asset library's version.
+   *
+   * @var string
+   */
+  protected $version;
+
+  /**
+   * The asset library's website.
+   *
+   * @var string
+   */
+  protected $website = '';
+
+  /**
+   * The asset library's dependencies (on other asset libraries).
+   *
+   * @var array
+   */
+  protected $dependencies = array();
+
+  protected $predecessors = array();
+
+  protected $successors = array();
+
+  public function __construct(array $values = array()) {
+    parent::__construct();
+    // TODO do it right.
+    $vals = array_intersect_key($values, array_flip(array('title', 'version', 'website', 'dependencies')));
+    foreach ($vals as $key => $val) {
+      $this->$key = $val;
+    }
+  }
+
+  /**
+   * Set the asset library's title.
+   *
+   * @param string $title
+   *   The title of the asset library.
+   *
+   * @return \Drupal\Core\Asset\AssetLibrary
+   *   The asset library, to allow for chaining.
+   */
+  public function setTitle($title) {
+    $this->attemptWrite();
+    $this->title = $title;
+    return $this;
+  }
+
+  /**
+   * Get the asset library's title.
+   *
+   * @return string
+   *   The title of the asset library.
+   */
+  public function getTitle() {
+    return $this->title;
+  }
+
+  /**
+   * Set the asset library's website.
+   *
+   * @param string $website
+   *   The website of the asset library.
+   *
+   * @return \Drupal\Core\Asset\AssetLibrary
+   *   The asset library, to allow for chaining.
+   */
+  public function setWebsite($website) {
+    $this->attemptWrite();
+    $this->website = $website;
+    return $this;
+  }
+
+  /**
+   * Get the asset library's website.
+   *
+   * @return string
+   *   The website of the asset library.
+   */
+  public function getWebsite() {
+    return $this->website;
+  }
+
+  /**
+   * Set the asset library's version.
+   *
+   * @param string $version
+   *   The version of the asset library.
+   *
+   * @return \Drupal\Core\Asset\AssetLibrary
+   *   The asset library, to allow for chaining.
+   */
+  public function setVersion($version) {
+    $this->attemptWrite();
+    $this->version = $version;
+    return $this;
+  }
+
+  /**
+   * Get the asset library's version.
+   *
+   * @return string
+   *   The version of the asset library.
+   */
+  public function getVersion() {
+    return $this->version;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasDependencies() {
+    return !empty($this->dependencies);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addDependency($module, $name) {
+    $this->attemptWrite();
+    $this->dependencies[] = array($module, $name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearDependencies() {
+    $this->attemptWrite();
+    $this->dependencies = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDependencyInfo() {
+    return $this->dependencies;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function before($asset) {
+    $this->successors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function after($asset) {
+    $this->predecessors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPredecessors() {
+    return $this->predecessors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSuccessors() {
+    return $this->successors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearSuccessors() {
+    $this->successors = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearPredecessors() {
+    $this->predecessors = array();
+  }
+
+  /**
+   * Checks if the asset library is frozen, throws an exception if it is.
+   */
+  protected function attemptWrite() {
+    if ($this->isFrozen()) {
+      throw new \LogicException('Metadata cannot be modified on a frozen AssetLibrary.');
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/Collection/Iterator/AssetSubtypeFilterIterator.php b/core/lib/Drupal/Core/Asset/Collection/Iterator/AssetSubtypeFilterIterator.php
new file mode 100644
index 0000000..5b2c1e5
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/Iterator/AssetSubtypeFilterIterator.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\Iterator\AssetSubtypeFilterIterator.
+ */
+
+namespace Drupal\Core\Asset\Collection\Iterator;
+
+/**
+ * Given an Iterator whose elements are AssetInterface instances, this iterator
+ * will only accept those assets whose type string matches the string passed
+ * to this instance's constructor.
+ */
+class AssetSubtypeFilterIterator extends \FilterIterator {
+
+  /**
+   * The type string against which assets should be compared.
+   *
+   * @var string
+   */
+  protected $match;
+
+  public function __construct(\Iterator $iterator, $match) {
+    parent::__construct($iterator);
+    $this->match = $match;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function accept() {
+    return $this->current()->getAssetType() === $this->match;
+  }
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionAggregator.php b/core/lib/Drupal/Core/Asset/CssCollectionAggregator.php
new file mode 100644
index 0000000..626cc1c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/CssCollectionAggregator.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssCollectionAggregator.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\Aggregate\CssAggregateAsset;
+use Drupal\Core\Asset\Collection\AssetCollection;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+use Drupal\Core\Asset\GroupSort\AssetGroupSorterInterface;
+
+/**
+ * Aggregates CSS assets.
+ */
+class CssCollectionAggregator implements AssetCollectionAggregatorInterface {
+
+  /**
+   * The group-and-sorter to use to produce the optimal aggregable list.
+   *
+   * @var AssetGroupSorterInterface
+   */
+  protected $sorter;
+
+  /**
+   * An array of optimal groups for the assets currently being processed.
+   *
+   * This is ephemeral state; it is only stored as an object property in order
+   * to avoid doing certain processing twice.
+   *
+   * @var array
+   */
+  protected $optimal;
+
+  /**
+   * @var \SplObjectStorage;
+   */
+  protected $optimal_lookup;
+
+  public function __construct(AssetGroupSorterInterface $sorter) {
+    $this->sorter = $sorter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function aggregate(AssetCollectionInterface $collection) {
+    $tsl = $this->sorter->groupAndSort($collection);
+
+    $processed = new AssetCollection();
+    $last_key = FALSE;
+    foreach ($tsl as $asset) {
+      $key = $this->sorter->getGroupingKey($asset);
+
+      if ($key && $key !== $last_key) {
+        $aggregate = new CssAggregateAsset($asset->getMetadata());
+        $processed->add($aggregate);
+      }
+
+      $aggregate->add($asset);
+      $last_key = $key;
+    }
+
+    return $processed;
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerNouveaux.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerNouveaux.php
new file mode 100644
index 0000000..1e64377
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerNouveaux.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssCollectionOptimizerNouveaux.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+
+/**
+ * Optimizes a collection of CSS assets.
+ */
+class CssCollectionOptimizerNouveaux implements AssetCollectionOptimizerNouveauxInterface {
+
+  /**
+   * A CSS asset aggregator.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionAggregatorInterface
+   */
+  protected $aggregator;
+
+  /**
+   * A CSS asset optimizer.
+   *
+   * @var \Drupal\Core\Asset\CssOptimizer
+   */
+  protected $optimizer;
+
+  /**
+   * An asset dumper.
+   *
+   * @var \Drupal\Core\Asset\AssetDumper
+   */
+  protected $dumper;
+
+  /**
+   * The state key/value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs a CssCollectionOptimizerNouveaux.
+   *
+   * @param \Drupal\Core\Asset\AssetCollectionAggregatorInterface
+   *   The aggregator for CSS assets.
+   * @param \Drupal\Core\Asset\AssetOptimizerInterface
+   *   The optimizer for a single CSS asset.
+   * @param \Drupal\Core\Asset\AssetDumperInterface
+   *   The dumper for optimized CSS assets.
+   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   *   The state key/value store.
+   */
+  public function __construct(AssetCollectionAggregatorInterface $aggregator, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, KeyValueStoreInterface $state) {
+    $this->aggregator = $aggregator;
+    $this->optimizer = $optimizer;
+    $this->dumper = $dumper;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function optimize(AssetCollectionInterface $collection) {
+    $collection = $this->aggregator->aggregate($collection);
+
+    // Get the map of all aggregates that have been generated so far.
+    $map = $this->state->get('drupal_css_cache_files') ?: array();
+    foreach ($collection as $asset) {
+      if ($asset->isPreprocessable()) {
+        $id = $asset->id();
+        $uri = isset($map[$id]) ? $map[$id] : '';
+        if (empty($uri) || !file_exists($uri)) {
+          // TODO optimizer needs to be refactored to basically just set filters.
+          $this->optimizer->optimize($asset);
+          // TODO refactor dumper to not need second param
+          $this->dumper->dump($asset, 'css');
+        }
+      }
+    }
+
+    return $collection;
+  }
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Exception/AssetTypeMismatchException.php b/core/lib/Drupal/Core/Asset/Exception/AssetTypeMismatchException.php
new file mode 100644
index 0000000..ed651bf
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Exception/AssetTypeMismatchException.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Exception\AssetTypeMismatchException.
+ */
+
+namespace Drupal\Core\Asset\Exception;
+
+/**
+ * Thrown when asset subtypes (i.e., CSS vs. JS) are incorrectly mixed.
+ *
+ * For example, if a CSS asset is added to a JS collection, this should be
+ * thrown.
+ */
+class AssetTypeMismatchException extends \InvalidArgumentException {}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Exception/LockedObjectException.php b/core/lib/Drupal/Core/Asset/Exception/LockedObjectException.php
new file mode 100644
index 0000000..192928c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Exception/LockedObjectException.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Exception\LockedObjectException.
+ */
+
+namespace Drupal\Core\Asset\Exception;
+
+/**
+ * Exception thrown when a locking-protected operation is attempted on a locked
+ * object, or if a locking/unlocking operation is performed incorrectly.
+ */
+class LockedObjectException extends \LogicException {}
diff --git a/core/lib/Drupal/Core/Asset/Exception/UnsupportedAsseticBehaviorException.php b/core/lib/Drupal/Core/Asset/Exception/UnsupportedAsseticBehaviorException.php
new file mode 100644
index 0000000..c25473c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Exception/UnsupportedAsseticBehaviorException.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\UnsupportedAsseticBehaviorException.
+ */
+
+namespace Drupal\Core\Asset\Exception;
+
+/**
+ * Assetic supports certain interactions with methods that we do not. This
+ * exception is thrown when such methods are touched.
+ */
+class UnsupportedAsseticBehaviorException extends \LogicException {}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/ExternalAsset.php b/core/lib/Drupal/Core/Asset/ExternalAsset.php
new file mode 100644
index 0000000..2d9705c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/ExternalAsset.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\ExternalAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Util\PathUtils;
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\BaseAsset;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+
+class ExternalAsset extends BaseAsset {
+
+  protected $sourceUrl;
+
+  public function __construct(AssetMetadataBag $metadata, $sourceUrl, $filters = array()) {
+    if (FALSE === strpos($sourceUrl, '://')) {
+      throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL.', $sourceUrl));
+    }
+
+    $this->sourceUrl = $sourceUrl;
+    $this->ignoreErrors = FALSE; // TODO expose somehow
+
+    list($scheme, $url) = explode('://', $sourceUrl, 2);
+    list($host, $path) = explode('/', $url, 2);
+
+    parent::__construct($metadata, $filters, $scheme.'://'.$host, $path);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->sourceUrl;
+  }
+
+  /**
+   * Returns the time the current asset was last modified.
+   *
+   * @todo copied right from Assetic. needs to be made more Drupalish.
+   *
+   * @return integer|null A UNIX timestamp
+   */
+  public function getLastModified() {
+    if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(array('http' => array('method' => 'HEAD'))))) {
+      foreach ($http_response_header as $header) {
+        if (0 === stripos($header, 'Last-Modified: ')) {
+          list(, $mtime) = explode(':', $header, 2);
+
+          return strtotime(trim($mtime));
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(FilterInterface $additionalFilter = NULL) {
+    // TODO dumb and kinda wrong, decide how to do this right.
+    throw new UnsupportedAsseticBehaviorException('Drupal does not support the retrieval or manipulation of remote assets.');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php
new file mode 100644
index 0000000..3125b26
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php
@@ -0,0 +1,267 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Core\Asset\AssetCollector.
+ */
+
+namespace Drupal\Core\Asset\Factory;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+use Drupal\Core\Asset\Exception\LockedObjectException;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+
+/**
+ * A class that helps to create and collect assets.
+ *
+ * This class should be set with appropriate defaults, injected with an AssetBag
+ * for collection, then injected into an asset-producing segment of code in
+ * order to ease the creation and collection of asset information.
+ */
+class AssetCollector implements AssetCollectorInterface {
+
+  /**
+   * The collection used to store any assets that are added.
+   *
+   * @var \Drupal\Core\Asset\Collection\AssetCollectionInterface
+   */
+  protected $collection;
+
+  /**
+   * Flag indicating whether or not the object is locked.
+   *
+   * Locking prevents modifying the underlying defaults or swapping in/out the
+   * contained collection.
+   *
+   * @var bool
+   */
+  protected $locked = FALSE;
+
+  /**
+   * The key with which the lock was set.
+   *
+   * An identical value (===) must be provided to unlock the collector.
+   *
+   * There are no type restrictions.
+   *
+   * @var mixed
+   */
+  protected $lockKey;
+
+  /**
+   * The default metadata bag that will be cloned and injected into all CSS
+   * assets that are created.
+   *
+   * @var CssMetadataBag
+   */
+  protected $defaultCssMetadata;
+
+  /**
+   * The default metadata bag that will be cloned and injected into all JS
+   * assets that are created.
+   *
+   * @var JsMetadataBag
+   */
+  protected $defaultJsMetadata;
+
+  /**
+   * The last CSS asset created by this collector, if any.
+   *
+   * This is used to conveniently create sequencing relationships between CSS
+   * assets as they pass through the collector.
+   *
+   * @var AssetInterface
+   */
+  protected $lastCss;
+
+  /**
+   * A map of asset source type string ids to their fully qualified classes.
+   *
+   * @var array
+   */
+  protected $classMap = array(
+    'file' => 'Drupal\\Core\\Asset\\FileAsset',
+    'external' => 'Drupal\\Core\\Asset\\ExternalAsset',
+    'string' => 'Drupal\\Core\\Asset\\StringAsset',
+  );
+
+  public function __construct(AssetCollectionInterface $collection = NULL) {
+    $this->restoreDefaults();
+
+    if (!is_null($collection)) {
+      $this->setCollection($collection);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(AssetInterface $asset) {
+    if (empty($this->collection)) {
+      throw new \RuntimeException('No collection is currently attached to this collector.');
+    }
+    $this->collection->add($asset);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function create($asset_type, $source_type, $data, $options = array(), $filters = array(), $keep_last = TRUE) {
+    // TODO this normalization points to a deeper modeling problem.
+    $source_type = $source_type == 'inline' ? 'string' : $source_type;
+
+    if (!in_array($asset_type, array('css', 'js'))) {
+      throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are allowed, "%s" requested.', $asset_type));
+    }
+    if (!isset($this->classMap[$source_type])) {
+      throw new \InvalidArgumentException(sprintf('Only sources of type "file", "string", or "external" are allowed, "%s" requested.', $source_type));
+    }
+
+    $metadata = $this->getMetadataDefaults($asset_type);
+    if (!empty($options)) {
+      $metadata->replace($options);
+    }
+
+    $class = $this->classMap[$source_type];
+    $asset = new $class($metadata, $data, $filters);
+
+    if (!empty($this->collection)) {
+      $this->add($asset);
+    }
+
+    if ($asset_type == 'css' && !empty($this->lastCss)) {
+      $asset->after($this->lastCss);
+    }
+
+    if ($keep_last) {
+      $this->lastCss = $asset;
+    }
+
+    return $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearLastCss() {
+    unset($this->lastCss);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCollection(AssetCollectionInterface $collection) {
+    if ($this->isLocked()) {
+      throw new LockedObjectException('The collector instance is locked. A new collection cannot be attached to a locked collector.');
+    }
+    $this->collection = $collection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearCollection() {
+    if ($this->isLocked()) {
+      throw new LockedObjectException('The collector instance is locked. Collections cannot be cleared on a locked collector.');
+    }
+    $this->collection = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasCollection() {
+    return $this->collection instanceof AssetCollectionInterface;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function lock($key) {
+    if ($this->isLocked()) {
+      throw new LockedObjectException('Collector is already locked.', E_WARNING);
+    }
+
+    $this->locked = TRUE;
+    $this->lockKey = $key;
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unlock($key) {
+    if (!$this->isLocked()) {
+      throw new LockedObjectException('Collector is not locked', E_WARNING);
+    }
+
+    if ($this->lockKey !== $key) {
+      throw new LockedObjectException('Attempted to unlock Collector with incorrect key.', E_WARNING);
+    }
+
+    $this->locked = FALSE;
+    $this->lockKey = NULL;
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isLocked() {
+    return $this->locked;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if an invalid metadata type is provided (i.e., not 'css' or 'js').
+   */
+  public function setDefaultMetadata(AssetMetadataBag $metadata) {
+    if ($this->isLocked()) {
+      throw new LockedObjectException('The collector instance is locked. Asset defaults cannot be modified on a locked collector.');
+    }
+
+    $type = $metadata->getType();
+
+    if ($type === 'css') {
+      $this->defaultCssMetadata = $metadata;
+    }
+    elseif ($type === 'js') {
+      $this->defaultJsMetadata = $metadata;
+    }
+    else {
+      throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are supported, "%s" requested.', $type));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadataDefaults($type) {
+    if ($type === 'css') {
+      return clone $this->defaultCssMetadata;
+    }
+    elseif ($type === 'js') {
+      return clone $this->defaultJsMetadata;
+    }
+    else {
+      throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are supported, "%s" requested.', $type));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function restoreDefaults() {
+    if ($this->isLocked()) {
+      throw new LockedObjectException('The collector instance is locked. Asset defaults cannot be modified on a locked collector.');
+    }
+    $this->defaultCssMetadata = new CssMetadataBag();
+    $this->defaultJsMetadata = new JsMetadataBag();
+  }
+}
+
diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetCollectorInterface.php b/core/lib/Drupal/Core/Asset/Factory/AssetCollectorInterface.php
new file mode 100644
index 0000000..2c5a817
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Factory/AssetCollectorInterface.php
@@ -0,0 +1,210 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Factory\AssetCollectorInterface.
+ */
+
+namespace Drupal\Core\Asset\Factory;
+
+use Drupal\Core\Asset\Exception\LockedObjectException;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+
+/**
+ * Interface for asset collectors, which help to create and collect assets.
+ *
+ * A "collector" is an elaboration on a factory pattern. Collectors can
+ * optionally contain a collection that is designed to accommodate the type of
+ * asset produced by the factory. If the collector has a collection, then
+ * calling its factory methods will cause the created object to automatically
+ * be added to the contained collection. Thus, the collector can be safely
+ * injected into code whose only responsibility should be to append new items
+ * to the collection.
+ */
+interface AssetCollectorInterface {
+
+  /**
+   * Adds an asset to the contained collection.
+   *
+   * It is not necessary to call this method on assets that were created via the
+   * create() method; that is done implicitly.
+   *
+   * @param AssetInterface $asset
+   *   The asset to add to the contained collection.
+   *
+   * @throws \RuntimeException
+   *   Thrown if the collector has no contained collection.
+   */
+  public function add(AssetInterface $asset);
+
+  /**
+   * Creates an asset, stores it in the collector's collection, and returns it.
+   *
+   * TODO flesh out these docs to be equivalent to drupal_add_css/js()
+   *
+   * @param string $asset_type
+   *      A string indicating the asset type - must be 'css' or 'js'.
+   * @param string $source_type
+   *      A string indicating the source type - 'file', 'external' or 'string'.
+   * @param string $data
+   *      A string containing data that defines the asset. Appropriate values vary
+   *      depending on the source_type param:
+   *      - 'file': the relative path to the file, or a stream wrapper URI.
+   *      - 'external': the absolute path to the external asset.
+   *      - 'string': a string containing valid CSS or Javascript to be injected
+   *      directly onto the page.
+   * @param array $options
+   *      (optional) An array of metadata to explicitly set on the asset. These
+   *      will override metadata defaults that are injected onto the asset at
+   *      creation time.
+   * @param array $filters
+   *      (optional) An array of filters to apply to the object
+   *      TODO this should, maybe, be removed entirely
+   * @param bool $keep_last
+   *      (optional) Whether or not to retain the created asset for automated
+   *      ordering purposes. Only applies to CSS. Note that passing FALSE will not
+   *      prevent a CSS asset that is being created from automatically being
+   *      after() the existing lastCss asset, if one exists. For that,
+   *
+   * @see clearLastCss().
+   *
+   * @return \Drupal\Core\Asset\AssetInterface
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if an invalid asset type or source type is passed.
+   */
+  public function create($asset_type, $source_type, $data, $options = array(), $filters = array(), $keep_last = TRUE);
+
+  /**
+   * Clears the asset stored in lastCss.
+   *
+   * Ordinarily, using the create() factory to generate a CSS asset object will
+   * automatically set up an ordering relationship between that asset and the
+   * previous CSS asset that was created. This is intended to facilitate the
+   * rigid ordering that authors likely expect for CSS assets declared together
+   * in a contiguous series.
+   *
+   * This method clears the last stored CSS asset. It should be called when the
+   * end of such a contiguous series is reached, or by the asset creator
+   * themselves if they want to avoid the creation of the ordering relationship.
+   *
+   * @return AssetCollector
+   *   The current AssetCollector instance, for easy chaining.
+   */
+  public function clearLastCss();
+
+  /**
+   * Sets the internal collection for this collector.
+   *
+   * As long as this collection is present, the collector will automatically add
+   * all assets generated via its create() method to the collection.
+   *
+   * @param AssetCollectionInterface $collection
+   *
+   * @return void
+   *
+   * @throws LockedObjectException
+   *   Thrown if the collector is locked when this method is called.
+   */
+  public function setCollection(AssetCollectionInterface $collection);
+
+  /**
+   * Clears the internal collection for this collector.
+   *
+   * @return void
+   *
+   * @throws LockedObjectException
+   *   Thrown if the collector is locked when this method is called.
+   */
+  public function clearCollection();
+
+  /**
+   * Indicates whether or not this collector currently contains a collection.
+   *
+   * @return bool
+   */
+  public function hasCollection();
+
+  /**
+   * Locks this collector, using the provided key.
+   *
+   * The collector can only be unlocked by providing the same key. Key
+   * comparison is done using the identity operator (===), so avoid using an
+   * object as a key if there is any chance the collector will be serialized.
+   *
+   * @param mixed $key
+   *   The key used to lock the collector.
+   *
+   * @return void
+   *
+   * @throws LockedObjectException
+   *   Thrown if the collector is already locked.
+   */
+  public function lock($key);
+
+  /**
+   * Attempts to unlock the collector with the provided key.
+   *
+   * Key comparison is done using the identity operator (===).
+   *
+   * @param mixed $key
+   *   The key with which to unlock the collector.
+   *
+   * @return void
+   *
+   * @throws LockedObjectException
+   *   Thrown if the incorrect key is provided, or if the collector is not
+   *   locked.
+   */
+  public function unlock($key);
+
+  /**
+   * Indicates whether this collector is currently locked.
+   *
+   * @return bool
+   */
+  public function isLocked();
+
+  /**
+   * Sets the default metadata for a particular type.
+   *
+   * The type of metadata is determined internally by calling
+   * AssetMetadataBag::getType().
+   *
+   * @param AssetMetadataBag $metadata
+   *   The default metadata object.
+   *
+   * @return void
+   */
+  public function setDefaultMetadata(AssetMetadataBag $metadata);
+
+  /**
+   * Gets a clone of the metadata bag for a given asset type.
+   *
+   * Clones are returned in order to ensure there is a unique metadata object
+   * for every asset, and that the default metadata contained in the collector
+   * cannot be modified externally.
+   *
+   * @param $type
+   *   A string, 'css' or 'js', indicating the type of metadata to retrieve.
+   *
+   * @return AssetMetadataBag
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if a type other than 'css' or 'js' is provided.
+   */
+  public function getMetadataDefaults($type);
+
+  /**
+   * Restores metadata default bags to their default state.
+   *
+   * This simply creates new instances of CssMetadataBag and JsMetadataBag, as
+   * those classes have the normal defaults as hardmapped properties.
+   *
+   * @throws LockedObjectException
+   *   Thrown if the incorrect key is provided.
+   */
+  public function restoreDefaults();
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/FileAsset.php b/core/lib/Drupal/Core/Asset/FileAsset.php
new file mode 100644
index 0000000..8665c0e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/FileAsset.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\FileAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Util\PathUtils;
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\BaseAsset;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+class FileAsset extends BaseAsset {
+
+  protected $source;
+
+  public function __construct(AssetMetadataBag $metadata, $source, $filters = array()) {
+    $sourceRoot = dirname($source);
+    $sourcePath = basename($source);
+    $this->source = $source;
+
+    parent::__construct($metadata, $filters, $sourceRoot, $sourcePath);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->source;
+  }
+
+  /**
+   * Returns the time the current asset was last modified.
+   *
+   * @return integer|null A UNIX timestamp
+   *
+   * @throws \RuntimeException
+   *   Thrown if the source file does not exist.
+   */
+  public function getLastModified() {
+    if (!is_file($this->source)) {
+      throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $this->source));
+    }
+
+    return filemtime($this->source);
+  }
+
+  /**
+   * Loads the asset into memory and applies load filters.
+   *
+   * You may provide an additional filter to apply during load.
+   *
+   * @param FilterInterface $additionalFilter An additional filter
+   *
+   * @throws \RuntimeException
+   *   Thrown if the source file does not exist.
+   */
+  public function load(FilterInterface $additionalFilter = NULL) {
+    if (!is_file($this->source)) {
+      throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $this->source));
+    }
+
+    $this->doLoad(file_get_contents($this->source), $additionalFilter);
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/GroupSort/AssetGraphSorter.php b/core/lib/Drupal/Core/Asset/GroupSort/AssetGraphSorter.php
new file mode 100644
index 0000000..6311f9f
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/GroupSort/AssetGraphSorter.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Sort\AssetGraphSorter.
+ */
+
+namespace Drupal\Core\Asset\GroupSort;
+
+use Drupal\Core\Asset\AssetGraph;
+use Gliph\Traversal\DepthFirst;
+use Gliph\Visitor\DepthFirstBasicVisitor;
+
+/**
+ * Sorts an AssetCollectionInterface's contents into a list using a graph.
+ */
+abstract class AssetGraphSorter implements AssetGroupSorterInterface {
+
+  /**
+   * Creates a queue of starting vertices that will facilitate an ideal TSL.
+   *
+   * @param AssetGraph $original
+   * @param AssetGraph $transpose
+   *
+   * @return \SplQueue $queue
+   *   A queue of vertices
+   */
+  protected function createSourceQueue(AssetGraph $original, AssetGraph $transpose) {
+    $reach_visitor = new DepthFirstBasicVisitor();
+
+    // Find source vertices (outdegree 0) in the original graph
+    $sources = DepthFirst::find_sources($original, $reach_visitor);
+
+    // Traverse the transposed graph to get reachability data on each vertex
+    DepthFirst::traverse($transpose, $reach_visitor, clone $sources);
+
+    // Sort vertices via a PriorityQueue based on total reach
+    $pq = new \SplPriorityQueue();
+    foreach ($sources as $vertex) {
+      $pq->insert($vertex, count($reach_visitor->getReachable($vertex)));
+    }
+
+    // Dump the priority queue into a normal queue
+    // TODO maybe gliph should support pq/heaps as a queue type on which to operate?
+    $queue = new \SplQueue();
+    foreach ($pq as $vertex) {
+      $queue->push($vertex);
+    }
+
+    return $queue;
+  }
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/GroupSort/AssetGroupSorterInterface.php b/core/lib/Drupal/Core/Asset/GroupSort/AssetGroupSorterInterface.php
new file mode 100644
index 0000000..eeb771c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/GroupSort/AssetGroupSorterInterface.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Sort\AssetGroupSorterInterface.
+ */
+
+namespace Drupal\Core\Asset\GroupSort;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+
+/**
+ * Interface for classes that sort asset collections for output.
+ */
+interface AssetGroupSorterInterface {
+
+  /**
+   * Sorts the provided collection into an output-safe linear list.
+   *
+   * Accounts for dependency and ordering metadata.
+   *
+   * @param AssetCollectionInterface $collection
+   *   The collection to group and sort.
+   *
+   * @return array
+   *   A sorted, linear list of assets that respects all necessary dependency
+   *   information.
+   */
+  public function groupAndSort(AssetCollectionInterface $collection);
+
+  /**
+   * Provides a string key identifying the grouping parameters for an asset.
+   *
+   * Assets with the same grouping key are in alignment, meaning that they can
+   * be safely aggregated together into a single, composite asset.
+   *
+   * @param AssetInterface $asset
+   *   The asset for which to produce a grouping key.
+   *
+   * @return string|FALSE
+   *   A string containing grouping parameters, or FALSE if the asset is
+   *   ineligible for grouping.
+   */
+  public static function getGroupingKey(AssetInterface $asset);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/GroupSort/CssGraphSorter.php b/core/lib/Drupal/Core/Asset/GroupSort/CssGraphSorter.php
new file mode 100644
index 0000000..3f373c5
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/GroupSort/CssGraphSorter.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\GroupSort\CssGraphSorter.
+ */
+
+namespace Drupal\Core\Asset\GroupSort;
+
+use Drupal\Core\Asset\OptimallyGroupedTSLVisitor;
+use Drupal\Core\Asset\ExternalAsset;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\FileAsset;
+use Drupal\Core\Asset\Collection\AssetCollectionInterface;
+use Drupal\Core\Asset\AssetGraph;
+use Gliph\Traversal\DepthFirst;
+use Drupal\Core\Asset\StringAsset;
+
+/**
+ * Performs a graph sort on CSS assets.
+ */
+class CssGraphSorter extends AssetGraphSorter {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getGroupingKey(AssetInterface $asset) {
+    $meta = $asset->getMetadata();
+    // The browsers for which the CSS item needs to be loaded is part of the
+    // information that determines when a new group is needed, but the order
+    // of keys in the array doesn't matter, and we don't want a new group if
+    // all that's different is that order.
+    $browsers = $meta->get('browsers');
+    ksort($browsers);
+
+    if ($asset instanceof FileAsset) {
+      // Compose a string key out of the set of relevant properties.
+      // TODO - this ignores group, which is used in core's current implementation. wishful thinking? maybe, maybe not.
+      // TODO media has been pulled out - needs to be handled by the aggregator, wrapping css in media queries
+      $k = $asset->isPreprocessable()
+        ? implode(':', array('file', $meta->get('every_page'), implode('', $browsers)))
+        : FALSE;
+    }
+    else if ($asset instanceof StringAsset) {
+      // String items are always grouped.
+      // TODO use the term 'inline' here? do "string" and "inline" necessarily mean the same?
+      $k = implode(':', 'string', implode('', $browsers));
+    }
+    else if ($asset instanceof ExternalAsset) {
+      // Never group external assets.
+      $k = FALSE;
+    }
+    else {
+      throw new \UnexpectedValueException(sprintf('Unknown CSS asset type "%s" somehow made it into the CSS collection during grouping.', get_class($asset)));
+    }
+
+    return $k;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function groupAndSort(AssetCollectionInterface $collection) {
+    // We need to define the optimum minimal group set, given metadata
+    // boundaries across which aggregates cannot be safely made.
+    $optimal = array();
+
+    // Also create an SplObjectStorage to act as a lookup table on an asset to
+    // its group, if any.
+    // TODO try and find an elegant way to pass this out so we don't have to calculate keys twice
+    $optimal_lookup = new \SplObjectStorage();
+
+    // Finally, create a specialized directed adjacency list that will capture
+    // all ordering information.
+    $graph = new AssetGraph();
+
+    foreach ($collection->getCss() as $asset) {
+      $graph->addVertex($asset);
+
+      $k = self::getGroupingKey($asset);
+
+      if ($k === FALSE) {
+        // Record no optimality information for ungroupable assets; they will
+        // be visited normally and rearranged as needed.
+        continue;
+      }
+
+      if (!isset($optimal[$k])) {
+        // Create an SplObjectStorage to represent each set of assets that would
+        // optimally be grouped together.
+        $optimal[$k] = new \SplObjectStorage();
+      }
+      $optimal[$k]->attach($asset, $k);
+      $optimal_lookup->attach($asset, $optimal[$k]);
+    }
+
+    // First, transpose the graph in order to get an appropriate answer
+    $transpose = $graph->transpose();
+
+    // Create a queue of start vertices to prime the traversal.
+    $queue = $this->createSourceQueue($graph, $transpose);
+
+    // Now, create the visitor and walk the graph to get an optimal TSL.
+    $visitor = new OptimallyGroupedTSLVisitor($optimal, $optimal_lookup);
+    DepthFirst::traverse($transpose, $visitor, $queue);
+
+    return $visitor->getTSL();
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Metadata/AssetMetadataBag.php b/core/lib/Drupal/Core/Asset/Metadata/AssetMetadataBag.php
new file mode 100644
index 0000000..4681ea9
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Metadata/AssetMetadataBag.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetMetadataBag.
+ */
+
+namespace Drupal\Core\Asset\Metadata;
+
+/**
+ * A bag for holding asset metadata.
+ *
+ * For each declared property, this bag keeps track of both a default value and
+ * an explicit value. Defaults can only be set in the constructor, explicit
+ * values can be set at any time. Explicit values are coalesced over default
+ * values.
+ *
+ * TODO this is totally not specific to assets - move it somewhere more generic?
+ * TODO it's maybe not so important to rigorously control access to the defaults data
+ */
+abstract class AssetMetadataBag implements \IteratorAggregate, \Countable {
+
+  /**
+   * Contains default values.
+   *
+   * @var array
+   */
+  protected $default = array();
+
+  /**
+   * Contains explicitly set values.
+   *
+   * @var array
+   */
+  protected $explicit = array();
+
+  public function __construct(array $default = array()) {
+    $this->default = array_replace_recursive($this->default, $default);
+  }
+
+  /**
+   * Indicates the type of asset for which this metadata is intended.
+   *
+   * @return string
+   *   A string indicating type - 'js' or 'css' are the expected values.
+   */
+  abstract public function getType();
+
+  public function all() {
+    return array_replace_recursive($this->default, $this->explicit);
+  }
+
+  public function keys() {
+    return array_keys($this->all());
+  }
+
+  public function has($key) {
+    return array_key_exists($key, $this->explicit) ||
+      array_key_exists($key, $this->default);
+  }
+
+  public function set($key, $value) {
+    $this->explicit[$key] = $value;
+  }
+
+  /**
+   * Reverts the associated with the passed key back to its default.
+   *
+   * If no default is set, the value for that key simply disappears.
+   *
+   * @param $key
+   *   The key identifying the value to revert.
+   *
+   * @return void
+   */
+  public function revert($key) {
+    unset($this->explicit[$key]);
+  }
+
+  public function isDefault($key) {
+    return !array_key_exists($key, $this->explicit) &&
+      array_key_exists($key, $this->default);
+  }
+
+  public function add(array $values = array()) {
+    $this->explicit = array_replace_recursive($this->explicit, $values);
+  }
+
+  public function replace(array $values = array()) {
+    $this->explicit = $values;
+  }
+
+  public function get($key) {
+    if (array_key_exists($key, $this->explicit)) {
+      return $this->explicit[$key];
+    }
+
+    if (array_key_exists($key, $this->default)) {
+      return $this->default[$key];
+    }
+  }
+
+  public function getIterator() {
+    return new \ArrayIterator($this->all());
+  }
+
+  public function count() {
+    return count($this->all());
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Metadata/CssMetadataBag.php b/core/lib/Drupal/Core/Asset/Metadata/CssMetadataBag.php
new file mode 100644
index 0000000..d4c6322
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Metadata/CssMetadataBag.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssMetadataBag.
+ */
+
+namespace Drupal\Core\Asset\Metadata;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Manages CSS asset default and explicit metadata.
+ */
+class CssMetadataBag extends AssetMetadataBag {
+
+  protected $default = array(
+    'group' => CSS_AGGREGATE_DEFAULT, // TODO Just removing this would be *awesome*.
+    'every_page' => FALSE,
+    'media' => 'all',
+    'preprocess' => TRUE,
+    'browsers' => array(
+      'IE' => TRUE,
+      '!IE' => TRUE,
+    ),
+  );
+
+  public function __construct(array $default = array()) {
+    $this->default = array_replace_recursive($this->default, $default);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getType() {
+    return 'css';
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Metadata/JsMetadataBag.php b/core/lib/Drupal/Core/Asset/Metadata/JsMetadataBag.php
new file mode 100644
index 0000000..39927c0
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Metadata/JsMetadataBag.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\JsMetadataBag.
+ */
+
+namespace Drupal\Core\Asset\Metadata;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Manages Javascript asset default and explicit metadata.
+ */
+class JsMetadataBag extends AssetMetadataBag {
+
+  protected $default = array(
+    'group' => JS_DEFAULT,
+    'every_page' => FALSE,
+    'scope' => 'header',
+    'cache' => TRUE,
+    'preprocess' => TRUE,
+    'attributes' => array(),
+    'version' => NULL,
+    'browsers' => array(),
+  );
+
+  public function __construct(array $default = array()) {
+    $this->default = array_replace_recursive($this->default, $default);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getType() {
+    return 'js';
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/OptimallyGroupedTSLVisitor.php b/core/lib/Drupal/Core/Asset/OptimallyGroupedTSLVisitor.php
new file mode 100644
index 0000000..cc77d0e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/OptimallyGroupedTSLVisitor.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\OptimallyGroupedTSLVisitor.
+ */
+
+namespace Drupal\Core\Asset;
+use Gliph\Visitor\DepthFirstVisitorInterface;
+
+/**
+ * DepthFirst visitor intended for use with a asset data that will select the
+ * optimal valid TSL, given a preferred grouping of vertices.
+ */
+class OptimallyGroupedTSLVisitor implements DepthFirstVisitorInterface {
+
+  /**
+   * @var array
+   */
+  protected $tsl;
+
+  /**
+   * @var array
+   */
+  protected $groups;
+
+  /**
+   * @var \SplObjectStorage
+   */
+  protected $vertexMap;
+
+  /**
+   * Creates a new optimality visitor.
+   *
+   * @param array $groups
+   *   An array of SplObjectStorage, the contents of each representing an
+   *   optimal grouping.
+   *
+   * @param \SplObjectStorage $vertex_map
+   *   A map of vertices to the group in which they reside, if any.
+   */
+  public function __construct($groups, \SplObjectStorage $vertex_map) {
+    $this->tsl = array();
+    $this->groups = $groups;
+    $this->vertexMap = $vertex_map;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onInitializeVertex($vertex, $source, \SplQueue $queue) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onBackEdge($vertex, \Closure $visit) {
+    // TODO: Implement onBackEdge() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onStartVertex($vertex, \Closure $visit) {
+    $this->active->attach($vertex);
+
+    // If there's a record in the vertex map, it means this vertex has an
+    // optimal group. Remove it from that group, as it being here means it's
+    // been visited.
+    if ($this->vertexMap->contains($vertex)) {
+      $this->vertexMap[$vertex]->detach($vertex);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onExamineEdge($from, $to, \Closure $visit) {}
+
+  /**
+   * Here be the unicorns.
+   *
+   * Once the depth-first traversal is done for a vertex, rather than
+   * simply pushing it onto the TSL and moving on (as in a basic depth-first
+   * traversal), if the finished vertex is a member of an optimality group, then
+   * visit all other (unvisited) members of that optimality group.
+   *
+   * This ensures the final TSL has the tightest possible adherence to the
+   * defined optimal groupings while still respecting the DAG.
+   *
+   */
+  public function onFinishVertex($vertex, \Closure $visit) {
+    // TODO this still isn't quite optimal; it can split groups unnecessarily. tweak a little more.
+    // TODO explore risk of hitting the 100 call stack limit
+    if ($this->vertexMap->contains($vertex)) {
+      foreach ($this->vertexMap[$vertex] as $vertex) {
+        $visit($vertex);
+      }
+    }
+    $this->tsl[] = $vertex;
+  }
+
+  /**
+   * Returns the TSL produced by a depth-first traversal.
+   *
+   * @return array
+   *   A topologically sorted list of vertices.
+   */
+  public function getTSL() {
+    return $this->tsl;
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/StringAsset.php b/core/lib/Drupal/Core/Asset/StringAsset.php
new file mode 100644
index 0000000..e98000e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/StringAsset.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\StringAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Filter\FilterInterface;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Asset\BaseAsset;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+class StringAsset extends BaseAsset {
+
+  /**
+   * The string id of this asset.
+   *
+   * This is generated by hashing the content of the asset when the object is
+   * first created. The id does NOT change if the content is changed later.
+   *
+   * @var string
+   */
+  protected $id;
+
+  protected $lastModified;
+
+  public function __construct(AssetMetadataBag $metadata, $content, $filters = array()) {
+    if (!is_string($content)) {
+      throw new \InvalidArgumentException('StringAsset requires a string for its content.');
+    }
+
+    $this->id= empty($content) ? Crypt::randomStringHashed(32) : hash('sha256', $content);
+    $this->setContent($content);
+    $this->lastModified = REQUEST_TIME; // TODO this is terrible
+
+    parent::__construct($metadata, $filters);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->id;
+  }
+
+  public function setLastModified($last_modified) {
+    $this->lastModified = $last_modified;
+  }
+
+  public function getLastModified() {
+    return $this->lastModified;
+  }
+
+  public function load(FilterInterface $additionalFilter = NULL) {
+    $this->doLoad($this->getContent(), $additionalFilter);
+  }
+}
diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php
index 579e841..1768991 100644
--- a/core/modules/block/lib/Drupal/block/BlockBase.php
+++ b/core/modules/block/lib/Drupal/block/BlockBase.php
@@ -11,6 +11,7 @@
 use Drupal\block\BlockInterface;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Asset\Factory\AssetCollector;
 
 /**
  * Defines a base block implementation that most blocks plugins will extend.
@@ -181,5 +182,10 @@ public function getMachineNameSuggestion() {
 
     return $transliterated;
   }
-
+  /**
+   * {@inheritdoc}
+   */
+  public function declareAssets(AssetCollector $collector) {}
 }
+
+
diff --git a/core/modules/block/lib/Drupal/block/BlockPluginInterface.php b/core/modules/block/lib/Drupal/block/BlockPluginInterface.php
index b5433e6..d625616 100644
--- a/core/modules/block/lib/Drupal/block/BlockPluginInterface.php
+++ b/core/modules/block/lib/Drupal/block/BlockPluginInterface.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
 use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\Asset\Factory\AssetCollector;
 
 /**
  * Defines the required interface for all block plugins.
@@ -122,4 +123,12 @@ public function blockSubmit($form, &$form_state);
    */
   public function getMachineNameSuggestion();
 
+  /**
+   * Declares the assets required by this block to a collector.
+   *
+   * @param \Drupal\Core\Asset\Factory\AssetCollector $collector
+   *
+   * @return void
+   */
+  public function declareAssets(AssetCollector $collector);
 }
diff --git a/core/tests/Drupal/Tests/Core/Asset/Aggregate/BaseAggregateAssetTest.php b/core/tests/Drupal/Tests/Core/Asset/Aggregate/BaseAggregateAssetTest.php
new file mode 100644
index 0000000..5a04a0c
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/Aggregate/BaseAggregateAssetTest.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Asset\Aggregate\BaseAggregateAssetTest.
+ */
+
+namespace Drupal\Tests\Core\Asset\Aggregate;
+
+use Drupal\Tests\Core\Asset\AssetUnitTest;
+
+/**
+ *
+ * @group Asset
+ */
+class BaseAggregateAssetTest extends AssetUnitTest {
+
+  protected $aggregate;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset aggregate tests',
+      'description' => 'Unit tests on BaseAggregateAsset',
+      'group' => 'Asset',
+    );
+  }
+
+  public function getAggregate($defaults = array()) {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag', $defaults);
+    $this->aggregate = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Aggregate\\BaseAggregateAsset');
+  }
+
+  public function testId() {
+  }
+
+  public function testGetAssetType() {
+
+  }
+
+  public function testGetMetadata() {
+
+  }
+
+  public function testContains() {
+
+  }
+
+  public function testGetById() {
+
+  }
+
+  public function testIsPreprocessable() {
+
+  }
+
+  public function testAll() {
+
+  }
+
+  public function testEnsureFilter() {
+
+  }
+
+  public function testGetFilters() {
+
+  }
+
+  public function testClearFilters() {
+
+  }
+
+  public function testGetContent() {
+
+  }
+
+  public function testSetContent() {
+
+  }
+
+  public function testGetSourceRoot() {
+
+  }
+
+  public function testGetSourcePath() {
+
+  }
+
+  public function testGetTargetPath() {
+
+  }
+
+  public function testSetTargetPath() {
+
+  }
+
+  public function testGetLastModified() {
+
+  }
+
+  public function testGetIterator() { // ??
+
+  }
+
+  public function testIsEmpty() {
+
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetGraphTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetGraphTest.php
new file mode 100644
index 0000000..b83698d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetGraphTest.php
@@ -0,0 +1,258 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Asset\AssetGraphTest.
+ */
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Core\Asset\AssetGraph;
+use Drupal\Core\Asset\BaseAsset;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ *
+ * @group Asset
+ */
+class AssetGraphTest extends AssetUnitTest {
+
+  /**
+   * @var AssetGraph
+   */
+  protected $graph;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset graph test',
+      'description' => 'Tests that custom additions in the asset graph work correctly.',
+      'group' => 'Asset',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    $this->graph = new AssetGraph();
+  }
+
+  /**
+   * Generates a simple mock asset object.
+   *
+   * @param string $id
+   *   An id to give the asset; it will returned from the mocked
+   *   AssetInterface::id() method.
+   *
+   * @return \PHPUnit_Framework_MockObject_MockObject
+   *   A mock of a BaseAsset object.
+   */
+  public function createBasicAssetMock($id = 'foo') {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $mock = $this->getMockBuilder('\\Drupal\\Core\\Asset\\BaseAsset')
+      ->setConstructorArgs(array($mockmeta))
+      ->getMock();
+
+    $mock->expects($this->any())
+      ->method('id')
+      ->will($this->returnValue($id));
+
+    $mock->expects($this->once())
+      ->method('getPredecessors')
+      ->will($this->returnValue(array()));
+
+    $mock->expects($this->once())
+      ->method('getSuccessors')
+      ->will($this->returnValue(array()));
+
+    return $mock;
+  }
+
+  public function doCheckVertexCount($count, AssetGraph $graph = NULL) {
+    $found = array();
+    $graph = is_null($graph) ? $this->graph : $graph;
+
+    $graph->eachVertex(function ($vertex) use (&$found) {
+      $found[] = $vertex;
+    });
+
+    $this->assertCount($count, $found);
+  }
+
+  public function doCheckVerticesEqual($vertices, AssetGraph $graph = NULL) {
+    $found = array();
+    $graph = is_null($graph) ? $this->graph : $graph;
+
+    $graph->eachVertex(function ($vertex) use (&$found) {
+      $found[] = $vertex;
+    });
+
+    $this->assertEquals($vertices, $found);
+  }
+
+  public function testAddSingleVertex() {
+    $mock = $this->createBasicAssetMock();
+
+    $mock->expects($this->exactly(2))
+      ->method('id')
+      ->will($this->returnValue('foo'));
+
+    $this->graph->addVertex($mock);
+
+    $this->doCheckVerticesEqual(array($mock));
+  }
+
+  /**
+   * @expectedException \Gliph\Exception\InvalidVertexTypeException
+   */
+  public function testAddInvalidVertexType() {
+    $this->graph->addVertex(new \stdClass());
+  }
+
+  /**
+   * @expectedException \LogicException
+   */
+  public function testExceptionOnRemoval() {
+    $mock = $this->createBasicAssetMock();
+    $this->graph->addVertex($mock);
+    $this->graph->removeVertex($mock);
+  }
+
+  public function testAddUnconnectedVertices() {
+    $foo = $this->createBasicAssetMock('foo');
+    $bar = $this->createBasicAssetMock('bar');
+
+    $this->graph->addVertex($foo);
+    $this->graph->addVertex($bar);
+
+    $this->doCheckVerticesEqual(array($foo, $bar));
+  }
+
+  /**
+   * Tests that edges are automatically created correctly when assets have
+   * sequencing information.
+   */
+  public function testAddConnectedVertices() {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $foo = $this->getMockBuilder('\\Drupal\\Core\\Asset\\BaseAsset')
+      ->setConstructorArgs(array($mockmeta))
+      ->getMock();
+
+    $foo->expects($this->exactly(3))
+      ->method('id')
+      ->will($this->returnValue('foo'));
+
+    $foo->expects($this->once())
+      ->method('getPredecessors')
+      ->will($this->returnValue(array('bar')));
+
+    $foo->expects($this->once())
+      ->method('getSuccessors')
+      ->will($this->returnValue(array('baz')));
+
+    $bar = $this->createBasicAssetMock('bar');
+    $baz = $this->createBasicAssetMock('baz');
+
+    $this->graph->addVertex($foo);
+    $this->graph->addVertex($bar);
+    $this->graph->addVertex($baz);
+
+    $this->doCheckVerticesEqual(array($foo, $bar, $baz));
+
+    $lister = function($vertex) use (&$out) {
+      $out[] = $vertex;
+    };
+
+    $out = array();
+    $this->graph->eachAdjacent($foo, $lister);
+    $this->assertEquals(array($bar), $out);
+
+    $out = array();
+    $this->graph->eachAdjacent($baz, $lister);
+    $this->assertEquals(array($foo), $out);
+
+    $out = array();
+    $this->graph->eachAdjacent($bar, $lister);
+    $this->assertEmpty($out);
+
+    // Now add another vertex with sequencing info that targets already-inserted
+    // vertices.
+
+    $qux = $this->getMockBuilder('\\Drupal\\Core\\Asset\\BaseAsset')
+      ->setConstructorArgs(array($mockmeta))
+      ->getMock();
+
+    $qux->expects($this->exactly(2))
+      ->method('id')
+      ->will($this->returnValue('qux'));
+
+    // Do this one with the foo vertex itself, not its string id.
+    $qux->expects($this->once())
+      ->method('getPredecessors')
+      ->will($this->returnValue(array($foo)));
+
+    $qux->expects($this->once())
+      ->method('getSuccessors')
+      ->will($this->returnValue(array('bar', 'baz')));
+
+    $this->graph->addVertex($qux);
+
+    $this->doCheckVerticesEqual(array($foo, $bar, $baz, $qux));
+
+    $out = array();
+    $this->graph->eachAdjacent($qux, $lister);
+    $this->assertEquals(array($foo), $out);
+
+    $out = array();
+    $this->graph->eachAdjacent($bar, $lister);
+    $this->assertEquals(array($qux), $out);
+
+    $out = array();
+    $this->graph->eachAdjacent($baz, $lister);
+    $this->assertEquals(array($foo, $qux), $out);
+  }
+
+  public function testTranspose() {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $foo = $this->getMockBuilder('\\Drupal\\Core\\Asset\\BaseAsset')
+      ->setConstructorArgs(array($mockmeta))
+      ->getMock();
+
+    $foo->expects($this->exactly(3))
+      ->method('id')
+      ->will($this->returnValue('foo'));
+
+    $foo->expects($this->once())
+      ->method('getPredecessors')
+      ->will($this->returnValue(array('bar')));
+
+    $foo->expects($this->once())
+      ->method('getSuccessors')
+      ->will($this->returnValue(array('baz')));
+
+    $bar = $this->createBasicAssetMock('bar');
+    $baz = $this->createBasicAssetMock('baz');
+
+    $this->graph->addVertex($foo);
+    $this->graph->addVertex($bar);
+    $this->graph->addVertex($baz);
+
+    $transpose = $this->graph->transpose();
+    $this->doCheckVerticesEqual(array($foo, $bar, $baz), $transpose);
+
+    // Verify that the transpose has a fully inverted edge set.
+    $lister = function($vertex) use (&$out) {
+      $out[] = $vertex;
+    };
+
+    $out = array();
+    $transpose->eachAdjacent($bar, $lister);
+    $this->assertEquals(array($foo), $out);
+
+    $out = array();
+    $transpose->eachAdjacent($foo, $lister);
+    $this->assertEquals(array($baz), $out);
+
+    $out = array();
+    $transpose->eachAdjacent($baz, $lister);
+    $this->assertEmpty($out);
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetUnitTest.php
new file mode 100644
index 0000000..dab09b3
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetUnitTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Asset\AssetUnitTest.
+ */
+
+namespace Drupal\Tests\Core\Asset;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Provides base standard fixtures and mocks for Asset tests.
+ */
+abstract class AssetUnitTest extends UnitTestCase {
+
+  public function createMockFileAsset($type) {
+    $asset = $this->getMock('Drupal\\Core\\Asset\\FileAsset', array(), array(), '', FALSE);
+    $asset->expects($this->any())
+      ->method('getAssetType')
+      ->will($this->returnValue($type));
+
+    $asset->expects($this->any())
+      ->method('id')
+      ->will($this->returnValue($this->randomName()));
+
+    return $asset;
+  }
+}
\ No newline at end of file
diff --git a/core/tests/Drupal/Tests/Core/Asset/AsseticAdapterAssetTest.php b/core/tests/Drupal/Tests/Core/Asset/AsseticAdapterAssetTest.php
new file mode 100644
index 0000000..69a2bdb
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AsseticAdapterAssetTest.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdboyer
+ * Date: 9/19/13
+ * Time: 2:13 PM
+ */
+
+namespace Drupal\Tests\Core\Asset;
+use Drupal\Core\Asset\AsseticAdapterAsset;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests for the AsseticAdapterAsset, which ensures certain Assetic methods
+ * cannot be called by any child method.
+ *
+ * @group Asset
+ */
+class AsseticAdapterAssetTest extends UnitTestCase {
+
+  /**
+   * @var AsseticAdapterAsset
+   */
+  protected $mock;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Assetic adapter asset test',
+      'description' => 'Tests that certain Assetic methods throw known exceptions in a Drupal context',
+      'group' => 'Asset',
+    );
+  }
+
+  public function setUp() {
+    $this->mock = $this->getMockForAbstractClass('Drupal\\Core\\Asset\\AsseticAdapterAsset');
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException
+   */
+  public function testGetVars() {
+    $this->mock->getVars();
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException
+   */
+  public function testSetValues() {
+    $this->mock->setValues(array());
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException
+   */
+  public function testGetValues() {
+    $this->mock->getValues();
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/BaseAssetTest.php b/core/tests/Drupal/Tests/Core/Asset/BaseAssetTest.php
new file mode 100644
index 0000000..f40fc23
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/BaseAssetTest.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Asset\BaseAssetTest.
+ */
+
+namespace Drupal\Tests\Core\Asset;
+use Drupal\Core\Asset\BaseAsset;
+
+/**
+ *
+ * @group Asset
+ */
+class BaseAssetTest extends AssetUnitTest {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Base Asset tests',
+      'description' => 'Unit tests for Drupal\'s BaseAsset.',
+      'group' => 'Asset',
+    );
+  }
+
+  /**
+   * Creates a BaseAsset for testing purposes.
+   *
+   * @param $type
+   *
+   * @return BaseAsset;
+   */
+  public function createBaseAsset($defaults = array()) {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag', $defaults);
+    return $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta));
+  }
+
+  public function testGetMetadata() {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta));
+
+    $this->assertSame($mockmeta, $asset->getMetadata());
+  }
+
+  public function testGetAssetType() {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $mockmeta->expects($this->once())
+      ->method('getType')
+      ->will($this->returnValue('css'));
+    $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta));
+
+    $this->assertEquals('css', $asset->getAssetType());
+  }
+
+  public function testIsPreprocessable() {
+    $mockmeta = $this->getMock('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $mockmeta->expects($this->once())
+      ->method('get')
+      ->with('preprocess')
+      ->will($this->returnValue(TRUE));
+    $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta));
+
+    $this->assertTrue($asset->isPreprocessable());
+  }
+
+  /**
+   * Tests all dependency-related methods.
+   */
+  public function testDependencies() {
+    $asset = $this->createBaseAsset();
+
+    $asset->addDependency('foo', 'bar');
+    $this->assertEquals(array(array('foo', 'bar')), $asset->getDependencyInfo());
+    $this->assertTrue($asset->hasDependencies());
+
+    $asset->clearDependencies();
+    $this->assertEmpty($asset->getDependencyInfo());
+
+    $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass);
+
+    try {
+      foreach ($invalid as $val) {
+        $asset->addDependency($val, $val);
+        $this->fail('Was able to create an ordering relationship with an inappropriate value.');
+      }
+    } catch (\InvalidArgumentException $e) {}
+  }
+
+  public function testSuccessors() {
+    $asset = $this->createBaseAsset();
+    $dep = $this->createBaseAsset();
+
+    $asset->before('foo');
+    $asset->before($dep);
+
+    $this->assertEquals(array('foo', $dep), $asset->getSuccessors());
+
+    $asset->clearSuccessors();
+    $this->assertEmpty($asset->getSuccessors());
+
+    $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass);
+
+    try {
+      foreach ($invalid as $val) {
+        $asset->before($val);
+        $this->fail('Was able to create an ordering relationship with an inappropriate value.');
+      }
+    } catch (\InvalidArgumentException $e) {}
+  }
+
+  public function testPredecessors() {
+    $asset = $this->createBaseAsset();
+    $dep = $this->createBaseAsset();
+
+    $asset->after('foo');
+    $asset->after($dep);
+    $this->assertEquals(array('foo', $dep), $asset->getPredecessors());
+
+    $asset->clearPredecessors();
+    $this->assertEmpty($asset->getPredecessors());
+
+    $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass);
+
+    try {
+      foreach ($invalid as $val) {
+        $asset->after($val);
+        $this->fail('Was able to create an ordering relationship with an inappropriate value.');
+      }
+    } catch (\InvalidArgumentException $e) {}
+  }
+
+  public function testClone() {
+    $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta));
+
+    $clone = clone $asset;
+    $this->assertNotSame($mockmeta, $clone->getMetadata());
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/Collection/AssetCollectionTest.php b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetCollectionTest.php
new file mode 100644
index 0000000..6298407
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetCollectionTest.php
@@ -0,0 +1,246 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Asset\AssetCollectionTest.
+ */
+
+
+namespace Drupal\Tests\Core\Asset\Collection;
+
+use Drupal\Core\Asset\Collection\AssetCollection;
+use Drupal\Tests\Core\Asset\AssetUnitTest;
+
+/**
+ * @group Asset
+ */
+class AssetCollectionTest extends AssetUnitTest {
+
+  /**
+   * @var AssetCollection
+   */
+  protected $collection;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset collection tests',
+      'description' => 'Unit tests on AssetCollection',
+      'group' => 'Asset',
+    );
+  }
+
+  public function setUp() {
+    $this->collection = new AssetCollection();
+  }
+
+  public function testAdd() {
+    $css = $this->createMockFileAsset('css');
+    $js = $this->createMockFileAsset('js');
+
+    $this->collection->add($css);
+    $this->collection->add($js);
+
+    $this->assertContains($css, $this->collection);
+    $this->assertContains($js, $this->collection);
+  }
+
+  public function testGetCss() {
+    $css = $this->createMockFileAsset('css');
+    $js = $this->createMockFileAsset('js');
+
+    $this->collection->add($css);
+    $this->collection->add($js);
+
+    $css_result = array();
+    foreach ($this->collection->getCss() as $asset) {
+      $css_result[] = $asset;
+    }
+
+    $this->assertEquals(array($css), $css_result);
+  }
+
+  public function testGetJs() {
+    $css = $this->createMockFileAsset('css');
+    $js = $this->createMockFileAsset('js');
+
+    $this->collection->add($css);
+    $this->collection->add($js);
+
+    $js_result = array();
+    foreach ($this->collection->getJs() as $asset) {
+      $js_result[] = $asset;
+    }
+
+    $this->assertEquals(array($js), $js_result);
+  }
+
+  public function testAll() {
+    $css = $this->createMockFileAsset('css');
+    $js = $this->createMockFileAsset('js');
+
+    $this->collection->add($css);
+    $this->collection->add($js);
+
+    $this->assertEquals(array($css->id() => $css, $js->id() => $js), $this->collection->all());
+  }
+
+  public function testRemoveByAsset() {
+    $stub = $this->createMockFileAsset('css');
+
+    $this->collection->add($stub);
+    $this->collection->remove($stub);
+
+    $this->assertNotContains($stub, $this->collection);
+  }
+
+  public function testRemoveById() {
+    $stub = $this->createMockFileAsset('css');
+
+    $this->collection->add($stub);
+    $this->collection->remove($stub->id());
+
+    $this->assertNotContains($stub, $this->collection);
+  }
+
+  /**
+   * @expectedException OutOfBoundsException
+   */
+  public function testRemoveNonexistentId() {
+    $this->assertFalse($this->collection->remove('foo'));
+    $this->collection->remove('foo', FALSE);
+  }
+
+  /**
+   * @expectedException OutOfBoundsException
+   */
+  public function testRemoveNonexistentAsset() {
+    $stub = $this->createMockFileAsset('css');
+    $this->assertFalse($this->collection->remove($stub));
+    $this->collection->remove($stub, FALSE);
+  }
+
+  public function testRemoveInvalidType() {
+    $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass);
+    try {
+      foreach ($invalid as $val) {
+        $this->collection->remove($val);
+        $this->fail('AssetCollection::remove() did not throw exception on invalid argument type.');
+      }
+    } catch (\InvalidArgumentException $e) {}
+  }
+
+  public function testMergeCollection() {
+    $coll2 = new AssetCollection();
+    $stub1 = $this->createMockFileAsset('css');
+    $stub2 = $this->createMockFileAsset('js');
+
+    $coll2->add($stub1);
+    $this->collection->mergeCollection($coll2);
+
+    $this->assertContains($stub1, $this->collection);
+    $this->assertTrue($coll2->isFrozen());
+
+    $coll3 = new AssetCollection();
+    $coll3->add($stub1);
+    $coll3->add($stub2);
+    // Ensure no duplicates, and don't freeze merged bag
+    $this->collection->mergeCollection($coll3, FALSE);
+
+    $contained = array(
+      $stub1->id() => $stub1,
+      $stub2->id() => $stub2,
+    );
+    $this->assertEquals($contained, $this->collection->all());
+    $this->assertFalse($coll3->isFrozen());
+  }
+
+  /**
+   * Tests that all methods should be disabled by freezing the collection
+   * correctly trigger an exception.
+   */
+  public function testExceptionOnWriteWhenFrozen() {
+    $stub = $this->createMockFileAsset('css');
+    $write_protected = array(
+      'add' => $stub,
+      'remove' => $stub,
+      'mergeCollection' => $this->getMock('\\Drupal\\Core\\Asset\\Collection\\AssetCollection'),
+    );
+
+    $this->collection->freeze();
+    foreach ($write_protected as $method => $arg) {
+      try {
+        $this->collection->$method($arg);
+        $this->fail('Was able to run writable method on frozen AssetCollection');
+      }
+      catch (\LogicException $e) {}
+    }
+  }
+
+  /**
+   * @expectedException OutOfBoundsException
+   */
+  public function testGetById() {
+    $metamock = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+
+    $asset = $this->getMock('\\Drupal\\Core\\Asset\\FileAsset', array(), array($metamock, 'foo'));
+    $asset->expects($this->once())
+      ->method('id')
+      ->will($this->returnValue('foo'));
+
+    $this->collection->add($asset);
+    $this->assertSame($asset, $this->collection->getById('foo'));
+
+    // Nonexistent asset
+    $this->assertFalse($this->collection->getById('bar'));
+
+    // Nonexistent asset, non-graceful
+    $this->collection->getById('bar', FALSE);
+  }
+
+  public function testIsEmpty() {
+    $this->assertTrue($this->collection->isEmpty());
+  }
+
+  public function testSort() {
+    $stub1 = $this->createMockFileAsset('css');
+    $stub2 = $this->createMockFileAsset('js');
+    $stub3 = $this->createMockFileAsset('css');
+
+    $this->collection->add($stub1);
+    $this->collection->add($stub2);
+    $this->collection->add($stub3);
+
+    $assets = array(
+      $stub1->id() => $stub1,
+      $stub2->id() => $stub2,
+      $stub3->id() => $stub3,
+    );
+
+    $dummysort = function ($a, $b) {
+      return strnatcasecmp($a, $b);
+    };
+
+    $this->collection->sort($dummysort);
+    uksort($assets, $dummysort);
+    $this->assertEquals($assets, $this->collection->all());
+  }
+
+    public function testKsort() {
+    $stub1 = $this->createMockFileAsset('css');
+    $stub2 = $this->createMockFileAsset('js');
+    $stub3 = $this->createMockFileAsset('css');
+
+    $this->collection->add($stub1);
+    $this->collection->add($stub2);
+    $this->collection->add($stub3);
+
+    $assets = array(
+      $stub1->id() => $stub1,
+      $stub2->id() => $stub2,
+      $stub3->id() => $stub3,
+    );
+
+    $this->collection->ksort();
+    ksort($assets);
+    $this->assertEquals($assets, $this->collection->all());
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/Collection/AssetLibraryTest.php b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetLibraryTest.php
new file mode 100644
index 0000000..5608951
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetLibraryTest.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Asset\AssetLibraryTest.
+ */
+
+namespace Drupal\Tests\Core\Asset\Collection;
+
+use Drupal\Core\Asset\Collection\AssetLibrary;
+use Drupal\Tests\Core\Asset\AssetUnitTest;
+
+/**
+ *
+ * @group Asset
+ */
+class AssetLibraryTest extends AssetUnitTest {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset Library tests',
+      'description' => 'Tests that the AssetLibrary behaves correctly.',
+      'group' => 'Asset',
+    );
+  }
+
+  public function getLibraryFixture() {
+    $library = new AssetLibrary();
+    $library->setTitle('foo')
+      ->setVersion('1.2.3')
+      ->setWebsite('http://foo.bar')
+      ->addDependency('foo', 'bar');
+    return $library;
+  }
+
+  public function testConstructorValueInjection() {
+    $values = array(
+      'title' => 'foo',
+      'version' => '1.2.3',
+      'website' => 'http://foo.bar',
+      'dependencies' => array(array('foo', 'bar')),
+    );
+    $library = new AssetLibrary($values);
+
+    $fixture = $this->getLibraryFixture();
+    $this->assertEquals($fixture->getTitle(), $library->getTitle(), 'Title passed correctly through the constructor.');
+    $this->assertEquals($fixture->getVersion(), $library->getVersion(), 'Version passed correctly through the constructor.');
+    $this->assertEquals($fixture->getWebsite(), $library->getWebsite(), 'Website passed correctly through the constructor.');
+    $this->assertEquals($fixture->getDependencyInfo(), $library->getDependencyInfo(), 'Dependencies information passed correctly through the constructor.');
+  }
+
+  public function testAddDependency() {
+    $library = $this->getLibraryFixture();
+    $library->addDependency('baz', 'bing');
+    $this->assertEquals($library->getDependencyInfo(), array(array('foo', 'bar'), array('baz', 'bing')), 'Dependencies added to library successfully.');
+  }
+
+  public function testClearDependencies() {
+    $library = $this->getLibraryFixture();
+    $library->clearDependencies();
+    $this->assertEmpty($library->getDependencyInfo(), 'Dependencies recorded in the library were cleared correctly.');
+  }
+
+  public function testFrozenNonwriteability() {
+    $library = $this->getLibraryFixture();
+    $library->freeze();
+    try {
+      $library->setTitle('bar');
+      $this->fail('No exception thrown when attempting to set a new title on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->setVersion('2.3.4');
+      $this->fail('No exception thrown when attempting to set a new version on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->setWebsite('http://bar.baz');
+      $this->fail('No exception thrown when attempting to set a new website on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->addDependency('bing', 'bang');
+      $this->fail('No exception thrown when attempting to add a new dependency on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->clearDependencies();
+      $this->fail('No exception thrown when attempting to clear dependencies from a frozen library.');
+    }
+    catch (\LogicException $e) {}
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/Factory/AssetCollectorTest.php b/core/tests/Drupal/Tests/Core/Asset/Factory/AssetCollectorTest.php
new file mode 100644
index 0000000..a853afe
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/Factory/AssetCollectorTest.php
@@ -0,0 +1,289 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Asset\AssetCollectorTest.
+ */
+
+namespace Drupal\Tests\Core\Asset\Factory;
+
+if (!defined('CSS_AGGREGATE_THEME')) {
+  define('CSS_AGGREGATE_THEME', 100);
+}
+
+if (!defined('CSS_AGGREGATE_DEFAULT')) {
+  define('CSS_AGGREGATE_DEFAULT', 0);
+}
+
+if (!defined('JS_DEFAULT')) {
+  define('JS_DEFAULT', 0);
+}
+
+use Drupal\Core\Asset\Collection\AssetCollection;
+use Drupal\Core\Asset\Factory\AssetCollector;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+use Drupal\Tests\Core\Asset\AssetUnitTest;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Unit tests for AssetCollector.
+ *
+ * @group Asset
+ */
+class AssetCollectorTest extends AssetUnitTest {
+
+  /**
+   * @var \Drupal\Core\Asset\Factory\AssetCollector
+   */
+  protected $collector;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset Collector tests',
+      'description' => 'Tests that the AssetCollector system works correctly.',
+      'group' => 'Asset',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    $this->collector = new AssetCollector();
+  }
+
+  /**
+   * Tests that the collector injects provided metadata to created assets.
+   */
+  public function testMetadataInjection() {
+    $asset = $this->collector->create('css', 'file', 'foo', array('group' => CSS_AGGREGATE_THEME));
+    $meta = $asset->getMetadata();
+    $this->assertEquals(CSS_AGGREGATE_THEME, $meta->get('group'), 'Collector injected user-passed parameters into the created asset.');
+    $this->assertFalse($meta->isDefault('group'));
+  }
+
+  public function testDefaultPropagation() {
+    // Test that defaults are correctly applied by the factory.
+    $meta = new CssMetadataBag(array('every_page' => TRUE, 'group' => CSS_AGGREGATE_THEME));
+    $this->collector->setDefaultMetadata($meta);
+    $css1 = $this->collector->create('css', 'file', 'foo');
+
+    $asset_meta = $css1->getMetadata();
+    $this->assertTrue($asset_meta->get('every_page'));
+    $this->assertEquals(CSS_AGGREGATE_THEME, $asset_meta->get('group'));
+  }
+
+  /**
+   * @expectedException \RuntimeException
+   */
+  public function testExceptionOnAddingAssetWithoutCollectionPresent() {
+    $asset = $this->collector->create('css', 'string', 'foo');
+    $this->collector->add($asset);
+  }
+
+  /**
+   * TODO separate test for an explicit add() call.
+   */
+  public function testAssetsImplicitlyArriveInInjectedCollection() {
+    $collection = new AssetCollection();
+    $this->collector->setCollection($collection);
+
+    $asset = $this->collector->create('css', 'file', 'bar');
+    $this->assertContains($asset, $collection->getCss(), 'Created asset was implicitly added to collection.');
+  }
+
+  public function testAddAssetExplicitly() {
+    $collection = new AssetCollection();
+    $this->collector->setCollection($collection);
+
+    $mock = $this->createMockFileAsset('css');
+    $this->collector->add($mock);
+
+    $this->assertContains($mock, $collection);
+  }
+
+  public function testSetCollection() {
+    $collection = new AssetCollection();
+    $this->collector->setCollection($collection);
+    $this->assertTrue($this->collector->hasCollection());
+  }
+
+  public function testClearCollection() {
+    $collection = new AssetCollection();
+    $this->collector->setCollection($collection);
+    $this->collector->clearCollection();
+    $this->assertFalse($this->collector->hasCollection());
+  }
+
+  public function testLock() {
+    $this->assertTrue($this->collector->lock($this), 'Collector locked successfully.');
+    $this->assertTrue($this->collector->isLocked(), 'Collector accurately reports that it is locked via isLocked() method.');
+  }
+
+  public function testUnlock() {
+    $this->collector->lock($this);
+    $this->assertTrue($this->collector->unlock($this), 'Collector unlocked successfully when appropriate key was provided.');
+    $this->assertFalse($this->collector->isLocked(), 'Collector correctly reported unlocked state via isLocked() method after unlocking.');
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\LockedObjectException
+   */
+  public function testUnlockFailsWithoutCorrectSecret() {
+    $this->collector->lock('foo');
+    $this->collector->unlock('bar');
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\LockedObjectException
+   */
+  public function testUnlockFailsIfNotLocked() {
+    $this->collector->unlock('foo');
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\LockedObjectException
+   */
+  public function testLockFailsIfLocked() {
+    $this->collector->lock('foo');
+    $this->collector->lock('error');
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\LockedObjectException
+   */
+  public function testLockingPreventsSettingDefaults() {
+    $this->collector->lock($this);
+    $this->collector->setDefaultMetadata(new CssMetadataBag());
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\LockedObjectException
+   */
+  public function testLockingPreventsRestoringDefaults() {
+    $this->collector->lock($this);
+    $this->collector->restoreDefaults();
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\LockedObjectException
+   */
+  public function testLockingPreventsClearingCollection() {
+    $this->collector->lock($this);
+    $this->collector->clearCollection();
+  }
+
+  /**
+   * @expectedException \Drupal\Core\Asset\Exception\LockedObjectException
+   */
+  public function testLockingPreventsSettingCollection() {
+    $this->collector->lock($this);
+    $this->collector->setCollection(new AssetCollection());
+  }
+
+  public function testBuiltinDefaultAreTheSame() {
+    $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css'));
+    $this->assertEquals(new JsMetadataBag(), $this->collector->getMetadataDefaults('js'));
+  }
+
+  public function testChangeAndRestoreDefaults() {
+    $changed_css = new CssMetadataBag(array('foo' => 'bar', 'every_page' => TRUE));
+    $this->collector->setDefaultMetadata($changed_css);
+
+    $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css'));
+    $this->assertNotSame($changed_css, $this->collector->getMetadataDefaults('css'), 'Metadata is cloned on retrieval from collector.');
+
+    $this->collector->restoreDefaults();
+    $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css'));
+
+    // Do another check to ensure that both metadata bags are correctly reset
+    $changed_js = new JsMetadataBag(array('scope' => 'footer', 'fizzbuzz' => 'llama'));
+    $this->collector->setDefaultMetadata($changed_css);
+    $this->collector->setDefaultMetadata($changed_js);
+
+    $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css'));
+    $this->assertEquals($changed_js, $this->collector->getMetadataDefaults('js'));
+
+    $this->collector->restoreDefaults();
+    $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css'));
+    $this->assertEquals(new JsMetadataBag(), $this->collector->getMetadataDefaults('js'));
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testMetadataTypeMustBeCorrect() {
+    $mock = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag');
+    $mock->expects($this->once())
+      ->method('getType')
+      ->will($this->returnValue('foo'));
+
+    $this->collector->setDefaultMetadata($mock);
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetNonexistentDefault() {
+    $this->collector->getMetadataDefaults('foo');
+  }
+
+
+  public function testCreateCssFileAsset() {
+    $css_file = $this->collector->create('css', 'file', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\FileAsset', $css_file);
+    $this->assertEquals('css', $css_file->getAssetType());
+  }
+
+  public function testCreateStylesheetExternalAsset() {
+    $css_external = $this->collector->create('css', 'external', 'http://foo.bar/path/to/asset.css');
+    $this->assertInstanceOf('\Drupal\Core\Asset\ExternalAsset', $css_external);
+    $this->assertEquals('css', $css_external->getAssetType());
+  }
+
+  public function testCreateStylesheetStringAsset() {
+    $css_string = $this->collector->create('css', 'string', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\StringAsset', $css_string);
+    $this->assertEquals('css', $css_string->getAssetType());
+  }
+
+  public function testCreateJavascriptFileAsset() {
+    $js_file = $this->collector->create('js', 'file', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\FileAsset', $js_file);
+    $this->assertEquals('js', $js_file->getAssetType());
+  }
+
+  public function testCreateJavascriptExternalAsset() {
+    $js_external = $this->collector->create('js', 'external', 'http://foo.bar/path/to/asset.js');
+    $this->assertInstanceOf('\Drupal\Core\Asset\ExternalAsset', $js_external);
+    $this->assertEquals('js', $js_external->getAssetType());
+  }
+
+  public function testCreateJavascriptStringAsset() {
+    $js_string = $this->collector->create('js', 'string', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\StringAsset', $js_string);
+    $this->assertEquals('js', $js_string->getAssetType());
+  }
+
+  public function testLastCssAutoAfter() {
+    $css1 = $this->collector->create('css', 'file', 'foo.css');
+    $css2 = $this->collector->create('css', 'file', 'foo2.css');
+    $this->assertEquals(array($css1), $css2->getPredecessors());
+
+    $this->collector->clearLastCss();
+    $css3 = $this->collector->create('css', 'file', 'foo3.css');
+    $this->assertEmpty($css3->getPredecessors());
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testExceptionOnInvalidSourceType() {
+    $this->collector->create('foo', 'bar', 'baz');
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testExceptionOnInvalidAssetType() {
+    $this->collector->create('css', 'bar', 'qux');
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/Metadata/AssetMetadataBagTest.php b/core/tests/Drupal/Tests/Core/Asset/Metadata/AssetMetadataBagTest.php
new file mode 100644
index 0000000..00876f8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/Metadata/AssetMetadataBagTest.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Asset\AssetMetadataBagTest.
+ */
+
+namespace Drupal\Tests\Core\Asset\Metadata;
+
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ *
+ * @group Asset
+ */
+class AssetMetadataBagTest extends UnitTestCase {
+
+  /**
+   * @var AssetMetadataBag
+   */
+  protected $mock;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset Metadata bag test',
+      'description' => 'Tests various methods of AssetMetadatabag',
+      'group' => 'Asset',
+    );
+  }
+
+  public function createBag($args = array(array('foo' => 'bar', 'baz' => 'qux'))) {
+    return $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag', $args);
+  }
+
+  /**
+   * A unified test for all operations that rely on calling get() in order to
+   * verify their correctness.
+   */
+  public function testGetValueOperations() {
+    // First, ensure that constructor-injected defaults are correctly tracked.
+    $mock = $this->createBag();
+    $this->assertEquals('bar', $mock->get('foo'));
+    // Ensure that constructor-injected defaults are correctly reported as such.
+    $this->assertTrue($mock->isDefault('foo'));
+
+    // Set an explicit value, and ensure that it comes back out correctly.
+    $mock->set('bing', 'bang');
+    $this->assertEquals('bang', $mock->get('bing'));
+    $this->assertFalse($mock->isDefault('bing'));
+
+    // Set an explicit value that overrides a default, this time.
+    $mock->set('foo', 'kablow');
+    $this->assertEquals('kablow', $mock->get('foo'));
+    $this->assertFalse($mock->isDefault('foo'));
+
+    // Revert the set value, and ensure the old default comes through.
+    $mock->revert('foo');
+    $this->assertEquals('bar', $mock->get('foo'));
+    $this->assertTrue($mock->isDefault('foo'));
+
+    // Add value via add(), now
+    $mock->add(array('llama' => 'a pink one'));
+    $this->assertEquals('a pink one', $mock->get('llama'));
+    $this->assertFalse($mock->isDefault('llama'));
+
+    // Finally, check that getting an unknown key returns nothing
+    $this->assertNull($mock->get('nonexistent'));
+  }
+
+  public function testAll() {
+    $this->assertEquals(array('foo' => 'bar', 'baz' => 'qux'), $this->createBag()->all());
+  }
+
+  public function testKeys() {
+    $this->assertEquals(array('foo', 'baz'), $this->createBag()->keys());
+  }
+
+  public function testHas() {
+    $this->assertTrue($this->createBag()->has('foo'));
+  }
+
+  public function testIteration() {
+    $found = array();
+    foreach ($this->createBag() as $val) {
+      $found[] = $val;
+    }
+
+    $this->assertEquals(array('bar', 'qux'), $found);
+  }
+
+  public function testCount() {
+    $this->assertCount(2, $this->createBag());
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/Metadata/CssMetadataBagTest.php b/core/tests/Drupal/Tests/Core/Asset/Metadata/CssMetadataBagTest.php
new file mode 100644
index 0000000..08fbb83
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/Metadata/CssMetadataBagTest.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Asset\Metadata\CssMetadataBagTest.
+ */
+
+namespace Drupal\Tests\Core\Asset\Metadata;
+
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ *
+ * @group Asset
+ */
+class CssMetadataBagTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CSS Metadata bag test',
+      'description' => 'Tests various methods of CssMetadatabag',
+      'group' => 'Asset',
+    );
+  }
+
+  public function testGetType() {
+    $bag = new CssMetadataBag();
+    $this->assertEquals('css', $bag->getType());
+  }
+}
+
diff --git a/core/tests/Drupal/Tests/Core/Asset/Metadata/JsMetadataBagTest.php b/core/tests/Drupal/Tests/Core/Asset/Metadata/JsMetadataBagTest.php
new file mode 100644
index 0000000..7cc5600
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/Metadata/JsMetadataBagTest.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Asset\Metadata\JsMetadataBagTest.
+ */
+
+namespace Drupal\Tests\Core\Asset\Metadata;
+
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ *
+ * @group Asset
+ */
+class JsMetadataBagTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'JS Metadata bag test',
+      'description' => 'Tests various methods of JsMetadatabag',
+      'group' => 'Asset',
+    );
+  }
+
+  public function testGetType() {
+    $bag = new JsMetadataBag();
+    $this->assertEquals('js', $bag->getType());
+  }
+}
+
diff --git a/core/vendor/autoload.php b/core/vendor/autoload.php
index aac8b62..db8c291 100644
--- a/core/vendor/autoload.php
+++ b/core/vendor/autoload.php
@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer' . '/autoload_real.php';
 
-return ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2::getLoader();
+return ComposerAutoloaderInitf333b4f71be112d333dd097f0ffff964::getLoader();
diff --git a/core/vendor/autoload.php b/core/vendor/autoload.php.orig
similarity index 61%
copy from core/vendor/autoload.php
copy to core/vendor/autoload.php.orig
index aac8b62..01b379d 100644
--- a/core/vendor/autoload.php
+++ b/core/vendor/autoload.php.orig
@@ -4,4 +4,8 @@
 
 require_once __DIR__ . '/composer' . '/autoload_real.php';
 
+<<<<<<< HEAD
+return ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700::getLoader();
+=======
 return ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2::getLoader();
+>>>>>>> origin/8.x
diff --git a/core/vendor/composer/autoload_real.php b/core/vendor/composer/autoload_real.php
index b7dd370..ba8452a 100644
--- a/core/vendor/composer/autoload_real.php
+++ b/core/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2
+class ComposerAutoloaderInitf333b4f71be112d333dd097f0ffff964
 {
     private static $loader;
 
@@ -19,9 +19,9 @@ public static function getLoader()
             return self::$loader;
         }
 
-        spl_autoload_register(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitf333b4f71be112d333dd097f0ffff964', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        spl_autoload_unregister(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitf333b4f71be112d333dd097f0ffff964', 'loadClassLoader'));
 
         $vendorDir = dirname(__DIR__);
         $baseDir = dirname(dirname($vendorDir));
diff --git a/core/vendor/composer/autoload_real.php b/core/vendor/composer/autoload_real.php.orig
similarity index 77%
copy from core/vendor/composer/autoload_real.php
copy to core/vendor/composer/autoload_real.php.orig
index b7dd370..1f6a320 100644
--- a/core/vendor/composer/autoload_real.php
+++ b/core/vendor/composer/autoload_real.php.orig
@@ -2,7 +2,11 @@
 
 // autoload_real.php @generated by Composer
 
+<<<<<<< HEAD
+class ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700
+=======
 class ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2
+>>>>>>> origin/8.x
 {
     private static $loader;
 
@@ -19,9 +23,15 @@ public static function getLoader()
             return self::$loader;
         }
 
+<<<<<<< HEAD
+        spl_autoload_register(array('ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        spl_autoload_unregister(array('ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700', 'loadClassLoader'));
+=======
         spl_autoload_register(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
         spl_autoload_unregister(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader'));
+>>>>>>> origin/8.x
 
         $vendorDir = dirname(__DIR__);
         $baseDir = dirname(dirname($vendorDir));
diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json
index 8347a64..964eee0 100644
--- a/core/vendor/composer/installed.json
+++ b/core/vendor/composer/installed.json
@@ -147,79 +147,6 @@
         ]
     },
     {
-        "name": "kriswallsmith/assetic",
-        "version": "v1.1.1",
-        "version_normalized": "1.1.1.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/kriswallsmith/assetic.git",
-            "reference": "v1.1.1"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/v1.1.1",
-            "reference": "v1.1.1",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.1",
-            "symfony/process": ">=2.1,<3.0"
-        },
-        "require-dev": {
-            "cssmin/cssmin": "*",
-            "joliclic/javascript-packer": "*",
-            "kamicane/packager": "*",
-            "leafo/lessphp": "*",
-            "leafo/scssphp": "*",
-            "leafo/scssphp-compass": "*",
-            "mrclay/minify": "*",
-            "phpunit/phpunit": ">=3.7,<4.0",
-            "ptachoire/cssembed": "*",
-            "twig/twig": ">=1.6,<2.0"
-        },
-        "suggest": {
-            "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler",
-            "leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler",
-            "leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin",
-            "ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris",
-            "twig/twig": "Assetic provides the integration with the Twig templating engine"
-        },
-        "time": "2013-06-01 22:13:43",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.1-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-0": {
-                "Assetic": "src/"
-            },
-            "files": [
-                "src/functions.php"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Kris Wallsmith",
-                "email": "kris.wallsmith@gmail.com",
-                "homepage": "http://kriswallsmith.net/"
-            }
-        ],
-        "description": "Asset Management for PHP",
-        "homepage": "https://github.com/kriswallsmith/assetic",
-        "keywords": [
-            "assets",
-            "compression",
-            "minification"
-        ]
-    },
-    {
         "name": "symfony-cmf/routing",
         "version": "1.1.0-beta1",
         "version_normalized": "1.1.0.0-beta1",
@@ -2017,25 +1944,71 @@
         "homepage": "http://symfony.com"
     },
     {
+        "name": "sdboyer/gliph",
+        "version": "0.1.4",
+        "version_normalized": "0.1.4.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/sdboyer/gliph.git",
+            "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/sdboyer/gliph/zipball/aad932ef7d808105341cc9a36538e9fe2cb5ee82",
+            "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3"
+        },
+        "time": "2013-09-27 01:15:21",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "Gliph": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Sam Boyer",
+                "email": "tech@samboyer.org"
+            }
+        ],
+        "description": "A graph library for PHP.",
+        "homepage": "http://github.com/sdboyer/gliph",
+        "keywords": [
+            "gliph",
+            "graph",
+            "library",
+            "php",
+            "spl"
+        ]
+    },
+    {
         "name": "symfony/process",
-        "version": "v2.3.4",
-        "version_normalized": "2.3.4.0",
+        "version": "v2.3.6",
+        "version_normalized": "2.3.6.0",
         "target-dir": "Symfony/Component/Process",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/Process.git",
-            "reference": "1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b"
+            "reference": "81191e354fe9dad0451036ccf0fdf283649d3f1e"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/Process/zipball/1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b",
-            "reference": "1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b",
+            "url": "https://api.github.com/repos/symfony/Process/zipball/81191e354fe9dad0451036ccf0fdf283649d3f1e",
+            "reference": "81191e354fe9dad0451036ccf0fdf283649d3f1e",
             "shasum": ""
         },
         "require": {
             "php": ">=5.3.3"
         },
-        "time": "2013-08-22 06:42:25",
+        "time": "2013-10-09 21:17:57",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -2066,30 +2039,58 @@
         "homepage": "http://symfony.com"
     },
     {
-        "name": "sdboyer/gliph",
-        "version": "0.1.4",
-        "version_normalized": "0.1.4.0",
+        "name": "kriswallsmith/assetic",
+        "version": "v1.1.2",
+        "version_normalized": "1.1.2.0",
         "source": {
             "type": "git",
-            "url": "https://github.com/sdboyer/gliph.git",
-            "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82"
+            "url": "https://github.com/kriswallsmith/assetic.git",
+            "reference": "735cffd3982c6e8cdebe292d5db39d077f65890f"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/sdboyer/gliph/zipball/aad932ef7d808105341cc9a36538e9fe2cb5ee82",
-            "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82",
+            "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/735cffd3982c6e8cdebe292d5db39d077f65890f",
+            "reference": "735cffd3982c6e8cdebe292d5db39d077f65890f",
             "shasum": ""
         },
         "require": {
-            "php": ">=5.3"
+            "php": ">=5.3.1",
+            "symfony/process": "~2.1"
         },
-        "time": "2013-09-27 01:15:21",
+        "require-dev": {
+            "cssmin/cssmin": "*",
+            "joliclic/javascript-packer": "*",
+            "kamicane/packager": "*",
+            "leafo/lessphp": "*",
+            "leafo/scssphp": "*",
+            "leafo/scssphp-compass": "*",
+            "mrclay/minify": "*",
+            "phpunit/phpunit": "~3.7",
+            "ptachoire/cssembed": "*",
+            "twig/twig": "~1.6"
+        },
+        "suggest": {
+            "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler",
+            "leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler",
+            "leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin",
+            "ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris",
+            "twig/twig": "Assetic provides the integration with the Twig templating engine"
+        },
+        "time": "2013-07-19 00:03:27",
         "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.1-dev"
+            }
+        },
         "installation-source": "dist",
         "autoload": {
             "psr-0": {
-                "Gliph": "src/"
-            }
+                "Assetic": "src/"
+            },
+            "files": [
+                "src/functions.php"
+            ]
         },
         "notification-url": "https://packagist.org/downloads/",
         "license": [
@@ -2097,18 +2098,17 @@
         ],
         "authors": [
             {
-                "name": "Sam Boyer",
-                "email": "tech@samboyer.org"
+                "name": "Kris Wallsmith",
+                "email": "kris.wallsmith@gmail.com",
+                "homepage": "http://kriswallsmith.net/"
             }
         ],
-        "description": "A graph library for PHP.",
-        "homepage": "http://github.com/sdboyer/gliph",
+        "description": "Asset Management for PHP",
+        "homepage": "https://github.com/kriswallsmith/assetic",
         "keywords": [
-            "gliph",
-            "graph",
-            "library",
-            "php",
-            "spl"
+            "assets",
+            "compression",
+            "minification"
         ]
     }
 ]
diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json.orig
similarity index 99%
copy from core/vendor/composer/installed.json
copy to core/vendor/composer/installed.json.orig
index 8347a64..29e609d 100644
--- a/core/vendor/composer/installed.json
+++ b/core/vendor/composer/installed.json.orig
@@ -2067,6 +2067,19 @@
     },
     {
         "name": "sdboyer/gliph",
+<<<<<<< HEAD
+        "version": "0.1.3",
+        "version_normalized": "0.1.3.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/sdboyer/gliph.git",
+            "reference": "f85ca76fde4913e3b6996691672998e646e0c642"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/sdboyer/gliph/zipball/f85ca76fde4913e3b6996691672998e646e0c642",
+            "reference": "f85ca76fde4913e3b6996691672998e646e0c642",
+=======
         "version": "0.1.4",
         "version_normalized": "0.1.4.0",
         "source": {
@@ -2078,12 +2091,17 @@
             "type": "zip",
             "url": "https://api.github.com/repos/sdboyer/gliph/zipball/aad932ef7d808105341cc9a36538e9fe2cb5ee82",
             "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82",
+>>>>>>> origin/8.x
             "shasum": ""
         },
         "require": {
             "php": ">=5.3"
         },
+<<<<<<< HEAD
+        "time": "2013-09-22 03:30:09",
+=======
         "time": "2013-09-27 01:15:21",
+>>>>>>> origin/8.x
         "type": "library",
         "installation-source": "dist",
         "autoload": {
diff --git a/core/vendor/kriswallsmith/assetic/CHANGELOG-1.1.md b/core/vendor/kriswallsmith/assetic/CHANGELOG-1.1.md
index a5a8640..8bcf8bb 100644
--- a/core/vendor/kriswallsmith/assetic/CHANGELOG-1.1.md
+++ b/core/vendor/kriswallsmith/assetic/CHANGELOG-1.1.md
@@ -1,3 +1,11 @@
+1.1.2 (July 18, 2013)
+-------------------
+
+ * Fixed deep mtime on asset collections
+ * `CallablesFilter` now implements `DependencyExtractorInterface`
+ * Fixed detection of "partial" children in subfolders in `SassFilter`
+ * Restored `PathUtils` for BC
+
 1.1.1 (June 1, 2013)
 --------------------
 
diff --git a/core/vendor/kriswallsmith/assetic/src/Assetic/Asset/AssetCollection.php b/core/vendor/kriswallsmith/assetic/src/Assetic/Asset/AssetCollection.php
index 6cfa3e8..d115d62 100644
--- a/core/vendor/kriswallsmith/assetic/src/Assetic/Asset/AssetCollection.php
+++ b/core/vendor/kriswallsmith/assetic/src/Assetic/Asset/AssetCollection.php
@@ -128,6 +128,7 @@ public function getFilters()
     public function clearFilters()
     {
         $this->filters->clear();
+        $this->clones = new \SplObjectStorage();
     }
 
     public function load(FilterInterface $additionalFilter = null)
diff --git a/core/vendor/kriswallsmith/assetic/src/Assetic/Factory/LazyAssetManager.php b/core/vendor/kriswallsmith/assetic/src/Assetic/Factory/LazyAssetManager.php
index 5f8fe3f..b47db2e 100644
--- a/core/vendor/kriswallsmith/assetic/src/Assetic/Factory/LazyAssetManager.php
+++ b/core/vendor/kriswallsmith/assetic/src/Assetic/Factory/LazyAssetManager.php
@@ -11,6 +11,7 @@
 
 namespace Assetic\Factory;
 
+use Assetic\Asset\AssetCollectionInterface;
 use Assetic\Asset\AssetInterface;
 use Assetic\AssetManager;
 use Assetic\Factory\Loader\FormulaLoaderInterface;
@@ -206,34 +207,38 @@ public function isDebug()
 
     public function getLastModified(AssetInterface $asset)
     {
-        $mtime = $asset->getLastModified();
-        if (!$filters = $asset->getFilters()) {
-            return $mtime;
-        }
-
-        // prepare load path
-        $sourceRoot = $asset->getSourceRoot();
-        $sourcePath = $asset->getSourcePath();
-        $loadPath = $sourceRoot && $sourcePath ? dirname($sourceRoot.'/'.$sourcePath) : null;
-
-        $prevFilters = array();
-        foreach ($filters as $filter) {
-            $prevFilters[] = $filter;
+        $mtime = 0;
+        foreach ($asset instanceof AssetCollectionInterface ? $asset : array($asset) as $leaf) {
+            $mtime = max($mtime, $leaf->getLastModified());
 
-            if (!$filter instanceof DependencyExtractorInterface) {
+            if (!$filters = $leaf->getFilters()) {
                 continue;
             }
 
-            // extract children from asset after running all preceeding filters
-            $clone = clone $asset;
-            $clone->clearFilters();
-            foreach (array_slice($prevFilters, 0, -1) as $prevFilter) {
-                $clone->ensureFilter($prevFilter);
-            }
-            $clone->load();
-
-            foreach ($filter->getChildren($this->factory, $clone->getContent(), $loadPath) as $child) {
-                $mtime = max($mtime, $this->getLastModified($child));
+            // prepare load path
+            $sourceRoot = $leaf->getSourceRoot();
+            $sourcePath = $leaf->getSourcePath();
+            $loadPath = $sourceRoot && $sourcePath ? dirname($sourceRoot.'/'.$sourcePath) : null;
+
+            $prevFilters = array();
+            foreach ($filters as $filter) {
+                $prevFilters[] = $filter;
+
+                if (!$filter instanceof DependencyExtractorInterface) {
+                    continue;
+                }
+
+                // extract children from leaf after running all preceeding filters
+                $clone = clone $leaf;
+                $clone->clearFilters();
+                foreach (array_slice($prevFilters, 0, -1) as $prevFilter) {
+                    $clone->ensureFilter($prevFilter);
+                }
+                $clone->load();
+
+                foreach ($filter->getChildren($this->factory, $clone->getContent(), $loadPath) as $child) {
+                    $mtime = max($mtime, $this->getLastModified($child));
+                }
             }
         }
 
diff --git a/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/CallablesFilter.php b/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/CallablesFilter.php
index 25413b0..fafa52e 100644
--- a/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/CallablesFilter.php
+++ b/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/CallablesFilter.php
@@ -12,25 +12,29 @@
 namespace Assetic\Filter;
 
 use Assetic\Asset\AssetInterface;
+use Assetic\Factory\AssetFactory;
 
 /**
  * A filter that wraps callables.
  *
  * @author Kris Wallsmith <kris.wallsmith@gmail.com>
  */
-class CallablesFilter implements FilterInterface
+class CallablesFilter implements FilterInterface, DependencyExtractorInterface
 {
     private $loader;
     private $dumper;
+    private $extractor;
 
     /**
      * @param callable|null $loader
      * @param callable|null $dumper
+     * @param callable|null $extractor
      */
-    public function __construct($loader = null, $dumper = null)
+    public function __construct($loader = null, $dumper = null, $extractor = null)
     {
         $this->loader = $loader;
         $this->dumper = $dumper;
+        $this->extractor = $extractor;
     }
 
     public function filterLoad(AssetInterface $asset)
@@ -46,4 +50,14 @@ public function filterDump(AssetInterface $asset)
             $callable($asset);
         }
     }
+
+    public function getChildren(AssetFactory $factory, $content, $loadPath = null)
+    {
+        if (null !== $callable = $this->extractor) {
+            return $callable($factory, $content, $loadPath);
+        }
+
+        return array();
+    }
+
 }
diff --git a/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/LessFilter.php b/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/LessFilter.php
index 4acd38f..37c7567 100644
--- a/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/LessFilter.php
+++ b/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/LessFilter.php
@@ -161,8 +161,8 @@ public function filterDump(AssetInterface $asset)
     }
 
     /**
-     * @todo support for @import-once
-     * @todo support for @import (less) "lib.css"
+     * @todo support for import-once
+     * @todo support for import (less) "lib.css"
      */
     public function getChildren(AssetFactory $factory, $content, $loadPath = null)
     {
diff --git a/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/Sass/SassFilter.php b/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/Sass/SassFilter.php
index 24e618d..a97e1a8 100644
--- a/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/Sass/SassFilter.php
+++ b/core/vendor/kriswallsmith/assetic/src/Assetic/Filter/Sass/SassFilter.php
@@ -204,14 +204,14 @@ public function getChildren(AssetFactory $factory, $content, $loadPath = null)
             if (pathinfo($reference, PATHINFO_EXTENSION)) {
                 $needles = array(
                     $reference,
-                    '_'.$reference,
+                    self::partialize($reference),
                 );
             } else {
                 $needles = array(
                     $reference.'.scss',
                     $reference.'.sass',
-                    '_'.$reference.'.scss',
-                    '_'.$reference.'.sass',
+                    self::partialize($reference).'.scss',
+                    self::partialize($reference).'.sass',
                 );
             }
 
@@ -233,4 +233,21 @@ public function getChildren(AssetFactory $factory, $content, $loadPath = null)
 
         return $children;
     }
+
+    private static function partialize($reference)
+    {
+        $parts = pathinfo($reference);
+
+        if ('.' === $parts['dirname']) {
+            $partial = '_'.$parts['filename'];
+        } else {
+            $partial = $parts['dirname'].DIRECTORY_SEPARATOR.'_'.$parts['filename'];
+        }
+
+        if (isset($parts['extension'])) {
+            $partial .= '.'.$parts['extension'];
+        }
+
+        return $partial;
+    }
 }
diff --git a/core/vendor/kriswallsmith/assetic/src/Assetic/Util/PathUtils.php b/core/vendor/kriswallsmith/assetic/src/Assetic/Util/PathUtils.php
new file mode 100644
index 0000000..4b11b11
--- /dev/null
+++ b/core/vendor/kriswallsmith/assetic/src/Assetic/Util/PathUtils.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Assetic package, an OpenSky project.
+ *
+ * (c) 2010-2013 OpenSky Project Inc
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Assetic\Util;
+
+abstract class PathUtils extends VarUtils
+{
+    public static function resolvePath($path, array $vars, array $values)
+    {
+        return static::resolve($path, $vars, $values);
+    }
+}
diff --git a/core/vendor/kriswallsmith/assetic/src/Assetic/Util/VarUtils.php b/core/vendor/kriswallsmith/assetic/src/Assetic/Util/VarUtils.php
index 4cc9103..b2af980 100644
--- a/core/vendor/kriswallsmith/assetic/src/Assetic/Util/VarUtils.php
+++ b/core/vendor/kriswallsmith/assetic/src/Assetic/Util/VarUtils.php
@@ -38,7 +38,7 @@ public static function resolve($template, array $vars, array $values)
             }
 
             if (!isset($values[$var])) {
-                throw new \InvalidArgumentException(sprintf('The path "%s" contains the variable "%s", but was not given any value for it.', $template, $var));
+                throw new \InvalidArgumentException(sprintf('The template "%s" contains the variable "%s", but was not given any value for it.', $template, $var));
             }
 
             $map['{'.$var.'}'] = $values[$var];
diff --git a/core/vendor/symfony/process/Symfony/Component/Process/Process.php b/core/vendor/symfony/process/Symfony/Component/Process/Process.php
index 6d0b3c5..ff77c1b 100644
--- a/core/vendor/symfony/process/Symfony/Component/Process/Process.php
+++ b/core/vendor/symfony/process/Symfony/Component/Process/Process.php
@@ -54,15 +54,15 @@ class Process
     private $stderr;
     private $enhanceWindowsCompatibility;
     private $enhanceSigchildCompatibility;
-    private $pipes;
     private $process;
     private $status = self::STATUS_READY;
     private $incrementalOutputOffset;
     private $incrementalErrorOutputOffset;
     private $tty;
 
-    private $fileHandles;
-    private $readBytes;
+    private $useFileHandles = false;
+    /** @var ProcessPipes */
+    private $processPipes;
 
     private static $sigchild;
 
@@ -154,6 +154,7 @@ public function __construct($commandline, $cwd = null, array $env = null, $stdin
         }
         $this->stdin = $stdin;
         $this->setTimeout($timeout);
+        $this->useFileHandles = defined('PHP_WINDOWS_VERSION_BUILD');
         $this->enhanceWindowsCompatibility = true;
         $this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled();
         $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
@@ -237,18 +238,15 @@ public function start($callback = null)
             }
         }
 
-        $this->process = proc_open($commandline, $descriptors, $this->pipes, $this->cwd, $this->env, $this->options);
+        $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
 
         if (!is_resource($this->process)) {
             throw new RuntimeException('Unable to launch a new process.');
         }
         $this->status = self::STATUS_STARTED;
 
-        foreach ($this->pipes as $pipe) {
-            stream_set_blocking($pipe, false);
-        }
-
-        $this->writePipes();
+        $this->processPipes->unblock();
+        $this->processPipes->write(false, $this->stdin);
         $this->updateStatus(false);
         $this->checkTimeout();
     }
@@ -263,8 +261,8 @@ public function start($callback = null)
      *
      * @return Process The new process
      *
-     * @throws \RuntimeException When process can't be launch or is stopped
-     * @throws \RuntimeException When process is already running
+     * @throws RuntimeException When process can't be launch or is stopped
+     * @throws RuntimeException When process is already running
      *
      * @see start()
      */
@@ -291,8 +289,8 @@ public function restart($callback = null)
      *
      * @return integer The exitcode of the process
      *
-     * @throws \RuntimeException When process timed out
-     * @throws \RuntimeException When process stopped after receiving signal
+     * @throws RuntimeException When process timed out
+     * @throws RuntimeException When process stopped after receiving signal
      */
     public function wait($callback = null)
     {
@@ -300,22 +298,15 @@ public function wait($callback = null)
         if (null !== $callback) {
             $this->callback = $this->buildCallback($callback);
         }
-        while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) {
-            $this->checkTimeout();
-            $this->readPipes(true);
-        }
-        $this->updateStatus(false);
-        if ($this->processInformation['signaled']) {
-            if ($this->isSigchildEnabled()) {
-                throw new RuntimeException('The process has been signaled.');
-            }
 
-            throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
-        }
+        do {
+            $this->checkTimeout();
+            $running = defined('PHP_WINDOWS_VERSION_BUILD') ? $this->isRunning() : $this->processPipes->hasOpenHandles();
+            $close = !defined('PHP_WINDOWS_VERSION_BUILD') || !$running;;
+            $this->readPipes(true, $close);
+        } while ($running);
 
-        $time = 0;
-        while ($this->isRunning() && $time < 1000000) {
-            $time += 1000;
+        while ($this->isRunning()) {
             usleep(1000);
         }
 
@@ -384,7 +375,7 @@ public function signal($signal)
      */
     public function getOutput()
     {
-        $this->readPipes(false);
+        $this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
 
         return $this->stdout;
     }
@@ -416,7 +407,7 @@ public function getIncrementalOutput()
      */
     public function getErrorOutput()
     {
-        $this->readPipes(false);
+        $this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
 
         return $this->stderr;
     }
@@ -642,9 +633,13 @@ public function stop($timeout = 10, $signal = null)
                     $this->signal($signal ?: SIGKILL);
                 }
             }
+        }
 
-            $this->updateStatus(false);
+        $this->updateStatus(false);
+        if ($this->processInformation['running']) {
+            $this->close();
         }
+
         $this->status = self::STATUS_TERMINATED;
 
         return $this->exitcode;
@@ -951,38 +946,10 @@ public function checkTimeout()
      */
     private function getDescriptors()
     {
-        //Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
-        //Workaround for this problem is to use temporary files instead of pipes on Windows platform.
-        //@see https://bugs.php.net/bug.php?id=51800
-        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
-            $this->fileHandles = array(
-                self::STDOUT => tmpfile(),
-            );
-            if (false === $this->fileHandles[self::STDOUT]) {
-                throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
-            }
-            $this->readBytes = array(
-                self::STDOUT => 0,
-            );
-
-            return array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w'));
-        }
-
-        if ($this->tty) {
-            $descriptors = array(
-                array('file', '/dev/tty', 'r'),
-                array('file', '/dev/tty', 'w'),
-                array('file', '/dev/tty', 'w'),
-            );
-        } else {
-           $descriptors = array(
-                array('pipe', 'r'), // stdin
-                array('pipe', 'w'), // stdout
-                array('pipe', 'w'), // stderr
-            );
-        }
+        $this->processPipes = new ProcessPipes($this->useFileHandles);
+        $descriptors = $this->processPipes->getDescriptors();
 
-        if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+        if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
             // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
             $descriptors = array_merge($descriptors, array(array('pipe', 'w')));
 
@@ -1033,10 +1000,11 @@ protected function updateStatus($blocking)
             return;
         }
 
-        $this->readPipes($blocking);
-
         $this->processInformation = proc_get_status($this->process);
         $this->captureExitCode();
+
+        $this->readPipes($blocking, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
+
         if (!$this->processInformation['running']) {
             $this->close();
             $this->status = self::STATUS_TERMINATED;
@@ -1061,160 +1029,23 @@ protected function isSigchildEnabled()
     }
 
     /**
-     * Handles the windows file handles fallbacks.
-     *
-     * @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
-     */
-    private function processFileHandles($closeEmptyHandles = false)
-    {
-        $fh = $this->fileHandles;
-        foreach ($fh as $type => $fileHandle) {
-            fseek($fileHandle, $this->readBytes[$type]);
-            $data = fread($fileHandle, 8192);
-            if (strlen($data) > 0) {
-                $this->readBytes[$type] += strlen($data);
-                call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
-            }
-            if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) {
-                fclose($fileHandle);
-                unset($this->fileHandles[$type]);
-            }
-        }
-    }
-
-    /**
-     * Returns true if a system call has been interrupted.
-     *
-     * @return Boolean
-     */
-    private function hasSystemCallBeenInterrupted()
-    {
-        $lastError = error_get_last();
-
-        // stream_select returns false when the `select` system call is interrupted by an incoming signal
-        return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
-    }
-
-    /**
      * Reads pipes, executes callback.
      *
      * @param Boolean $blocking Whether to use blocking calls or not.
      */
-    private function readPipes($blocking)
-    {
-        if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
-            $this->processFileHandles(!$this->pipes);
-        }
-
-        if ($this->pipes) {
-            $r = $this->pipes;
-            $w = null;
-            $e = null;
-
-            // let's have a look if something changed in streams
-            if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(self::TIMEOUT_PRECISION * 1E6) : 0)) {
-                // if a system call has been interrupted, forget about it, let's try again
-                // otherwise, an error occured, let's reset pipes
-                if (!$this->hasSystemCallBeenInterrupted()) {
-                    $this->pipes = array();
-                }
-
-                return;
-            }
-
-            // nothing has changed
-            if (0 === $n) {
-                return;
-            }
-
-            $this->processReadPipes($r);
-        }
-    }
-
-    /**
-     * Writes data to pipes.
-     *
-     * @param Boolean $blocking Whether to use blocking calls or not.
-     */
-    private function writePipes()
+    private function readPipes($blocking, $close)
     {
-        if ($this->tty) {
-            $this->status = self::STATUS_TERMINATED;
-
-            return;
-        }
-
-        if (null === $this->stdin) {
-            fclose($this->pipes[0]);
-            unset($this->pipes[0]);
-
-            return;
-        }
-
-        $writePipes = array($this->pipes[0]);
-        unset($this->pipes[0]);
-        $stdinLen = strlen($this->stdin);
-        $stdinOffset = 0;
-
-        while ($writePipes) {
-            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
-                $this->processFileHandles();
-            }
-
-            $r = $this->pipes;
-            $w = $writePipes;
-            $e = null;
-
-            if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(static::TIMEOUT_PRECISION * 1E6) : 0)) {
-                // if a system call has been interrupted, forget about it, let's try again
-                if ($this->hasSystemCallBeenInterrupted()) {
-                    continue;
-                }
-                break;
-            }
-
-            // nothing has changed, let's wait until the process is ready
-            if (0 === $n) {
-                continue;
-            }
-
-            if ($w) {
-                $written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
-                if (false !== $written) {
-                    $stdinOffset += $written;
-                }
-                if ($stdinOffset >= $stdinLen) {
-                    fclose($writePipes[0]);
-                    $writePipes = null;
-                }
-            }
-
-            $this->processReadPipes($r);
+        if ($close) {
+            $result = $this->processPipes->readAndCloseHandles($blocking);
+        } else {
+            $result = $this->processPipes->read($blocking);
         }
-    }
 
-    /**
-     * Processes read pipes, executes callback on it.
-     *
-     * @param array $pipes
-     */
-    private function processReadPipes(array $pipes)
-    {
-        foreach ($pipes as $pipe) {
-            $type = array_search($pipe, $this->pipes);
-            $data = fread($pipe, 8192);
-
-            if (strlen($data) > 0) {
-                // last exit code is output and caught to work around --enable-sigchild
-                if (3 == $type) {
-                    $this->fallbackExitcode = (int) $data;
-                } else {
-                    call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
-                }
-            }
-            if (false === $data || feof($pipe)) {
-                fclose($pipe);
-                unset($this->pipes[$type]);
+        foreach ($result as $type => $data) {
+            if (3 == $type) {
+                $this->fallbackExitcode = (int) $data;
+            } else {
+                call_user_func($this->callback, $type === self::STDOUT ? self::OUT : self::ERR, $data);
             }
         }
     }
@@ -1237,13 +1068,9 @@ private function captureExitCode()
      */
     private function close()
     {
-        foreach ($this->pipes as $pipe) {
-            fclose($pipe);
-        }
-
-        $this->pipes = null;
         $exitcode = -1;
 
+        $this->processPipes->close();
         if (is_resource($this->process)) {
             $exitcode = proc_close($this->process);
         }
@@ -1258,13 +1085,6 @@ private function close()
             $this->exitcode = 128 + $this->processInformation['termsig'];
         }
 
-        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
-            foreach ($this->fileHandles as $fileHandle) {
-                fclose($fileHandle);
-            }
-            $this->fileHandles = array();
-        }
-
         return $this->exitcode;
     }
 
@@ -1280,11 +1100,8 @@ private function resetProcessData()
         $this->processInformation = null;
         $this->stdout = null;
         $this->stderr = null;
-        $this->pipes = null;
         $this->process = null;
         $this->status = self::STATUS_READY;
-        $this->fileHandles = null;
-        $this->readBytes = null;
         $this->incrementalOutputOffset = 0;
         $this->incrementalErrorOutputOffset = 0;
     }
diff --git a/core/vendor/symfony/process/Symfony/Component/Process/ProcessPipes.php b/core/vendor/symfony/process/Symfony/Component/Process/ProcessPipes.php
new file mode 100644
index 0000000..0c57d42
--- /dev/null
+++ b/core/vendor/symfony/process/Symfony/Component/Process/ProcessPipes.php
@@ -0,0 +1,305 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * ProcessPipes manages descriptors and pipes for the use of proc_open.
+ */
+class ProcessPipes
+{
+    /** @var array */
+    public $pipes = array();
+    /** @var array */
+    private $fileHandles = array();
+    /** @var array */
+    private $readBytes = array();
+    /** @var Boolean */
+    private $useFiles;
+
+    public function __construct($useFiles = false)
+    {
+        $this->useFiles = (Boolean) $useFiles;
+
+        // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+        // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+        //
+        // Please note that this work around prevents hanging but
+        // another issue occurs : In some race conditions, some data may be
+        // lost or corrupted.
+        //
+        // @see https://bugs.php.net/bug.php?id=51800
+        if ($this->useFiles) {
+            $this->fileHandles = array(
+                Process::STDOUT => tmpfile(),
+                Process::STDERR => tmpfile(),
+            );
+            if (false === $this->fileHandles[Process::STDOUT]) {
+                throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
+            }
+            if (false === $this->fileHandles[Process::STDERR]) {
+                throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
+            }
+            $this->readBytes = array(
+                Process::STDOUT => 0,
+                Process::STDERR => 0,
+            );
+        }
+    }
+
+    public function __destruct()
+    {
+        $this->close();
+    }
+
+    /**
+     * Sets non-blocking mode on pipes.
+     */
+    public function unblock()
+    {
+        foreach ($this->pipes as $pipe) {
+            stream_set_blocking($pipe, 0);
+        }
+    }
+
+    /**
+     * Closes file handles and pipes.
+     */
+    public function close()
+    {
+        $this->closeUnixPipes();
+        foreach ($this->fileHandles as $offset => $handle) {
+            fclose($handle);
+        }
+        $this->fileHandles = array();
+    }
+
+    /**
+     * Closes unix pipes.
+     *
+     * Nothing happens in case file handles are used.
+     */
+    public function closeUnixPipes()
+    {
+        foreach ($this->pipes as $pipe) {
+            fclose($pipe);
+        }
+        $this->pipes = array();
+    }
+
+    /**
+     * Returns an array of descriptors for the use of proc_open.
+     *
+     * @return array
+     */
+    public function getDescriptors()
+    {
+        if ($this->useFiles) {
+            return array(
+                array('pipe', 'r'),
+                $this->fileHandles[Process::STDOUT],
+                $this->fileHandles[Process::STDERR],
+            );
+        }
+
+        return array(
+            array('pipe', 'r'), // stdin
+            array('pipe', 'w'), // stdout
+            array('pipe', 'w'), // stderr
+        );
+    }
+
+    /**
+     * Reads data in file handles and pipes.
+     *
+     * @param Boolean $blocking Whether to use blocking calls or not.
+     *
+     * @return array An array of read data indexed by their fd.
+     */
+    public function read($blocking)
+    {
+        return array_replace($this->readStreams($blocking), $this->readFileHandles());
+    }
+
+    /**
+     * Reads data in file handles and pipes, closes them if EOF is reached.
+     *
+     * @param Boolean $blocking Whether to use blocking calls or not.
+     *
+     * @return array An array of read data indexed by their fd.
+     */
+    public function readAndCloseHandles($blocking)
+    {
+        return array_replace($this->readStreams($blocking, true), $this->readFileHandles(true));
+    }
+
+    /**
+     * Returns if the current state has open file handles or pipes.
+     *
+     * @return Boolean
+     */
+    public function hasOpenHandles()
+    {
+        if ($this->useFiles) {
+            return (Boolean) $this->fileHandles;
+        }
+
+        return (Boolean) $this->pipes;
+    }
+
+    /**
+     * Writes stdin data.
+     *
+     * @param Boolean $blocking Whether to use blocking calls or not.
+     * @param string  $stdin    The data to write.
+     */
+    public function write($blocking, $stdin)
+    {
+        if (null === $stdin) {
+            fclose($this->pipes[0]);
+            unset($this->pipes[0]);
+
+            return;
+        }
+
+        $writePipes = array($this->pipes[0]);
+        unset($this->pipes[0]);
+        $stdinLen = strlen($stdin);
+        $stdinOffset = 0;
+
+        while ($writePipes) {
+            $r = null;
+            $w = $writePipes;
+            $e = null;
+
+            if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(Process::TIMEOUT_PRECISION * 1E6) : 0)) {
+                // if a system call has been interrupted, forget about it, let's try again
+                if ($this->hasSystemCallBeenInterrupted()) {
+                    continue;
+                }
+                break;
+            }
+
+            // nothing has changed, let's wait until the process is ready
+            if (0 === $n) {
+                continue;
+            }
+
+            if ($w) {
+                $written = fwrite($writePipes[0], (binary) substr($stdin, $stdinOffset), 8192);
+                if (false !== $written) {
+                    $stdinOffset += $written;
+                }
+                if ($stdinOffset >= $stdinLen) {
+                    fclose($writePipes[0]);
+                    $writePipes = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Reads data in file handles.
+     *
+     * @return array An array of read data indexed by their fd.
+     */
+    private function readFileHandles($close = false)
+    {
+        $read = array();
+        $fh = $this->fileHandles;
+        foreach ($fh as $type => $fileHandle) {
+            if (0 !== fseek($fileHandle, $this->readBytes[$type])) {
+                continue;
+            }
+            $data = '';
+            $dataread = null;
+            while (!feof($fileHandle)) {
+                if (false !== $dataread = fread($fileHandle, 16392)) {
+                    $data .= $dataread;
+                }
+            }
+            if (0 < $length = strlen($data)) {
+                $this->readBytes[$type] += $length;
+                $read[$type] = $data;
+            }
+
+            if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) {
+                fclose($this->fileHandles[$type]);
+                unset($this->fileHandles[$type]);
+            }
+        }
+
+        return $read;
+    }
+
+    /**
+     * Reads data in file pipes streams.
+     *
+     * @param Boolean $blocking Whether to use blocking calls or not.
+     *
+     * @return array An array of read data indexed by their fd.
+     */
+    private function readStreams($blocking, $close = false)
+    {
+        $read = array();
+
+        $r = $this->pipes;
+        $w = null;
+        $e = null;
+
+        // let's have a look if something changed in streams
+        if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(Process::TIMEOUT_PRECISION * 1E6) : 0)) {
+            // if a system call has been interrupted, forget about it, let's try again
+            // otherwise, an error occured, let's reset pipes
+            if (!$this->hasSystemCallBeenInterrupted()) {
+                $this->pipes = array();
+            }
+
+            return $read;
+        }
+
+        // nothing has changed
+        if (0 === $n) {
+            return $read;
+        }
+
+        foreach ($r as $pipe) {
+            $type = array_search($pipe, $this->pipes);
+            $data = fread($pipe, 8192);
+
+            if (strlen($data) > 0) {
+                $read[$type] = $data;
+            }
+
+            if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
+                fclose($this->pipes[$type]);
+                unset($this->pipes[$type]);
+            }
+        }
+
+        return $read;
+    }
+
+    /**
+     * Returns true if a system call has been interrupted.
+     *
+     * @return Boolean
+     */
+    private function hasSystemCallBeenInterrupted()
+    {
+        $lastError = error_get_last();
+
+        // stream_select returns false when the `select` system call is interrupted by an incoming signal
+        return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
+    }
+}
diff --git a/core/vendor/symfony/process/Symfony/Component/Process/README.md b/core/vendor/symfony/process/Symfony/Component/Process/README.md
index 7b9f307..9bcc656 100644
--- a/core/vendor/symfony/process/Symfony/Component/Process/README.md
+++ b/core/vendor/symfony/process/Symfony/Component/Process/README.md
@@ -43,5 +43,5 @@ Resources
 You can run the unit tests with the following command:
 
     $ cd path/to/Symfony/Component/XXX/
-    $ composer.phar install --dev
+    $ composer.phar install
     $ phpunit
diff --git a/core/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php b/core/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php
index d0228f0..15c309a 100644
--- a/core/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php
+++ b/core/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php
@@ -69,17 +69,16 @@ public function testCallbacksAreExecutedWithStart()
     {
         $data = '';
 
-        $process = $this->getProcess('echo "foo";sleep 1;echo "foo"');
+        $process = $this->getProcess('echo foo && php -r "sleep(1);" && echo foo');
         $process->start(function ($type, $buffer) use (&$data) {
             $data .= $buffer;
         });
 
-        $start = microtime(true);
         while ($process->isRunning()) {
             usleep(10000);
         }
 
-        $this->assertEquals("foo\nfoo\n", $data);
+        $this->assertEquals(2, preg_match_all('/foo/', $data, $matches));
     }
 
     /**
@@ -102,10 +101,6 @@ public function testProcessResponses($expected, $getter, $code)
      */
     public function testProcessPipes($code, $size)
     {
-        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
-            $this->markTestSkipped('Test hangs on Windows & PHP due to https://bugs.php.net/bug.php?id=60120 and https://bugs.php.net/bug.php?id=51800');
-        }
-
         $expected = str_repeat(str_repeat('*', 1024), $size) . '!';
         $expectedLength = (1024 * $size) + 1;
 
@@ -119,6 +114,12 @@ public function testProcessPipes($code, $size)
 
     public function chainedCommandsOutputProvider()
     {
+        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+            return array(
+                array("2 \r\n2\r\n", '&&', '2')
+            );
+        }
+
         return array(
             array("1\n1\n", ';', '1'),
             array("2\n2\n", '&&', '2'),
@@ -131,10 +132,6 @@ public function chainedCommandsOutputProvider()
      */
     public function testChainedCommandsOutput($expected, $operator, $input)
     {
-        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
-            $this->markTestSkipped('Does it work on windows ?');
-        }
-
         $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
         $process->run();
         $this->assertEquals($expected, $process->getOutput());
@@ -173,7 +170,7 @@ public function testGetIncrementalErrorOutput()
 
     public function testGetOutput()
     {
-        $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
+        $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++; usleep(500); }')));
 
         $p->run();
         $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
@@ -308,7 +305,7 @@ public function testIsSuccessful()
 
     public function testIsSuccessfulOnlyAfterTerminated()
     {
-        $process = $this->getProcess('sleep 1');
+        $process = $this->getProcess('php -r "sleep(1);"');
         $process->start();
         while ($process->isRunning()) {
             $this->assertFalse($process->isSuccessful());
@@ -441,7 +438,7 @@ public function testPhpDeadlock()
     public function testRunProcessWithTimeout()
     {
         $timeout = 0.5;
-        $process = $this->getProcess('sleep 3');
+        $process = $this->getProcess('php -r "sleep(3);"');
         $process->setTimeout($timeout);
         $start = microtime(true);
         try {
@@ -459,7 +456,7 @@ public function testCheckTimeoutOnStartedProcess()
     {
         $timeout = 0.5;
         $precision = 100000;
-        $process = $this->getProcess('sleep 3');
+        $process = $this->getProcess('php -r "sleep(3);"');
         $process->setTimeout($timeout);
         $start = microtime(true);
 
@@ -480,6 +477,21 @@ public function testCheckTimeoutOnStartedProcess()
         $this->assertFalse($process->isSuccessful());
     }
 
+    public function testStartAfterATimeout()
+    {
+        $process = $this->getProcess('php -r "while(true) {echo \'\'; usleep(1000); }"');
+        $process->setTimeout(0.1);
+        try {
+            $process->run();
+            $this->fail('An exception should have been raised.');
+        } catch (\Exception $e) {
+
+        }
+        $process->start();
+        usleep(10000);
+        $process->stop();
+    }
+
     public function testGetPid()
     {
         $process = $this->getProcess('php -r "sleep(1);"');
@@ -597,11 +609,18 @@ public function pipesCodeProvider()
     {
         $variations = array(
             'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
-            'include \''.__DIR__.'/ProcessTestHelper.php\';',
+            'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
         );
 
+        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+            // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
+            $sizes = array(1, 2, 4, 8);
+        } else {
+            $sizes = array(1, 16, 64, 1024, 4096);
+        }
+
         $codes = array();
-        foreach (array(1, 16, 64, 1024, 4096) as $size) {
+        foreach ($sizes as $size) {
             foreach ($variations as $code) {
                 $codes[] = array($code, $size);
             }
diff --git a/core/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php b/core/vendor/symfony/process/Symfony/Component/Process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
similarity index 83%
rename from core/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php
rename to core/vendor/symfony/process/Symfony/Component/Process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
index 25cfb41..cdc7525 100644
--- a/core/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php
+++ b/core/vendor/symfony/process/Symfony/Component/Process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
@@ -8,9 +8,9 @@
 $read = array(STDIN);
 $write = array(STDOUT, STDERR);
 
-stream_set_blocking(STDIN, false);
-stream_set_blocking(STDOUT, false);
-stream_set_blocking(STDERR, false);
+stream_set_blocking(STDIN, 0);
+stream_set_blocking(STDOUT, 0);
+stream_set_blocking(STDERR, 0);
 
 $out = $err = '';
 while ($read || $write) {
@@ -26,7 +26,7 @@
     }
 
     if (in_array(STDOUT, $w) && strlen($out) > 0) {
-         $written = fwrite(STDOUT, (binary) $out, 1024);
+         $written = fwrite(STDOUT, (binary) $out, 32768);
          if (false === $written) {
              die(ERR_WRITE_FAILED);
          }
@@ -37,7 +37,7 @@
     }
 
     if (in_array(STDERR, $w) && strlen($err) > 0) {
-         $written = fwrite(STDERR, (binary) $err, 1024);
+         $written = fwrite(STDERR, (binary) $err, 32768);
          if (false === $written) {
              die(ERR_WRITE_FAILED);
          }
@@ -48,7 +48,7 @@
     }
 
     if ($r) {
-        $str = fread(STDIN, 1024);
+        $str = fread(STDIN, 32768);
         if (false !== $str) {
             $out .= $str;
             $err .= $str;
