mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-24 14:33:59 +00:00
Added key-sorting to make hashes referentially transparent
To do this, we are using the replacer callback of JSON.stringify, which is supported by all of our target browsers including IE8. We are also leaning on Object.keys and Array.reduce, the latter of which required adding a new fallback implementation for some browsers which do not support it yet. Change-Id: Ifa285ca3da4d94d962464f09414591532bbea79c
This commit is contained in:
parent
e113ae3d9f
commit
c8b4a28936
|
@ -446,7 +446,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'b',
|
||||
{
|
||||
'{"type":"link/WikiLink","data":{"title":"Foo bar","htmlAttributes":{"data-rt":"{\\"sHref\\":\\"foo bar\\"}","href":"Foo_bar","rel":"mw:WikiLink"}}}': {
|
||||
'{"data":{"htmlAttributes":{"data-rt":"{\\"sHref\\":\\"foo bar\\"}","href":"Foo_bar","rel":"mw:WikiLink"},"title":"Foo bar"},"type":"link/WikiLink"}': {
|
||||
'type': 'link/WikiLink',
|
||||
'data': {
|
||||
'title': 'Foo bar',
|
||||
|
@ -462,7 +462,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'a',
|
||||
{
|
||||
'{"type":"link/WikiLink","data":{"title":"Foo bar","htmlAttributes":{"data-rt":"{\\"sHref\\":\\"foo bar\\"}","href":"Foo_bar","rel":"mw:WikiLink"}}}': {
|
||||
'{"data":{"htmlAttributes":{"data-rt":"{\\"sHref\\":\\"foo bar\\"}","href":"Foo_bar","rel":"mw:WikiLink"},"title":"Foo bar"},"type":"link/WikiLink"}': {
|
||||
'type': 'link/WikiLink',
|
||||
'data': {
|
||||
'title': 'Foo bar',
|
||||
|
@ -478,7 +478,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'r',
|
||||
{
|
||||
'{"type":"link/WikiLink","data":{"title":"Foo bar","htmlAttributes":{"data-rt":"{\\"sHref\\":\\"foo bar\\"}","href":"Foo_bar","rel":"mw:WikiLink"}}}': {
|
||||
'{"data":{"htmlAttributes":{"data-rt":"{\\"sHref\\":\\"foo bar\\"}","href":"Foo_bar","rel":"mw:WikiLink"},"title":"Foo bar"},"type":"link/WikiLink"}': {
|
||||
'type': 'link/WikiLink',
|
||||
'data': {
|
||||
'title': 'Foo bar',
|
||||
|
@ -504,7 +504,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'F',
|
||||
{
|
||||
'{"type":"link/WikiLink","data":{"title":"Foo/Bar","htmlAttributes":{"href":"../../../Foo/Bar","rel":"mw:WikiLink"}}}': {
|
||||
'{"data":{"htmlAttributes":{"href":"../../../Foo/Bar","rel":"mw:WikiLink"},"title":"Foo/Bar"},"type":"link/WikiLink"}': {
|
||||
'type': 'link/WikiLink',
|
||||
'data': {
|
||||
'title': 'Foo/Bar',
|
||||
|
@ -519,7 +519,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'o',
|
||||
{
|
||||
'{"type":"link/WikiLink","data":{"title":"Foo/Bar","htmlAttributes":{"href":"../../../Foo/Bar","rel":"mw:WikiLink"}}}': {
|
||||
'{"data":{"htmlAttributes":{"href":"../../../Foo/Bar","rel":"mw:WikiLink"},"title":"Foo/Bar"},"type":"link/WikiLink"}': {
|
||||
'type': 'link/WikiLink',
|
||||
'data': {
|
||||
'title': 'Foo/Bar',
|
||||
|
@ -534,7 +534,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'o',
|
||||
{
|
||||
'{"type":"link/WikiLink","data":{"title":"Foo/Bar","htmlAttributes":{"href":"../../../Foo/Bar","rel":"mw:WikiLink"}}}': {
|
||||
'{"data":{"htmlAttributes":{"href":"../../../Foo/Bar","rel":"mw:WikiLink"},"title":"Foo/Bar"},"type":"link/WikiLink"}': {
|
||||
'type': 'link/WikiLink',
|
||||
'data': {
|
||||
'title': 'Foo/Bar',
|
||||
|
@ -556,7 +556,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'[',
|
||||
{
|
||||
'{"type":"link/ExtLink/Numbered","data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/Numbered"}}}': {
|
||||
'{"data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/Numbered"}},"type":"link/ExtLink/Numbered"}': {
|
||||
'type': 'link/ExtLink/Numbered',
|
||||
'data': {
|
||||
'href': 'http://www.mediawiki.org/',
|
||||
|
@ -571,7 +571,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'1',
|
||||
{
|
||||
'{"type":"link/ExtLink/Numbered","data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/Numbered"}}}': {
|
||||
'{"data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/Numbered"}},"type":"link/ExtLink/Numbered"}': {
|
||||
'type': 'link/ExtLink/Numbered',
|
||||
'data': {
|
||||
'href': 'http://www.mediawiki.org/',
|
||||
|
@ -586,7 +586,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
']',
|
||||
{
|
||||
'{"type":"link/ExtLink/Numbered","data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/Numbered"}}}': {
|
||||
'{"data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/Numbered"}},"type":"link/ExtLink/Numbered"}': {
|
||||
'type': 'link/ExtLink/Numbered',
|
||||
'data': {
|
||||
'href': 'http://www.mediawiki.org/',
|
||||
|
@ -608,7 +608,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'm',
|
||||
{
|
||||
'{"type":"link/ExtLink/URL","data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/URL"}}}': {
|
||||
'{"data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/URL"}},"type":"link/ExtLink/URL"}': {
|
||||
'type': 'link/ExtLink/URL',
|
||||
'data': {
|
||||
'href': 'http://www.mediawiki.org/',
|
||||
|
@ -623,7 +623,7 @@ ve.dm.example.domToDataCases = {
|
|||
[
|
||||
'w',
|
||||
{
|
||||
'{"type":"link/ExtLink/URL","data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/URL"}}}': {
|
||||
'{"data":{"href":"http://www.mediawiki.org/","htmlAttributes":{"href":"http://www.mediawiki.org/","rel":"mw:ExtLink/URL"}},"type":"link/ExtLink/URL"}': {
|
||||
'type': 'link/ExtLink/URL',
|
||||
'data': {
|
||||
'href': 'http://www.mediawiki.org/',
|
||||
|
|
|
@ -119,18 +119,87 @@ ve.extendObject = $.extend;
|
|||
/**
|
||||
* Generates a hash of an object based on its name and data.
|
||||
*
|
||||
* This is actually an alias for jQuery.json, which falls back to window.JSON if present.
|
||||
*
|
||||
* WARNING: 2 objects can have the same contents but not the same hash if the properties were set
|
||||
* in a different order. Recursive sorting may nessecary prior to hashing, or a hashing algorithm
|
||||
* that produces order-safe reults may need to be used here instead.
|
||||
* To avoid two objects with the same values generating different hashes, we utilize the replacer
|
||||
* argument of JSON.stringify and sort the object by key as it's being serialized. This may or may
|
||||
* not be the fastest way to do this; we should investigate this further.
|
||||
*
|
||||
* @static
|
||||
* @method
|
||||
* @param {Object} obj Object to generate hash for
|
||||
* @returns {String} Hash of object
|
||||
*/
|
||||
ve.getHash = $.toJSON;
|
||||
ve.getHash = function ( value ) {
|
||||
return JSON.stringify( value, ve.getHash.keySortReplacer );
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for ve.getHash which sorts objects by key.
|
||||
*
|
||||
* This is a callback passed into JSON.stringify.
|
||||
*
|
||||
* @static
|
||||
* @method
|
||||
* @param {String} key Property name of value being replaced
|
||||
* @param {Mixed} value Property value to replace
|
||||
* @returns {Mixed} Replacement value
|
||||
*/
|
||||
ve.getHash.keySortReplacer = function ( key, value ) {
|
||||
if ( value.constructor !== Object ) {
|
||||
return value;
|
||||
}
|
||||
return ve.reduceArray( ve.getObjectKeys( value ).sort(), function( sorted, key ) {
|
||||
sorted[key] = value[key];
|
||||
return sorted;
|
||||
}, {} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces an array using a callback function.
|
||||
*
|
||||
* Native reduce will be used if available.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/Reduce
|
||||
*
|
||||
* @static
|
||||
* @method
|
||||
* @param {Array} arr Array to reduce
|
||||
* @param {Function} callback Function to execute for each element in the array
|
||||
* @param {Mixed} [initialValue] First argument to the first call of the callback
|
||||
* @returns {Array} Reduced array
|
||||
*/
|
||||
ve.reduceArray = function ( arr, callback, initialValue ) {
|
||||
// Use native implementation if available
|
||||
if ( Array.prototype.reduce ) {
|
||||
return arr.reduce( callback, initialValue );
|
||||
}
|
||||
if ( arr === null || arr === undefined ) {
|
||||
throw new TypeError( 'Object is null or undefined' );
|
||||
}
|
||||
var curr,
|
||||
i = 0,
|
||||
length = arr.length || 0;
|
||||
if ( typeof callback !== 'function' ) {
|
||||
// ES5 : 'If IsCallable(callbackfn) is false, throw a TypeError exception.'
|
||||
throw new TypeError( 'First argument is not callable' );
|
||||
}
|
||||
if ( initialValue === undefined ) {
|
||||
if ( length === 0 ) {
|
||||
throw new TypeError( 'Array length is 0 and initialValue is undefined' );
|
||||
}
|
||||
curr = arr[0];
|
||||
// Start accumulating at the second element
|
||||
i = 1;
|
||||
} else {
|
||||
curr = initialValue;
|
||||
}
|
||||
while ( i < length ) {
|
||||
if ( i in arr ) {
|
||||
curr = callback.call( undefined, curr, arr[i], i, arr );
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return curr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets an array of all property names in an object.
|
||||
|
|
Loading…
Reference in a new issue