Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1013
diff -u -p -r1.1013 common.inc
--- includes/common.inc	11 Oct 2009 06:05:53 -0000	1.1013
+++ includes/common.inc	12 Oct 2009 22:14:03 -0000
@@ -2578,13 +2578,98 @@ function drupal_attributes(array $attrib
  *   an HTML string containing a link to the given path.
  */
 function l($text, $path, array $options = array()) {
+  global $theme;
+  static $theme_functions = array('theme_link');
+  static $theme_system_ready = FALSE;
+  static $just_theme_link = TRUE;
+
+  $variables = array(
+    'text' => $text,
+    'path' => $path,
+    'options' => $options,
+  );
+
+  // If the theme system is not initialized yet, then theme('link') cannot be
+  // called and theme_link() needs to be called directly. Also, since l() is
+  // called very often during a single page request, we statically cache
+  // preprocess and process functions to directly invoke them in subsequent
+  // calls.
+  if (!$theme_system_ready && isset($theme)) {
+    // Theme hooks can define include files, but those only need to be loaded
+    // once per request. A single call to theme() is fine, but subsequent
+    // invocations of l() will avoid this overhead.
+    $return = theme('link', $variables);
+
+    // Retrieve preprocess, process, and theme functions.
+    $hooks = theme_get_registry();
+    if (isset($hooks) && isset($hooks['link']) && isset($hooks['link']['function'])) {
+      $theme_functions = array();
+      foreach (array('preprocess functions', 'process functions') as $phase) {
+        if (isset($hooks['link'][$phase])) {
+          foreach ($hooks['link'][$phase] as $function) {
+            if (function_exists($function)) {
+              $theme_functions[] = $function;
+            }
+          }
+        }
+      }
+      // A void function is needed to handle the edge case of the theme function
+      // overridden, but not existing. We emulate what theme() would do, which
+      // is return NULL.
+      $theme_functions[] = function_exists($hooks['link']['function']) ? $hooks['link']['function'] : create_function('', '');
+      $theme_system_ready = TRUE;
+      if ((count($theme_functions) > 1) || ($theme_functions[0] != 'theme_link')) {
+        $just_theme_link = FALSE;
+      }
+    }
+  }
+  // Before the theme system is ready, or after it is, but if there are no
+  // preprocess or process functions and the theme function isn't overridden,
+  // just call theme_link().
+  elseif ($just_theme_link) {
+    $return = theme_link($variables, 'link');
+  }
+  // Once the theme system is ready and we've called theme('link') once,
+  // for subsequent calls to l(), mimic theme('link') using the cached data.
+  else {
+    foreach ($theme_functions as $function) {
+      $return = $function($variables, 'link');
+    }
+  }
+
+  return $return;
+}
+
+/**
+ * Format an internal Drupal link.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - text: The link text to output.
+ *   - path: The URL path component to link to.
+ *   - options: (optional) An array of options to pass to url().
+ *   See l() for more information.
+ * 
+ * @return
+ *   A HTML string containing a link to the given path.
+ * 
+ * @ingroup themeable
+ * 
+ * @todo If it is impossible to call l() before the theme system is initialized,
+ *   then move this function into theme.inc.
+ */
+function theme_link($variables) {
   global $language_url;
 
+  $text = $variables['text'];
+  $path = $variables['path'];
+  $options = $variables['options'];
+
   // Merge in defaults.
   $options += array(
-      'attributes' => array(),
-      'html' => FALSE,
-    );
+    'attributes' => array(),
+    'html' => FALSE,
+  );
 
   // Append active class.
   if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
@@ -4857,6 +4942,9 @@ function drupal_common_theme() {
     'status_messages' => array(
       'arguments' => array('display' => NULL),
     ),
+    'link' => array(
+      'arguments' => array('text' => NULL, 'path' => NULL, 'options' => array()),
+    ),
     'links' => array(
       'arguments' => array('links' => NULL, 'attributes' => array('class' => array('links')), 'heading' => array()),
     ),
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.83
diff -u -p -r1.83 common.test
--- modules/simpletest/tests/common.test	11 Oct 2009 02:14:43 -0000	1.83
+++ modules/simpletest/tests/common.test	12 Oct 2009 22:14:04 -0000
@@ -4,7 +4,7 @@
 /**
  * Tests for URL generation functions.
  */
-class CommonURLUnitTest extends DrupalUnitTestCase {
+class CommonURLUnitTest extends DrupalWebTestCase {
   public static function getInfo() {
     return array(
       'name' => 'URL generation tests',
