diff --git a/lib/global-this-polyfill.js b/lib/global-this-polyfill.js deleted file mode 100644 index 046eac61b..000000000 --- a/lib/global-this-polyfill.js +++ /dev/null @@ -1,49 +0,0 @@ -/* -https://github.com/ungap/global-this/blob/v0.4.4/esm/index.js - -Copyright (c) 2020, Andrea Giammarchi, @WebReflection - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -------- - -Patches for use in QUnit: - -- 2021-02-25: Export as module only, don't change global scope as QUnit must not - affect the host context (e.g. people may test their application intentionally - with different or no polyfills and we must not affect that). - -*/ - -let foundGlobalThis; - -(function (Object) { - if (typeof globalThis === "object") { - foundGlobalThis = globalThis; - } else { - this - ? get() - : (Object.defineProperty(Object.prototype, "_T_", { - configurable: true, - get: get, - }), - _T_); - - function get() { - foundGlobalThis = this || self; - delete Object.prototype._T_; - } - } -})(Object); - -export default foundGlobalThis; diff --git a/rollup.config.js b/rollup.config.js index 1df3007c9..3968887ab 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,6 +1,5 @@ /* eslint-env node */ -const fs = require( "fs" ); const { babel } = require( "@rollup/plugin-babel" ); const { nodeResolve } = require( "@rollup/plugin-node-resolve" ); const commonjs = require( "@rollup/plugin-commonjs" ); @@ -25,20 +24,7 @@ module.exports = { * Copyright OpenJS Foundation and other contributors\n\ * Released under the MIT license\n\ * https://jquery.org/license\n\ - */", - - intro: function() { - - // Define the (partial) ES6 Map polyfill for "fuzzysort". - // Per https://github.com/qunitjs/qunit/issues/1508: - // 1. Must not leak as global variable, since it's not full Map implementation. - // 2. Must be seen by fuzzysort as-is (e.g. not get renamed as normal - // variables in an imported file would be). - return fs.readFileSync( - __dirname + "/src/html-reporter/es6-map.js", - "utf8" - ).toString().trim(); - } + */" }, plugins: [ replace( { diff --git a/src/export.js b/src/export.js index 50699cd98..65f11fe4b 100644 --- a/src/export.js +++ b/src/export.js @@ -1,6 +1,5 @@ /* global module, exports, define */ -import { window, document, self } from "./globals"; -import globalThis from "../lib/global-this-polyfill"; +import { window, document, globalThis } from "./globals"; export default function exportQUnit( QUnit ) { let exportedModule = false; @@ -44,15 +43,8 @@ export default function exportQUnit( QUnit ) { exportedModule = true; } - // For Web/Service Workers - if ( self && self.WorkerGlobalScope && self instanceof self.WorkerGlobalScope ) { - self.QUnit = QUnit; - - exportedModule = true; - } - - // For other environments, such as SpiderMonkey (mozjs) and other - // embedded JavaScript engines + // For other environments, including Web Workers (globalThis === self), + // SpiderMonkey (mozjs), and other embedded JavaScript engines if ( !exportedModule ) { globalThis.QUnit = QUnit; } diff --git a/src/globals.js b/src/globals.js index e36e8de75..ee3be7c3f 100644 --- a/src/globals.js +++ b/src/globals.js @@ -1,10 +1,54 @@ -import globalThis from "../lib/global-this-polyfill"; +// We don't use global-this-polyfill [1], because it modifies +// the globals scope by default. QUnit must not affect the host context +// as developers may test their project may be such a polyfill, and/or +// they may intentionally test their project with and without certain +// polyfills and we must not affect that. It also uses an obscure +// mechanism that seems to sometimes causes a runtime error in older +// browsers (specifically Safari and IE versions that support +// Object.defineProperty but then report _T_ as undefined). +// [1] https://github.com/ungap/global-this/blob/v0.4.4/esm/index.js +// +// Another way is `Function('return this')()`, but doing so relies +// on eval which will cause a CSP error on some servers. +// +// Instead, simply check the four options that already exist +// in all supported environments. +function getGlobalThis() { + if ( typeof globalThis !== "undefined" ) { -export const window = globalThis.window; -export const self = globalThis.self; -export const console = globalThis.console; -export const setTimeout = globalThis.setTimeout; -export const clearTimeout = globalThis.clearTimeout; + // For SpiderMonkey, modern browsers, and recent Node.js + // eslint-disable-next-line no-undef + return globalThis; + } + if ( typeof self !== "undefined" ) { + + // For web workers + // eslint-disable-next-line no-undef + return self; + } + if ( typeof window !== "undefined" ) { + + // For document context in browsers + return window; + } + if ( typeof global !== "undefined" ) { + + // For Node.js + // eslint-disable-next-line no-undef + return global; + } + throw new Error( "Unable to locate global object" ); +} + + +// This avoids a simple `export const` assignment as that would lead Rollup +// to change getGlobalThis and use the same (generated) variable name there. +const g = getGlobalThis(); +export { g as globalThis }; +export const window = g.window; +export const console = g.console; +export const setTimeout = g.setTimeout; +export const clearTimeout = g.clearTimeout; export const document = window && window.document; export const navigator = window && window.navigator; @@ -12,10 +56,43 @@ export const navigator = window && window.navigator; export const localSessionStorage = ( function() { const x = "qunit-test-string"; try { - globalThis.sessionStorage.setItem( x, x ); - globalThis.sessionStorage.removeItem( x ); - return globalThis.sessionStorage; + g.sessionStorage.setItem( x, x ); + g.sessionStorage.removeItem( x ); + return g.sessionStorage; } catch ( e ) { return undefined; } }() ); + +// Basic fallback for ES6 Map +// Support: IE 9-10, Safari 7, PhantomJS +export const StringMap = typeof g.Map === "function" ? g.Map : function StringMap() { + var store = Object.create( null ); + var hasOwn = Object.prototype.hasOwnProperty; + this.get = function( strKey ) { + return store[ strKey ]; + }; + this.set = function( strKey, val ) { + if ( !hasOwn.call( store, strKey ) ) { + this.size++; + } + store[ strKey ] = val; + return this; + }; + this.delete = function( strKey ) { + if ( hasOwn.call( store, strKey ) ) { + delete store[ strKey ]; + this.size--; + } + }; + this.forEach = function( callback ) { + for ( var strKey in store ) { + callback( store[ strKey ], strKey ); + } + }; + this.clear = function() { + store = Object.create( null ); + this.size = 0; + }; + this.size = 0; +}; diff --git a/src/html-reporter/es6-map.js b/src/html-reporter/es6-map.js deleted file mode 100644 index 2ff7e2389..000000000 --- a/src/html-reporter/es6-map.js +++ /dev/null @@ -1,35 +0,0 @@ -// Support IE 9-10, Safari 7, PhantomJS: Partial Map fallback. -// Used by html.js (via fuzzysort.js), and test.js. -// -// FIXME: This check is broken. This file is embedded in the qunit.js closure, -// thus the Map var is hoisted in that scope, and starts undefined (not a function). -var Map = typeof Map === "function" ? Map : function StringMap() { - var store = Object.create( null ); - var hasOwn = Object.prototype.hasOwnProperty; - this.get = function( strKey ) { - return store[ strKey ]; - }; - this.set = function( strKey, val ) { - if ( !hasOwn.call( store, strKey ) ) { - this.size++; - } - store[ strKey ] = val; - return this; - }; - this.delete = function( strKey ) { - if ( hasOwn.call( store, strKey ) ) { - delete store[ strKey ]; - this.size--; - } - }; - this.forEach = function( callback ) { - for ( var strKey in store ) { - callback( store[ strKey ], strKey ); - } - }; - this.clear = function() { - store = Object.create( null ); - this.size = 0; - }; - this.size = 0; -}; diff --git a/src/test.js b/src/test.js index a24de6cf8..34757ec11 100644 --- a/src/test.js +++ b/src/test.js @@ -1,5 +1,4 @@ -import globalThis from "../lib/global-this-polyfill"; -import { setTimeout, clearTimeout } from "./globals"; +import { globalThis, setTimeout, clearTimeout, StringMap } from "./globals"; import { emit } from "./events"; import Assert from "./assert"; import Logger from "./logger"; @@ -30,7 +29,7 @@ export default function Test( settings ) { this.timeout = undefined; this.data = undefined; this.withData = false; - this.pauses = new Map(); + this.pauses = new StringMap(); this.nextPauseId = 1; extend( this, settings );