diff --git a/core/includes/common.inc b/core/includes/common.inc
index 9c65647..07e370a 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -6927,6 +6927,9 @@ function drupal_common_theme() {
     'tel' => array(
       'render element' => 'element',
     ),
+    'url' => array(
+      'render element' => 'element',
+    ),
     'form' => array(
       'render element' => 'element',
     ),
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 85bffc6..2666f71 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -3759,6 +3759,56 @@ function theme_tel($variables) {
 }
 
 /**
+ * Returns HTML for a url form element.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #title, #value, #description, #size, #maxlength,
+ *     #placeholder, #required, #attributes, #autocomplete_path.
+ *
+ * @ingroup themeable
+ */
+function theme_url($variables) {
+  $element = $variables['element'];
+  $element['#attributes']['type'] = 'url';
+  element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
+  _form_set_class($element, array('form-url'));
+
+  $extra = '';
+  if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
+    drupal_add_library('system', 'drupal.autocomplete');
+    $element['#attributes']['class'][] = 'form-autocomplete';
+
+    $attributes = array();
+    $attributes['type'] = 'hidden';
+    $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
+    $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
+    $attributes['disabled'] = 'disabled';
+    $attributes['class'][] = 'autocomplete';
+    $extra = '<input' . drupal_attributes($attributes) . ' />';
+  }
+
+  $output = '<input' . drupal_attributes($element['#attributes']) . ' />';
+
+  return $output . $extra;
+}
+
+/**
+ * Form element validation handler for #type 'url'.
+ *
+ * Note that #maxlength and #required is validated by _form_validate() already.
+ */
+function form_validate_url(&$element, &$form_state) {
+  $value = trim($element['#value']);
+  form_set_value($element, $value, $form_state);
+
+  if ($value !== '' && !valid_url($value, TRUE)) {
+    form_error($element, t('The URL %url is not valid.', array('%url' => $value)));
+  }
+}
+
+/**
  * Returns HTML for a form.
  *
  * @param $variables
diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc
index 93319bf..f5cf8e5 100644
--- a/core/modules/aggregator/aggregator.admin.inc
+++ b/core/modules/aggregator/aggregator.admin.inc
@@ -81,7 +81,8 @@ function aggregator_form_feed($form, &$form_state, stdClass $feed = NULL) {
     '#description' => t('The name of the feed (or the name of the website providing the feed).'),
     '#required' => TRUE,
   );
-  $form['url'] = array('#type' => 'textfield',
+  $form['url'] = array(
+    '#type' => 'url',
     '#title' => t('URL'),
     '#default_value' => isset($feed->url) ? $feed->url : '',
     '#maxlength' => 255,
@@ -146,10 +147,6 @@ function aggregator_form_feed($form, &$form_state, stdClass $feed = NULL) {
  */
 function aggregator_form_feed_validate($form, &$form_state) {
   if ($form_state['values']['op'] == t('Save')) {
-    // Ensure URL is valid.
-    if (!valid_url($form_state['values']['url'], TRUE)) {
-      form_set_error('url', t('The URL %url is invalid. Enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $form_state['values']['url'])));
-    }
     // Check for duplicate titles.
     if (isset($form_state['values']['fid'])) {
       $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE (title = :title OR url = :url) AND fid <> :fid", array(':title' => $form_state['values']['title'], ':url' => $form_state['values']['url'], ':fid' => $form_state['values']['fid']));
@@ -268,7 +265,7 @@ function aggregator_form_opml($form, &$form_state) {
     '#description' => t('Upload an OPML file containing a list of feeds to be imported.'),
   );
   $form['remote'] = array(
-    '#type' => 'textfield',
+    '#type' => 'url',
     '#title' => t('OPML Remote URL'),
     '#maxlength' => 1024,
     '#description' => t('Enter the URL of an OPML file. This file will be downloaded and processed only once on submission of the form.'),
@@ -316,11 +313,6 @@ function aggregator_form_opml_validate($form, &$form_state) {
   if (empty($form_state['values']['remote']) == empty($_FILES['files']['name']['upload'])) {
     form_set_error('remote', t('You must <em>either</em> upload a file or enter a URL.'));
   }
-
-  // Validate the URL, if one was entered.
-  if (!empty($form_state['values']['remote']) && !valid_url($form_state['values']['remote'], TRUE)) {
-    form_set_error('remote', t('This URL is not valid.'));
-  }
 }
 
 /**
diff --git a/core/modules/aggregator/aggregator.test b/core/modules/aggregator/aggregator.test
index 27c4673..e2dc4af 100644
--- a/core/modules/aggregator/aggregator.test
+++ b/core/modules/aggregator/aggregator.test
@@ -671,7 +671,7 @@ class ImportOPMLTestCase extends AggregatorTestCase {
 
     $edit = array('remote' => 'invalidUrl://empty');
     $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
-    $this->assertText(t('This URL is not valid.'), t('Error if the URL is invalid.'));
+    $this->assertText(t('The URL invalidUrl://empty is not valid.'), t('Error if the URL is invalid.'));
 
     $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
     $this->assertEqual($before, $after, t('No feeds were added during the three last form submissions.'));
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 02b7906..203c6e3 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -1793,7 +1793,7 @@ function comment_form($form, &$form_state, $comment) {
     '#access' => $is_admin || (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
   );
   $form['author']['homepage'] = array(
-    '#type' => 'textfield',
+    '#type' => 'url',
     '#title' => t('Homepage'),
     '#default_value' => $comment->homepage,
     '#maxlength' => 255,
@@ -1986,9 +1986,6 @@ function comment_form_validate($form, &$form_state) {
   if ($form_state['values']['mail'] && !valid_email_address($form_state['values']['mail'])) {
     form_set_error('mail', t('The e-mail address you specified is not valid.'));
   }
-  if ($form_state['values']['homepage'] && !valid_url($form_state['values']['homepage'], TRUE)) {
-    form_set_error('homepage', t('The URL of your homepage is not valid. Remember that it must be fully qualified, i.e. of the form <code>http://example.com/directory</code>.'));
-  }
 }
 
 /**
diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php
index c9fc562..5b23270 100644
--- a/core/modules/simpletest/drupal_web_test_case.php
+++ b/core/modules/simpletest/drupal_web_test_case.php
@@ -2243,6 +2243,7 @@ class DrupalWebTestCase extends DrupalTestCase {
           case 'text':
           case 'tel':
           case 'textarea':
+          case 'url':
           case 'hidden':
           case 'password':
             $post[$name] = $edit[$name];
diff --git a/core/modules/simpletest/tests/form.test b/core/modules/simpletest/tests/form.test
index 784da88..cd61d93 100644
--- a/core/modules/simpletest/tests/form.test
+++ b/core/modules/simpletest/tests/form.test
@@ -40,6 +40,9 @@ class FormsTestCase extends DrupalWebTestCase {
     $elements['telephone']['element'] = array('#title' => $this->randomName(), '#type' => 'tel');
     $elements['telephone']['empty_values'] = $empty_strings;
 
+    $elements['url']['element'] = array('#title' => $this->randomName(), '#type' => 'url');
+    $elements['url']['empty_values'] = $empty_strings;
+
     $elements['password']['element'] = array('#title' => $this->randomName(), '#type' => 'password');
     $elements['password']['empty_values'] = $empty_strings;
 
@@ -261,7 +264,7 @@ class FormsTestCase extends DrupalWebTestCase {
 
     // All the elements should be marked as disabled, including the ones below
     // the disabled container.
-    $this->assertEqual(count($disabled_elements), 33, t('The correct elements have the disabled property in the HTML code.'));
+    $this->assertEqual(count($disabled_elements), 34, 'The correct elements have the disabled property in the HTML code.');
 
     $this->drupalPost(NULL, $edit, t('Submit'));
     $returned_values['hijacked'] = drupal_json_decode($this->content);
@@ -397,7 +400,7 @@ class FormElementTestCase extends DrupalWebTestCase {
     $expected = 'placeholder-text';
     // Test to make sure textfields and passwords have the proper placeholder
     // text.
-    foreach (array('textfield', 'password') as $type) {
+    foreach (array('textfield', 'url', 'password') as $type) {
       $element = $this->xpath('//input[@id=:id and @placeholder=:expected]', array(
         ':id' => 'edit-' . $type,
         ':expected' => $expected,
diff --git a/core/modules/simpletest/tests/form_test.module b/core/modules/simpletest/tests/form_test.module
index e1e2435..b16896a 100644
--- a/core/modules/simpletest/tests/form_test.module
+++ b/core/modules/simpletest/tests/form_test.module
@@ -1027,7 +1027,7 @@ function form_test_select_submit($form, &$form_state) {
  * Builds a form to test the placeholder attribute.
  */
 function form_test_placeholder_test($form, &$form_state) {
-  foreach (array('textfield', 'textarea', 'password', 'tel') as $type) {
+  foreach (array('textfield', 'textarea', 'url', 'password', 'tel') as $type) {
     $form[$type] = array(
       '#type' => $type,
       '#title' => $type,
@@ -1102,7 +1102,7 @@ function form_test_checkboxes_radios($form, &$form_state, $customize = FALSE) {
  */
 function _form_test_disabled_elements($form, &$form_state) {
   // Elements that take a simple default value.
-  foreach (array('textfield', 'textarea', 'tel', 'hidden') as $type) {
+  foreach (array('textfield', 'textarea', 'tel', 'hidden', 'url') as $type) {
     $form[$type] = array(
       '#type' => $type,
       '#title' => $type,
@@ -1196,7 +1196,7 @@ function _form_test_disabled_elements($form, &$form_state) {
   $form['disabled_container'] = array(
     '#disabled' => TRUE,
   );
-  foreach (array('textfield', 'textarea', 'hidden') as $type) {
+  foreach (array('textfield', 'textarea', 'hidden', 'url') as $type) {
     $form['disabled_container']['disabled_container_' . $type] = array(
       '#type' => $type,
       '#title' => $type,
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 38c3f55..d7ceb74 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -374,6 +374,16 @@ function system_element_info() {
     '#theme' => 'tel',
     '#theme_wrappers' => array('form_element'),
   );
+  $types['url'] = array(
+    '#input' => TRUE,
+    '#size' => 60,
+    '#maxlength' => 255,
+    '#autocomplete_path' => FALSE,
+    '#process' => array('ajax_process_form'),
+    '#element_validate' => array('form_validate_url'),
+    '#theme' => 'url',
+    '#theme_wrappers' => array('form_element'),
+  );
   $types['machine_name'] = array(
     '#input' => TRUE,
     '#default_value' => NULL,
diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc
index d9fd86f..f56dd0b 100644
--- a/core/modules/update/update.manager.inc
+++ b/core/modules/update/update.manager.inc
@@ -492,7 +492,7 @@ function update_manager_install_form($form, &$form_state, $context) {
   );
 
   $form['project_url'] = array(
-    '#type' => 'textfield',
+    '#type' => 'url',
     '#title' => t('Install from a URL'),
     '#description' => t('For example: %url', array('%url' => 'http://ftp.drupal.org/files/projects/name.tar.gz')),
   );
@@ -592,12 +592,6 @@ function update_manager_install_form_validate($form, &$form_state) {
   if (!($form_state['values']['project_url'] XOR !empty($_FILES['files']['name']['project_upload']))) {
     form_set_error('project_url', t('You must either provide a URL or upload an archive file to install.'));
   }
-
-  if ($form_state['values']['project_url']) {
-    if (!valid_url($form_state['values']['project_url'], TRUE)) {
-      form_set_error('project_url', t('The provided URL is invalid.'));
-    }
-  }
 }
 
 /**
diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css
index 850a69c..f768597 100644
--- a/core/themes/bartik/css/style.css
+++ b/core/themes/bartik/css/style.css
@@ -1200,6 +1200,7 @@ select.form-select {
 }
 input.form-text,
 input.form-tel,
+input.form-url,
 textarea.form-textarea,
 select.form-select {
   border: 1px solid #ccc;
diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css
index 301659c..5a1c89e 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -602,6 +602,7 @@ div.teaser-checkbox .form-item,
 .form-disabled input.form-autocomplete,
 .form-disabled input.form-text,
 .form-disabled input.form-tel,
+.form-disabled input.form-url,
 .form-disabled input.form-file,
 .form-disabled textarea.form-textarea,
 .form-disabled select.form-select {
@@ -689,6 +690,7 @@ input.form-button-disabled:active {
 input.form-autocomplete,
 input.form-text,
 input.form-tel,
+input.form-url,
 input.form-file,
 textarea.form-textarea,
 select.form-select {
@@ -703,6 +705,7 @@ select.form-select {
 }
 input.form-text:focus,
 input.form-tel:focus,
+input.form-url:focus,
 input.form-file:focus,
 textarea.form-textarea:focus,
 select.form-select:focus {
