Script Loader: Add polyfill for window.URL, window.DOMRect.

Pending block editor revisions for WordPress 5.4 will make use of `window.URL` and `window.DOMRect`. These are not available in Internet Explorer (or pre-Chromium Edge for `DOMRect`) and must be polyfilled to avoid script errors.

The changes make use of the existing polyfill pattern, and existing `polyfill-library` dependency. The dependency is bumped to the latest version, since the previous version did not include the `DOMRect` polyfill.

Props jorgefilipecosta.
Fixes #49360.

Built from https://develop.svn.wordpress.org/trunk@47238


git-svn-id: http://core.svn.wordpress.org/trunk@47038 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
aduth 2020-02-10 15:17:07 +00:00
parent c14f2b74c0
commit 4bff827833
6 changed files with 602 additions and 2 deletions

View File

@ -0,0 +1,99 @@
(function (global) {
function number(v) {
return v === undefined ? 0 : Number(v);
}
function different(u, v) {
return u !== v && !(isNaN(u) && isNaN(v));
}
function DOMRect(xArg, yArg, wArg, hArg) {
var x, y, width, height, left, right, top, bottom;
x = number(xArg);
y = number(yArg);
width = number(wArg);
height = number(hArg);
Object.defineProperties(this, {
x: {
get: function () { return x; },
set: function (newX) {
if (different(x, newX)) {
x = newX;
left = right = undefined;
}
},
enumerable: true
},
y: {
get: function () { return y; },
set: function (newY) {
if (different(y, newY)) {
y = newY;
top = bottom = undefined;
}
},
enumerable: true
},
width: {
get: function () { return width; },
set: function (newWidth) {
if (different(width, newWidth)) {
width = newWidth;
left = right = undefined;
}
},
enumerable: true
},
height: {
get: function () { return height; },
set: function (newHeight) {
if (different(height, newHeight)) {
height = newHeight;
top = bottom = undefined;
}
},
enumerable: true
},
left: {
get: function () {
if (left === undefined) {
left = x + Math.min(0, width);
}
return left;
},
enumerable: true
},
right: {
get: function () {
if (right === undefined) {
right = x + Math.max(0, width);
}
return right;
},
enumerable: true
},
top: {
get: function () {
if (top === undefined) {
top = y + Math.min(0, height);
}
return top;
},
enumerable: true
},
bottom: {
get: function () {
if (bottom === undefined) {
bottom = y + Math.max(0, height);
}
return bottom;
},
enumerable: true
}
});
}
global.DOMRect = DOMRect;
}(this));

View File

@ -0,0 +1 @@
!function(e){function d(e){return void 0===e?0:Number(e)}function g(e,t){return e!==t&&!(isNaN(e)&&isNaN(t))}e.DOMRect=function(e,t,n,i){var u,r,o,c,f,a,m,b;u=d(e),r=d(t),o=d(n),c=d(i),Object.defineProperties(this,{x:{get:function(){return u},set:function(e){g(u,e)&&(u=e,f=a=void 0)},enumerable:!0},y:{get:function(){return r},set:function(e){g(r,e)&&(r=e,m=b=void 0)},enumerable:!0},width:{get:function(){return o},set:function(e){g(o,e)&&(o=e,f=a=void 0)},enumerable:!0},height:{get:function(){return c},set:function(e){g(c,e)&&(c=e,m=b=void 0)},enumerable:!0},left:{get:function(){return void 0===f&&(f=u+Math.min(0,o)),f},enumerable:!0},right:{get:function(){return void 0===a&&(a=u+Math.max(0,o)),a},enumerable:!0},top:{get:function(){return void 0===m&&(m=r+Math.min(0,c)),m},enumerable:!0},bottom:{get:function(){return void 0===b&&(b=r+Math.max(0,c)),b},enumerable:!0}})}}(this);

View File

@ -0,0 +1,493 @@
/* global Symbol */
// URL Polyfill
// Draft specification: https://url.spec.whatwg.org
// Notes:
// - Primarily useful for parsing URLs and modifying query parameters
// - Should work in IE8+ and everything more modern, with es5.js polyfills
(function (global) {
'use strict';
function isSequence(o) {
if (!o) return false;
if ('Symbol' in global && 'iterator' in global.Symbol &&
typeof o[Symbol.iterator] === 'function') return true;
if (Array.isArray(o)) return true;
return false;
}
function toArray(iter) {
return ('from' in Array) ? Array.from(iter) : Array.prototype.slice.call(iter);
}
(function() {
// Browsers may have:
// * No global URL object
// * URL with static methods only - may have a dummy constructor
// * URL with members except searchParams
// * Full URL API support
var origURL = global.URL;
var nativeURL;
try {
if (origURL) {
nativeURL = new global.URL('http://example.com');
if ('searchParams' in nativeURL) {
var url = new URL('http://example.com');
url.search = 'a=1&b=2';
if (url.href === 'http://example.com/?a=1&b=2') {
url.search = '';
if (url.href === 'http://example.com/') {
return;
}
}
}
if (!('href' in nativeURL)) {
nativeURL = undefined;
}
nativeURL = undefined;
}
} catch (_) {}
// NOTE: Doesn't do the encoding/decoding dance
function urlencoded_serialize(pairs) {
var output = '', first = true;
pairs.forEach(function (pair) {
var name = encodeURIComponent(pair.name);
var value = encodeURIComponent(pair.value);
if (!first) output += '&';
output += name + '=' + value;
first = false;
});
return output.replace(/%20/g, '+');
}
// NOTE: Doesn't do the encoding/decoding dance
function urlencoded_parse(input, isindex) {
var sequences = input.split('&');
if (isindex && sequences[0].indexOf('=') === -1)
sequences[0] = '=' + sequences[0];
var pairs = [];
sequences.forEach(function (bytes) {
if (bytes.length === 0) return;
var index = bytes.indexOf('=');
if (index !== -1) {
var name = bytes.substring(0, index);
var value = bytes.substring(index + 1);
} else {
name = bytes;
value = '';
}
name = name.replace(/\+/g, ' ');
value = value.replace(/\+/g, ' ');
pairs.push({ name: name, value: value });
});
var output = [];
pairs.forEach(function (pair) {
output.push({
name: decodeURIComponent(pair.name),
value: decodeURIComponent(pair.value)
});
});
return output;
}
function URLUtils(url) {
if (nativeURL)
return new origURL(url);
var anchor = document.createElement('a');
anchor.href = url;
return anchor;
}
function URLSearchParams(init) {
var $this = this;
this._list = [];
if (init === undefined || init === null) {
// no-op
} else if (init instanceof URLSearchParams) {
// In ES6 init would be a sequence, but special case for ES5.
this._list = urlencoded_parse(String(init));
} else if (typeof init === 'object' && isSequence(init)) {
toArray(init).forEach(function(e) {
if (!isSequence(e)) throw TypeError();
var nv = toArray(e);
if (nv.length !== 2) throw TypeError();
$this._list.push({name: String(nv[0]), value: String(nv[1])});
});
} else if (typeof init === 'object' && init) {
Object.keys(init).forEach(function(key) {
$this._list.push({name: String(key), value: String(init[key])});
});
} else {
init = String(init);
if (init.substring(0, 1) === '?')
init = init.substring(1);
this._list = urlencoded_parse(init);
}
this._url_object = null;
this._setList = function (list) { if (!updating) $this._list = list; };
var updating = false;
this._update_steps = function() {
if (updating) return;
updating = true;
if (!$this._url_object) return;
// Partial workaround for IE issue with 'about:'
if ($this._url_object.protocol === 'about:' &&
$this._url_object.pathname.indexOf('?') !== -1) {
$this._url_object.pathname = $this._url_object.pathname.split('?')[0];
}
$this._url_object.search = urlencoded_serialize($this._list);
updating = false;
};
}
Object.defineProperties(URLSearchParams.prototype, {
append: {
value: function (name, value) {
this._list.push({ name: name, value: value });
this._update_steps();
}, writable: true, enumerable: true, configurable: true
},
'delete': {
value: function (name) {
for (var i = 0; i < this._list.length;) {
if (this._list[i].name === name)
this._list.splice(i, 1);
else
++i;
}
this._update_steps();
}, writable: true, enumerable: true, configurable: true
},
get: {
value: function (name) {
for (var i = 0; i < this._list.length; ++i) {
if (this._list[i].name === name)
return this._list[i].value;
}
return null;
}, writable: true, enumerable: true, configurable: true
},
getAll: {
value: function (name) {
var result = [];
for (var i = 0; i < this._list.length; ++i) {
if (this._list[i].name === name)
result.push(this._list[i].value);
}
return result;
}, writable: true, enumerable: true, configurable: true
},
has: {
value: function (name) {
for (var i = 0; i < this._list.length; ++i) {
if (this._list[i].name === name)
return true;
}
return false;
}, writable: true, enumerable: true, configurable: true
},
set: {
value: function (name, value) {
var found = false;
for (var i = 0; i < this._list.length;) {
if (this._list[i].name === name) {
if (!found) {
this._list[i].value = value;
found = true;
++i;
} else {
this._list.splice(i, 1);
}
} else {
++i;
}
}
if (!found)
this._list.push({ name: name, value: value });
this._update_steps();
}, writable: true, enumerable: true, configurable: true
},
entries: {
value: function() { return new Iterator(this._list, 'key+value'); },
writable: true, enumerable: true, configurable: true
},
keys: {
value: function() { return new Iterator(this._list, 'key'); },
writable: true, enumerable: true, configurable: true
},
values: {
value: function() { return new Iterator(this._list, 'value'); },
writable: true, enumerable: true, configurable: true
},
forEach: {
value: function(callback) {
var thisArg = (arguments.length > 1) ? arguments[1] : undefined;
this._list.forEach(function(pair) {
callback.call(thisArg, pair.value, pair.name);
});
}, writable: true, enumerable: true, configurable: true
},
toString: {
value: function () {
return urlencoded_serialize(this._list);
}, writable: true, enumerable: false, configurable: true
}
});
function Iterator(source, kind) {
var index = 0;
this['next'] = function() {
if (index >= source.length)
return {done: true, value: undefined};
var pair = source[index++];
return {done: false, value:
kind === 'key' ? pair.name :
kind === 'value' ? pair.value :
[pair.name, pair.value]};
};
}
if ('Symbol' in global && 'iterator' in global.Symbol) {
Object.defineProperty(URLSearchParams.prototype, global.Symbol.iterator, {
value: URLSearchParams.prototype.entries,
writable: true, enumerable: true, configurable: true});
Object.defineProperty(Iterator.prototype, global.Symbol.iterator, {
value: function() { return this; },
writable: true, enumerable: true, configurable: true});
}
function URL(url, base) {
if (!(this instanceof global.URL))
throw new TypeError("Failed to construct 'URL': Please use the 'new' operator.");
if (base) {
url = (function () {
if (nativeURL) return new origURL(url, base).href;
var iframe;
try {
var doc;
// Use another document/base tag/anchor for relative URL resolution, if possible
if (Object.prototype.toString.call(window.operamini) === "[object OperaMini]") {
iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.documentElement.appendChild(iframe);
doc = iframe.contentWindow.document;
} else if (document.implementation && document.implementation.createHTMLDocument) {
doc = document.implementation.createHTMLDocument('');
} else if (document.implementation && document.implementation.createDocument) {
doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
doc.documentElement.appendChild(doc.createElement('head'));
doc.documentElement.appendChild(doc.createElement('body'));
} else if (window.ActiveXObject) {
doc = new window.ActiveXObject('htmlfile');
doc.write('<head><\/head><body><\/body>');
doc.close();
}
if (!doc) throw Error('base not supported');
var baseTag = doc.createElement('base');
baseTag.href = base;
doc.getElementsByTagName('head')[0].appendChild(baseTag);
var anchor = doc.createElement('a');
anchor.href = url;
return anchor.href;
} finally {
if (iframe)
iframe.parentNode.removeChild(iframe);
}
}());
}
// An inner object implementing URLUtils (either a native URL
// object or an HTMLAnchorElement instance) is used to perform the
// URL algorithms. With full ES5 getter/setter support, return a
// regular object For IE8's limited getter/setter support, a
// different HTMLAnchorElement is returned with properties
// overridden
var instance = URLUtils(url || '');
// Detect for ES5 getter/setter support
// (an Object.defineProperties polyfill that doesn't support getters/setters may throw)
var ES5_GET_SET = (function() {
if (!('defineProperties' in Object)) return false;
try {
var obj = {};
Object.defineProperties(obj, { prop: { 'get': function () { return true; } } });
return obj.prop;
} catch (_) {
return false;
}
}());
var self = ES5_GET_SET ? this : document.createElement('a');
var query_object = new URLSearchParams(
instance.search ? instance.search.substring(1) : null);
query_object._url_object = self;
Object.defineProperties(self, {
href: {
get: function () { return instance.href; },
set: function (v) { instance.href = v; tidy_instance(); update_steps(); },
enumerable: true, configurable: true
},
origin: {
get: function () {
if ('origin' in instance) return instance.origin;
return this.protocol + '//' + this.host;
},
enumerable: true, configurable: true
},
protocol: {
get: function () { return instance.protocol; },
set: function (v) { instance.protocol = v; },
enumerable: true, configurable: true
},
username: {
get: function () { return instance.username; },
set: function (v) { instance.username = v; },
enumerable: true, configurable: true
},
password: {
get: function () { return instance.password; },
set: function (v) { instance.password = v; },
enumerable: true, configurable: true
},
host: {
get: function () {
// IE returns default port in |host|
var re = {'http:': /:80$/, 'https:': /:443$/, 'ftp:': /:21$/}[instance.protocol];
return re ? instance.host.replace(re, '') : instance.host;
},
set: function (v) { instance.host = v; },
enumerable: true, configurable: true
},
hostname: {
get: function () { return instance.hostname; },
set: function (v) { instance.hostname = v; },
enumerable: true, configurable: true
},
port: {
get: function () { return instance.port; },
set: function (v) { instance.port = v; },
enumerable: true, configurable: true
},
pathname: {
get: function () {
// IE does not include leading '/' in |pathname|
if (instance.pathname.charAt(0) !== '/') return '/' + instance.pathname;
return instance.pathname;
},
set: function (v) { instance.pathname = v; },
enumerable: true, configurable: true
},
search: {
get: function () { return instance.search; },
set: function (v) {
if (instance.search === v) return;
instance.search = v; tidy_instance(); update_steps();
},
enumerable: true, configurable: true
},
searchParams: {
get: function () { return query_object; },
enumerable: true, configurable: true
},
hash: {
get: function () { return instance.hash; },
set: function (v) { instance.hash = v; tidy_instance(); },
enumerable: true, configurable: true
},
toString: {
value: function() { return instance.toString(); },
enumerable: false, configurable: true
},
valueOf: {
value: function() { return instance.valueOf(); },
enumerable: false, configurable: true
}
});
function tidy_instance() {
var href = instance.href.replace(/#$|\?$|\?(?=#)/g, '');
if (instance.href !== href)
instance.href = href;
}
function update_steps() {
query_object._setList(instance.search ? urlencoded_parse(instance.search.substring(1)) : []);
query_object._update_steps();
}
return self;
}
if (origURL) {
for (var i in origURL) {
if (origURL.hasOwnProperty(i) && typeof origURL[i] === 'function')
URL[i] = origURL[i];
}
}
global.URL = URL;
global.URLSearchParams = URLSearchParams;
}());
// Patch native URLSearchParams constructor to handle sequences/records
// if necessary.
(function() {
if (new global.URLSearchParams([['a', 1]]).get('a') === '1' &&
new global.URLSearchParams({a: 1}).get('a') === '1')
return;
var orig = global.URLSearchParams;
global.URLSearchParams = function(init) {
if (init && typeof init === 'object' && isSequence(init)) {
var o = new orig();
toArray(init).forEach(function(e) {
if (!isSequence(e)) throw TypeError();
var nv = toArray(e);
if (nv.length !== 2) throw TypeError();
o.append(nv[0], nv[1]);
});
return o;
} else if (init && typeof init === 'object') {
o = new orig();
Object.keys(init).forEach(function(key) {
o.set(key, init[key]);
});
return o;
} else {
return new orig(init);
}
};
}());
}(self));

File diff suppressed because one or more lines are too long

View File

@ -86,6 +86,8 @@ function wp_default_packages_vendor( &$scripts ) {
'wp-polyfill-fetch',
'wp-polyfill-formdata',
'wp-polyfill-node-contains',
'wp-polyfill-url',
'wp-polyfill-dom-rect',
'wp-polyfill-element-closest',
'wp-polyfill',
);
@ -97,7 +99,9 @@ function wp_default_packages_vendor( &$scripts ) {
'lodash' => '4.17.15',
'wp-polyfill-fetch' => '3.0.0',
'wp-polyfill-formdata' => '3.0.12',
'wp-polyfill-node-contains' => '3.26.0-0',
'wp-polyfill-node-contains' => '3.42.0',
'wp-polyfill-url' => '3.42.0',
'wp-polyfill-dom-rect' => '3.42.0',
'wp-polyfill-element-closest' => '2.0.2',
'wp-polyfill' => '7.4.4',
);
@ -122,6 +126,8 @@ function wp_default_packages_vendor( &$scripts ) {
array(
'\'fetch\' in window' => 'wp-polyfill-fetch',
'document.contains' => 'wp-polyfill-node-contains',
'window.URL' => 'wp-polyfill-url',
'window.DOMRect' => 'wp-polyfill-dom-rect',
'window.FormData && window.FormData.prototype.keys' => 'wp-polyfill-formdata',
'Element.prototype.matches && Element.prototype.closest' => 'wp-polyfill-element-closest',
)

View File

@ -13,7 +13,7 @@
*
* @global string $wp_version
*/
$wp_version = '5.4-alpha-47237';
$wp_version = '5.4-alpha-47238';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.