diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index b85f0fdeac..50e7227cd0 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1051,9 +1051,8 @@ class ModelAdmin(BaseModelAdmin):
                 attr = obj._meta.pk.attname
             value = obj.serializable_value(attr)
             return SimpleTemplateResponse('admin/popup_response.html', {
-                'pk_value': escape(pk_value),  # for possible backwards-compatibility
-                'value': escape(value),
-                'obj': escapejs(obj)
+                'value': value,
+                'obj': obj,
             })
 
         elif "_continue" in request.POST:
diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html
index 5b5c22d34e..2a837a1686 100644
--- a/django/contrib/admin/templates/admin/edit_inline/stacked.html
+++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html
@@ -21,10 +21,10 @@
 
 <script type="text/javascript">
 (function($) {
-  $("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({
-    prefix: '{{ inline_admin_formset.formset.prefix }}',
-    deleteText: "{% trans "Remove" %}",
-    addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|capfirst %}Add another {{ verbose_name }}{% endblocktrans %}"
+  $("#{{ inline_admin_formset.formset.prefix|escapejs }}-group .inline-related").stackedFormset({
+    prefix: "{{ inline_admin_formset.formset.prefix|escapejs }}",
+    deleteText: "{% filter escapejs %}{% trans "Remove" %}{% endfilter %}",
+    addText: "{% filter escapejs %}{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|capfirst %}Add another {{ verbose_name }}{% endblocktrans %}{% endfilter %}"
   });
 })(django.jQuery);
 </script>
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html
index 379595a044..a890cc334d 100644
--- a/django/contrib/admin/templates/admin/edit_inline/tabular.html
+++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html
@@ -74,10 +74,10 @@
 <script type="text/javascript">
 
 (function($) {
-  $("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
-    prefix: "{{ inline_admin_formset.formset.prefix }}",
-    addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|capfirst as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
-    deleteText: "{% trans 'Remove' %}"
+  $("#{{ inline_admin_formset.formset.prefix|escapejs }}-group .tabular.inline-related tbody tr").tabularFormset({
+    prefix: "{{ inline_admin_formset.formset.prefix|escapejs }}",
+    addText: "{% filter escapejs %}{% blocktrans with inline_admin_formset.opts.verbose_name|capfirst as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}{% endfilter %}",
+    deleteText: "{% filter escapejs %}{% trans 'Remove' %}{% endfilter %}"
   });
 })(django.jQuery);
 </script>
diff --git a/django/contrib/admin/templates/admin/popup_response.html b/django/contrib/admin/templates/admin/popup_response.html
index 3f8de11102..626ed98eda 100644
--- a/django/contrib/admin/templates/admin/popup_response.html
+++ b/django/contrib/admin/templates/admin/popup_response.html
@@ -4,11 +4,11 @@
   <body>
     <script type="text/javascript">
       {% if action == 'change' %}
-        opener.dismissChangeRelatedObjectPopup(window, "{{ value }}", "{{ obj }}", "{{ new_value }}");
+        opener.dismissChangeRelatedObjectPopup(window, "{{ value|escapejs }}", "{{ obj|escapejs }}", "{{ new_value|escapejs }}");
       {% elif action == 'delete' %}
-        opener.dismissDeleteRelatedObjectPopup(window, "{{ value }}");
+        opener.dismissDeleteRelatedObjectPopup(window, "{{ value|escapejs }}");
       {% else %}
-        opener.dismissAddRelatedObjectPopup(window, "{{ value }}", "{{ obj }}");
+        opener.dismissAddRelatedObjectPopup(window, "{{ value|escapejs }}", "{{ obj|escapejs }}");
       {% endif %}
     </script>
   </body>
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index fd0021c057..491c78870f 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -15,7 +15,7 @@ from django.template.loader import render_to_string
 from django.utils import six
 from django.utils.encoding import force_text
 from django.utils.html import (
-    escape, format_html, format_html_join, smart_urlquote,
+    escape, escapejs, format_html, format_html_join, smart_urlquote,
 )
 from django.utils.safestring import mark_safe
 from django.utils.text import Truncator
@@ -50,7 +50,7 @@ class FilteredSelectMultiple(forms.SelectMultiple):
         # TODO: "id_" is hard-coded here. This should instead use the correct
         # API to determine the ID dynamically.
         output.append('SelectFilter.init("id_%s", "%s", %s); });</script>\n'
-            % (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked)))
+            % (name, escapejs(self.verbose_name), int(self.is_stacked)))
         return mark_safe(''.join(output))
 
 
diff --git a/tests/admin_inlines/test_templates.py b/tests/admin_inlines/test_templates.py
new file mode 100644
index 0000000000..1ed52c9115
--- /dev/null
+++ b/tests/admin_inlines/test_templates.py
@@ -0,0 +1,21 @@
+from __future__ import unicode_literals
+
+from django.template.loader import render_to_string
+from django.test import SimpleTestCase
+
+
+class TestTemplates(SimpleTestCase):
+    def test_javascript_escaping(self):
+        context = {
+            'inline_admin_formset': {
+                'formset': {'prefix': 'my-prefix'},
+                'opts': {'verbose_name': 'verbose name\\'},
+            },
+        }
+        output = render_to_string('admin/edit_inline/stacked.html', context)
+        self.assertIn('prefix: "my\\u002Dprefix",', output)
+        self.assertIn('addText: "Add another Verbose name\\u005C"', output)
+
+        output = render_to_string('admin/edit_inline/tabular.html', context)
+        self.assertIn('prefix: "my\\u002Dprefix",', output)
+        self.assertIn('addText: "Add another Verbose name\\u005C"', output)
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index c262de7d27..19260baf4d 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -78,7 +78,7 @@ class TestInline(TestDataMixin, TestCase):
         # The heading for the m2m inline block uses the right text
         self.assertContains(response, '<h2>Author-book relationships</h2>')
         # The "add another" label is correct
-        self.assertContains(response, 'Add another Author-book relationship')
+        self.assertContains(response, 'Add another Author\\u002Dbook relationship')
         # The '+' is dropped from the autogenerated form prefix (Author_books+)
         self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"')
 
@@ -524,7 +524,7 @@ class TestInlinePermissions(TestCase):
         response = self.client.get(reverse('admin:admin_inlines_author_add'))
         # No change permission on books, so no inline
         self.assertNotContains(response, '<h2>Author-book relationships</h2>')
-        self.assertNotContains(response, 'Add another Author-Book Relationship')
+        self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
 
     def test_inline_add_fk_noperm(self):
@@ -538,7 +538,7 @@ class TestInlinePermissions(TestCase):
         response = self.client.get(self.author_change_url)
         # No change permission on books, so no inline
         self.assertNotContains(response, '<h2>Author-book relationships</h2>')
-        self.assertNotContains(response, 'Add another Author-Book Relationship')
+        self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
 
     def test_inline_change_fk_noperm(self):
@@ -554,7 +554,7 @@ class TestInlinePermissions(TestCase):
         response = self.client.get(reverse('admin:admin_inlines_author_add'))
         # No change permission on Books, so no inline
         self.assertNotContains(response, '<h2>Author-book relationships</h2>')
-        self.assertNotContains(response, 'Add another Author-Book Relationship')
+        self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
 
     def test_inline_add_fk_add_perm(self):
@@ -573,7 +573,7 @@ class TestInlinePermissions(TestCase):
         response = self.client.get(self.author_change_url)
         # No change permission on books, so no inline
         self.assertNotContains(response, '<h2>Author-book relationships</h2>')
-        self.assertNotContains(response, 'Add another Author-Book Relationship')
+        self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
         self.assertNotContains(response, 'id="id_Author_books-0-DELETE"')
 
@@ -583,7 +583,7 @@ class TestInlinePermissions(TestCase):
         response = self.client.get(self.author_change_url)
         # We have change perm on books, so we can add/change/delete inlines
         self.assertContains(response, '<h2>Author-book relationships</h2>')
-        self.assertContains(response, 'Add another Author-book relationship')
+        self.assertContains(response, 'Add another Author\\u002Dbook relationship')
         self.assertContains(response, '<input type="hidden" id="id_Author_books-TOTAL_FORMS" '
                             'value="4" name="Author_books-TOTAL_FORMS" />', html=True)
         self.assertContains(response, '<input type="hidden" id="id_Author_books-0-id" '
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 6bb44c0a8c..8ead8ee93c 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -24,6 +24,7 @@ from django.core.checks import Error
 from django.core.files import temp as tempfile
 from django.core.urlresolvers import NoReverseMatch, resolve, reverse
 from django.forms.utils import ErrorList
+from django.template.loader import render_to_string
 from django.template.response import TemplateResponse
 from django.test import (
     TestCase, modify_settings, override_settings, skipUnlessDBFeature,
@@ -3490,6 +3491,30 @@ action)</option>
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.template_name, 'admin/popup_response.html')
 
+    def test_popup_template_escaping(self):
+        context = {
+            'new_value': 'new_value\\',
+            'obj': 'obj\\',
+            'value': 'value\\',
+        }
+        output = render_to_string('admin/popup_response.html', context)
+        self.assertIn(
+            'opener.dismissAddRelatedObjectPopup(window, "value\\u005C", "obj\\u005C");', output
+        )
+
+        context['action'] = 'change'
+        output = render_to_string('admin/popup_response.html', context)
+        self.assertIn(
+            'opener.dismissChangeRelatedObjectPopup(window, '
+            '"value\\u005C", "obj\\u005C", "new_value\\u005C");', output
+        )
+
+        context['action'] = 'delete'
+        output = render_to_string('admin/popup_response.html', context)
+        self.assertIn(
+            'opener.dismissDeleteRelatedObjectPopup(window, "value\\u005C");', output
+        )
+
 
 @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'],
     ROOT_URLCONF="admin_views.urls")
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 5083295c46..1f0ec00eed 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -264,17 +264,23 @@ class AdminForeignKeyRawIdWidget(TestDataMixin, DjangoTestCase):
 
 class FilteredSelectMultipleWidgetTest(DjangoTestCase):
     def test_render(self):
-        w = widgets.FilteredSelectMultiple('test', False)
+        # Backslash in verbose_name to ensure it is JavaScript escaped.
+        w = widgets.FilteredSelectMultiple('test\\', False)
         self.assertHTMLEqual(
             w.render('test', 'test'),
-            '<select multiple="multiple" name="test" class="selectfilter">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 0); });</script>\n'
+            '<select multiple="multiple" name="test" class="selectfilter">\n</select>'
+            '<script type="text/javascript">addEvent(window, "load", function(e) '
+            '{SelectFilter.init("id_test", "test\\u005C", 0); });</script>\n'
         )
 
     def test_stacked_render(self):
-        w = widgets.FilteredSelectMultiple('test', True)
+        # Backslash in verbose_name to ensure it is JavaScript escaped.
+        w = widgets.FilteredSelectMultiple('test\\', True)
         self.assertHTMLEqual(
             w.render('test', 'test'),
-            '<select multiple="multiple" name="test" class="selectfilterstacked">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 1); });</script>\n'
+            '<select multiple="multiple" name="test" class="selectfilterstacked">\n</select>'
+            '<script type="text/javascript">addEvent(window, "load", function(e) '
+            '{SelectFilter.init("id_test", "test\\u005C", 1); });</script>\n'
         )