diff --git a/.gitignore b/.gitignore
index 1e00d3dd4b..b78c3a0fbc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,8 +44,9 @@ scss-lint-report.xml
# grunt-contrib-sass cache
.sass-cache
-# Jekyll metadata
+# Jekyll metadata and extra config file for `github` script
docs/.jekyll-metadata
+twbsconfig.yml
# Folders to ignore
bower_components
diff --git a/build/postcss.config.js b/build/postcss.config.js
index aef4679fa7..cadd98d03b 100644
--- a/build/postcss.config.js
+++ b/build/postcss.config.js
@@ -9,7 +9,7 @@ module.exports = (ctx) => ({
browsers: [
//
// Official browser support policy:
- // https://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#supported-browsers
+ // https://getbootstrap.com/docs/4.0/getting-started/browsers-devices/#supported-browsers
//
'Chrome >= 45', // Exact version number here is kinda arbitrary
'Firefox ESR',
diff --git a/docs/4.0/components/collapse.md b/docs/4.0/components/collapse.md
index c77d436fb0..9e889efb27 100644
--- a/docs/4.0/components/collapse.md
+++ b/docs/4.0/components/collapse.md
@@ -216,7 +216,7 @@ $('#myCollapsible').collapse({
#### `.collapse('toggle')`
-Toggles a collapsible element to shown or hidden. **Returns to the caller before the collapsible element has actually been shown or hidden (i.e. before the `shown.bs.collapse` or `hidden.bs.collapse` event occurs).
+Toggles a collapsible element to shown or hidden. **Returns to the caller before the collapsible element has actually been shown or hidden** (i.e. before the `shown.bs.collapse` or `hidden.bs.collapse` event occurs).
#### `.collapse('show')`
diff --git a/docs/4.0/components/list-group.md b/docs/4.0/components/list-group.md
index 8dc25a0dca..5fad36d5a6 100644
--- a/docs/4.0/components/list-group.md
+++ b/docs/4.0/components/list-group.md
@@ -56,7 +56,7 @@ Be sure to **not use the standard `.btn` classes here**.
{% example html %}
-
+
Cras justo odio
Dapibus ac facilisis in
diff --git a/js/src/modal.js b/js/src/modal.js
index d21a137fb6..ab73230c8f 100644
--- a/js/src/modal.js
+++ b/js/src/modal.js
@@ -68,6 +68,7 @@ const Modal = (($) => {
DATA_TOGGLE : '[data-toggle="modal"]',
DATA_DISMISS : '[data-dismiss="modal"]',
FIXED_CONTENT : '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top',
+ STICKY_CONTENT : '.sticky-top',
NAVBAR_TOGGLER : '.navbar-toggler'
}
@@ -441,6 +442,13 @@ const Modal = (($) => {
$(element).data('padding-right', actualPadding).css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`)
})
+ // Adjust sticky content margin
+ $(Selector.STICKY_CONTENT).each((index, element) => {
+ const actualMargin = $(element)[0].style.marginRight
+ const calculatedMargin = $(element).css('margin-right')
+ $(element).data('margin-right', actualMargin).css('margin-right', `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`)
+ })
+
// Adjust navbar-toggler margin
$(Selector.NAVBAR_TOGGLER).each((index, element) => {
const actualMargin = $(element)[0].style.marginRight
@@ -464,8 +472,8 @@ const Modal = (($) => {
}
})
- // Restore navbar-toggler margin
- $(Selector.NAVBAR_TOGGLER).each((index, element) => {
+ // Restore sticky content and navbar-toggler margin
+ $(`${Selector.STICKY_CONTENT}, ${Selector.NAVBAR_TOGGLER}`).each((index, element) => {
const margin = $(element).data('margin-right')
if (typeof margin !== 'undefined') {
$(element).css('margin-right', margin).removeData('margin-right')
diff --git a/js/src/util.js b/js/src/util.js
index 69fb8283cc..cd3f1fb6a2 100644
--- a/js/src/util.js
+++ b/js/src/util.js
@@ -117,7 +117,7 @@ const Util = (($) => {
}
try {
- const $selector = $(selector)
+ const $selector = $(document).find(selector)
return $selector.length > 0 ? selector : null
} catch (error) {
return null
diff --git a/js/tests/unit/modal.js b/js/tests/unit/modal.js
index 09c3524434..5b265df158 100644
--- a/js/tests/unit/modal.js
+++ b/js/tests/unit/modal.js
@@ -433,6 +433,48 @@ $(function () {
.bootstrapModal('show')
})
+ QUnit.test('should adjust the inline margin of sticky elements when opening and restore when closing', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+ var $element = $('').appendTo('#qunit-fixture')
+ var originalPadding = $element.css('margin-right')
+
+ $('')
+ .on('hidden.bs.modal', function () {
+ var currentPadding = $element.css('margin-right')
+ assert.strictEqual(currentPadding, originalPadding, 'sticky element margin should be reset after closing')
+ $element.remove()
+ done()
+ })
+ .on('shown.bs.modal', function () {
+ var expectedPadding = parseFloat(originalPadding) - $(this).getScrollbarWidth() + 'px'
+ var currentPadding = $element.css('margin-right')
+ assert.strictEqual(currentPadding, expectedPadding, 'sticky element margin should be adjusted while opening')
+ $(this).bootstrapModal('hide')
+ })
+ .bootstrapModal('show')
+ })
+
+ QUnit.test('should store the original margin of sticky elements in data-margin-right before showing', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+ var $element = $('').appendTo('#qunit-fixture')
+ var originalPadding = '0px'
+ $element.css('margin-right', originalPadding)
+
+ $('')
+ .on('hidden.bs.modal', function () {
+ assert.strictEqual(typeof $element.data('margin-right'), 'undefined', 'data-margin-right should be cleared after closing')
+ $element.remove()
+ done()
+ })
+ .on('shown.bs.modal', function () {
+ assert.strictEqual($element.data('margin-right'), originalPadding, 'original sticky element margin should be stored in data-margin-right')
+ $(this).bootstrapModal('hide')
+ })
+ .bootstrapModal('show')
+ })
+
QUnit.test('should adjust the inline margin of the navbar-toggler when opening and restore when closing', function (assert) {
assert.expect(2)
var done = assert.async()
@@ -555,4 +597,40 @@ $(function () {
})
.trigger('click')
})
+
+ QUnit.test('should not parse target as html', function (assert) {
+ assert.expect(1)
+ var done = assert.async()
+
+ var $toggleBtn = $('')
+ .appendTo('#qunit-fixture')
+
+ $toggleBtn.trigger('click')
+ setTimeout(function () {
+ assert.strictEqual($('#modal-test').length, 0, 'target has not been parsed and added to the document')
+ done()
+ }, 1)
+ })
+
+ QUnit.test('should not execute js from target', function (assert) {
+ assert.expect(0)
+ var done = assert.async()
+
+ // This toggle button contains XSS payload in its data-target
+ // Note: it uses the onerror handler of an img element to execute the js, because a simple script element does not work here
+ // a script element works in manual tests though, so here it is likely blocked by the qunit framework
+ var $toggleBtn = $('')
+ .appendTo('#qunit-fixture')
+ // The XSS payload above does not have a closure over this function and cannot access the assert object directly
+ // However, it can send a click event to the following control button, which will then fail the assert
+ $('