]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Add back support for IE 11
authorJohann-S <johann.servoire@gmail.com>
Sat, 16 Mar 2019 14:10:23 +0000 (16:10 +0200)
committerXhmikosR <xhmikosr@gmail.com>
Sun, 17 Mar 2019 23:11:05 +0000 (01:11 +0200)
build/build-plugins.js
js/src/button.js
js/src/dom/eventHandler.js
js/src/dom/polyfill.js
js/src/dom/selectorEngine.js
js/src/util/index.js
js/tests/browsers.js
js/tests/karma.conf.js
js/tests/unit/modal.js
js/tests/unit/tests-polyfills.js [new file with mode: 0644]

index 8569e979f1a251bd439ad0b8e3ded1b77c299e2e..8a2873341e4336945fb39ca59c48c93b6a1aaee3 100644 (file)
@@ -31,7 +31,6 @@ const bsPlugins = {
   Data: path.resolve(__dirname, '../js/src/dom/data.js'),
   EventHandler: path.resolve(__dirname, '../js/src/dom/eventHandler.js'),
   Manipulator: path.resolve(__dirname, '../js/src/dom/manipulator.js'),
-  Polyfill: path.resolve(__dirname, '../js/src/dom/polyfill.js'),
   SelectorEngine: path.resolve(__dirname, '../js/src/dom/selectorEngine.js'),
   Alert: path.resolve(__dirname, '../js/src/alert.js'),
   Button: path.resolve(__dirname, '../js/src/button.js'),
@@ -69,7 +68,8 @@ function getConfigByPluginKey(pluginKey) {
   if (
     pluginKey === 'Data' ||
     pluginKey === 'Manipulator' ||
-    pluginKey === 'Polyfill' ||
+    pluginKey === 'EventHandler' ||
+    pluginKey === 'SelectorEngine' ||
     pluginKey === 'Util' ||
     pluginKey === 'Sanitizer'
   ) {
@@ -79,17 +79,6 @@ function getConfigByPluginKey(pluginKey) {
     }
   }
 
-  if (pluginKey === 'EventHandler' || pluginKey === 'SelectorEngine') {
-    return {
-      external: [
-        bsPlugins.Polyfill
-      ],
-      globals: {
-        [bsPlugins.Polyfill]: 'Polyfill'
-      }
-    }
-  }
-
   if (pluginKey === 'Alert' || pluginKey === 'Tab') {
     return defaultPluginConfig
   }
@@ -161,7 +150,6 @@ function build(plugin) {
     'Data',
     'EventHandler',
     'Manipulator',
-    'Polyfill',
     'SelectorEngine'
   ]
 
index 6453137e4ecb6e30c4df42b0e581d1b3fe578b6c..78b0fea8cda22bc21f1ddc985fb187f15aaf7eeb 100644 (file)
@@ -166,12 +166,18 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, eve
 
 EventHandler.on(document, Event.FOCUS_DATA_API, Selector.DATA_TOGGLE_CARROT, event => {
   const button = SelectorEngine.closest(event.target, Selector.BUTTON)
-  button.classList.add(ClassName.FOCUS)
+
+  if (button) {
+    button.classList.add(ClassName.FOCUS)
+  }
 })
 
 EventHandler.on(document, Event.BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, event => {
   const button = SelectorEngine.closest(event.target, Selector.BUTTON)
-  button.classList.remove(ClassName.FOCUS)
+
+  if (button) {
+    button.classList.remove(ClassName.FOCUS)
+  }
 })
 
 /**
index 1774650533525a8bddb21f2b9dea4784b42ca75d..65c671facd41f4e74f85b06f44ae0d201f837274 100644 (file)
@@ -6,7 +6,7 @@
  */
 
 import { jQuery as $ } from '../util/index'
-import Polyfill from './polyfill'
+import { createCustomEvent, defaultPreventedPreservedOnDispatch } from './polyfill'
 
 /**
  * ------------------------------------------------------------------------
@@ -305,7 +305,7 @@ const EventHandler = {
       evt = document.createEvent('HTMLEvents')
       evt.initEvent(typeEvent, bubbles, true)
     } else {
-      evt = new CustomEvent(event, {
+      evt = createCustomEvent(event, {
         bubbles,
         cancelable: true
       })
@@ -326,7 +326,7 @@ const EventHandler = {
     if (defaultPrevented) {
       evt.preventDefault()
 
-      if (!Polyfill.defaultPreventedPreservedOnDispatch) {
+      if (!defaultPreventedPreservedOnDispatch) {
         Object.defineProperty(evt, 'defaultPrevented', {
           get: () => true
         })
index f6cd23bdb9998bed5b8cd076dd3bdaf68f7548e4..fd857cb38cf1f0ec502f9a64a029a5c0e1fea77a 100644 (file)
@@ -1,3 +1,5 @@
+/* istanbul ignore file */
+
 /**
  * --------------------------------------------------------------------------
  * Bootstrap (v4.3.1): dom/polyfill.js
 
 import { getUID } from '../util/index'
 
-/* istanbul ignore next */
-const Polyfill = (() => {
-  // MSEdge resets defaultPrevented flag upon dispatchEvent call if at least one listener is attached
-  const defaultPreventedPreservedOnDispatch = (() => {
-    const e = new CustomEvent('Bootstrap', {
-      cancelable: true
-    })
+let { matches, closest } = Element.prototype
+let find = Element.prototype.querySelectorAll
+let findOne = Element.prototype.querySelector
+let createCustomEvent = (eventName, params) => {
+  const cEvent = new CustomEvent(eventName, params)
 
-    const element = document.createElement('div')
-    element.addEventListener('Bootstrap', () => null)
+  return cEvent
+}
 
-    e.preventDefault()
-    element.dispatchEvent(e)
-    return e.defaultPrevented
-  })()
+if (typeof window.CustomEvent !== 'function') {
+  createCustomEvent = (eventName, params) => {
+    params = params || { bubbles: false, cancelable: false, detail: null }
 
-  let find = Element.prototype.querySelectorAll
-  let findOne = Element.prototype.querySelector
+    const evt = document.createEvent('CustomEvent')
 
-  const scopeSelectorRegex = /:scope\b/
-  const supportScopeQuery = (() => {
-    const element = document.createElement('div')
+    evt.initCustomEvent(eventName, params.bubbles, params.cancelable, params.detail)
+    return evt
+  }
+}
 
-    try {
-      element.querySelectorAll(':scope *')
-    } catch (error) {
-      return false
+const workingDefaultPrevented = (() => {
+  const e = document.createEvent('CustomEvent')
+
+  e.initEvent('Bootstrap', true, true)
+  e.preventDefault()
+  return e.defaultPrevented
+})()
+
+if (!workingDefaultPrevented) {
+  const origPreventDefault = Event.prototype.preventDefault
+
+  Event.prototype.preventDefault = function () {
+    if (!this.cancelable) {
+      return
     }
 
-    return true
-  })()
+    origPreventDefault.call(this)
+    Object.defineProperty(this, 'defaultPrevented', {
+      get() {
+        return true
+      },
+      configurable: true
+    })
+  }
+}
 
-  if (!supportScopeQuery) {
-    find = function (selector) {
-      if (!scopeSelectorRegex.test(selector)) {
-        return this.querySelectorAll(selector)
-      }
+// MSEdge resets defaultPrevented flag upon dispatchEvent call if at least one listener is attached
+const defaultPreventedPreservedOnDispatch = (() => {
+  const e = createCustomEvent('Bootstrap', {
+    cancelable: true
+  })
 
-      const hasId = Boolean(this.id)
+  const element = document.createElement('div')
+  element.addEventListener('Bootstrap', () => null)
 
-      if (!hasId) {
-        this.id = getUID('scope')
-      }
+  e.preventDefault()
+  element.dispatchEvent(e)
+  return e.defaultPrevented
+})()
+
+if (!matches) {
+  matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector
+}
 
-      let nodeList = null
-      try {
-        selector = selector.replace(scopeSelectorRegex, `#${this.id}`)
-        nodeList = this.querySelectorAll(selector)
-      } finally {
-        if (!hasId) {
-          this.removeAttribute('id')
-        }
+if (!closest) {
+  closest = function (selector) {
+    let element = this
+
+    do {
+      if (matches.call(element, selector)) {
+        return element
       }
 
-      return nodeList
+      element = element.parentElement || element.parentNode
+    } while (element !== null && element.nodeType === 1)
+
+    return null
+  }
+}
+
+const scopeSelectorRegex = /:scope\b/
+const supportScopeQuery = (() => {
+  const element = document.createElement('div')
+
+  try {
+    element.querySelectorAll(':scope *')
+  } catch (error) {
+    return false
+  }
+
+  return true
+})()
+
+if (!supportScopeQuery) {
+  find = function (selector) {
+    if (!scopeSelectorRegex.test(selector)) {
+      return this.querySelectorAll(selector)
     }
 
-    findOne = function (selector) {
-      if (!scopeSelectorRegex.test(selector)) {
-        return this.querySelector(selector)
-      }
+    const hasId = Boolean(this.id)
 
-      const matches = find.call(this, selector)
+    if (!hasId) {
+      this.id = getUID('scope')
+    }
 
-      if (typeof matches[0] !== 'undefined') {
-        return matches[0]
+    let nodeList = null
+    try {
+      selector = selector.replace(scopeSelectorRegex, `#${this.id}`)
+      nodeList = this.querySelectorAll(selector)
+    } finally {
+      if (!hasId) {
+        this.removeAttribute('id')
       }
-
-      return null
     }
-  }
 
-  return {
-    defaultPreventedPreservedOnDispatch,
-    find,
-    findOne
+    return nodeList
   }
-})()
 
-export default Polyfill
+  findOne = function (selector) {
+    if (!scopeSelectorRegex.test(selector)) {
+      return this.querySelector(selector)
+    }
+
+    const matches = find.call(this, selector)
+
+    if (typeof matches[0] !== 'undefined') {
+      return matches[0]
+    }
+
+    return null
+  }
+}
+
+export {
+  createCustomEvent,
+  find,
+  findOne,
+  matches,
+  closest,
+  defaultPreventedPreservedOnDispatch
+}
index a54b18e5817613742ca9748b20c129ce1f988a3f..fad3a43b544dada995eb45b7193e69fb8089d422 100644 (file)
@@ -5,7 +5,7 @@
  * --------------------------------------------------------------------------
  */
 
-import Polyfill from './polyfill'
+import { find as findFn, findOne, matches, closest } from './polyfill'
 import { makeArray } from '../util/index'
 
 /**
@@ -14,12 +14,11 @@ import { makeArray } from '../util/index'
  * ------------------------------------------------------------------------
  */
 
-const { find: findFn, findOne } = Polyfill
 const NODE_TEXT = 3
 
 const SelectorEngine = {
   matches(element, selector) {
-    return element.matches(selector)
+    return matches.call(element, selector)
   },
 
   find(selector, element = document.documentElement) {
@@ -72,7 +71,7 @@ const SelectorEngine = {
       return null
     }
 
-    return element.closest(selector)
+    return closest.call(element, selector)
   },
 
   prev(element, selector) {
index aea369558aeb251bbb66c4e72acd2780182a5305..5788c8749e84de0528003823766127f7421d1caf 100644 (file)
@@ -71,7 +71,10 @@ const getTransitionDurationFromElement = element => {
 }
 
 const triggerTransitionEnd = element => {
-  element.dispatchEvent(new Event(TRANSITION_END))
+  const evt = document.createEvent('HTMLEvents')
+
+  evt.initEvent(TRANSITION_END, true, true)
+  element.dispatchEvent(evt)
 }
 
 const isElement = obj => (obj[0] || obj).nodeType
index 859f9505ccbf6ba3f185515df32f0d57e9e45edf..a0d43da864ae9f2f3f700fe52885f154af0b26fa 100644 (file)
@@ -30,6 +30,13 @@ const browsers = {
     browser: 'Edge',
     browser_version: 'latest'
   },
+  ie11Win10: {
+    base: 'BrowserStack',
+    os: 'Windows',
+    os_version: '10',
+    browser: 'IE',
+    browser_version: '11.0'
+  },
   chromeWin10: {
     base: 'BrowserStack',
     os: 'Windows',
index 122d95753b4479a7f931d8e891619ed7c7c0986f..16f07cbf1a29854753f9b4c489234b5da3e9103e 100644 (file)
@@ -79,8 +79,9 @@ if (bundle) {
   conf.detectBrowsers = detectBrowsers
   files = files.concat([
     jqueryFile,
+    'js/tests/unit/tests-polyfills.js',
     'dist/js/bootstrap.js',
-    'js/tests/unit/*.js'
+    'js/tests/unit/!(tests-polyfills).js'
   ])
 } else if (browserStack) {
   conf.hostname = ip.address()
@@ -97,6 +98,7 @@ if (bundle) {
   reporters.push('BrowserStack')
   files = files.concat([
     jqueryFile,
+    'js/tests/unit/tests-polyfills.js',
     'js/coverage/dist/util/util.js',
     'js/coverage/dist/util/sanitizer.js',
     'js/coverage/dist/dom/polyfill.js',
@@ -107,7 +109,7 @@ if (bundle) {
     'js/coverage/dist/dom/!(polyfill).js',
     'js/coverage/dist/tooltip.js',
     'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js
-    'js/tests/unit/*.js',
+    'js/tests/unit/!(tests-polyfills).js',
     'js/tests/unit/dom/*.js',
     'js/tests/unit/util/*.js'
   ])
@@ -121,6 +123,7 @@ if (bundle) {
   )
   files = files.concat([
     jqueryFile,
+    'js/tests/unit/tests-polyfills.js',
     'js/coverage/dist/util/util.js',
     'js/coverage/dist/util/sanitizer.js',
     'js/coverage/dist/dom/polyfill.js',
@@ -131,7 +134,7 @@ if (bundle) {
     'js/coverage/dist/dom/!(polyfill).js',
     'js/coverage/dist/tooltip.js',
     'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js
-    'js/tests/unit/*.js',
+    'js/tests/unit/!(tests-polyfills).js',
     'js/tests/unit/dom/*.js',
     'js/tests/unit/util/*.js'
   ])
index 87d778b8687085e806bf20d76fb0b7aae64fa2c5..82b37f2367106b8472d3a16de3b54ffcaf1f9f64 100644 (file)
@@ -731,7 +731,14 @@ $(function () {
   })
 
   QUnit.test('should enforce focus', function (assert) {
-    assert.expect(2)
+    var isIE11 = Boolean(window.MSInputMethodContext) && Boolean(document.documentMode)
+
+    if (isIE11) {
+      assert.expect(1)
+    } else {
+      assert.expect(2)
+    }
+
     var done = assert.async()
 
     var $modal = $([
@@ -759,14 +766,18 @@ $(function () {
         done()
       }
 
-      document.addEventListener('focusin', focusInListener)
+      if (isIE11) {
+        done()
+      } else {
+        document.addEventListener('focusin', focusInListener)
 
-      var focusInEvent = new Event('focusin')
-      Object.defineProperty(focusInEvent, 'target', {
-        value: $('#qunit-fixture')[0]
-      })
+        var focusInEvent = new Event('focusin')
+        Object.defineProperty(focusInEvent, 'target', {
+          value: $('#qunit-fixture')[0]
+        })
 
-      document.dispatchEvent(focusInEvent)
+        document.dispatchEvent(focusInEvent)
+      }
     })
       .bootstrapModal('show')
   })
diff --git a/js/tests/unit/tests-polyfills.js b/js/tests/unit/tests-polyfills.js
new file mode 100644 (file)
index 0000000..4f2583e
--- /dev/null
@@ -0,0 +1,28 @@
+// Polyfills for our unit tests
+(function () {
+  'use strict'
+
+  // Event constructor shim
+  if (!window.Event || typeof window.Event !== 'function') {
+    var origEvent = window.Event
+    window.Event = function (inType, params) {
+      params = params || {}
+      var e = document.createEvent('Event')
+      e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable))
+      return e
+    }
+
+    window.Event.prototype = origEvent.prototype
+  }
+
+  if (typeof window.CustomEvent !== 'function') {
+    window.CustomEvent = function (event, params) {
+      params = params || { bubbles: false, cancelable: false, detail: null }
+      var evt = document.createEvent('CustomEvent')
+      evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
+      return evt
+    }
+
+    CustomEvent.prototype = window.Event.prototype
+  }
+})()